ГлавнаяMS ACCESS Об объектном подходе в VBA на примере изменения курсора мышки над элементами формы
Об объектном подходе в VBA на примере изменения курсора мышки над элементами формы
Автор Egorka
26.09.2002 г.
Для изменения курсора над элементами формы необходимо знать где движется курсор 'грызуна' и как его изменить. Как изменить - более менее понятно - найти нужные API, разобраться и сделать. А вот как определить где? Класс заранее не знает ни количество, ни типы control'ов для которых необходимо перехватывать OnMouseMove, а тут еще и секции формы...
1. Кого и сколько withevents? Это самое интересное место (на мой взгляд). Основную работу в моем примере выполняет класс clsMultiControlMonitor, он позволяет перехватывать события для любого количества объектов различных типов. Для работы класса используются служебные классы *Listener (т.е. clsCbBxListener - служебный класс для ComdoBox). В VBA можно сделать переменную withevents и сделать обработчики для нее, а как сделать массив или collection withevents? Принцип простой. Для каждого типа объекта создаем служебный класс (objectListener), где объявляем переменную withevents нужного типа и обработчики необходимых событий. Вот из них то мы и делаем collection. Т.е. для каждого TextBox'а, события которого мы хотим отслеживать, создаем служебный clsTxBxListener, добавляем в collection (можно в массив) и ждем от него сообщений. Да, совсем забыл, служебный класс должен как-то сообщить parent объекту (clsMultiControlMonitor), что произошло событие, для этого он должен иметь ссылку на головной объект. В обработчиках можно было напрямую вызывать какой-либо метод clsMultiControlMonitor, но я использовал интерфейс (ifcEventListener) для универсальности служебных классов (их можно будет использовать не только для clsMultiControlMonitor). Т.е. clsMultiControlMonitor implements ifcEventListener. Это значит, что clsMultiControlMonitor должен реализовать метод getEvent, в который служебный класс передает имя объекта, в котором произошло событие (свое имя), тип события и параметры события (Cancel, Button, Shift, X, Y). С другой стороны, parent объект должен уметь общаться со служебными классами. Он должен указать какие события нужно перехватывать и иметь возможность изменения имени служебного объекта (имени которое он укажет в getEvent). В нашем случае я использовал специальные имена для секций формы, что бы иметь возможность отличать события секций от событий control'ов. Для унификации общения со служебными классами (в коде мы можем не знать тип конкретного *Listener) я использовал интерфейс ifcEventGenerator. Т.е. каждый objectListener implements ifcEventGenerator и должен реализовать два метода: Listen (обрабатывать событие указанного типа) и Property Let Name (новое имя objectListener, по умолчанию используется control.Name). Вот как бы идея.
Реализация. addControl - определяет тип объекта, создает соответствующий objectListener, передает себя в качестве получателя событий и добавляет его в Collection cllControls. addControlEvent - назначает типы перехватываемых в objectListener событий. newControlName - меняет имя objectListener. ifcEventListener_getEvent - реализует централизованную обработку событий (получает сообщения от всех служебных классов хранящихся в cllControls). В нашем случае он просто передает события другому объекту который реализует ifcEventListener, т.е. объекту clsChangeCursor. Destroy - убивает все объекты в cllControls , его выполнение обязательно, т.к. каждый navy хранит ссылку на clsMultiControlMonitor, если Destroy не выполнить, после закрытия формы объект остается в памяти и Access скорее всего упадет.
2. Пора менять курсор. Класс clsChangeCursor является надстройкой над clsMultiControlMonitor. Для того, что бы получать события он естественно implements ifcEventListener. addCursor - загружает курсор из указанного файла и добавляет его в cllCursors с указанным ключом. addControl - добавляет control в clsMultiControlMonitor, назначает перехватываемые события (у нас одно evMouseMove) и связывает его с необходимым курсором (ключ курсора в cllCursors). ifcEventListener_getEvent - централизованно обрабатывает события и реализует логику по изменению курсора. Интерес представляет Init, помимо всего остального он сразу добавляет доступные секции формы в clsMultiControlMonitor и назначает им специальные имена. Destroy - не доверяем ни кому, делаем сами на Unload формы. Собственно изменение курсора выполняют две API функции SetCursor и SetClassLong. SetClassLong - связывает окно с указанным курсором, но этого не достаточно. Во-первых, Access создает подокно (не всегда) для элементов имеющих фокус. Для ListBox'а окошко создается всегда, независимо от фокуса (проверял только в XP). Во-вторых, при проведении мышкой над некоторыми элементами Access принудительно изменяет вид курсора. Например, над TextBox'ом в текстовый маркер. Пришлось добавить SetCursor - изменяет текущий курсор. Кое-где будет заметно мерцание курсора.
3. Как это использовать? Я не ставил своей целью создать полнофункциональный класс для изменения курсора в форме. Этим примером я хотел привлечь ваше внимание к идее, которая реализована в clsMultiControlMonitor. Применение этого класса гораздо шире. Довольно часто при создании классов необходима централизованная обработка событий различных элементов и объектов размещенных на форме. Например, вы хотите подсвечивать поля, данные которых пользователь изменил в текущей записи. Нет проблем, добавьте в служебные классы обработку события AfterUpdate. После этого не сложно написать класс-надстройку над clsMultiControlMonitor, который при открытии формы сам добавит все элементы подключенные к данным в clsMultiControlMonitor, в getEvent реализует подсветку полей, а в Form_BeforeUpdate что-нибудь спросит у пользователя и восстановит отображение контролов. Или, например, создав служебный класс для форм вы сможете централизованно отслеживать события во всех открытых формах. Тем не менее, вы хотите менять курсор мышки.
Cкопируйте все модули классов из примера. Возможно, вам придется самостоятельно написать служебные классы для других типов контролов.
В модуле формы, в которой вы хотите использовать изменение курсора, на уровне модуля объявляете переменную типа clsChangeCursor, например: octlSZ as clsChangeCursor
Выполняете метод .Init Me
При открытии формы:
создаете новый экземпляр объекта clsChangeCursor, например : Set octlSz = New clsChangeCursor
добавляете методом .addCursor имяКурсора, ИмяФайлакурсора все курсоры которые вы хотите использовать, например: octlSz.addCursor "Курсор1", "C:WINDOWSCURSORSAPPSTART.ANI" octlSz.addCursor "Курсор2", "C:WINDOWSCURSORSHOURGLAS.ANI"
добавляете методом .addControl Контрол, ИмяКурсора все контролы для которых вы хотите поменять курсор, например: octlSz.addControl Me!Field1, "Курсор1" octlSz.addControl Me!lst1, "Курсор2"
Вместо заключения. Обычно классы в VBA мы используем в основном для инкапсуляции кода. Т.е. берем набор функций и процедур связанных по смыслу или связанных с определенной задачей и оформляем в виде более-менее автономного класса. Это удобно, правильно и хорошо. Оставив на поверхности только Public методы и свойства, мы скрываем внутри класса не только саму логику задачи, но и ее реализацию. О достоинствах использования классов написано итак достаточно. Я бы хотел сказать пару слов в защиту объектности VBA. Да, VBA нехватает наследования для того чтобы стать полноценным объектным языком. Тем не менее, VBA имеет достаточно средств для абстрагирования и написания именно объектного кода. Давайте не забывать об этом, некоторые задачи раскладываются легче и красивее. Не скажу, что вам легче станет писать и возможно ваш код будет работать медленнее, но вы наверняка получите удовольствие. Главное в этом деле не перестараться. Мой пример ни в коем случае не является образцом объектного кода. Он просто показывает возможности объектного подхода при решении задач в VBA. Реализация работы clsMultiControlMonitor возможна и традиционным подходом, без вспомогательных классов и мудреных Implements