Создание пользовательского представления

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

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

Исходный код DragItemAround доступен через Соединение Разработчика Apple.

Выделение представления

Приложения создают новый экземпляр использования объекта представления initWithFrame:, определяемый инициализатор для NSView класс. Подкласс может указать другой метод как свой определяемый инициализатор, но initWithFrame: метод должен обеспечить основную требуемую функциональность. Как пример, NSTextView реализация initWithFrame: создает весь набор контейнерных объектов, связанных с NSTextView экземпляр, в то время как определяемый инициализатор, initWithFrame:textContainer: ожидает, что базовые контейнерные объекты будут предоставлены явно. initWithFrame: метод создает набор, и затем вызывает initWithFrame:textContainer:. Ваши пользовательские классы должны проявить тот же подход.

DraggableItemView переопределения класса initWithFrame: и устанавливает представленные свойства перемещаемого элемента к значениям по умолчанию, как показано в Перечислении 4-1.

Перечисление 4-1  DraggableItemView реализация initWithFrame:

- (id)initWithFrame:(NSRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
          // setup the initial properties of the
          // draggable item
          [self setItemPropertiesToDefault:self];
       }
    return self;
}

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

Перечисление 4-2  DraggableItemView реализация setItemPropertiesToDefault:

- (void)setItemPropertiesToDefault:sender
{
    [self setLocation:NSMakePoint(0.0,0.0)];
    [self setItemColor:[NSColor redColor]];
    [self setBackgroundColor:[NSColor whiteColor]];
}

Инициализация экземпляров представления, создаваемых в интерфейсном разработчике

Экземпляры представления, создающиеся в Интерфейсном Разработчике, не вызывают initWithFrame: когда их файлы пера загружаются, который часто вызывает беспорядок. Помните, что Интерфейсный Разработчик архивирует объект, когда он сохранил файл пера, таким образом, экземпляр представления будет уже создан и initWithFrame: будет уже вызван.

awakeFromNib метод предоставляет возможность для обеспечения инициализации представления, когда это создается в результате загружаемого файла пера. Когда файл пера, содержащий объект представления, загружается, каждый экземпляр представления получает awakeFromNib обменивайтесь сообщениями, когда будут разархивированы все объекты. Это обеспечивает объект возможность инициализировать любые атрибуты, не архивирующиеся с объектом в Интерфейсном Разработчике. DraggableItemView класс чрезвычайно прост, и не реализует awakeFromNib.

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

Если Вы не создали палитру Interface Builder для своего пользовательского представления, существует два метода, которые можно использовать для создания экземпляров подкласса в Интерфейсном Разработчике. Первое использует Пользовательский элемент прокси Представления в Интерфейсной палитре контейнеров Разработчика. Это представление является заместителем для Вашего пользовательского представления, позволяя Вам расположить и измерить представление относительно других представлений. Вы тогда указываете подкласс NSView то, что представление представляет использование инспектора. Когда файл пера загружается приложением, пользовательский прокси представления создает новый экземпляр указанного подкласса представления и инициализирует его с помощью initWithFrame: метод, проводя любые флаги автоизменения размеров по мере необходимости. Экземпляр представления тогда получает awakeFromNib сообщение.

Второй метод применяется, когда Ваш пользовательский подкласс наследовался от представления, что Интерфейсный Разработчик предоставляет прямую поддержку для. Для использования в своих интересах встроенной поддержки Интерфейсного Разработчика создайте экземпляр представления, что у Интерфейсного Разработчика есть прямая поддержка, и затем используйте инспектора для изменения имени класса на пользовательский подкласс. Например, можно создать NSScrollView экземпляр в Интерфейсном Разработчике и указывает что пользовательский подкласс (MyScrollView) должен использоваться вместо этого, снова с помощью инспектора. В этом случае, когда файл пера загружается приложением, экземпляр представления был уже создан и MyScrollView реализация initWithFrame: никогда не вызывается. MyScrollView экземпляр получает awakeFromNib обменивайтесь сообщениями и может сконфигурировать себя соответственно.

Рисование содержания представления

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

