在可可中拖动一个矩形

时间:2011-12-05 22:24:03

标签: cocoa drag-and-drop

我在NSView的自定义子类上绘制一个矩形,然后可以在视图的边框内拖动:

enter image description here

执行此操作的代码是:

    // Get the starting location of the mouse down event.
NSPoint location = [self convertPoint: [event locationInWindow] fromView: nil];

// Break out if this is not within the bounds of the rect.
if (!NSPointInRect(location, [self boundsOfAllControlPoints])) {
    return;
}

while (YES) {

    // Begin modal mouse tracking, looking for mouse dragged and mouse up events
    NSEvent *trackingEvent = [[self window] nextEventMatchingMask:(NSLeftMouseDraggedMask | NSLeftMouseUpMask)];

    // Get tracking location and convert it to point in the view.
    NSPoint trackingLocation = [self convertPoint:[trackingEvent locationInWindow] fromView:nil];

    // Calculate the delta's of x and y compared to the previous point.
    long dX = location.x - trackingLocation.x;
    long dY = location.y - trackingLocation.y;

    // Update all points in the rect
    for (int i = 0; i < 4; i++) {
        NSPoint newPoint = NSMakePoint(points[i].x - dX, points[i].y - dY);
        points[i] = newPoint;
    }

    NSLog(@"Tracking location x: %f y: %f", trackingLocation.x, trackingLocation.y);

    // Set current location as previous location.
    location = trackingLocation;

    // Ask for a redraw.
    [self setNeedsDisplay:YES];

    // Stop mouse tracking if a mouse up is received.
    if ([trackingEvent type] == NSLeftMouseUp) {
        break;
    }

}

我基本上抓住了一个鼠标按下事件并检查它的位置是否在可拖动的矩形内。如果是,我开始在trackingEvent中跟踪鼠标的移动。我计算x和y坐标的delta,为可拖动的rect创建新点,并请求刷新视图显示。

虽然它有效但看起来有点“业余”,因为在拖动过程中,鼠标指针会赶上被拖动的形状并最终会越过它的边界。在其他拖动操作中,从拖动操作的开始到结束,将看到鼠标指针固定到被拖动对象的位置。

造成这种影响的原因是什么?

修改

我在Rob的回答后改变了我的方法并采用了三种方法:

- (void) mouseDown: (NSEvent*) event {

    // There was a mouse down event which might be in the thumbnail rect.

    [self setDragStartPoint: [self convertPoint: [event locationInWindow] fromView: nil]];

    // Indicate we have a valid start of a drag.
    if (NSPointInRect([self dragStartPoint], [self boundsOfAllControlPoints])) {
        [self setValidDrag: YES];
    }

}

- (void) mouseDragged: (NSEvent *) anEvent {

    // Return if a valid drag was not detected during a mouse down event.
    if (![self validDrag]) {
        return;
    }

    NSLog(@"Tracking a drag.");

    // Get tracking location and convert it to point in the view.
    NSPoint trackingLocation = [self convertPoint: [anEvent locationInWindow] fromView: nil];

    // Calculate the delta's of x and y compared to the previous point.
    long dX = [self dragStartPoint].x - trackingLocation.x;
    long dY = [self dragStartPoint].y - trackingLocation.y;

    // Update all points in the rect
    for (int i = 0; i < 4; i++) {
        NSPoint newPoint = NSMakePoint(points[i].x - dX, points[i].y - dY);
        points[i] = newPoint;
    }

    // Ask for a redraw.
    [self setNeedsDisplay:YES];

    NSLog(@"Tracking location x: %f y: %f", trackingLocation.x, trackingLocation.y);

    // Set current location as previous location.
    [self setDragStartPoint: trackingLocation];

    NSLog(@"Completed mouseDragged method. Allow for repaint.");

}

- (void) mouseUp: (NSEvent *) anEvent {

    // End the drag.
    [self setValidDrag: NO];
    [self setNeedsDisplay: YES];

}

虽然效果略好一些,但是最终拖动鼠标指针的移动方向后仍然存在明显的延迟。当我在拖动过程中缓慢移动鼠标时,这一点尤为明显。

编辑2:

知道了。问题在于计算增量。我用了很长时间,但我应该使用浮动。现在效果很好。

1 个答案:

答案 0 :(得分:10)

您在绘制期间保持事件循环,这意味着正方形永远不会重绘。您拨打setNeedsDisplay:的电话不会吸引任何内容。它只标记要重绘的视图。在此例程返回之前,它无法重绘,在释放鼠标按钮之前,您不会这样做。

阅读Handling Mouse Dragging Operations以获得有关如何在Cocoa中实现拖动的完整讨论。您需要从mouseDown:返回并覆盖mouseDragged:mouseUp:,或者您需要自己抽取事件循环以便绘制周期可以处理。

我倾向于推荐第一种方法,即使它需要多种方法。抽取事件循环可能会产生非常令人惊讶的错误,应谨慎使用。我经验中最常见的错误是由于在您抽取事件循环时延迟选择器触发,导致“额外”代码在拖动例程中运行。在某些情况下,这可能会导致重入和死锁。 (我已经发生了这种情况......)