将子视图带到 mouseDown 的顶部:并继续接收事件(用于拖动)
好的,基本上我有一个带有一系列稍微重叠的子视图的视图。单击子视图时,它会通过非常简单的两行移动到顶部(除其他外):
-(void)mouseDown:(NSEvent *)theEvent {
if (_selected) { // Don't do anything this subview is already in the foreground
return;
}
NSView *superview = [self superview];
[self removeFromSuperview];
[superview addSubview:self positioned:NSWindowAbove relativeTo:nil];
}
这工作正常,似乎是执行此操作的“正常”方法。
问题是我现在向视图引入一些拖动逻辑,因此我需要响应 -mouseDragged:
。不幸的是,由于视图已从视图层次结构中删除并在此过程中重新添加,因此我无法让视图到达前台并在同一鼠标操作中拖动。它接收 mouseDown:
,但从不接收 mouseDragged:
或任何后续事件。只有当我松开鼠标并再次单击视图时,它才会拖动,因为第二次单击不会进行任何视图层次结构处理。
我可以做些什么来允许视图像这样移动到前台,并继续接收随后的 -mouseDragged:
和 -mouseUp:
事件?
我开始沿着在超级视图中重写 -hitTest:
的思路并拦截 mouseDown 事件,以便在视图实际接收事件之前将视图带到前台。这里的问题是,在 -hitTest:
中,我如何区分我实际执行命中测试的事件类型?我不想将子视图移动到前台以处理其他鼠标事件,例如 mouseMoved 等
。我实际上已经在我的超级视图中完成了此操作,但感觉就像是直接从 -hitTest:
触发事件的非常糟糕的代码味道。
-(NSView *)hitTest:(NSPoint)aPoint {
NSView *view = [super hitTest:aPoint];
if ([view isKindOfClass:[EDTabView class]] && ![(EDTabView *)view isActive]) {
// Effectively simulate two mouseDown events in sequence for this special case
[view mouseDown:nil]; // Works because I know too much about the implementation
}
return view;
}
有两件事让人感觉不对劲。最大的问题是我在 -hitTest:
返回之前执行操作,然后在 -hitTest:
完成后按照 AppKit 的处理方式执行操作。第二个是我没有要传递的事件,所以我传递的是零。它在这种情况下有效,因为我知道事件处理程序实际上并不处理任何事件数据,但这并不是很好。另一种方法是将整个逻辑移至超级视图中,但这里的关注点分离感觉更糟。
更新 |这个 -hitTest:
hack 被破坏了......当我没有单击鼠标时,例如当我将某些东西拖到视图顶部时,它就会被触发。仍在寻找解决此问题的好方法。
答案可以从字面上指示视图在拖动操作期间如何离开并重新进入其超级视图,或者(也许更优雅)将视图移动到前台的替代方法,而不是 [view removeFromSuperview]; [superview addSubview:view ...];
。
Ok, basically I have a view with a series of slightly overlapping subviews. When a subview is clicked, it moves to the top (among other things), by a really simple two-liner:
-(void)mouseDown:(NSEvent *)theEvent {
if (_selected) { // Don't do anything this subview is already in the foreground
return;
}
NSView *superview = [self superview];
[self removeFromSuperview];
[superview addSubview:self positioned:NSWindowAbove relativeTo:nil];
}
This works fine and seems to be the "normal" way to do this.
The problem is that I'm now introducing some drag logic to the view, so I need to respond to -mouseDragged:
. Unfortunately, since the view is removed from the view hierarchy and re-added in the process, I can't have the view come to the foreground and be dragged in the same mouse action. It receives mouseDown:
, but never receives mouseDragged:
or any subsequent events. It only drags if I leave go of the mouse and click on the view again, since the second click doesn't do any view hierarchy juggling.
Is there anything I can do that will allow the view to move to the foreground like this, AND continue to receive the subsequent -mouseDragged:
and -mouseUp:
events that follow?
I started along the lines of thinking of overriding -hitTest:
in the superview and intercepting the mouseDown event in order to bring the view to the foreground before it actually receives the event. The problem here is, from within -hitTest:
, how do I distinguish what type of event I'm actually performing the hit test for? I wouldn't want to move the subview to the foreground for other mouse events, such as mouseMoved etc.
UPDATE | I've actually done this, in my superview, but it feels like a really bad code smell triggering an event from -hitTest:
directly.
-(NSView *)hitTest:(NSPoint)aPoint {
NSView *view = [super hitTest:aPoint];
if ([view isKindOfClass:[EDTabView class]] && ![(EDTabView *)view isActive]) {
// Effectively simulate two mouseDown events in sequence for this special case
[view mouseDown:nil]; // Works because I know too much about the implementation
}
return view;
}
There are two things that feel wrong with this. The biggest is that I'm performing the action before -hitTest:
has even returned, then performing as handled by AppKit after -hitTest:
has completed. The second is that I don't have an event to pass, so I'm passing nil. It works in this case since I know the event handler doesn't actually process any of the event data, but it's not great. The alternative was to move the entire logic into the superview, but the separation of concerns here feels even worse.
UPDATE | This -hitTest:
hack is broken... it gets triggered when I'm not making a mouse click, such as when I'm dragging something over the top of the view. Still looking for a good solution to this problem.
An answer can either literally indicate how a view can leave and re-enter its superview during a drag operation, or (perhaps more elegant), an alternative way to move a view to the foreground other than [view removeFromSuperview]; [superview addSubview:view ...];
.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
好吧,我一时兴起尝试了一些不合逻辑的事情。
而不是将视图移动到前台:
我只是删除了
removeFromSuperview
行,想知道如果您尝试将视图添加到超级视图两次,AppKit 实际上会做什么(我假设它提供了防止这种情况发生的保护) )。它有效,至少在 Snow Leopard 下是有效的,并且解决了很多问题。正如方法名称所暗示的那样,父视图中已经存在的视图不会被添加,但它确实会设置其位置,而不必先将其从父视图中删除。
Ok, I went out on a whim and tried something illogical.
Instead of this to move a view to the foreground:
I simply dropped the
removeFromSuperview
line, wondering what AppKit actually does if you try to add a view to a superview twice (I assumed it provides protection against it happening).It works, at least under Snow Leopard, and it solves a lot of problems. The view being already present in the superview isn't added, as the method name suggests, but it does get its position set without having to first remove it from the superview.
我找到了你的帖子,因为我遇到了同样的问题(顺便说一句,这很烦人)。正确的解决方案是使用您自己的特殊比较器函数对子视图进行排序。在父视图上,调用 -sortSubviewsUsingFunction:context: 方法。
I found your post since I had the same problem (quite annoying thing, BTW). The right solution is to sort subviews, using your own special comparator function. On a parent view, call -sortSubviewsUsingFunction:context: method.
要以编程方式触发鼠标事件,您可以使用 使用 CGEventCreateMouseEvent() 执行双击
如果您有大量内容移动,您应该甚至在操作之前调用 CGEventCreate(NULL); 甚至可能执行以下操作:
这给用户带来了拖放项目的错觉,即使它们从一个超级视图移动到另一个超级视图。
To trigger Mouse Events programatically you can use Performing a double click using CGEventCreateMouseEvent()
If you have a lot of content moving you should even call
CGEventCreate(NULL);
before the operation and maybe even do something like:This gives the illusion for the user to drag and drop items even if they move from a superview to another.