Если необходимо вызвать непосредственное получение представления, отправьте представлению один из display... сообщения объявляются обоими NSView и NSWindow. Можно также заблокировать внимание на представление сами, нарисовать что-то, и затем разблокировать фокус. Однако регистрация задержала рисующие запросы через setNeedsDisplay: или setNeedsDisplayInRect: методы являются предпочтительным подходом, потому что это более эффективно.

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

Реализация drawRect: Метод

Для конкретного подкласса NSView для отображения любого вида содержания это должно только реализовать drawRect: метод. Этот метод вызывается во время процесса дисплея для генерации кода, которым это представляется сервером окна в растровое изображение. drawRect: берет отдельный аргумент, прямоугольник, описывающий область, которая должна быть нарисована в собственной системе координат получателя.

DraggableItemView реализация drawRect: заполняет границы представления с указанным цветом фона. Это тогда вычисляет границы перемещаемого элемента (Перечисление 4-3) и заполняет его указанным цветом.

Перечисление 4-3  DraggableItemView реализация calculatedItemBounds:

- (NSRect)calculatedItemBounds
{
    NSRect calculatedRect;
 
    // calculate the bounds of the draggable item
    // relative to the location
    calculatedRect.origin=location;
 
    // the example assumes that the width and height
    // are fixed values
    calculatedRect.size.width=60.0;
    calculatedRect.size.height=20.0;
 
    return calculatedRect;
}

Полноценное внедрение drawRect: показан в Перечислении 4-4.

Перечисление 4-4  DraggableItemView реализация drawRect:

- (void)drawRect:(NSRect)rect
{
    // erase the background by drawing white
    [[NSColor whiteColor] set];
    [NSBezierPath fillRect:rect];
 
    // set the current color for the draggable item
    [[self itemColor] set];
 
    // draw the draggable item
    [NSBezierPath fillRect:[self calculatedItemBounds]];
}

Отправление рисования указаний и данных к серверу окна имеет стоимость и лучше минимизировать ту стоимость, если это возможно. Можно сделать это путем тестирования, пересекает ли определенная графическая форма прямоугольник что drawRect: метод просят нарисовать. Посмотрите, что Представление Оптимизации Рисует для получения дополнительной информации, а также дополнительные рекомендации производительности.

Отмечание представления как нуждающийся в дисплее

Наиболее распространенный способ того, чтобы заставлять представление восстановить изображение состоит в том, чтобы сказать ему, что его изображение недопустимо. На каждом проходят через цикл событий, все представления, которые должны восстановить изображение, делают так. NSView определяет два метода для маркировки изображения представления как недопустимые: setNeedsDisplay:, который лишает законной силы весь прямоугольник границ представления, и setNeedsDisplayInRect:, который лишает законной силы часть представления. Автоматическим дисплеем представлений управляет их окно; можно повернуть это поведение от использования NSWindow setAutodisplay: метод. Необходимо редко должны быть сделать это, однако; механизм автодисплея хорошо подходит для большинства видов обновления, и восстановить изображение.

Механизм автодисплея вызывает различные методы, фактически выполняющие работу отображения. Можно также использовать эти методы, чтобы вынудить представление восстановить изображение себя сразу при необходимости. display и displayRect: дубликаты к упомянутым выше методам; оба заставляют получатель восстанавливать изображение себя независимо от того, должен ли он или нет. Два дополнительных метода, displayIfNeeded и displayIfNeededInRect:, восстановите изображение лишенных законной силы прямоугольников в получателе, если он был отмечен недопустимый с методами выше. Фактически нарисованные прямоугольники, как гарантируют, будут, по крайней мере, отмеченными как недопустимые, но представление может объединить их в прямоугольники большего размера для сохранения многократных вызовов drawRect:.

Если Вы хотите исключить фоновые представления из рисования, вынуждая дисплей произойти безусловно, можно использовать NSView методы, явно опускающие отступать до непрозрачного наследователя. Эти методы, displayRectIgnoringOpacity:, displayIfNeededIgnoringOpacity, и displayIfNeededInRectIgnoringOpacity:.

