Создание пользовательского представления
NSView
класс действует в основном как абстрактный суперкласс; обычно Вы создаете экземпляры его подклассов, не NSView
самостоятельно. NSView
обеспечивает общий механизм для отображения содержания на экране и для обработки событий от нажатия мыши и событий клавиатуры, но ее экземпляры испытывают недостаток в возможности фактически нарисовать что-либо. Если Ваше приложение должно вывести на экран содержание или обработать события от нажатия мыши и события клавиатуры определенным способом, необходимо будет создать пользовательский подкласс NSView
.
Для обеспечения конкретного примера эта глава описывает реализацию DraggableItemView
, подкласс NSView
. DraggableItemView
класс выводит на экран простой элемент и позволяет пользователю перетаскивать его в представлении. Представление также поддерживает перемещение элемента путем нажатия клавиш со стрелками и выбирания цвета элемента. Это обеспечивает кодирующее значение ключа соответствие для расположения элемента, его цвета и цвета фона представления. Класс иллюстрирует следующие задачи программирования представления:
Выделение и освобождение представления.
Рисование содержания представления.
Отмечание частей представления для обновления в ответ на изменения значения.
Ответ на инициируемые пользователями события от нажатия мыши.
Обновление курсора, когда мышь по перемещаемому элементу.
Ответ на инициируемые пользователями события нажатия клавиши.
Реализация
NSResponder
методы действия.Обеспечение кодирующих значение ключа совместимых средств доступа для его устанавливаемых свойств.
Исходный код 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]; |
} |