Основы обработки событий
Некоторые задачи, найденные в коде обработки событий, характерны для событий больше чем одного типа. Следующие разделы описывают эти основные задачи обработки событий. Часть информации, представленной в этой главе, обсуждена подробно, но использование различных примеров, в Создании Пользовательской главы Представления Руководства по программированию Представления.
Подготовка пользовательского представления для получения событий
Несмотря на то, что любой тип объекта респондента может обработать события, NSView
объекты являются безусловно наиболее распространенным получателем событий. Они обычно - первые объекты реагировать и на события от нажатия мыши и на ключевые события, часто изменяя их появление в реакции на те события. Много классов Набора Приложения реализованы для обработки событий, но отдельно NSView
не делает. Когда Вы создаете свое собственное представление, и Вы хотите, чтобы оно реагировало на события, необходимо добавить возможности обработки событий к нему.
Шаги абсолютного минимума, требуемые для этого, являются немногими:
Если Ваше пользовательское представление должно быть первым респондентом для ключевых событий или сообщений действия, переопределение
acceptsFirstResponder
.Реализуйте один или больше
NSResponder
методы для обработки событий определенных типов.Если Ваше представление должно обработать сообщения действия, переданные ему через цепочку респондента, реализуйте методы соответствующих мер.
Представление, которое является первым респондентом, принимает ключевые события и сообщения действия перед другими объектами в окне. Это также обычно принимает участие в ключевом цикле (см. Управление Интерфейсом Клавиатуры). По умолчанию объекты представления отказываются от состояния первого респондента путем возврата NO
в acceptsFirstResponder
. Если Вы хотите, чтобы Ваше пользовательское представление реагировало на ключевые события и действия, Ваш класс должен переопределить этот метод для возврата YES
:
- (BOOL)acceptsFirstResponder { |
return YES; |
} |
Второй элемент в списке выше — переопределение NSResponder
метод для обработки события — является, конечно, большим предметом и о чем самые остающиеся главы в этом документе. Но существует несколько основных инструкций для рассмотрения для сообщений о событиях:
Если необходимо, исследуйте переданный - в
NSEvent
объект проверить, что это - событие, которое Вы обрабатываете и, если так, узнаете, как обработать его.Вызовите надлежащее
NSEvent
методы, чтобы помочь сделать это определение. Например, Вы могли бы видеть, какие модифицирующие клавиши были нажаты (modifierFlags
), узнайте, является ли событие mouseDown двойным - или тройной щелчок (clickCount
), или, для ключевых событий, получают связанные символы (characters
иcharactersIgnoringModifiers
).Если Ваша реализация
NSResponder
метод такой какmouseDown:
полностью обрабатывает событие, оно не должно вызывать реализацию суперкласса того метода.NSView
наследовалсяNSResponder
реализации методов для обработки событий от нажатия мыши; в этих методах,NSResponder
просто передает сообщение цепочка респондента. Один из этих объектов в цепочке респондента может реализовать метод в пути, гарантирующем, что Ваше пользовательское представление не будет видеть последующих связанных событий от нажатия мыши. Из-за этого, пользовательскогоNSView
объекты не должны вызыватьsuper
в их реализацияхNSResponder
методы обработки событий мыши такой какmouseDown:
,mouseDragged:
иmouseUp:
если не известно, что наследованная реализация обеспечивает некоторую необходимую функциональность.Если Вы не обрабатываете событие, передайте его цепочка респондента. Несмотря на то, что можно непосредственно передать сообщение следующему респонденту, обычно лучше передать сообщение к суперклассу. Если Ваш суперкласс не обрабатывает событие, он передает сообщение к своему суперклассу, и т.д., до
NSResponder
достигнут. По умолчаниюNSResponder
передачи все сообщения о событиях цепочка респондента. Например, вместо этого:- (void)mouseDown:(NSEvent *)theEvent {
// determine if I handle theEvent
// if not...
[[self nextResponder] mouseDown:theEvent];
}
сделайте это:
- (void)mouseDown:(NSEvent *)theEvent {
// determine if I handle theEvent
// if not...
[super mouseDown:theEvent];
}
Если Ваш подкласс должен обработать определенные события часть времени — только некоторые введенные символы, возможно — тогда это должно переопределить метод события для обработки случаев, которыми это интересуется и вызвать реализацию суперкласса иначе. Это позволяет суперклассу ловить случаи, он интересуется, и в конечном счете позволяет событию продолжаться продвигающийся вдоль цепочки респондента, если он не обрабатывается.
Если Вы сознательно хотите обойти реализацию суперкласса
NSResponder
метод — говорит, что Ваш суперклассNSForm
— и все же передайте событие цепочка респондента, снова пошлите сообщение[self nextResponder]
.
Реализация методов действия
Сообщения действия обычно отправляются NSMenuItem
объекты или NSControl
объекты. Последние объекты обычно сотрудничают с один или больше NSCell
объекты. Объектно-ориентированные памяти ячейки селектор метода идентификация действия обмениваются сообщениями, чтобы быть отправленными и ссылка на целевой объект. (Пункт меню инкапсулирует свое собственное действие и целевые данные.), Когда по пункту меню или объекту управления щелкают или иначе управляют, это получает селектор действия и целевой объект — в случае управления, от одной из его ячеек — и отправляет сообщение в цель.
Можно установить селектор действия и предназначаться для программно использования, соответственно, методов setAction:
и setTarget:
(объявленный NSActionCell
, NSMenuItem
, и другие классы). Однако Вы обычно указываете их в Интерфейсном Разработчике. В этом приложении Вы подключаете объект управления к другому объекту (цель) в файле пера Перетаскиванием управления от объекта управления до цели и затем выбора метода действия цели вызвать. Если Вы хотите, чтобы сообщение действия было не предназначено, можно или поставить цель к nil
программно или, в Интерфейсном Разработчике, делают соединение между пунктом меню или управлением и значком First Responder в окне файла пера, как показано на рисунке 3-1.
От Интерфейсного Разработчика можно генерировать заголовочный файл и файл реализации для проекта XCode, включающие объявление и скелетную реализацию, соответственно, для каждого метода действия, определенного для класса. Эти Интерфейсные Определенные методы разработчика имеют возврат «значение» IBAction
, который действует как тег к обозначенному, что соединение целевого действия архивируется в файле пера. Можно также добавить объявления и скелетные реализации методов действия сами; в этом случае тип возврата void
.) Остающаяся требуемая часть подписи является единственным параметром, введенным как id
и названный (условно) sender
.
Перечисление 3-1 иллюстрирует прямую реализацию метода действия, переключающего индикатор AM-PM часов, когда пользователь нажимает кнопку.
Перечисление 3-1 Простая реализация метода действия
- (IBAction)toggleAmPm:(id)sender { |
[self incrementHour:12 andMinute: 0]; |
} |
Методы действия, в отличие от этого NSResponder
методы события, не имейте реализаций по умолчанию, таким образом, подклассы респондента не должны вслепую передавать сообщения действия к super
. Передача действия обменивается сообщениями, цепочка респондента в Наборе Приложения утверждена просто на том, реагирует ли объект на метод, в отличие от этого с передачей сообщений о событиях. Конечно, если Вы знаете, что суперкласс действительно фактически реализует метод, можно передать его от подкласса, но иначе сделать нет.
Важная функция сообщений действия - то, что можно передать сообщения обратно sender
получить дополнительную информацию или связанные данные. Например, пункты меню в данном меню, возможно, представляли объекты, присвоенные им; например, пункт меню с заголовком «Красного» цвета мог бы иметь представленный объект, который является NSColor
объект. Можно получить доступ к этому объекту путем отправки representedObject
к sender
.
Можно взять функцию обмена сообщениями назад методов действия один шаг вперед при помощи его для динамичного изменения sender
цель, действие, заголовок и подобные атрибуты. Вот простой тестовый сценарий: Вы хотите управлять индикатором хода выполнения ( NSProgressIndicator
объект) с единственной кнопкой; один щелчок кнопки запускает индикатор и изменяет заголовок кнопки для «Останавливаний», и затем следующий щелчок останавливает индикатор и изменяет заголовок для поддержки для «Запущений». Перечисление 3-2 показывает один способ сделать это.
Перечисление 3-2 , Сбрасывающее цель и действие sender
— хорошая реализация
- (IBAction)controlIndicator:(id)sender |
{ |
[[sender cell] setTarget:indicator]; // indicator is NSProgressIndicator |
if ( [[sender title] isEqualToString:@"Start"] ) { |
[[sender cell] setAction:@selector(startAnimation:)]; |
[sender setTitle:@"Stop"]; |
} else { |
[[sender cell] setAction:@selector(stopAnimation:)]; |
[sender setTitle:@"Start"]; |
} |
[[sender cell] performClick:self]; |
// set target and action back to what they were |
[[sender cell] setAction:@selector(controlIndicator:)]; |
[[sender cell] setTarget:self]; |
} |
Однако эта реализация требует, чтобы цель и информация о действии были задержаны к тому, чем они были после того, как перенаправленное сообщение действия отправляется через performClick:
. Вы могли упростить эту реализацию путем вызова непосредственно sendAction:to:from:
, метод, используемый объектом приложения (NSApp
) диспетчеризировать сообщения действия (см. Перечисление 3-3).
Перечисление 3-3 , Сбрасывающее цель и действие sender
— лучшая реализация
- (IBAction)controlIndicator:(id)sender |
{ |
SEL theSelector; |
if ( [[sender title] isEqualToString:@"Start"] ) { |
theSelector = @selector(startAnimation:); |
[sender setTitle:@"Stop"]; |
} else { |
theSelector = @selector(stopAnimation:); |
[sender setTitle:@"Start"]; |
} |
[NSApp sendAction:theSelector to:indicator from:sender]; |
} |
В сообщениях действия клавиатуры метод действия вызывается в результате определенного нажатия клавиши, интерпретируемого через механизм привязок клавиш. Поскольку такие сообщения таким образом тесно связаны с определенными ключевыми событиями, реализация метода действия может получить событие путем отправки currentEvent
к NSApp
и затем запрос NSEvent
объект для подробных данных. Перечисление 3-7 дает пример этого метода. Посмотрите Путь Ключевых событий для сводки сообщений действия клавиатуры; также посмотрите Привязки клавиш для описания того механизма.
Получение расположения события
Можно получить расположение мыши или события указателя планшета путем отправки locationInWindow
к NSEvent
объект. Но, поскольку имя метода обозначает, это расположение ( NSPoint
структура), находится в основной системе координат окна, не в системе координат представления, обычно обрабатывающего событие. Поэтому представление должно преобразовать точку в свою собственную систему координат с помощью метода convertPoint:fromView:
, как показано в Перечислении 3-4.
Перечисление 3-4 , Преобразовывающее перетащенное мышью расположение, чтобы быть в системе координат представления
- (void)mouseDragged:(NSEvent *)event { |
NSPoint eventLocation = [event locationInWindow]; |
center = [self convertPoint:eventLocation fromView:nil]; |
[self setNeedsDisplay:YES]; |
} |
Второй параметр convertPoint:fromView:
nil
указать, что преобразование от основной системы координат окна.
Следует иметь в виду что locationInWindow
метод не является подходящим для ключевых событий, периодических событий или любого другого типа события кроме событий указателя планшета и мыши.
Тестирование на флаги типа события и модификатора
При случае Вы, возможно, должны были бы обнаружить тип события. Однако Вы не должны делать этого в методе обработки событий NSResponder
потому что тип события очевиден из имени метода: rightMouseDragged:
, keyDown:
, tabletProximity:
, и т.д. Но откуда-либо в приложении можно всегда получать в настоящее время обработанное событие путем отправки currentEvent
к NSApp
. Для обнаружения, какое событие это отправить type
к NSEvent
возразите и затем сравните возвращенное значение с одним из NSEventType
константы. Перечисление 3-5 дает пример этого.
Тестирование перечисления 3-5 на тип события
NSEvent *currentEvent = [NSApp currentEvent]; |
NSPoint mousePoint = [controlView convertPoint: [currentEvent locationInWindow] fromView:nil]; |
switch ([currentEvent type]) { |
case NSLeftMouseDown: |
case NSLeftMouseDragged: |
[self doSomethingWithEvent:currentEvent]; |
break; |
default: |
// If we find anything other than a mouse down or dragged we are done. |
return YES; |
} |
Общий тест, выполняемый в методах обработки событий, узнает, были ли определенные модифицирующие клавиши нажаты одновременно как нажатие клавиши, щелчок мышью или подобное пользовательское действие. Модифицирующая клавиша обычно передает специальное значение для события. Пример кода в Перечислении 3-6 показывает реализацию mouseDown:
это определяет, была ли Командная клавиша нажата, в то время как щелкнули мышью. Если это было, это поворачивает получатель (представление) 90 градусов. Идентификация модифицирующих клавиш требует, чтобы отправил обработчик событий modifierFlags
к переданному - в объекте-событии и затем выполняют ПОРАЗРЯДНУЮ ОПЕРАЦИЮ И на возвращенном значении с помощью один или больше констант маски модификатора, объявленных в NSEvent.h
.
Тестирование перечисления 3-6 на модифицирующие клавиши нажало — метод события
- (void)mouseDown:(NSEvent *)theEvent { |
// if Command-click rotate 90 degrees |
if ([theEvent modifierFlags] & NSCommandKeyMask) { |
[self setFrameRotation:[self frameRotation]+90.0]; |
[self setNeedsDisplay:YES]; |
} |
} |
Можно протестировать объект-событие, чтобы узнать, был ли кто-либо от ряда модифицирующих клавиш нажат путем выполнения битового «ИЛИ» с константами маски модификатора, как в Перечислении 3-7. (Отметьте также, что этот пример показывает использование currentEvent
метод в методе действия клавиатуры.)
Тестирование перечисления 3-7 на модифицирующие клавиши нажало — метод действия
- (void)moveLeft:(id)sender { |
// Use left arrow to decrement the time. If a shift key is down, use a big step size. |
BOOL shiftKeyDown = ([[NSApp currentEvent] modifierFlags] & |
(NSShiftKeyMask | NSAlphaShiftKeyMask)) !=0; |
[self incrementHour:0 andMinute:-(shiftKeyDown ? 15 : 1)]; |
} |
Далее, можно искать определенные комбинации модифицирующей клавиши путем соединения отдельных тестов модифицирующей клавиши с логической операцией И (&&
). Например, если бы метод в качестве примера в Перечислении 3-6 должен был искать Щелчок при нажатой клавише Shift команды, а не Щелчок команды, полный тест был бы следующим:
if ( ( [theEvent modifierFlags] & NSCommandKeyMask ) && |
( [theEvent modifierFlags] & NSShiftKeyMask ) ) |
В дополнение к тестированию NSEvent
объект для типов одиночных соревнований, можно также ограничить события, выбранные от очереди событий до указанных типов. Можно выполнить эту фильтрацию типов событий в nextEventMatchingMask:untilDate:inMode:dequeue:
метод (NSApplication
и NSWindow
) или nextEventMatchingMask:
метод NSWindow
. Второй параметр всех этих методов берет один или больше констант маски типа события, объявленных в NSEvent.h
— например, NSLeftMouseDraggedMask
, NSFlagsChangedMask
, и NSTabletProximityMask
. Можно или указать эти константы индивидуально или выполнить битовое «ИЛИ» на них.
Поскольку nextEventMatchingMask:untilDate:inMode:dequeue:
метод используется почти исключительно в замкнутых циклах для обработки связанной серии событий от нажатия мыши, использование его описано в Обработке Событий от нажатия мыши.
Связанные с респондентом задачи
Следующие разделы описывают задачи, связанные с состоянием первого респондента объектов.
Определение состояния Первого Респондента
Обычно NSResponder
объект может всегда определять - ли это в настоящее время первый респондент путем выяснения у его окна (или оно, если это NSWindow
объект) для первого респондента и затем сравнения себя к тому объекту. Вы спрашиваете NSWindow
объект для первого респондента путем отправки ему a firstResponder
сообщение. Для NSView
объект, это сравнение было бы похоже на следующий бит кода:
if ([[self window] firstResponder] == self) { |
// do something based upon first-responder status |
} |
Сложность этого простого сценария происходит с текстовыми полями. Когда текстовое поле имеет фокус ввода, это не первый респондент. Вместо этого полевой редактор для окна является первым респондентом; если Вы отправляете firstResponder
к NSWindow
объект, NSTextView
объект (полевой редактор) - то, что возвращается. Определить если данный NSTextField
в настоящее время активно, получите первого респондента из окна и узнайте, что это NSTextView
возразите и если его делегат равен NSTextField
объект. Перечисление 3-8 показывает, как Вы могли бы сделать это.
Перечисление 3-8 , Определяющее, является ли текстовое поле первым респондентом
if ( [[[self window] firstResponder] isKindOfClass:[NSTextView class]] && |
[window fieldEditor:NO forObject:nil] != nil ) { |
NSTextField *field = [[[self window] firstResponder] delegate]; |
if (field == self) { |
// do something based upon first-responder status |
} |
} |
Управление, которое редактирует полевой редактор, всегда является текущим делегатом полевого редактора, таким образом (поскольку пример показывает) можно получить текстовое поле путем выяснения полевого делегата редактора. Для больше на полевом редакторе, посмотрите Текстовые поля, текстовые Представления и Полевого Редактора.
Установка первого респондента
Можно программно изменить первого респондента путем отправки makeFirstResponder:
к NSWindow
объект; параметром этого сообщения должен быть объект респондента (т.е. объект, наследовавшийся от NSResponder
). Это сообщение инициирует своего рода протокол, в котором объект теряет свое первое состояние респондента, и другой получает его.
makeFirstResponder:
всегда спрашивает текущего первого респондента, если это готово оставить свое состояние путем отправки егоresignFirstResponder
.Если возвращается текущий первый респондент
NO
когда отправлено это сообщение,makeFirstResponder:
сбои и аналогично возвращаютсяNO
.Объект представления или другой респондент могут отказаться оставлять первое состояние респондента по многим причинам, такой как тогда, когда действие является неполным.
Если возвращается текущий первый респондент
YES
кresignFirstResponder
, тогда новый первый респондент отправляется abecomeFirstResponder
сообщение, чтобы сообщить ему, что это может быть первый респондент.Этот объект может возвратиться
NO
отклонить присвоение, когдаNSWindow
самостоятельно становится первым респондентом.
Рисунок 3-2 и рисунок 3-3 иллюстрируют два возможных исхода этого протокола.
Перечисление 3-9 показывает пользовательское NSCell
класс (в этом случае подкласс NSActionCell
) реализация resignFirstResponder
и becomeFirstResponder
управлять кольцом клавиатурного фокуса его суперкласса.
Отставка перечисления 3-9 и становление первым респондентом
- (BOOL)becomeFirstResponder { |
BOOL okToChange = [super becomeFirstResponder]; |
if (okToChange) [self setKeyboardFocusRingNeedsDisplayInRect: [self bounds]]; |
return okToChange; |
} |
- (BOOL)resignFirstResponder { |
BOOL okToChange = [super resignFirstResponder]; |
if (okToChange) [self setKeyboardFocusRingNeedsDisplayInRect: [self bounds]]; |
return okToChange; |
} |
Когда окно является занявшим первое место на экране, можно также установить начального первого респондента для окна — т.е. первый набор респондента. Можно установить начального первого респондента программно путем отправки setInitialFirstResponder:
к NSWindow
объект. Можно также установить его при создании пользовательского интерфейса в Интерфейсном Разработчике. Чтобы сделать это, завершите следующие шаги.
Перетащите управление от значка окна в окне файла пера к
NSView
объект в пользовательском интерфейсе.В области Connections инспектора выберите
initialFirstResponder
выход и нажимает Connect.
После awakeFromNib
вызывается для всех объектов в файле пера, NSWindow
делает первого респондента, вообще был установлен как начальный первый респондент в файле пера. Обратите внимание на то, что Вы не должны отправлять setInitialFirstResponder:
к NSWindow
объект в awakeFromNib
и ожидайте, что сообщение будет эффективным.