В DraggableItemView пример, setNeedsDisplayInRect: вызывается, когда расположение перемещаемого элемента установлено явно, когда расположение смещается, и когда изменяется цвет элемента. Когда цвет фона выбран, все представление отмечено как нуждающийся в дисплее.

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

Непрозрачность представления

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

Если экземпляр представления может гарантировать, что заполнит все пиксели в своих границах с помощью непрозрачных цветов, он должен реализовать метод isOpaque, возврат YES. NSView реализация isOpaque возвраты NO. Подклассы должны переопределить этот метод для возврата YES если все пиксели в содержании представления будут нарисованы непрозрачно.

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

Перечисление 4-5  DraggableItemView реализация isOpaque

- (BOOL)isOpaque
{
    // If the background color is opaque, return YES
    // otherwise, return NO
    return [[self backgroundColor] alphaComponent] >= 1.0 ? YES : NO;
}

Ответ на пользовательские события и действия

Представления обычно являются получателями большей части события и сообщений действия. NSView разделите на подклассы переопределяет надлежащие методы обработки событий, объявленные NSResponder класс. Когда экземпляр пользовательского экземпляра представления является первым респондентом, это получает сообщения о событиях, поскольку они отправляются перед другими объектами. Точно так же путем реализации методов действия, часто отправляемых другими объектами пользовательского интерфейса, такими как пункты меню, когда пользовательский экземпляр представления является первым респондентом, он получает те сообщения. Посмотрите Руководство по Обработке событий Какао для полного обсуждения обработки событий и цепочки респондента.

От сообщений о событиях отказываются цепочка респондента от первого респондента. Для всех представлений, за исключением представления содержания окна, следующий респондент представления является его суперпредставлением. Когда экземпляры представления вставляются в иерархию представления, следующий респондент установлен автоматически. Вы никогда не должны отправлять setNextResponder: обменивайтесь сообщениями непосредственно к объекту представления. Если необходимо добавить объекты к цепочке респондента, необходимо добавить их наверху цепочки респондента окна — путем разделения на подклассы NSWindow самостоятельно, если это не имеет никакого делегата или класса делегата, если это делает.

Как класс, обрабатывающий дисплей, NSView обычно получатель событий от нажатия мыши и событий клавиатуры. События от нажатия мыши запускаются при представлении, что щелчок происходит в и отказан цепочка респондента. События клавиатуры запускаются в первом респонденте и отказаны цепочка респондента.

Становление первым респондентом

Представление, которое является первым респондентом, получает ключевые события и сообщения действия перед другими объектами. Представления могут распространить это, они могут стать первым респондентом путем переопределения acceptsFirstResponder сообщение и возврат YES. Значение по умолчанию NSResponder возвраты реализации NO. Если представление не является первым респондентом, оно получает только сообщения мыши вниз. Поскольку DraggableItemView объект реагирует на основные ключевые вниз события, а также NSResponder сообщения действия, сгенерированные в ответ на нажатие клавиш со стрелками, оно возвращается YES для acceptsFirstResponder как показано в Перечислении 4-6.

Перечисление 4-6  DraggableItemView реализация acceptsFirstResponder

- (BOOL)acceptsFirstResponder
{
    return YES;
}

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

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

Представления, которые могут стать первым респондентом и обработать ключевые события обычно, принимают участие в ключевом цикле представления окна. Цикл ключевого представления позволяет пользователю переключаться между представлениями в окно путем нажатия клавиш Tab или Shift-Tab. NSView обеспечивает много методов для установки и получения представлений в цикле ключевого представления. Чаще всего упорядочивание цикла ключевого представления установлено в Интерфейсном Разработчике путем соединения представления с другим представлением nextKeyView выход.

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

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

По умолчанию представление не получает события mouseDown, если это не находится в frontmost окне, называемом ключевым окном. Путем переопределения acceptsFirstMouse: метод и возврат YES, окно сразу становится ключевым окном и реагирует на мышь вниз.

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

