How to handle dragging of subviews ?
19/12/03 10:08
I want the use to be able to drag subviews of my main drawing views, but I have problem to find out the right pattern of routines to overwrite, when the user drag the subview, the whole window is moving. What is the correct solution to this ?
Here is a working solution, probably not the only one:
You need to have both container and draggable views be of subclasses of NSView.
In the "ContainerView", you need to overwrite both - (NSView *)hitTest:(NSPoint)aPoint and - (void)mouseDragged:(NSEvent *)theEvent.
- (NSView *)hitTest:(NSPoint)aPoint
{
NSEnumerator *subviews = [[self subviews] objectEnumerator] ;
NSView *hitView ;
fHitView = nil ;
while (hitView = [subviews nextObject]) {
NSRect frame = [hitView frame] ;
if (NSPointInRect(aPoint,frame))
if ([hitView isKindOfClass:[DraggableView class]] && ![(DraggableView *)hitView dragEnabled]) {
return hitView ;
}
else {
fHitView = hitView ;
fHitPoint = aPoint ;
fFrameWhenHit = [hitView frame] ;
return self ;
}
}
return nil ;
}
- (void)mouseDragged:(NSEvent *)theEvent
{
if (fHitView != nil) {
NSPoint locationInWindow = [theEvent locationInWindow] ;
NSPoint locationInMySelf = [self convertPoint:locationInWindow fromView:[[self window] contentView]] ;
[fHitView setFrame:NSOffsetRect(fFrameWhenHit,locationInMySelf.x - fHitPoint.x, locationInMySelf.y - fHitPoint.y)] ;
[self setNeedsDisplay:YES] ;
}
}
The principle is simple; you keep track of which draggable view was hit, what was the mouse position and its frame of that time. When dragging occurs, you update the frame of the dragged view and refresh the container view. Just be careful when converting the current mouse position to container's coordinates.
Regarding the "DraggableView" class:
For this example, we have taken as hypothesis that the draggable view is so only at specific circumstances by implementing a "dragEnabled" method. Here we just test for the option key to be down. You have also to overwrite the - (NSView *)hitTest:(NSPoint)aPoint, but with a different purpose:
- (NSView *)hitTest:(NSPoint)aPoint
{
if ([self dragEnabled])
return nil ;
return [super hitTest:aPoint] ;
}
- (BOOL)dragEnabled
{
return NSAlternateKeyMask == ([[NSApp currentEvent] modifierFlags] & NSAlternateKeyMask) ;
}
The complete example can be downloaded here.
Of course, from here, up to you to implement multiple selection and constrained dragging.
You need to have both container and draggable views be of subclasses of NSView.
In the "ContainerView", you need to overwrite both - (NSView *)hitTest:(NSPoint)aPoint and - (void)mouseDragged:(NSEvent *)theEvent.
- (NSView *)hitTest:(NSPoint)aPoint
{
NSEnumerator *subviews = [[self subviews] objectEnumerator] ;
NSView *hitView ;
fHitView = nil ;
while (hitView = [subviews nextObject]) {
NSRect frame = [hitView frame] ;
if (NSPointInRect(aPoint,frame))
if ([hitView isKindOfClass:[DraggableView class]] && ![(DraggableView *)hitView dragEnabled]) {
return hitView ;
}
else {
fHitView = hitView ;
fHitPoint = aPoint ;
fFrameWhenHit = [hitView frame] ;
return self ;
}
}
return nil ;
}
- (void)mouseDragged:(NSEvent *)theEvent
{
if (fHitView != nil) {
NSPoint locationInWindow = [theEvent locationInWindow] ;
NSPoint locationInMySelf = [self convertPoint:locationInWindow fromView:[[self window] contentView]] ;
[fHitView setFrame:NSOffsetRect(fFrameWhenHit,locationInMySelf.x - fHitPoint.x, locationInMySelf.y - fHitPoint.y)] ;
[self setNeedsDisplay:YES] ;
}
}
The principle is simple; you keep track of which draggable view was hit, what was the mouse position and its frame of that time. When dragging occurs, you update the frame of the dragged view and refresh the container view. Just be careful when converting the current mouse position to container's coordinates.
Regarding the "DraggableView" class:
For this example, we have taken as hypothesis that the draggable view is so only at specific circumstances by implementing a "dragEnabled" method. Here we just test for the option key to be down. You have also to overwrite the - (NSView *)hitTest:(NSPoint)aPoint, but with a different purpose:
- (NSView *)hitTest:(NSPoint)aPoint
{
if ([self dragEnabled])
return nil ;
return [super hitTest:aPoint] ;
}
- (BOOL)dragEnabled
{
return NSAlternateKeyMask == ([[NSApp currentEvent] modifierFlags] & NSAlternateKeyMask) ;
}
The complete example can be downloaded here.
Of course, from here, up to you to implement multiple selection and constrained dragging.