Обработка событий от нажатия мыши

События от нажатия мыши являются одним из двух самых частых видов событий, обработанных приложением (другой вид быть, конечно, ключевые события). Щелчки мышью — которые вовлекают пользователя, нажимающего и затем отпускающего кнопку мыши — обычно, указывают выбор, но что означает выбор, оставлен до объекта, отвечающего на событие. Например, щелчок мышью мог сказать отвечающему объекту изменить его появление и затем отправить сообщение действия. Мышь перетаскивает, обычно указывают, что представление получения должно переместить себя или рисованный объект в его границах. Следующие разделы описывают, как Вы могли бы обработать мышь вниз, мышь, и перетаскивать мышь события.

Обзор событий от нажатия мыши

Перед входом, “как к” обработки события от нажатия мыши, давайте рассмотрим некоторые центральные факты о событиях от нажатия мыши, обсужденных в Архитектуре События, Объектах-событиях и Типах и Основах Обработки событий:

Обработка щелчков мышью

Одна из самых ранних вещей рассмотреть в обработке событий mouseDown то, ли получение NSView объект должен стать первым респондентом, что означает, что это будет первый кандидат на последующие ключевые события и сообщения действия. Представления, обрабатывающие графические элементы, которые пользователь может выбрать — рисующие формы или текст, например — должны обычно принимать первое состояние респондента на событии mouseDown путем переопределения acceptsFirstResponder метод для возврата YES, как обсуждено в Подготовке Пользовательского Представления для Получения Событий.

По умолчанию событие mouseDown в окне, которое не является ключевым окном просто, переносит на следующий период окно и делает его ключом; событие не отправляется в NSView возразите, по которому происходит щелчок мышью. NSView может требовать начального события mouseDown, однако, путем переопределения acceptsFirstMouse: возвратиться YES. Параметром этого метода является событие mouseDown, произошедшее в неключевом окне, которое объект представления может исследовать, чтобы определить, хочет ли это получить событие от нажатия мыши и потенциально стать первым респондентом. Вы хотите поведение по умолчанию этого метода в, например, управление, влияющее на выбранный объект в окне. Однако в определенных случаях является надлежащим переопределить это поведение, такой что касается средств управления, которые должны получить mouseDown: сообщения, даже когда окно неактивно. Примерами средств управления, поддерживающих этот щелчок - через поведение, являются кнопки строки заголовка окна.

В Вашей реализации NSResponder метод события от нажатия мыши, часто первая вещь, которую Вы могли бы сделать, исследуют переданный - в NSEvent объект решить, является ли это событием, Вы хотите обработать. Если это - событие, которое Вы обрабатываете, то Вы, возможно, должны извлечь информацию из NSEvent возразите, чтобы помочь Вам обработать его. В частности можно получить следующую информацию от NSEvent объект:

Много объектов представления в Наборе Приложения (таких как средства управления и пункты меню) изменяют свое появление в ответ на события mouseDown, иногда только до последующего события mouseUp. Выполнение этого предоставляет визуальное подтверждение пользователю, что их действие является эффективным или что, теперь выбран объект, по которому щелкают. Перечисление 4-1 показывает простой пример этого.

Перечисление 4-1  Простая обработка щелчка мышью — появление изменяющегося представления

- (void)mouseDown:(NSEvent *)theEvent {
    [self setFrameColor:[NSColor redColor]];
    [self setNeedsDisplay:YES];
}
 
- (void)mouseUp:(NSEvent *)theEvent {
    [self setFrameColor:[NSColor greenColor]];
    [self setNeedsDisplay:YES];
}
 
- (void)drawRect:(NSRect)rect {
    [[self frameColor] set];
    NSRectFill(rect);
}

Но много объектов представления, особенно средства управления и ячейки, действительно больше, чем просто измените их появление в ответ на щелчки мышью. Одна общая парадигма для представления, по которому щелкают, для отправки сообщения действия в целевой объект (где и действие и цель являются устанавливаемыми свойствами представления). Как показано в Перечислении 4-2, представление обычно пересылает сообщение mouseUp: вместо mouseDown:, таким образом давая пользователям возможность изменить их умы середина щелчка.

Перечисление 4-2  Простая обработка щелчка мышью — отправка сообщения действия

