Отличия одного клика от двойного щелчка в 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 - нет.)