Основы обработки событий

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

Подготовка пользовательского представления для получения событий

Несмотря на то, что любой тип объекта респондента может обработать события, NSView объекты являются безусловно наиболее распространенным получателем событий. Они обычно - первые объекты реагировать и на события от нажатия мыши и на ключевые события, часто изменяя их появление в реакции на те события. Много классов Набора Приложения реализованы для обработки событий, но отдельно NSView не делает. Когда Вы создаете свое собственное представление, и Вы хотите, чтобы оно реагировало на события, необходимо добавить возможности обработки событий к нему.

Шаги абсолютного минимума, требуемые для этого, являются немногими:

Представление, которое является первым респондентом, принимает ключевые события и сообщения действия перед другими объектами в окне. Это также обычно принимает участие в ключевом цикле (см. Управление Интерфейсом Клавиатуры). По умолчанию объекты представления отказываются от состояния первого респондента путем возврата NO в acceptsFirstResponder. Если Вы хотите, чтобы Ваше пользовательское представление реагировало на ключевые события и действия, Ваш класс должен переопределить этот метод для возврата YES:

- (BOOL)acceptsFirstResponder {
    return YES;
}

Второй элемент в списке выше — переопределение NSResponder метод для обработки события — является, конечно, большим предметом и о чем самые остающиеся главы в этом документе. Но существует несколько основных инструкций для рассмотрения для сообщений о событиях:

Реализация методов действия

Сообщения действия обычно отправляются NSMenuItem объекты или NSControl объекты. Последние объекты обычно сотрудничают с один или больше NSCell объекты. Объектно-ориентированные памяти ячейки селектор метода идентификация действия обмениваются сообщениями, чтобы быть отправленными и ссылка на целевой объект. (Пункт меню инкапсулирует свое собственное действие и целевые данные.), Когда по пункту меню или объекту управления щелкают или иначе управляют, это получает селектор действия и целевой объект — в случае управления, от одной из его ячеек — и отправляет сообщение в цель.

Можно установить селектор действия и предназначаться для программно использования, соответственно, методов setAction: и setTarget: (объявленный NSActionCell, NSMenuItem, и другие классы). Однако Вы обычно указываете их в Интерфейсном Разработчике. В этом приложении Вы подключаете объект управления к другому объекту (цель) в файле пера Перетаскиванием управления от объекта управления до цели и затем выбора метода действия цели вызвать. Если Вы хотите, чтобы сообщение действия было не предназначено, можно или поставить цель к nil программно или, в Интерфейсном Разработчике, делают соединение между пунктом меню или управлением и значком First Responder в окне файла пера, как показано на рисунке 3-1.

Рисунок 3-1  , Соединяющий непредназначенное действие в Интерфейсном Разработчике
Connecting an untargeted action in Interface Builder

От Интерфейсного Разработчика можно генерировать заголовочный файл и файл реализации для проекта 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). Это сообщение инициирует своего рода протокол, в котором объект теряет свое первое состояние респондента, и другой получает его.

  1. makeFirstResponder: всегда спрашивает текущего первого респондента, если это готово оставить свое состояние путем отправки его resignFirstResponder.

  2. Если возвращается текущий первый респондент NO когда отправлено это сообщение, makeFirstResponder: сбои и аналогично возвращаются NO.

    Объект представления или другой респондент могут отказаться оставлять первое состояние респондента по многим причинам, такой как тогда, когда действие является неполным.

  3. Если возвращается текущий первый респондент YES к resignFirstResponder, тогда новый первый респондент отправляется a becomeFirstResponder сообщение, чтобы сообщить ему, что это может быть первый респондент.

  4. Этот объект может возвратиться NO отклонить присвоение, когда NSWindow самостоятельно становится первым респондентом.

Рисунок 3-2 и рисунок 3-3 иллюстрируют два возможных исхода этого протокола.

Рисунок 3-2  , Делающий представление первый респондент — текущее представление отказывается оставлять
Making a view a first responder—current view refuses to resign status
statusFigure 3-3
  , Делающий представление первый респондент — новое представление становится первым респондентом
Making a view a first responder—new view becomes first responder

Перечисление 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 объект. Можно также установить его при создании пользовательского интерфейса в Интерфейсном Разработчике. Чтобы сделать это, завершите следующие шаги.

  1. Перетащите управление от значка окна в окне файла пера к NSView объект в пользовательском интерфейсе.

  2. В области Connections инспектора выберите initialFirstResponder выход и нажимает Connect.

После awakeFromNib вызывается для всех объектов в файле пера, NSWindow делает первого респондента, вообще был установлен как начальный первый респондент в файле пера. Обратите внимание на то, что Вы не должны отправлять setInitialFirstResponder: к NSWindow объект в awakeFromNib и ожидайте, что сообщение будет эффективным.