Окно определяет который представление в иерархии представления отправить событие mouseDown с помощью NSView метод hitTest:. Как только корректное представление расположено, оно отправляется a mouseDown: событие. Существуют соответствующие события mouseDown, отправленные для действий, сделанных с правой кнопкой мыши, а также с другими кнопками мыши с помощью rightMouseDown: и otherMouseDown: методы соответственно. Расположение события от нажатия мыши в системе координат окна получателя возвращается путем отправки объекта-события, переданного mouseDown: метод a locationInWindow сообщение. Для перевода точки в систему координат представления используйте метод convertPoint:fromView: передача nil как параметр представления. Перечисление 4-7 иллюстрирует DraggableItemView реализация подкласса mouseDown: метод.

Перечисление 4-7  DraggableItemView реализация mouseDown:

-(void)mouseDown:(NSEvent *)event
{
    NSPoint clickLocation;
    BOOL itemHit=NO;
 
    // convert the mouse-down location into the view coords
    clickLocation = [self convertPoint:[event locationInWindow]
                  fromView:nil];
 
    // did the mouse-down occur in the item?
    itemHit = [self isPointInItem:clickLocation];
 
    // Yes it did, note that we're starting to drag
    if (itemHit) {
    // flag the instance variable that indicates
    // a drag was actually started
    dragging=YES;
 
    // store the starting mouse-down location;
    lastDragLocation=clickLocation;
 
    // set the cursor to the closed hand cursor
    // for the duration of the drag
    [[NSCursor closedHandCursor] push];
    }
}

Эта реализация получает расположение мыши вниз и преобразовывает его в систему координат представления. Так как подкласс элемента перетаскивания позволяет пользователю перетаскивать элемент только, когда событие mouseDown происходит в перемещаемом прямоугольнике, реализация вызывает isPointInItem: метод, который, как показывают в Перечислении 4-8, протестировал, была ли мышь вниз в границах перемещаемого элемента. Если это, переменная экземпляра перетаскивания установлена в YES отметить, что представление не должно игнорировать mouseDragged: события. Чтобы лучше отразить пользователю, что перетаскивание происходит, курсор установлен в закрытый ручной курсор.

Перечисление 4-8  DraggableItemView реализация isPointInItem:

- (BOOL)isPointInItem:(NSPoint)testPoint
{
    BOOL itemHit=NO;
 
    // test first if we're in the rough bounds
    itemHit = NSPointInRect(testPoint,[self calculatedItemBounds]);
 
    // yes, lets further refine the testing
    if (itemHit) {
    // if this was a non-rectangular shape, you would refine
    // the hit testing here
    }
 
    return itemHit;
}

Заметьте что mouseDown: реализация в Перечислении 4-7 не вызывает супер реализацию. NSView реализация по умолчанию класса для событий обработки мыши наследована от NSResponder и передайте событие цепочка респондента для обработки, обойдя рассматриваемое представление полностью. Обычно пользовательское NSView подкласс не должен вызывать супер реализацию ни одного из методов события от нажатия мыши.

Представления часто должны отслеживать перетаскивание мыши после того, как будет получено событие mouseDown. В то время как кнопка мыши удерживается и перемещения мыши, представление получает mouseDragged: сообщения. DraggableItemView реализация mouseDragged: показан в Перечислении 4-9.

Перечисление 4-9  DraggableItemView реализация mouseDragged:

-(void)mouseDragged:(NSEvent *)event
{
    if (dragging) {
       NSPoint newDragLocation=[self convertPoint:[event locationInWindow]
                                         fromView:nil];
 
 
       // offset the item by the change in mouse movement
       // in the event
       [self offsetLocationByX:(newDragLocation.x-lastDragLocation.x)
                          andY:(newDragLocation.y-lastDragLocation.y)];
 
       // save the new drag location for the next drag event
       lastDragLocation=newDragLocation;
 
       // support automatic scrolling during a drag
       // by calling NSView's autoscroll: method
       [self autoscroll:event];
    }
}

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

