【问题标题】:Distinguishing a single click from a double click in Cocoa on the Mac在 Mac 上的 Cocoa 中区分单击和双击
【发布时间】:2009-11-03 02:40:40
【问题描述】:

我有一个自定义的NSView(它是其中之一,它们都住在NSCollectionView 中——我认为这无关紧要,但谁知道呢)。当我单击视图时,我希望它更改其选择状态(并相应地重绘自身);当我双击视图时,我希望它为刚刚双击的对象弹出一个更大的预览窗口。

我的第一个看起来像这样:

- (void)mouseUp: (NSEvent *)theEvent {
    if ([theEvent clickCount] == 1) [model setIsSelected: ![model isSelected]];
    else if ([theEvent clickCount] == 2) if ([model hasBeenDownloaded]) [mainWindowController showPreviewWindowForPicture:model];
}

大部分工作正常。除了,当我双击视图时,选择状态会改变并且窗口会弹出。这不正是我想要的。

看来我有两个选择。我可以在响应双击时恢复选择状态(撤消错误的单击),或者我可以使用某种NSTimer 解决方案在响应单击之前建立延迟。换句话说,我可以确保在更改选择状态之前不会再次单击。

这看起来更优雅,所以这是我一开始采用的方法。我从 Google 中找到的唯一真正的指导是在一个名称中带有连字符的未命名网站上。这种方法主要适用于一个很大的警告。

悬而未决的问题是“我的NSTimer 应该等待多长时间?”。未命名的站点建议使用 Carbon 函数GetDblTime()。除了在 64 位应用程序中无法使用之外,我能找到的唯一文档说它正在返回时钟滴答声。而且我不知道如何将这些转换为NSTimer 的秒数。

那么这里的“正确”答案是什么?摸索GetDblTime()?双击“撤消”选择?我无法弄清楚 Cocoa 惯用的方法。

【问题讨论】:

  • 在您检查 [model hasBeenDownloaded] 之后,我只需将 [model setIsSelected:NO] 添加到块中。
  • 是的,这就是我现在正在做的事情。这是我的第一个大型 Cocoa 应用程序,所以我试图找出适合这种情况的最佳实践。谷歌帮助不大,所以我认为 Stack Overflow 问题比较合适。

标签: cocoa mouse nsview


【解决方案1】:

延迟选择状态的更改是(据我所见)推荐的这样做方式。

实现起来非常简单:

- (void)mouseUp:(NSEvent *)theEvent
{
    if([theEvent clickCount] == 1) {
        [model performSelector:@selector(toggleSelectedState) afterDelay:[NSEvent doubleClickInterval]];
    }
    else if([theEvent clickCount] == 2)
    {
        if([model hasBeenDownloaded])
        {
                [NSRunLoop cancelPreviousPerformRequestsWithTarget: model]; 
                [mainWindowController showPreviewWindowForPicture:model];
        }
    }
}

(请注意,在 10.6 中,双击间隔可作为 NSEvent 上的类方法访问)

【讨论】:

  • 我没有注意到 NSEvent 的扩展,这真是太好了。
  • 假设我们的目标是 10.5。 [NSEvent doubleClickInterval] 有替代方案吗?
  • 上面的代码对我来说不太适用:单击选择器从未取消过。我通过使用它来工作: [NSRunLoop cancelPreviousPerformRequestsWithTarget: model];而不是 [NSRunLoop currentRunLoop]
  • @DaveDeLong:您能否将 Mark 的评论添加为您的答案的更新?
【解决方案2】:

如果你的单击和双击操作真的是分开的和不相关的,你需要在第一次单击时使用一个计时器,等待是否会发生双击。在任何平台上都是如此。

但这会在用户通常不喜欢的单击操作中引入尴尬的延迟。所以你不会经常看到这种方法。

更好的方法是让您的单击和双击操作相互关联和互补。例如,如果您在 Finder 中单击一个图标,它会被选中(立即),如果您双击一个图标,它会被选中并(立即)打开。这才是你应该追求的行为。

也就是说,单击的后果应该与您的双击命令有关。这样,您可以在双击处理程序中处理单击的效果,而无需使用计时器。

