【问题标题】:Bring a subview to the top on mouseDown: AND keep receiving events (for dragging)在 mouseDown 上将子视图置于顶部:并继续接收事件(用于拖动)
【发布时间】:2011-06-02 05:24:57
【问题描述】:

好的,基本上我有一个包含一系列稍微重叠的子视图的视图。当一个子视图被点击时,它会通过一个非常简单的两条线移动到顶部(除其他外):

-(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 处理的操作。第二个是我没有要传递的事件,所以我传递的是 nil。它适用于这种情况,因为我知道事件处理程序实际上并不处理任何事件数据,但这并不是很好。另一种方法是将整个逻辑移到超级视图中,但这里的关注点分离感觉更糟。

更新 |这个-hitTest: hack 被破坏了......当我没有点击鼠标时它会被触发,例如当我将某些东西拖到视图顶部时。仍在寻找解决此问题的好方法。

答案可以直接指示视图在拖动操作期间如何离开并重新进入其父视图,或者(也许更优雅)将视图移动到前台而不是[view removeFromSuperview]; [superview addSubview:view ...]; 的另一种方法。

【问题讨论】:

  • 我现在尝试从-mouseDown: 操作中手动驱动事件循环,但在-mouseUp: 事件之前没有收到任何事件。从父视图中删除并重新放回的视图确实让 AppKit 感到困惑。

标签: cocoa macos mouseevent nsview hittest


【解决方案1】:

好吧,我一时兴起尝试了一些不合逻辑的东西。

而不是将视图移到前台:

[theView removeFromSuperview];
[theSuperview addSubview:theView positioned:NSWindowAbove relativeTo:nil];

我只是删除了removeFromSuperview 行,想知道如果您尝试将视图添加到超级视图两次,AppKit 实际会做什么(我假设它可以防止这种情况发生)。

至少在 Snow Leopard 下有效,并且解决了很多问题。正如方法名称所暗示的那样,父视图中已经存在的视图没有添加,但它确实可以设置其位置,而无需先将其从父视图中删除。

【讨论】:

    【解决方案2】:

    我找到了您的帖子,因为我遇到了同样的问题(顺便说一句,这很烦人)。正确的解决方案是使用您自己的特殊比较器功能对子视图进行排序。在父视图上,调用 -sortSubviewsUsingFunction:context: 方法。

    【讨论】:

    • 这种方法的问题虽然是正确的,但如果视图是图层支持的,则排序是动画的。 OP自己的答案也会发生这种情况。
    【解决方案3】:

    要以编程方式触发鼠标事件,您可以使用Performing a double click using CGEventCreateMouseEvent()

    CGEventRef ourEvent = CGEventCreate(NULL); //save the mouse event to track location
    PostMouseEvent(kCGMouseButtonLeft, NX_LMOUSEDOWN, CGEventGetLocation(ourEvent));
    
    void PostMouseEvent(CGMouseButton button, CGEventType type, const CGPoint point)
    {
       CGEventRef theEvent = CGEventCreateMouseEvent(NULL, type, point, button);
       CGEventSetType(theEvent, type);
       CGEventPost(kCGHIDEventTap, theEvent);
       CFRelease(theEvent);
    }
    

    如果您有很多内容要移动,您甚至应该在操作前致电CGEventCreate(NULL);,甚至可以执行以下操作:

    CGEventRef ourEvent = CGEventCreate(NULL);
    
    //Some long operation
    
    CGEventRef dragEvent = CGEventCreate(NULL);
    PostMouseEvent(kCGMouseButtonLeft, NX_LMOUSEDOWN, CGEventGetLocation(ourEvent));
    PostMouseEvent(kCGMouseButtonLeft, NX_LMOUSEDRAGGED, CGEventGetLocation(dragEvent));
    

    即使项目从超级视图移动到另一个视图,用户也会产生拖放项目的错觉。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-12-03
      • 1970-01-01
      相关资源
      最近更新 更多