- (void)mouseDown:(NSEvent *)theEvent {
    [self setFrameColor:[NSColor redColor]];
    [self setNeedsDisplay:YES];
}
 
- (void)mouseUp:(NSEvent *)theEvent {
    [self setFrameColor:[NSColor greenColor]];
    [self setNeedsDisplay:YES];
    [NSApp sendAction:[self action] to:[self target] from:self];
}
 
- (SEL)action {return action; }
 
- (void)setAction:(SEL)newAction {
    action = newAction;
}
 
- (id)target { return target; }
 
- (void)setTarget:(id)newTarget {
    target = newTarget;
}

Перечисление 4-3 дает более сложный, реальный пример. (Это из проекта в качестве примера для приложения Эскиза.) Эта реализация mouseDown: определяет, дважды щелкнули ли пользователи по графическому объекту и, если они сделали, включает редактирование того объекта. Иначе, если объект палитры выбран, он создает экземпляр того объекта в расположении щелчка мышью.

Перечисление 4-3  , Обрабатывающее событие mouseDown — приложение Эскиза

- (void)mouseDown:(NSEvent *)theEvent {
    Class theClass = [[SKTToolPaletteController sharedToolPaletteController] currentGraphicClass];
    if ([self editingGraphic]) {
        [self endEditing];
    }
    if ([theEvent clickCount] > 1) {
        NSPoint curPoint = [self convertPoint:[theEvent locationInWindow] fromView:nil];
        SKTGraphic *graphic = [self graphicUnderPoint:curPoint];
        if (graphic && [graphic isEditable]) {
            [self startEditingGraphic:graphic withEvent:theEvent];
            return;
        }
    }
    if (theClass) {
        [self clearSelection];
        [self createGraphicOfClass:theClass withEvent:theEvent];
    } else {
        [self selectAndTrackMouseWithEvent:theEvent];
    }
}

Классы Набора Приложения, реализующие средства управления, управляют этим поведением целевого действия для Вас.

Обработка операций перетаскивания мыши

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

С Набором Приложения можно проявить один из двух общих подходов при обработке перетащенных мышью событий. Первый подход переопределяет три NSResponder методы mouseDown:, mouseDragged:, и mouseUp: (для операций левой кнопки мыши). Для каждой последовательности перетаскивания Набор Приложения отправляет a mouseDown: обменивайтесь сообщениями к объекту респондента, затем отправляйте один или больше mouseDragged: сообщения и концы последовательность с a mouseUp: сообщение. Подход С тремя методами описывает этот подход.

Другой подход обрабатывает события от нажатия мыши в последовательности перетаскивания как единственное событие, от мыши вниз, посредством перетаскивания, к мыши. Чтобы сделать это, объект респондента должен обычно закорачивать нормальный цикл событий приложения путем ввода отслеживающего событие цикла, чтобы отфильтровать и обработать только события от нажатия мыши интереса. Например, NSButton возразите выделяет себя на событие mouseDown, затем следует за расположением мыши во время перетаскивания, выделения, когда мышь внутри и невыделение, когда мышь снаружи. Если мышь находится внутри на событии mouseUp, объект кнопки отправляет свое сообщение действия. Этот подход описан в Отслеживающем мышь Подходе Цикла.

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

Реализация частного лица mouseDown:, mouseDragged:, и mouseUp: методы часто являются лучшим проектным решением при записи событийно-управляемого приложения. Каждый из методов имеет ясно определенный объем, часто приводящий к более ясному коду. Этот подход также делает намного проще для подклассов переопределить поведение для обработки мыши вниз, перетащенной мышью, и события mouseUp. Однако этот метод может потребовать большего количества переменных кода и переменных экземпляра.

Подход с тремя методами

Для обработки перетаскивающей мышь работы можно переопределить три NSResponder методы, отмечающие дискретные этапы перетаскивающей мышь работы: mouseDown:, mouseDragged:, и mouseUp: (или, для перетаскивания правильной мыши, rightMouseDown:, rightMouseDragged:, и rightMouseUp:). Перетаскивающая мышь последовательность состоит из одной mouseDown: сообщение, сопровождаемое (обычно) многократным mouseDragged: сообщения и заключение mouseUp: сообщение.

