区分单击与Mac上的Cocoa双击

时间:2009-11-03 02:40:41

标签: cocoa mouse nsview

我有一个自定义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解决方案,以便在响应单击之前构建延迟。换句话说,我可以确保在更改选择状态之前没有第二次点击。

这似乎更优雅,所以这是我最初采用的方法。我从谷歌找到的唯一真正指导是在一个未命名的网站上,其名称中带有连字符。这种方法主要适用于一个重要的警告。

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

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

5 个答案:

答案 0 :(得分:40)

推迟改变选择状态是(从我所见)推荐的做法。

实施起来非常简单:

- (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上的类方法访问

答案 1 :(得分:7)

如果您的单击和双击操作实际上是独立且无关的,则需要在第一次单击时使用计时器,然后等待以查看是否会发生双击。在任何平台上都是如此。

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

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

换句话说,单击的后果应与双击命令相关。这样,您就可以处理双击处理程序中单击的效果,而无需使用计时器。

答案 2 :(得分:2)

向自定义视图添加两个属性。

// 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];
        }
    }
}

使用targetdoubleAction注册为控制器。

// 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.
}

答案 3 :(得分:1)

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

您是否可以指向任何其他将双击中的第一次点击视为与单击不同的应用程序?我想不出任何......

答案 4 :(得分:1)

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@的戴夫·德隆(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(在点:CGPoint上)并使用以下方法调用它的原因是:event.location(在:self上)是我传入的任何点-包括CGPoint.zero-最终到达了singleClick Action如(0.0,9.223372036854776e + 18)。我将为此提起诉讼,但就目前而言,绕过执行是必经之路。 (其他对象似乎可以正常工作,但CGPoints不能。)