offsetLocationByX:andY: метод, вызванный mouseDragged: метод показан в Перечислении 4-10. Это отмечает область перемещаемого элемента как нуждающийся в дисплее прежде и после изменения расположения элемента требуемой суммой. Если возвращается представление YES когда отправлено isFlipped сообщение, смещение в вертикальном направлении умножается на-1 для соответствия зеркально отраженным координатам представления. В DraggableItemView реализация код является factored в свой собственный метод, потому что это будет снова использовано позже.

Перечисление 4-10  DraggableItemView реализация offsetLocationByX:andY:

- (void)offsetLocationByX:(float)x andY:(float)y
{
    // tell the display to redraw the old rect
    [self setNeedsDisplayInRect:[self calculatedItemBounds]];
 
    // since the offset can be generated by both mouse moves
    // and moveUp:, moveDown:, etc.. actions, we'll invert
    // the deltaY amount based on if the view is flipped or
    // not.
    int invertDeltaY = [self isFlipped] ? -1: 1;
 
    location.x=location.x+x;
    location.y=location.y+y*invertDeltaY;
 
    // invalidate the new rect location so that it'll
    // be redrawn
    [self setNeedsDisplayInRect:[self calculatedItemBounds]];
 
}

Наконец, когда кнопка мыши отпускается, представление получает a mouseUp: сообщение. DraggableItemView реализация, показанная в Перечислении 4-11, обновляет переменную экземпляра перетаскивания, чтобы указать, что действие перетаскивания завершило и сбрасывает курсор. invalidateCursorRectsForView: сообщение обсуждено в конце этого раздела.

Перечисление 4-11  DraggableItemView реализация mouseUp:

-(void)mouseUp:(NSEvent *)event
{
    dragging=NO;
 
    // finished dragging, restore the cursor
    [NSCursor pop];
 
    // the item has moved, we need to reset our cursor
    // rectangle
    [[self window] invalidateCursorRectsForView:self];
}

Второй метод для обработки перетаскивания мыши иногда используется, обычно называемый «срыванием» цикла событий. Приложение может реализовать mouseDown: метод и цикл постоянно, собирая перетащенные мышью события до события mouseUp получены. События, не соответствующие маску события, остаются в конечном счете очередью и обрабатываются, когда существует цикл.

Если DraggableItemView класс должен был реализовать то же поведение с помощью этого метода, это только реализует mouseDown: метод, устраняя mouseDragged: и mouseUp: реализации метода. mouseDown: реализация, показанная в Перечислении 4-12, использует «срывающий» метод.

  Альтернатива перечисления 4-12 mouseDown: реализация

-(void)mouseDown:(NSEvent *)event
{
    BOOL loop = YES;
 
    NSPoint clickLocation;
 
    // convert the initial mouse-down location into the view coords
    clickLocation = [self convertPoint:[event locationInWindow]
                  fromView:nil];
 
    // did the mouse-down occur in the draggable item?
    if ([self isPointInItem:clickLocation]) {
        // we're dragging, so let's set the cursor
    // to the closed hand
    [[NSCursor closedHandCursor] push];
 
    NSPoint newDragLocation;
 
    // the tight event loop pattern doesn't require the use
    // of any instance variables, so we'll use a local
    // variable localLastDragLocation instead.
    NSPoint localLastDragLocation;
 
    // save the starting location as the first relative point
    localLastDragLocation=clickLocation;
 
    while (loop) {
        // get the next event that is a mouse-up or mouse-dragged event
        NSEvent *localEvent;
        localEvent= [[self window] nextEventMatchingMask:NSLeftMouseUpMask | NSLeftMouseDraggedMask];
 
 
        switch ([localEvent type]) {
        case NSLeftMouseDragged:
 
            // convert the new drag location into the view coords
            newDragLocation = [self convertPoint:[localEvent locationInWindow]
                        fromView:nil];
 
 
            // offset the item and update the display
            [self offsetLocationByX:(newDragLocation.x-localLastDragLocation.x)
                       andY:(newDragLocation.y-localLastDragLocation.y)];
 
            // update the relative drag location;
            localLastDragLocation=newDragLocation;
 
            // support automatic scrolling during a drag
            // by calling NSView's autoscroll: method
            [self autoscroll:localEvent];
 
            break;
        case NSLeftMouseUp:
            // mouse up has been detected,
            // we can exit the loop
            loop = NO;
 
            // finished dragging, restore the cursor
            [NSCursor pop];
 
            // the rectangle has moved, we need to reset our cursor
            // rectangle
            [[self window] invalidateCursorRectsForView:self];
 
            break;
        default:
            // Ignore any other kind of event.
            break;
        }
    }
    };
    return;
}