Подкласс, реализовывая эти методы часто должен объявлять, что переменные экземпляра содержат изменяющиеся значения или состояния различных вещей между последовательными событиями. Этими вещами могли быть геометрические объекты, такие как прямоугольники или точки (соответствующий для просмотра кадров или областей в представлениях), или они могли быть простым указанием булевых значений, например, что выбран объект. В mouseDown: метод, представление обычно инициализирует любые связанные с перетаскиванием переменные экземпляра; в mouseDragged: метод это могло бы обновить те переменные экземпляра или проверить их до выполнения действия; и в mouseUp: метод, это часто сбрасывает те переменные экземпляра к их начальным значениям.

Поскольку перетаскивающие мышь операции часто перерисовывают объект в инкрементно изменяющих местоположениях, куда пользователи перетаскивают тот объект, реализации трех перетаскивающих мышь методов обычно должны находить расположение каждого события от нажатия мыши в системе координат представления. Как объяснено в Получении Расположения События, это требует, чтобы отправило представление locationInWindow к переданному - в NSEvent возразите и затем используйте convertPoint:fromView: метод для преобразования получающегося расположения в систему локальной координаты. Когда изменения в расположении или геометрии требуют, чтобы представление перерисовало себя или часть себя, представление отмечает области, нуждающиеся в дисплее с помощью setNeedsDisplay: или setNeedsDisplayInRect: метод и представление позже просят перерисовать себя (в drawRect:) через механизм автодисплея (предполагающий, что механизм не выключен).

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

Перечисление 4-4  , Обрабатывающее перетаскивающую мышь работу — подход с тремя методами

- (void)mouseDown:(NSEvent *)theEvent {
    // mouseInCloseBox and trackingCloseBoxHit are instance variables
    if (mouseInCloseBox = NSPointInRect([self convertPoint:[theEvent locationInWindow] fromView:nil], closeBox)) {
        trackingCloseBoxHit = YES;
        [self setNeedsDisplayInRect:closeBox];
    }
    else if ([theEvent clickCount] > 1) {
        [[self window] miniaturize:self];
        return;
    }
}
 
- (void)mouseDragged:(NSEvent *)theEvent {
    NSPoint windowOrigin;
    NSWindow *window = [self window];
 
    if (trackingCloseBoxHit) {
        mouseInCloseBox = NSPointInRect([self convertPoint:[theEvent locationInWindow] fromView:nil], closeBox);
        [self setNeedsDisplayInRect:closeBox];
        return;
    }
 
    windowOrigin = [window frame].origin;
 
    [window setFrameOrigin:NSMakePoint(windowOrigin.x + [theEvent deltaX], windowOrigin.y - [theEvent deltaY])];
}
 
- (void)mouseUp:(NSEvent *)theEvent {
    if (NSPointInRect([self convertPoint:[theEvent locationInWindow] fromView:nil], closeBox)) {
        [self tryToCloseWindow];
        return;
    }
    trackingCloseBoxHit = NO;
    [self setNeedsDisplayInRect:closeBox];
}

Отслеживающий мышь подход цикла

Отслеживающий мышь метод для обработки перетаскивающих мышь операций применяется в отдельном методе, обычно (но не обязательно) в mouseDown:. Респондент реализации возражает, сначала объявляет и возможно инициализирует одну или более локальных переменных для использования в цикле. Часто одна из этих переменных содержит значение, часто булевская переменная, использующаяся для управления циклом. Когда некоторому условию удовлетворяют, обычно получение события mouseUp, значение переменной изменяется; когда эта переменная тестируется в следующий раз через цикл, управление выходит из цикла.

Центральный метод отслеживающего мышь цикла NSApplication метод nextEventMatchingMask:untilDate:inMode:dequeue: и NSWindow метод того же имени. Эти методы выбирают события от очереди событий, имеющие типы, указанные одной или более константами маски типа; для перетаскивания мыши эти константы обычно NSLeftMouseDraggedMask и NSLeftMouseUpMask. События других типов оставляют в очереди. (Параметр режима цикла выполнения обоих nextEventMatchingMask:untilDate:inMode:dequeue: методы должны быть NSEventTrackingRunLoopMode.)

После получения события mouseDown выберите последующие события от нажатия мыши в использовании цикла nextEventMatchingMask:untilDate:inMode:dequeue:. Процесс NSLeftMouseDragged события, поскольку Вы обработали бы их в mouseDragged: метод (описанный в Подходе С тремя методами); точно так же дескриптор NSLeftMouseUp события, поскольку Вы обработали бы их в mouseUp:. Обычно события mouseUp указывают, что управление выполнением должно убежать из цикла.