【讨论】:

  • 所以我已经对此进行了检查,我认为问题在于我的应用程序中的选择很粘(以避免必须使用键盘技巧来选择多个项目),这是不寻常的。单击一个项目:它已被选中。单击另一个项目:现在两者都被选中。双击第一个项目:它在一个窗口中打开并且未被选中。我会重新评估,看看这是否是最好的用户界面。感谢您的深思。
  • +1 for “在您的单击分析中引入了尴尬的延迟”。在任何平台上都是如此,因为必须等待查看另一次点击是否在预定义的时间窗口(通常为 500 毫秒)内到达。
【解决方案3】:

@Dave DeLong 在 Swift 4.2 (Xcode 10, macOS 10.13) 中的解决方案,修改为与 event.location(in: view) 一起使用

var singleClickPoint: CGPoint?

override func mouseDown(with event: NSEvent) {
singleClickPoint = event.location(in: self)
perform(#selector(GameScene.singleClickAction), with: nil, afterDelay: NSEvent.doubleClickInterval)
 if event.clickCount == 2 {
    RunLoop.cancelPreviousPerformRequests(withTarget: self)
    singleClickPoint = nil
//do whatever you want on double-click
}
}

@objc func singleClickAction(){
guard let singleClickPoint = singleClickPoint else {return}
//do whatever you want on single-click
}

我不使用 singleClickAction(at point: CGPoint) 并使用 event.location(in: self) 调用它的原因是我传入的任何点 - 包括 CGPoint.zero - 最终都会到达 singleClick Action为 (0.0, 9.223372036854776e+18)。我将为此提交一个雷达,但就目前而言,绕过执行是要走的路。 (其他对象似乎工作得很好,但 CGPoints 不行。)

【讨论】:

    【解决方案4】:

    就个人而言,我认为您需要问自己为什么要这种非标准行为。

    您能否指出任何其他将双击中的第一次单击视为与单击不同的应用程序?想不出来...

    【讨论】:

    • 抛开标准或非标准行为,我实际上有一个很好的理由。我正在编写一个应用程序,专门帮助我妈妈通过电子邮件发送她孙子的照片。她很容易被电脑的东西弄糊涂,所以我试图颠覆不可避免的“我试着双击,但它做了别的事情!救命!”称呼。 :)
    • Darren 让我相信我的整个 UI 可能需要重新考虑(请参阅我对他的回答的评论)。所以我再看看它。感谢您的深思。
    • (评论 b/c 这出现在结果的第一页):我通过单击在贝塞尔曲线上创建一个新点,并在双击时结束曲线,所以我需要将两者分开。 2018年的解决方案是使用手势识别器处理双击,并在mouseDown中延迟单击处理。见stackoverflow.com/a/51851026/1922825
    【解决方案5】:

    将两个属性添加到您的自定义视图中。

    // CustomView.h
    @interface CustomView : NSView {
      @protected
        id      m_target;
        SEL     m_doubleAction;
    }
    @property (readwrite) id target;
    @property (readwrite) SEL doubleAction;
    
    @end
    

    在您的自定义视图中覆盖mouseUp: 方法。

    // CustomView.m
    #pragma mark - MouseEvents
    
    - (void)mouseUp:(NSEvent*)event {
        if (event.clickCount == 2) {
            if (m_target && m_doubleAction && [m_target respondsToSelector:m_doubleAction]) {
                [m_target performSelector:m_doubleAction];
            }
        }
    }
    

    使用doubleAction 将您的控制器注册为target

    // CustomController.m
    - (id)init {
        self = [super init];
        if (self) {
            // Register self for double click events.
            [(CustomView*)m_myView setTarget:self];
            [(CustomView*)m_myView setDoubleAction:@selector(doubleClicked:)];
        }
        return self;
    }
    

    实现双击发生时应该做什么。

    // CustomController.m
    - (void)doubleClicked:(id)sender {
      // DO SOMETHING.
    }
    

    【讨论】:

    • 请注意,在仍然按下鼠标的情况下单击某个字段并将鼠标拖出该字段会被解释为clickCount = 0。此外,在某些情况下,使用clickCount > 1 识别双击/三次单击可能更智能。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-04-29
    • 2015-04-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多