Отслеживание движений мыши

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

Перемещенные в мышь события инициируются NSWindow экземпляр, содержащий представление. Для представления для получения перемещенных в мышь событий это должно явно запросить их путем отправки его окна a setAcceptsMouseMovedEvents: сообщение, передавая YES как параметр. Когда включено, представление получает mouseMoved: события каждый раз, когда курсор расположен в представлении. К сожалению, не возможно включить перемещенные в мышь события для единственного представления с помощью этого метода.

NSView класс позволяет экземпляру представления регистрировать прямоугольники отслеживания. При регистрации объекта, поскольку владелец прямоугольника отслеживания заставляет владельца получать mouseEntered: и mouseExited: сообщения как курсор входят и существуют прямоугольник. Приложение регистрирует прямоугольники отслеживания с помощью NSView метод addTrackingRect:owner:userData:assumeInside:. Прямоугольник отслеживания предоставлен в системе координат представления, и владелец является объектом, который получит mouseEntered: и mouseExited: сообщения. userData параметр является любым произвольным объектом, который будет предоставлен как userData объект в NSEvent объект передал mouseEntered: и mouseExited: методы. assumeInside параметр указывает, как ли курсор, должно предполагаться, в прямоугольнике отслеживания первоначально. Метод возвращает тег отслеживания, идентифицирующий прямоугольник отслеживания, и тег отслеживания используется, чтобы не зарегистрировать владельца для отслеживания уведомлений с помощью метода removeTrackingRect:. Приложение может зарегистрировать прямоугольники отслеживания только для представлений, в настоящее время выводящихся на экран в окне.

Несмотря на то, что отслеживающие прямоугольники создаются и используются представлениями, они фактически сохраняются окном представления. Когда представление делает, в результате отслеживающие прямоугольники автоматически не перемещают или изменяют размеры. Это - ответственность подкласса удалить и повторно зарегистрировать прямоугольники отслеживания, когда кадр представления изменяется, или это вставляется как подпредставление. Это обычно делается путем переопределения NSView метод resetCursorRects.

NSView также обеспечивает методы для поддержки общего использования отслеживания прямоугольников; изменение курсора в результате мыши, вводящей прямоугольник. addCursorRect:cursor: метод позволяет Вам регистрировать прямоугольник с помощью системы координат представления и указывать курсор, который должен быть выведен на экран, в то время как мышь по тому прямоугольнику. Прямоугольники курсора энергозависимы. Когда окно представления изменяет размеры, кадр или границы представления изменяются, представление перемещено в иерархию, или представление прокручивается, представление получает a resetCursorRects сообщение. Подклассы должны переопределить resetCursorRects и зарегистрируйте любые требуемые прямоугольники курсора и прямоугольники отслеживания в том методе. removeCursorRect:cursor: метод позволяет Вам явно удалять прямоугольник курсора, соответствующий предоставленные параметры точно. discardCursorRects метод удаляет все прямоугольники курсора для представления.

DraggableItemView обеспечивает визуальную обратную связь, что курсор по перемещаемому элементу путем изменения курсора на открытый дескриптор. Реализация resetCursorRects, показанный в Перечислении 4-13, отбрасывает все текущие прямоугольники курсора и добавляет новый прямоугольник курсора для границ перемещаемого элемента.

Перечисление 4-13  DraggableItemView реализация resetCursorRects

-(void)resetCursorRects
{
    // remove the existing cursor rects
    [self discardCursorRects];
 
    // add the draggable item's bounds as a cursor rect
 
    // clip the draggable item's bounds to the view's visible rect
    NSRect clippedItemBounds = NSIntersectionRect([self visibleRect], [self calculatedItemBounds]);
 
    // if the clipped item bounds isn't empty then the item is at least partially
    // in the visible rect. Register the clipped item bounds
    if (!NSIsEmptyRect(clippedItemBounds)) {
         [self addCursorRect:clippedItemBounds cursor:[NSCursor openHandCursor]];
    }
}