mouseDown: шаблон метода в Перечислении 4-5 показывает один возможный вид модального цикла событий.

Перечисление 4-5  , Обрабатывающее притягивание мыши отслеживающий мышь цикл — простой пример

- (void)mouseDown:(NSEvent *)theEvent {
    BOOL keepOn = YES;
    BOOL isInside = YES;
    NSPoint mouseLoc;
 
    while (keepOn) {
        theEvent = [[self window] nextEventMatchingMask: NSLeftMouseUpMask |
                NSLeftMouseDraggedMask];
        mouseLoc = [self convertPoint:[theEvent locationInWindow] fromView:nil];
        isInside = [self mouse:mouseLoc inRect:[self bounds]];
 
        switch ([theEvent type]) {
            case NSLeftMouseDragged:
                    [self highlight:isInside];
                    break;
            case NSLeftMouseUp:
                    if (isInside) [self doSomethingSignificant];
                    [self highlight:NO];
                    keepOn = NO;
                    break;
            default:
                    /* Ignore any other kind of event. */
                    break;
        }
 
    };
 
    return;
}

Этот цикл преобразовывает расположение мыши и проверяет, является ли это в получателе. Это выделяет себя с помощью вымышленного highlight: метод и, при получении события mouseUp, это вызывает doSomethingSignificant выполнять важное действие. Вместо простого выделения, пользовательского NSView объект мог бы переместить выбранный объект, составить графическое изображение согласно расположению мыши и т.д.

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

Перечисление 4-6  , Обрабатывающее притягивание мыши отслеживающий мышь цикл — объединяет пример

- (void)mouseDown:(NSEvent *)theEvent
{
    if ([theEvent modifierFlags] & NSAlternateKeyMask)  {
        BOOL                dragActive = YES;
        NSPoint             location = [renderView convertPoint:[theEvent locationInWindow] fromView:nil];
        NSAutoreleasePool   *myPool = nil;
        NSEvent*            event = NULL;
        NSWindow            *targetWindow = [renderView window];
 
        myPool = [[NSAutoreleasePool alloc] init];
        while (dragActive) {
            event = [targetWindow nextEventMatchingMask:(NSLeftMouseDraggedMask | NSLeftMouseUpMask)
                                untilDate:[NSDate distantFuture]
                                inMode:NSEventTrackingRunLoopMode
                                dequeue:YES];
            if(!event)
                continue;
            location = [renderView convertPoint:[event locationInWindow] fromView:nil];
            switch ([event type]) {
                case NSLeftMouseDragged:
                    annotationPeel = (location.x * 2.0 / [renderView bounds].size.width);
                    [imageLayer showLens:(annotationPeel <= 0.0)];
                    [peelOffFilter setValue:[NSNumber numberWithFloat:annotationPeel] forKey:@"inputTime"];
                    [self refresh];
                    break;
 
                case NSLeftMouseUp:
                    dragActive = NO;
                    break;
 
                default:
                    break;
            }
        }
        [myPool release];
    } else {
        // other tasks handled here......
    }
}

Отслеживающий мышь цикл управляется только, пока пользователь фактически перемещает мышь. Если пользователь нажмет кнопку мыши, но никогда не перемещать саму мышь, это не будет работать, например, для порождения непрерывной прокрутки. Для этого Ваш цикл должен запустить периодический поток событий с помощью NSEvent метод класса startPeriodicEventsAfterDelay:withPeriod:, и добавьте NSPeriodicMask к битовому полю маски, переданному nextEventMatchingMask:. В switch оператор объект представления реализации может тогда проверить на события типа NSPeriodic и примите любые меры, в которых это нуждается к — прокрутка представления документа или перемещение шага в анимации, например. Если необходимо проверить расположение мыши во время периодического события, можно использовать NSWindow метод mouseLocationOutsideOfEventStream.

Отфильтровывание ключевых событий во время отслеживающих мышь операций

