Отличия одного клика от двойного щелчка в Cocoa на Mac

У меня есть пользовательский 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 -диоматический подход.

Ответы

Ответ 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)

Ответ 2

Если операции с одним щелчком мыши и двойным щелчком действительно раздельны и не связаны друг с другом, вам нужно использовать таймер при первом щелчке и ждать, если произойдет двойной щелчок. Это верно на любой платформе.

Но это приводит к неудобной задержке в операции с одним кликом, которую пользователи обычно не любят. Поэтому вы не видите такой подход, который используется очень часто.

Лучший подход заключается в том, чтобы ваши операции с одним щелчком мыши и двойным щелчком были связаны и дополняли друг друга. Например, если вы щелкнете один раз значок в Finder, он будет выбран (сразу), и если вы дважды щелкните значок, он будет выбран и открыт (сразу). Это поведение, к которому вы должны стремиться.

Другими словами, последствия одного щелчка должны быть связаны с вашей командой двойного щелчка. Таким образом, вы можете иметь дело с эффектами одного щелчка в вашем обработчике двойного щелчка, не прибегая к использованию таймера.

Ответ 3

Лично я думаю, вам нужно спросить себя, почему вы хотите это нестандартное поведение.

Можете ли вы указать на любое другое приложение, которое обрабатывает первый щелчок двойным щелчком, поскольку отличается от одного щелчка? Я не могу придумать...

Ответ 4

Добавьте два свойства в пользовательский вид.

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

Зарегистрируйте свой контроллер как target с помощью doubleAction.

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

Ответ 5

Решение @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(in: self), заключается в том, что любая точка, которую я передаю, включая CGPoint.zero, в конечном итоге попадает в действие singleClick как (0,0, 9,223372036854776е + 18). Я буду подавать радар для этого, но пока обходной путь - это путь. (Другие объекты работают нормально, а CGPoints - нет.)