Добавление прямоугольника курсора для представления автоматически не ограничивает прямоугольник курсора видимой областью представления. Необходимо сделать это сами путем нахождения пересечения предложенного прямоугольника курсора с видимым прямоугольником представления. Если получающийся прямоугольник не пуст, он должен быть передан как первый параметр addCursorRect:cursor: метод.

Вы никогда не должны вызывать resetCursorRects непосредственно; вместо этого отправьте окно представления invalidateCursorRectsForView: сообщение, передавая надлежащее представление. DraggableItemView возразите должен сбросить его прямоугольник курсора каждый раз перемещаемые перемещения элемента. mouseUp: реализация, показанная в Перечислении 4-11, отправляет окно представления invalidateCursorRectsForView: сообщение, передавая само представление как параметр. Аналогично, в версии mouseDown: это срывает цикл событий, показанный в Перечислении 4-12, invalidateCursorRectsForView: когда событие mouseUp обнаруживается, сообщение отправляется.

Обработка ключевых событий в представлении

Как обсуждено в Становлении Первым Респондентом, представление получает ключевые вниз события, только если это переопределяет acceptsFirstResponder и возвраты YES. Поскольку DraggableItemView объект реагирует на пользовательские нажатия клавиш, класс переопределяет этот метод и возвраты YES.

Существуют связанные методы двух ключей вниз, предоставленные NSResponder: методы keyDown: и performKeyEquivalent:. NSResponder также объявляет много действий респондента, инициированных ключевыми вниз событиями. Эти действия отображают определенные нажатия клавиш на общие действия. Путем реализации методов соответствующих мер можно обойти переопределение более сложного keyDown: метод.

Ваше пользовательское представление должно переопределить performKeyEquivalent: метод, если Ваше представление реагирует на простые ключевые эквиваленты. Использование в качестве примера ключевого эквивалента устанавливает клавишу Return как ключевой эквивалент кнопки. Когда пользователь нажимает Return, действия кнопки, как будто по нему щелкнули. Реализация подкласса performKeyEquivalent: должен возвратиться YES если это обработало ключевое событие, NO если от этого нужно отказаться цепь событий. Если представление реализует performKeyEquivalent:, это обычно также не реализует keyDown:.

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

Перечисление 4-14  DraggableItemView реализация keyDown:

- (void)keyDown:(NSEvent *)event
{
    BOOL handled = NO;
    NSString  *characters;
 
    // get the pressed key
    characters = [event charactersIgnoringModifiers];
 
    // is the "r" key pressed?
    if ([characters isEqual:@"r"]) {
        // Yes, it is
        handled = YES;
 
        // reset the rectangle
        [self setItemPropertiesToDefault:self];
    }
    if (!handled)
        [super keyDown:event];
 
}

Представление обрабатывает NSResponder методы действия путем простой реализации надлежащего метода. DraggableItemView класс реализует четыре из этих методов, соответствуя вверх, вниз, влево и вправо действия перемещения. Реализации показаны в Перечислении 4-15.

Перечисление 4-15  DraggableItemView реализация moveUp:, moveDown:, moveLeft:, и moveRight: действия

-(void)moveUp:(id)sender
{
    [self offsetLocationByX:0 andY: 10.0];
    [[self window] invalidateCursorRectsForView:self];
}
 
-(void)moveDown:(id)sender
{
    [self offsetLocationByX:0 andY:-10.0];
    [[self window] invalidateCursorRectsForView:self];
}
 
-(void)moveLeft:(id)sender
{
    [self offsetLocationByX:-10.0 andY:0.0];
    [[self window] invalidateCursorRectsForView:self];
}
 
-(void)moveRight:(id)sender
{
    [self offsetLocationByX:10.0 andY:0.0];
    [[self window] invalidateCursorRectsForView:self];
}