Потенциальной проблемой с отслеживающим мышь кодом является пользователь, нажимающий сочетание клавиш, которое является командой, такой как Команда-z (отмена), в то время как работа отслеживания в стадии реализации. Поскольку Ваш отслеживающий мышь код (или в подходе с тремя методами или в отслеживающем мышь цикле) не ищет то ключевое событие, код не мог бы знать, как обработать ту ключевую команду или мог обработать ее неправильно с нежелательными последствиями.

Проблема здесь с отслеживающим мышь циклом не могла бы быть с готовностью очевидной потому что, в конце концов, nextEventMatchingMask:untilDate:inMode:dequeue: цикл гарантирует, что только поставлены отслеживающие мышь события. Почему ключевые события были бы проблемой? Рассмотрите код в Перечислении 4-7. В то время как этот отслеживающий мышь цикл активен, скажем, пользователь дает некоторые ключевые эквивалентные команды.

Перечисление 4-7  Типичный отслеживающий мышь цикл

- (void)mouseDown:(NSEvent *)theEvent {
  NSPoint pos;
 
  while ((theEvent = [[self window] nextEventMatchingMask:
    NSLeftMouseUpMask | NSLeftMouseDraggedMask])) {
 
    NSPoint pos = [self convertPoint:[theEvent locationInWindow]
                            fromView:nil];
 
    if ([theEvent type] == NSLeftMouseUp)
      break;
 
    // Do some other processing...
  }
}

Что происходит после того, как отслеживающий мышь цикл завершает? После того, как пользователь отпускает кнопки мыши, приложение обрабатывает все незаконченные команды, соответствующие мнемонике пользователь, нажатый во время цикла. Эффекты этой задержанной обработки являются, вероятно, нежелательным. Можно принять меры против этого путем фильтрации потока событий для ключевых событий и затем явно игнорирования их. Перечисление 4-8 показывает, как можно изменить код выше для выполнения этого (новый код курсивом).

Перечисление 4-8  Типичный отслеживающий мышь цикл — с отрицаемыми ключевыми событиями

- (void)mouseDown:(NSEvent *)theEvent {
  NSPoint pos;
 
  while ((theEvent = [[self window] nextEventMatchingMask:
      NSLeftMouseUpMask | NSLeftMouseDraggedMask | NSKeyDownMask])) {
 
    NSPoint pos = [self convertPoint:[theEvent locationInWindow]
                            fromView:nil];
 
    if ([theEvent type] == NSLeftMouseUp)
        break;
         else if ([theEvent type] == NSKeyDown) {
                NSBeep();
                continue;
    }
 
    // Do some other processing...
  }
}

Для перетаскивания операций обработал с подходом с тремя методами, ситуация с одновременной мышью и ключевыми событиями несколько отличается. В этом случае AppKit обрабатывает события клавиатуры, как он обычно делает во время отслеживания. Если пользователь нажимает мнемосхему команды, даже в то время как работа отслеживания продолжается, объект приложения диспетчеризирует соответствующие сообщения своим целям. Таким образом, если, например, в приложении получения пользователь перетаскивает синий круг, они просто создали, и затем (возможно, случайно) нажимает Command-x (сокращение), в то время как кнопка мыши все еще снижается, код, обрабатывающий работу сокращения, собирается работать, удаляя объект, который перетаскивал пользователь.

Решение этой проблемы включает несколько шагов:

  • Объявите булеву переменную экземпляра, отражающую, когда работа перетаскивания в стадии реализации.

  • Установите эту переменную в YES в mouseDown: и сброс это к NO в mouseUp:.

  • Переопределение performKeyEquivalent: проверять значение переменной экземпляра и, если работа перетаскивания происходит, для отбрасывания ключевого события.

Перечисление 4-9 показывает код реализации для этого (isBeingManipulated булева переменная экземпляра).

Перечисление 4-9  , Отбрасывающее ключевые события во время работы перетаскивания с подходом с тремя методами

@implementation MyView
 
- (BOOL)performKeyEquivalent:(NSEvent *)anEvent
{
  if (isBeingManipulated) {
    if ([anEvent type] == NSKeyDown) // Can get NSKeyUp here too
      NSBeep ();
    return YES; // Claim we handled it
  }
 
  return NO;
}
 
- (void)mouseDown:(NSEvent *)anEvent
{
  isBeingManipulated = YES;
  // other code goes  here...
}
 
- (void)mouseUp:(NSEvent *)anEvent
{
  isBeingManipulated = NO;
  // other code goes here ...
}
 
@end

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