Каждый из методов в Перечислении 4-15 сместил расположение перемещаемого элемента в надлежащем направлении с помощью offsetLocationByX:andY: метод, передавая сумму для возмещения прямоугольника. Вертикальное смещение корректируется offsetLocationByX:andY: реализация как надлежащая, если зеркально отражается представление. После перемещения прямоугольника каждый метод лишает законной силы прямоугольники курсора. Эта функциональность, возможно, также была реализована в keyDown: непосредственно путем исследования символа Unicode нажатой клавиши, обнаружения клавиш со стрелками и действия соответственно. Однако использование методов действия респондента позволяет командам быть повторно отображенными пользователем.

Обработка Методов действия через Цепочку Респондента

NSResponder не единственный класс, который может генерировать события на цепочке респондента. Любое управление, реализующее целевые методы действия, может отправить те действия через цепочку респондента, а не к конкретному объекту путем соединения управления с первым прокси респондента в Интерфейсном Разработчике и указания действия. Детальное обсуждение передающих сообщений действия через цепочку респондента доступно в «Цепочке Респондента» в Руководстве по Обработке событий Какао.

DraggableItemView класс реализует changeColor: метод, отправляющийся через цепочку респондента, когда цвет изменяется в панели Color. Перечисление 4-16 показывает DraggableItemView реализация changeColor:.

Перечисление 4-16  DraggableItemView реализация changeColor:

- (void)changeColor:(id)sender
{
    // Set the color in response
    // to the color changing in the Color panel.
    // get the new color by asking the sender, the Color panel
    [self setItemColor:[sender color]];
}

Когда панель Color видима и экземпляр DraggableItemView класс является первым респондентом, изменение цвета в панели Color заставляет прямоугольник изменять цвет.

Методы доступа свойства

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

DraggableItemView класс реализует метода get и методы доступа метода set для следующих свойств: itemColor, backgroundColor, и location. Каждый из методов доступа метода set тестирует, чтобы видеть, отличается ли новое значение от текущей стоимости и, если это - сохраняет новое значение и отмечает представление как бывший должный восстановить изображение надлежащей части. Кроме того, setLocation: когда расположение изменяется, метод также лишает законной силы прямоугольник отслеживания курсора.

Перечисление 4-17  DraggableItemView методы доступа

- (void)setItemColor:(NSColor *)aColor
{
    if (![itemColor isEqual:aColor]) {
        [itemColor release];
        itemColor = [aColor retain];
 
        // if the colors are not equal, mark the
        // draggable rect as needing display
        [self setNeedsDisplayInRect:[self calculatedItemBounds]];
    }
}
 
 
- (NSColor *)itemColor
{
    return [[itemColor retain] autorelease];
}
 
- (void)setBackgroundColor:(NSColor *)aColor
{
    if (![backgroundColor isEqual:aColor]) {
        [backgroundColor release];
        backgroundColor = [aColor retain];
 
        // if the colors are not equal, mark the
        // draggable rect as needing display
        [self setNeedsDisplayInRect:[self calculatedItemBounds]];
    }
}
 
 
- (NSColor *)backgroundColor
{
    return [[backgroundColor retain] autorelease];
}
 
- (void)setLocation:(NSPoint)point
{
    // test to see if the point actually changed
    if (!NSEqualPoints(point,location)) {
        // tell the display to redraw the old rect
        [self setNeedsDisplayInRect:[self calculatedItemBounds]];
 
        // reassign the rect
        location=point;
 
        // display the new rect
        [self setNeedsDisplayInRect:[self calculatedItemBounds]];
 
        // invalidate the cursor rects
        [[self window] invalidateCursorRectsForView:self];
    }
}
 
- (NSPoint)location {
    return location;
}
 

Освобождение представления

dealloc метод вызывают, когда представление сохраняет количество, нуль. Ваше приложение никогда не должно вызывать dealloc явно. Механизм автовыпуска вызывает его в надлежащих случаях.

DraggableItemView реализация dealloc выпускает объект цвета отображения и вызывает супер реализацию dealloc.

- (void)dealloc
{
    [color release];
    color=nil;
    [super dealloc];
}