[an error occurred while processing this directive][an error occurred while processing this directive] Создание элемента управления ActiveX
- Создание проекта
- Добавление нового свойства
- Добавление нового метода
- Добавление нового события
- Корректировка страниц свойств
- Связь данных
OLE-элементы управления создаются в проектах специального типа. Каркас приложения подготавливается при помощи инструментального средства
MFC ActiveX ControlWizard (OLE ControlWizard), а затем в него добавляется код, определяющий специфику элемента. На втором этапе широко применяется средство ClassWizard.Перечислим основные моменты создания OLE-элемента управления:
- создание остова проекта;
- создания графического образа инструментальной кнопки, задающей элемент управления;
- добавление новых свойств;
- добавление новых методов;
- добавление событий;
- корректировка базовой страницы свойств и добавление новых страниц;
- связывание данных.
Создание проекта
Остов проекта создается при помощи средства MFC ActiveX ControlWizard (OLE ControlWizard) за два шага, на каждом из которых можно выбрать нужные опции. Следует отметить, что чаще всего принимаются значения, предлагаемые по умолчанию.
Средство ControlWizard создаст совокупность файлов, имена которых по умолчанию строятся с использованием имени проекта. К примеру, для проекта Name (все опции которого выбираются по умолчанию), создаются следующие основные файлы (см.
Приложение 4):NameCtl.h, NameCtl.сpp - объявляют и реализуют класс CNameCtrl, производный от базового класса COleControl из библиотеки MFC. Он определяет базовые свойства и поведение OCX-объектов.
Name.h, Name.cpp - необходимы для инициализации DLL-файла, содержащего OCX-объект Name Control, его выгрузки и выполнения других инициализационных функций. Они определяют класс CNameApp, производный от COleControlModule. Методы InitInstance() b ExitInstance() этого класса создают экземпляр класса, регистрируют и инициализируют элемент управления.
NamePpg.h, NamePpg.cpp - определяют класс CNameProgPage, задающий базовую страницу свойств элемента управления.
NameCtl.bmp - содержит графический образ инструментальной кнопки, появляющейся на панели инструментов при добавлении OLE-элемента в новый проект, являющегося контейнером для OCX-объектов. По умолчанию есть некоторый стандартный образ элемента управления.
Name.rc, Resource.h - файл ресурсов проекта и его заголовочный файл.
Name.odl - содержит исходный код на языке описания объекта (Object Description Language), по которому Visual C++ генерирует доступную для других клиентов OLE Automation библиотеку типа, содержащую интерфейс элемента управления. Odl-файл элемента управления представляет собой исходный текст библиотеки типов элемента управления. Этот файл преобразовывается утилитой Make Type Library (mktypelib.exe) в библиотеку типов (файл с расширением tlb), которая будет в виде ресурса включена в ocx-файл.
Рассмотрим классы, создаваемые средством MFC ActiveX ControlWizard подробнее.
Главный класс модуля OCX-объекта
Объявление класса
Файл Name.h является основным файлом заголовков для элемента управления Name Control, в нем объявляется класс CNameApp. Этот класс является потомком класса COleControlModule, а не класса CWinApp. Это справедливо обычно для всех элементов управления, построенных на основе MFC.
Класс же COleControlModule библиотеки MFC в свою очередь просто является производным от класса CWinApp, в котором для удобства переопределены методы InitInstance и ExitInstance.
Методы класса
Файл Name.cpp - это основной исходный файл для элемента управления Name Control. Функциями этого файла являются: регистрация элемента управления; обеспечение инициализации элемента управления; удаление регистрации элемента управления, когда он больше не нужен.
В этом файле происходит создание глобального объекта класса CNameApp, порожденного от класса ColeControlModule. В проекте может быть только один объект приложения, т.е. один объект класса, порожденного от COleControlModule.
MFC ActiveX ControlWizard сгенерировал GUID (уникальный идентификатор) для библиотеки типов элемента управления с именем _tlid и принял, что старший номер версии библиотеки равен 1, а младший номер – 0. Эта информация будет записана в реестр во время выполнения макроса INPLEMENT_OLETYPELIB, находящего в NameCtl.cpp.
Файл Name.cpp содержит реализацию методов класса CNameApp. Метод InitInstance отвечает за инициализацию DLL-файла: Он вызывается системой при первой загрузке элемента управления в память. Также этот метод вызывается при создании каждого экземпляра Name Control. В этом методе можно выполнить собственные методы инициализации, однако необходимо всегда в первую очередь вызвать метод InitInstance базового класса COleControlModule.
Метод
ExitInstance вызывается непосредственно перед тем, как элемент управления выгружается из памяти, и очищает память, освобождает дескрипторы, удаляет GDI-объекты и т.д:Регистрация элемента управления
В файле Name.cpp кроме методов класса CNameApp определены две функции:
DllRegisterServer (заносит данные в системный реестр) и DllUnregisterServer (удаляет данные из системного реестра):Элементы управления OLE являются саморегистрирующимися. Это означает, что они способны записывать информацию о себе в системный реестр Windows. Им не нужны ни файлы регистрации (имеющие расширение reg), ни какой-либо другой внешний механизм.
Функция
DllRegisterServer реализована во всех OLE-элементах управления. Она представляет собой экспортируемую функцию, которая служит точкой входа при регистрации элемента. Это глобальная функция, вызов которой осуществляют внешние функции или приложения.При вызове функция
DllRegisterServer выполняет следующие действия. Сначала она регистрирует библиотеку типов элемента управления посредством вызова функции AfxOleRegisterTypeLib. (Этот этап включает создание и обновление параметров элемента управления в ключе HKEY_CLASSES_ROOT\TypeLib. Исходный файл библиотеки типов автоматически генерируется MFC ActiveX ControlWizard. В нем описываются свойства, методы, события элемента управления и специфическая для данного класса информация). Затем функция DllRegisterServer регистрирует все фабрики классов данного приложения в системном реестре посредством метода COleObjectFactoryEx::UpdateRegistryAll. ( Фабрика классов представляет собой COM-объект, реализующий интерфейс IClassFactory и отвечающий за производство COM-серверов заданного типа (CLSID). Библиотеки OLE не могут создавать серверы без участия фабрики классов. Понятие фабрики классов рассматривается при исследовании файла NameCtl.cpp.)При компиляции элемента управления с использованием Microsoft Developer Studio последнее, что делает программа, - это вызывает функцию
DllRegisterServer, что приводит к автоматической регистрации элемента управления на данной машине. Если необходимо инсталлировать элемент управления на другой машине, нужно каким-то образом вызвать его функцию DllRegisterServer. Обычно это делает регистрационная программа, которую необходимо предоставить. Зарегистрировать OCX-объект можно и при помощи утилиты regsvr32.exe, которая обычно находится в каталоге C:\WINDOWS\SYSTEM.Функция
DllUnregisterServer является дополнением к функции DllRegisterServer. Элементы управления OLE самостоятельно удаляют свою регистрацию. Это значит, что библиотека типов, фабрика классов и информация об элементе может быть автоматически удалена из системного реестра. Функция DllUnregisterServer не вызывается автоматически из Developer Studio. Ее должны вызывать исключительно внешние функции или приложения, которые хотят полностью удалить элемент из системы. Например, эту функцию могла бы вызвать программа деинсталляции.Макрос AFX_MANAGE_STATE(p)
Макрос
AFX_MANAGE_STATE встречается в функциях DllRegisterServer и DllUnregisterServer. Этот макрос необходим некоторым методам управления OLE для выполнения переключения состояния модуля.Для начала уточним понятие “состояние модуля”. Во время выполнения MFC-программы библиотека хранит информацию о состоянии приложения. Эта информация включает дескрипторы окон, таблицы связей дескрипторов и MFC-объектов и др. Если выполняется только базовый модуль (DLL-файлы не подгружаются), то может существовать лишь один набор данных о состоянии.
Однако при подключении DLL-файлов, использующих библиотеку MFC (как в случае элементов управления OLE), могут возникать проблемы. Каждый элемент управления сохраняет свою информацию о состоянии. Когда поток выполнения “входит” в элемент управления, последний должен сообщить MFC о том, что наступила его очередь выполняться и следует изменить указатель на набор данных о состоянии так, чтобы он указывал на набор данных элемента управления. Элемент управления делает это с помощью макроса
AFX_MANAGE_STATE. Такая переадресация должна происходить во всех точках входа в DLL, то есть во всех функциях, экспортируемых библиотекой, поскольку именно там она получает управление.Поскольку все элементы управления OLE экспортируют функции
DllRegisterServer и DllUnregisterServer, тоне удивительно, что первым оператором в теле обеих функций является макрос AFX_MANAGE_STATE. Если не выполнить изменение состояния, результат работы программы может быть непредсказуемым. Наличие такого макроса в функциях, которые не экспортируются, необязательно.Класс OLE-элемента управления
Объявление класса
Класс OLE-элемента управления объявлен в файле NameCtl.h и реализуется в файле NameCtl.cpp. Базовый класс элемента управления наследуется от класса
COleControl - стандартного базового класса библиотеки MFC, который используется элементами управления OLE. Этот класс, будучи потомком класса CWnd, наследует все функции объекта окна, а также располагает дополнительными, специфическими для OLE функциями, такими как генерация событий и поддержка методов и свойств OLE-элемента управления.В классе
COleControl реализованы все интерфейсы, необходимые для работы элемента управления. Он скрывает детали реализации элемента и имеет понятный, объектно-ориентированный интерфейс, который используется элементами управления при выполнении собственных функций.Конструктор и деструктор класса
В конструкторе класса CNameCtrl происходит вызов функции
InitializeIIDs. Эта функция должна вызываться только в конструкторе элемента управления. Она сообщает базовому классу идентификаторы интерфейсов (IID) элемента управления. В данном случае класс COleControl получает идентификатор базового интерфейса автоматизации и идентификатор интерфейса диспетчеризации.Конструктор класса в своем первоначальном виде ничего не делает, но в него можно по мере необходимости добавить код, очищающий все данные экземпляра элемента управления.
Методы класса
Класс CNameCtrl переопределяет некоторые методы базового класса:
OnDraw, DoPropExchange, OnResetState. Метод OnDraw вызывается операционной системой, когда элемент управления должен быть перерисован.Вызов метода
DoPropExchange происходит, когда элемент управления записывается или считывается из постоянного хранилища (с диска). Эта функция отвечает за сохранение (запись на диск) и восстановление (чтение с диска) информации о свойствах элемента и его версии.Вызов метода
OnResetState осуществляет система, когда элемент управления должен сбросить свои свойства (установить их в первоначальные значения). Если ранее сохраненный элемент управления не может быть правильно воссоздан в памяти (то есть не могут быть правильно восстановлены значения его свойств), то система дает указание вновь созданному элементу управления (в который помещаются данные, считанные с диска) присвоить своим свойствам значения, имеющие хоть какой-то смысл, т.е. значения по умолчанию.Поддержка элементов управления
Объявление класса CNameCtrl включает в себя несколько макросов, обеспечивающих поддержку элементов управления OLE.
Макрос
DECLARE_OLECREATE_EX(CNameCtrl) объявляет фабрику классов и метод GetClassID для класса элемента управления. Метод GetClassID (объявляемый в этом макросе) вызывается системой, когда ей необходимо получить CLSID элемента. CLSID используется библиотеками OLE для регистрации, создания элемента управления и т.д.Фабрика классов представляет собой OLE-объект, который используется для производства экземпляров других объектов. Фабрики классов элемента управления располагаются внутри OCX-файла, вместе с самим элементом. Библиотека MFC сама обеспечивает реализацию фабрики классов (разработчику не нужно о ней беспокоиться). В основном фабрика классов реализована в
базовом классе COleObjectFactoryEx . Фактически конструктор фабрики классов для элемента управления просто передает свои параметры базовому классу, в котором скрыты детали реализации.Рассмотрим подробнее назначение класса COleObjectFactoryEx. Класс COleObjectFactoryEx является синонимом класса COleObjectFactory. Он реализует фабрику классов OLE и содержит методы для:
- управления регистрации объектов;
- обновления системного реестра, а также регистрации элемента управления во время выполнения программы (это необходимо для информирования библиотек OLE о том, что объект находится в режиме выполнения и готов к приему сообщений);
- принудительного лицензирования путем задания ограничений на использование элемента зарегистрированными разработчиками (на этапе проектирования) и зарегистрированными приложениями (на этапе выполнения);
- регистрации фабрики классов элемента управления в системном реестре.
Инициализация фабрики классов элемента управления и формирование GUID (CLSID) производятся в файле реализации. Эти действия выполняет макрос
IMPLEMENT_OLECREATE_EX. В этом макросе реализуется фабрика классов элемента управления и метод GetClassID. Фабрика классов создает экземпляры элемента управления. Метод GetClassID возвращает CLSID элемента всем, кто его запрашивает, например библиотекам OLE.В объявлении класса присутствует макрос
DECLARE_OLETYPELIB(CNameCtrl), который объявляет метод GetTypeLib для класса элемента управления.. Этот макрос должен присутствовать в файле заголовков элемента управления.Метод
GetTypeLib элемента управления реализуется макросом IMPLEMENT_OLETYPELIB в файле реализации класса. Библиотека типов представляет собой файл, содержащий информацию о типе элемента управления, его интерфейсах и элементах автоматизации (к ним относятся свойства, методы и события). Сгенерированный метод GetTypeLib вызывается системой, когда ей нужна информация о библиотеке типов элемента. Эти данные сравниваются с данными из системного реестра, и если обнаруживается совпадение, то информация передается системе.Элемент управления OLE использует страницы свойств, чтобы дать пользователю возможность просматривать и изменять свойства элемента управления. Страницы свойств реализуются в виде диалоговых окон с закладками (tabs). В макросе
DECLARE_PROPPAGEIDS(CNameCtrl) объявляются идентификаторы и методы, используемые этим механизмом.Макрос
DECLARE_OLECTLTYPE(CNameCtrl) объявляет методы, которые обрабатывают информацию о типе элемента и данные флагов MiscStatus.Информация о типе элемента управления записана в переменной типа const с именем
_dwNameOleMisc А макрос IMPLEMENT_OLECTLTYPE в реализации класса определяет два метода элемента управления GetUserTypeNameID и GetMiscStatus. Метод GetUserTypeNameID возвращает идентификатор ресурса строки (константа IDS_Name), содержащей имя типа элемента управления. Метод GetMiscStatus вызывается внешними объектами, например контейнером, для получения информации об элементе управления. Значения, записанные в переменной _dwNameOleMisc, генерируются при создании проекта и являются выбором установок проекта.Идентификаторы интерфейсов (IID) автоматизации
Строки файла реализации класса, объявляющие
IID_Dname и IID_DNameEvents определяют идентификаторы интерфейсов элемента управления. Первый из них идентифицирует реализацию IDispatch - базовый интерфейс диспетчеризации класса. Этот интерфейс используется клиентами автоматизации для редактирования и просмотра элемента управления и выполнения его методов. Второй идентификатор определяет интерфейс диспетчеризации событий класса. Этот интерфейс используется элементом управления для генерации событий. Оба идентификатора хранятся в системном реестре в ключе HKEY_CLASSES_ROOT\Interface.Таблица сообщений класса
Класс CNameCtrl c помощью макроса
DECLARE_MESSAGE_MAP объявляет привычную уже таблицу сообщений (схему сообщений) класса, при помощи чего сообщает системе, что класс будет обрабатывать сообщения. Сама таблица сообщений объявляется в файле реализации класса.За реализацию технологии OLE-автоматизации, то есть за предоставление внешнему миру методов и свойств элемента, отвечает OLE-интерфейс IDispatch. Библиотека MFC обеспечивает поддержку этой технологии с помощью конструкции под названием “схема диспетчеризации” (dispatch map). Эта схема представляет собой эквивалент знакомой уже схемы сообщений (message map). Подобно тому, как схемы сообщений скрывают детали взаимодействия с каналом сообщений Windows, схемы диспетчеризации скрывают детали OLE-автоматизации.
В этой таблице сообщений в файле реализации изначально присутствует только одна строка - макрос ON_OLEVERB(). Этот макрос связывает указанный глагол idsVerbName (сообщение, которое элемент управления будет обрабатывать) с конкретным методом memberFxn элемента управления.
Схема диспетчеризации класса
В объявлении класса присутствует макрос
DECLARE_DISPATCH_MAP, который объявляет схему диспетчеризации. Посредством нее элемент управления связывает свои свойства с соответствующими методами записи, чтения и оповещения (последние вызываются при изменении значения свойства). Дополнительно устанавливается связь между идентификаторами диспетчеризации (DISPID) и фактическими методами.Сначала схема диспетчеризации в файле реализации имеет одну запись, сделанную при помощи макроса
DISP_FUNCTION_ID, который связывает между собой следующие элементы: имя класса; внешнее имя метода; идентификатор (DISPID) метода; метод C++, соответствующий вызову метода автоматизации; список параметров метода (тип VARIANT); тип значения, возвращаемого методом (тип VARIANT). Эта информация необходима системе для того, чтобы направлять вызовы методов автоматизации по правильному пути. Когда контроллер автоматизации вызывает метод при помощи его внешнего имени, такой вызов преобразуется в вызов внутренней функции элемента управления. В основе этого механизма лежит DISPID. Каждому объявленному свойству или методу должен соответствовать свой макрос в схеме диспетчеризации класса.Схема диспетчеризации событий класса
В объявлении класса макрос
DECLARE_EVENT_MAP, который объявляет схему событий (event map). Подобно тому, как доступ к свойствам и методам элемента управления осуществляется с помощью схемы диспетчеризации, доступ к событиям элемента осуществляется посредством схемы событий. В ней имена и идентификаторы событий связываются с функциями, ответственными за генерацию событий.На этапе проектирования у элемента нет событий по умолчанию, поэтому этот схема событий в файле реализации пуста.
Идентификаторы DISPID
Различные идентификаторы свойств, методов и событий (DISPID) располагаются между макросами AFX_DISP_ID. Эти идентификаторы используются в ODL-файле элемента управления. По мере добавления в элемент свойств, методов и событий этот раздел будет заполняться средством ClassWizard.
Класс страницы свойств OLE-элемента управления
Страницы свойств - это OLE-объекты, которые дают пользователю возможность просматривать и изменять свойства элемента управления OLE. Страницы свойств содержат один или несколько стандартных диалоговых элементов управления, связанных с различными свойствами. Страницы свойств отображаются в виде модальных или немодальных диалоговых окон с закладками, причем каждой странице соответствует своя закладка. Чтобы отобразить на экране страницы свойств элемента управления, необходимо вызвать его “глагол свойств” - OLEVERB_PROPETIES.
По умолчанию средство MFC ActiveX ControlWizard создает одну страницу свойств, за работу которой в рассматриваемом случае OLE-элемента управления отвечает класс CNamePropPpg.
Объявление класса
Класс страницы свойств CNamePropPage объявляется в файле NamePpg.h. Этот класс сгенерирован при создании проекта. Он реализует одну пустую страницу свойств. Этот класс является прямым потомком класса
COlePropertyPage, базового класса всех страниц свойств.Класс CNamePropPage является OLE-объектом. Поэтому ему необходима фабрика классов, генерацию которой обеспечивает макрос
DECLARE_OLECREATE_EX. Фабрика классов и генерация CLSID для класса страницы свойств реализована в файле NamePpg.cpp при помощи макроса IMPLEMENT_OLECREATE_EX.Стандартные элементы управления страницы свойств
Страницы свойств - это просто дочерние диалоговые окна, обычно содержащие ряд стандартных элементов управления. Как и в случае обычных диалоговых окон, можно связать с каждым элементом управления диалога соответствующую ему переменную - элемент класса диалога. Такие элементы объявляются средством ClassWizard в классе страницы свойств в следующем блоке AFX_DATA.
Обмен данными между диалоговым окном страницы свойств и его элементами управления посредством связанных с элементами управления переменных класса осуществляется при помощи метода DoDataExchange(), который реализован в файле NamePpg.cpp. Этот метод использует функции обмена и проверки данных (DDX/DDV-макросы).
Редактирование записей системного реестра для класса страницы свойств
Добавление и удаление записей из системного реестра для класса CNamePropPage производится в методе UpdateRegistry фабрики классов. Этот метод фабрики классов регистрирует либо снимает регистрацию класса CNamePropPage. Это делает страницу свойств доступной для всех OLE-серверов и контейнеров.
Добавление нового свойства
Как и любой объект, OLE-элемент управления имеет свои методы и свойства. Однако не все они видимы в контейнере. Чтобы стать видимым, они должны быть созданы средством ClassWizad при вызове его вкладки OLE Automation. Видимые свойства и методы контейнер использует для связи с элементом. Используя доступ к свойствам элемента, можно организовать передачу данных между контейнером и элементом управления. Вызывая в контейнере методы элемента, можно организовать обработку этих данных. Это означает по существу реализацию OLE Automation для элементов управления.
Типы свойств
Свойства (properties) определяют внешний вид и поведение элемента управления. У всех свойств есть следующие атрибуты:
- Имя
- это читабельное имя, доступное внешнему миру. Контроллеры автоматизации используют имена для доступа и их модификации.- Тип
- у всех свойств есть связанные с ними типы данных. Так как свойства доступны внешнему миру посредством механизма OLE-автоматизации, то их типы ограничиваются набором типов OLE, рассматриваемых ниже.- Значение
- каждое свойство имеет определенное значение заданного типа.- Идентификаторы диспетчеризации (DISPID)
- у всех элементов автоматизации, то есть свойств, методов и событий, есть числовые идентификаторы (GUID), под которыми они известны системе. Об установлении соответствия между именами свойств и их DISPID заботится класс ColeControl, реализуя интерфейс IDispatch.- Символьная константа
- обычно с идентификатором каждого свойства элемента управления связана символьная константа. Вместо доступа к свойствам по их именам и идентификаторам, клиенты могут использовать символьные константы.Элемент управления может предоставлять свои свойства непосредственно по имени либо с помощью специальных методов чтения/записи, называемых методами свойств
(properties methods).С точки зрения реализации, свойства являются элементами класса C++, определенными в классе реализации элемента управления. Клиенты получают доступ к свойствам по имени, идентификатору DISPID или символьной константе. В элементе управления OLE свойство реализуется либо с использованием элемента класса и метода оповещения (notification method), либо с помощью пары методов - чтения/записи (методов Get/Set).
Свойства бывают следующих типов: базовые
(stock), внешние (ambient), расширенные (extended) и пользовательские (custom).Базовыми называются свойства, определенные фирмой Microsoft. Это набор характеристик, которые обычно свойственны всем элементам управления независимо от их типа и выполняемых функций (например, цвет фона). Следует отметить, что элемент управления не обязан поддерживать базовые свойства. Любой элемент, который хочет реализовать базовые свойства, может использовать для доступа к ним стандартные имена и DISPID, определенные фирмой Microsoft, а также методы чтения/записи класса COleControl (например, GetBackColor и SetBackColor). Эти методы могут быть вызваны как из самого элемента управления, так из внешнего объекта с помощью средств автоматизации.
В классе COleControl есть несколько специальных методов, сообщающих элементу об изменении его базовых свойств (OnBackColorChanged, OnBorderStylerChanged, OnEnabledChanged, OnFontChanged,OnForeColorChanged, OnTextChanged). Данные методы объявлены виртуальными, поэтому их можно спокойно изменять. Это следует делать в том случае, если элементу необходимо отслеживать изменения базовых свойств и реагировать на них.
Внешние свойства представляют собой стандартный набор свойств, доступных только для чтения и реализованных во всех контейнерах. Они предоставляют элементу управления информацию о его окружении, т.е. о контейнере. Элементы управления могут использовать эту информацию для согласования своего визуального представления в соответствии с интерфейсом контейнера. Для чтения внешних свойств контейнера используются методы, реализованные в классе COleControl и имеющие префикс Ambient...(). Необходимо отметить, что внешние свойства реализуются контейнером, а элемент только считывает их. Нужно добавить, что контейнеры не обязаны реализовывать такие свойства.
Расширенными свойствами называются такие свойства, которые контейнер связывает с каждым конкретным элементом управления. Обратим внимание на то, что контейнер сам несет ответственность за эти свойства и “подключает” их к каждому элементу по своему усмотрению. Для того, чтобы контейнер мог связать расширенные свойства с элементом управления, он во время встраивания элемента создает небольшой вспомогательный объект, называемый расширенным элементом управления. Посредством механизма агрегации этот объект принимает все вызовы методов свойств элемента управления, что позволяет ему обрабатывать их прежде, чем они попадут к реальному элементу. По существу, расширенный элемент управления является внешней оболочкой элемента управления OLE.
Пользовательскими называются свойства, которые определены разработчиком OCX-объекта. Пользовательские свойства применяются для придания отличительных черт элементу управления. Каждому пользовательскому свойству необходимо присвоить уникальное имя и DISPID. Кроме того, следует убедиться что это свойство имеет один из типов OLE.
Добавление свойств
В процессе проектирования OCX-объекта можно добавлять два вида свойств: базовые и пользовательские. Несколько базовых свойств (другое название - стандартные свойства) определены и поддерживаются в базовом классе COleControl. Это общие свойства, пригодные для большинства проектируемых OLE-элементов управления: Appearance - внешний вид (например, 3-мерный или плоский); BackColor - цвет фона; BorderStyle - тиль рамки; Caption - заголовок элемента; Enabled - состояние доступен/недоступен; Font - шрифт при печати заголовка и других сообщений, связанных с элементом управления; ForeColor - цвет переднего плана; hWnd - маркер (дескриптор) окна; ReadyState - состояние готовности; Text - текст, другое название свойства Caption.
Наряду с базовыми свойствами можно задать новое свойство, соответствующее специфике проектируемого элемента, не определенное в базовом классе. Возможны четыре способа реализации добавляемого свойства:
Member Variable - реализация свойства предполагает введение в созданный класс элемента управления переменной, видимой в контейнере. Это простейший способ реализации. Однако чаще применяются три следующих, когда наряду с переменной создаются и другие элементы класса;
Member Variable With Notification - наряду с переменной создается специальная функция уведомления. Она автоматически вызывается в каркасе приложения всякий раз при изменении значения свойства, что позволяет элементу управления должным образом отреагировать на изменение своего свойства - например, организовать свою перерисовку;
Set/Get Methods - предполагает защищенный доступ к переменной класса, задающей свойство. Хотя сама переменная будет не видима в контейнере, здесь будут доступны автоматически построенные методы Get и Set. Первый позволяет прочитать значение, второй - задать новое значение свойства. Кроме защищенности свойства, этот способ удобен тем, что позволяет организовывать дополнительные вычисления, например, в методе Set можно произвести проверку, разрещающую или запрещающую изменение свойств;
Parameterized - параметризированная реализация применяется, когда переменная класса должна быть массивом и свойство задается не единственным скалярным значением, а совокупностью значений. Эта реализация предполагает построение методов Get и Set. Автоматически эти методы создаются с параметром, включающим индексы элемента массива.
Все свойства имеют определенный тип. Так как OLE не является частью языка Visual C++, то типы OLE отличаются от привычных типов C++. Рассмотрим основные типы OLE: BSTR - строка, каждый символ которой занимает 2 байта; I2 - целое длиной 2 байта; I4 - целое длиной 4 байта; UI4 - целое без знака длиной 4 байта; R4 - вещественное с плавающей точкой длиной 4 байта; R8 - вещественное с плавающей точкой длиной 8 байта; BOOL - булевское значение (целое длиной 2 байта); VARIANT - для переменных, тип значений которых может варьироваться; PTR - указатель на объект; OLE_COLOR - тип (UI4), введенный для переменных, задающих цвет.
Тип свойства выбирается из списка “Type” при добавлении нового свойства при помощи средства ClassWizard. В этом списке, как правило, указываются не типы OLE, а совместимые с ними типы, например тип short, который автоматически транслируется в тип I2. Однако в списке “Type” есть и OLE-типы, например, типы BSTR и OLE_COLOR.
Процедура добавления свойства
Для этого необходимо вызвать ClassWizard и выбрать вкладку OLE Autonation, а затем:
- В поле “Class name” необходимо выбрать имя класса элемента управления, производного от COleControl. Напомним, что производные классы наследуют от базового класса его данные и методы. Так все OLE-элементы управления могут использовать открытые для них методы базового класса COleControl.
- При помощи кнопки “Add Property” вызвать диалоговое окно для добавления свойств.
- Для пользовательских свойств в окно комбинированного списка “External Name” ввести его имя, а для базовых - выбрать имя из этого списка.
- В группе “Implementation” для пользовательских свойств включить один из переключателей: “Member Variable” или “Get/Set”. Для базового свойства будет автоматически выбран переключатель “Stock”. Выбор “Member Variable” задает два первых способа реализации свойства. По умолчанию предлагается второй способ с построением функции уведомления. Если отказаться от нее, то реализуется первый способ. Выбор переключателя “Get/Set” задает третий и четвертый способы реализации свойства. Ввод информации в окне “Parameter List” означает выбор параметризированного свойства, и тогда параметры методов Get и Set будут включать индексы элемента массива, задающего свойство.
- Из списка “Type” следует выбрать тип свойства. Для базовых свойств тип задается автоматически.
- Задав все характеристики, следует подтвердить создание нового свойства, выбрав кнопку
“Ok”.Действия, выполняемые ClassWizard, при добавлении свойства
Для пользовательского свойства, при добавлении которого был включен переключатель “Member Variable”, ClassWizard вставит в файл CNameCtl.h описание переменной класса и заголовок функции уведомления (если не было отказа). Созданная переменная, кстати, будет видима в контейнере, который будет содержать OLE-элемент управления. Остов функции уведомления создается и вставляется в файл CNameCtl.cpp. Тело функции содержит единственный вызов SetModifiedFlag().
Когда контейнер сохраняет свое состояние, сохраняется и свойство встроенного OLE-элемента управления. Поэтому всякий раз, когда меняется свойство, необходимо включить флаг модификации состояния документа. Это и делает функция уведомления. Все остальные возможные действия, связанные с изменением состояния, программист должен добавить сам.
Для пользовательского свойства, при добавлении которого был включен переключатель
“Get/Set methods”, ClassWizard создаст и добавит в h- и cpp-файлы заголовки и остовы этих методов. В процессе добавления свойств можно отказаться от одного из этих методов, и тогда свойство будет предназначаться “только для чтения” или “только для записи”.Хотя ClassWizard создает остовы методов Get и Set, пользоваться ими без доработки нельзя, так как он не создает переменной класса. Ее нужно добавить вручную и соответственно откорректировать тела методов. Следует заметить, что сама переменная не будет видима в контейнере - она скрыта от него, доступны лишь методы Get и Set.
Следующая важная часть работы ClassWizard - формирование раздела DISPATCH_MAP (раздела схемы диспетчеризации) в файле NameCtl.cpp. Для каждого пользовательского или базового свойства добавляется соответствующий макрос. Для базовых свойств добавляется один из макросов с префиксом DISP_STOCKPROP_ в соответствии с задаваемым базовым свойством. Для пользовательских свойств добавляемый макрос зависит от способа реализации свойства. Каждому из четырех способов реализации соответствует свой макрос: DISP_PROPERTY(), DISP
_PROPERTY_NOTIFY(), DISP_PROPERTY_EX(), DISP_PROPERTY_PARAM(). Параметрами макросов являются имя класса и свойства, тип свойства, и, в зависимости от типа макроса, имена переменной класса и функции уведомления или имена Get и Set методов.С каждым свойством ClassWizard связывает идентификатор диспетчеризации ID и добавляет информацию о нем в файл Name.odl.
“Постоянные” свойства и их инициализация
В классе CNameCtrl построен остов метода
DoDropExchange - аналог метода DoDataExchange класса диалоговых окон. Этот метод вызывается всякий раз, когда OLE-элемент управления создается, читается или записывается в файл на диск. В ее тело для каждого свойства можно добавить вызов метода с префиксом PX (Propertiy eXchange) - аналог DDX-метода.Свойство, для которого добавлен вызов PX-метода, называется постоянным (persistent). PX-метод позволяет задать значение постоянного свойства по умолчанию. Для каждого типа свойства применяется свой PX-метод.
Добавление нового метода
Методы элемента управления могут быть как видимые (методы автоматизации), так и невидимые
в контейнере (обработчики сообщений класса элемента управления, методы класса для решения частных, вспомогательных задач). Способы их создания различны.Методы автоматизации
Методы автоматизации – это функции-члены класса элемента управления, которые образуют программный интерфейс элемента управления и доступны внешнему миру посредством OLE-автоматизации. Другими словами, они вызываются контроллерами автоматизации и сообщают элементу управления, что он должен сделать.
В общем случае методы автоматизации обладают характерными чертами, такими, как:
- Внешнее имя
метода – это его текстовое, читабельное имя, доступное внешнему миру. Контроллер автоматизации использует данное имя для вызова метода.- Внутреннее имя
– это его имя в исходных файлах. У элемента управления может существовать метод автоматизации, который доступен внешнему миру с одним именем, а внутри класса реализован с другим. Внутренние и внешние имена методов могут быть как одинаковыми, так и разными. Выбор этого полностью зависит от разработчика.- Идентификатор диспетчеризации
(DISPID) – это числовой идентификатор, который позволяет различать отдельные свойства, методы и события. У каждого метода есть соответствующий ему DISPID. Эти идентификаторы используются главным образом в odl-файле элемента управления, а интерфейс Idispatch определяет соответствие между текстовым именем метода и его DISPID.Методы реализуются в виде обычных методов C++. Ограничением является лишь то, что типы параметров и возвращаемых значений должны поддерживать механизм транспортировки (marshaling). Следует помнить, что смысл автоматизации заключается в том, что она позволяет одной программе управлять работой другой программы. Чтобы обеспечить поддержку автоматизации
, следует использовать типы семейства VARIANT (VT_xxx).Методы автоматизации делятся на две основные категории: базовые и пользовательские. Фирма Microsoft определила и реализовала ряд базовых методов автоматизации.
Refresh (заставляет элемент перерисовать себя), DoClick (имитирует щелчок мышью на элементе управления) и AboutBox() (отображает на экране модальное диалоговое окно “About” элемента управления). Все эти методы имеют отрицательные DISPID.Все остальные методы элемента управления определяет разработчик. Такие методы называются пользовательскими. Пользовательские методы, наряду с базовыми, составляют интерфейс автоматизации элемента управления. Эти методы позволяют программам-клиентам управлять внешним видом и поведением элемента управления.
Добавление различных методов
Чтобы добавить в класс элемента управления метод автоматизации – метод, который может быть вызван в контейнере, – следует вызвать ClassWizard и выбрать вкладку “OLE Automation”. В появившемся окне следует выбрать кнопку “Add Method”. Затем в диалоговом окне в полях “External name” и “Internal name” ввести имя нового метода (внешнее и внутренне имена могут отличаться, внутреннее имя метода используется в классе элемента, при помощи внешнего имени контейнер может вызывать метод). Возвращаемое методом значение типа выбирается в списке “Return type”. После того, как ClassWizard создаст остов метода, программист может добавить в него код.
ClassWizard наряду с созданием остова метода добавляет в объявление класса описание созданного метода, а в схему диспетчеризации класса (в файле реализации класса) добавляет соответствующий макрос. В раздел интерфейса диспетчеризации odl-файла в его подраздел methods ClassWizard добавит строку, связывающую с методом идентификатор диспетчеризации.
Для определения реакции элемента управления на действия пользователя следует добавить в класс обработчик сообщения. Для построения остовов обработчиков сообщений следует вызвать ClassWizard и выбрать вкладку “Message Map”. Выбрав в поле “Class name” класс, а в поле “Message” необходимое сообщение, следует нажать кнопку “Add Function”. ClassWizard не только добавит в класс объявление созданных методов и их остовы, но и в таблицу обрабатываемых сообщений вставит соответствующие строки.
Добавление методов, решающих частные задачи, производится вручную, без использования каких-либо средств автоматизированного проектирования.
Добавление нового события
При возникновении некоторой ситуации, чаще всего связанной с действиями пользователя, OCX-объект может “зажечь” событие, вызвав связанную с ним специальную функцию. Рассмотрим механизм генерации и обработки событий.
Типы событий
С помощью событий элемент управления OLE сообщает контейнеру о том, что “что-то произошло”. Стандартными примерами событий являются ввод данных с клавиатуры, перемещения и щелчки мыши, а также изменение внутреннего состояния элемента управления. Уникальность событий заключается в том, что элемент управления определяет тип события, но обрабатывается оно контейнером.
Доступ к методам обработки событий осуществляется с помощью средств OLE-автоматизации. Это означает, что они: имеют идентификаторы (DISPID); могут иметь произвольное число параметров; могут возвращать определенные значения.
Элемент управления OLE использует события для оповещения о том, что произошло некоторое действие или выполнилось некоторое условие (в библиотеке типов элемента управления следует описать все события, которые может сгенерировать данный элемент). Элемент управления определяет, что именно произошло, и генерирует требуемое событие, а контейнер его распознает и соответствующим образом обрабатывает. Для этого в контейнере должен быть реализован соответствующий набор методов-обработчиков событий.
Для реализации событий элементы управления и их контейнеры используют так называемые точки соединения (connection point). При встраивании элемента управления в контейнер сканирует все точки соединения элемента управления, пытаясь найти ту, которая обозначена как интерфейс диспетчеризации событий IDispatch (эта информация хранится в odl-файле элемента управления). В случае успеха контейнер подключает к ней свой интерфейс IDispatch, устанавливая, таким образом, канал связи.
События можно разделить на несколько видов: базовые события
(stock events); события-запросы (request events); события-предупреждения (before events); события-следствия (after events); события-директивы (do events). Независимо от вида, все события различаются по имени и идентификатору (DISPID). Для каждой группы событий действуют свои соглашения о правилах присвоения имен, последовательности вызовов и т.п. Однако, по существу, все события одинаковы. Все они генерируются (или порождается) элементом управления и через точку соединения попадают в контейнер. Получив событие, контейнер реагирует на него по своему усмотрению.Так же как и у базовых свойств, у базовых событий есть заранее определенные имена и идентификаторы (DISPID). Приведем список всех базовых событий (их смысл понятен из названия): Click, DblClick, KeyDown, KeyPress, KeyUp, MouseDown, MouseMove, MouseUp, Error. В классе COleControl для генерации событий существуют методы, предназначенные для генерации всех базовых событий, их имена начинаются с Fire (например, FireClick). Для генерации пользовательских событий применяется методв FireEvent. (Пользовательскими называются события, которые сам программист добавляет к элементу управления. Они отличаются от базовых свойств тем, что класс ColeControl не генерирует их автоматически и не имеет стандартных методов оповещения, которые позволяют отслеживать их возникновение).
В классе ColeControl имеется ряд методов оповещения, вызываемых библиотекой MFC при генерации базовых событий: OnClick, OnKeyDownEvent, OnKeyPressEvent, OnKeyUpEvent. Эти методы являются виртуальными, поэтому элемент управления также может перехватывать события и самостоятельно их обрабатывать.
Для того, чтобы дать контейнеру возможность отменить какое-нибудь действие, элемент управления может генерировать события-запросы. Например, если пользователь решил закрыть элемент управления, тот может выдать событие RequestClose. Это позволяет процедуре-обработчику, находящейся в контейнере, отменить закрытие. Иначе говоря, элемент управления запрашивает разрешение сделать что-то, а контейнер должен решить, выполнять этот запрос или нет.
По умолчанию, последним параметром методов, генерирующих события-запросы, является ссылочная переменная типа CancelBoolean (тип данных, определенный в OLE). Эта переменная традиционно имеет имя Cancel, а ее значение указывает, должен ли элемент управления отвергнуть запрошенное действие (Cancel=TRUE) или выполнить его (Cancel=FALSE).
Во время события-запроса происходит следующее. OLE-элемент-управления устанавливает значение переменной Cancel в FALSE, тем самым давая знать контейнеру, что он должен что-то сделать. Элемент управления генерирует событие, вызывая соответствующий метод автоматизации в формате Request<Action>, где <Action> - запрошенное действие. Контейнер, в зависимости от запроса и текущего состояния системы, устанавливает значение параметра Cancel в TRUE (заставляя элемент управления отменить действие) или FALSE (разрешая элементу управления выполнить действие). Когда метод, сгенерировавший событие, завершается, OLE-элемент управления проверяет значение параметра Cancel и предпринимает соответствующие действия.
Перед тем как сгенерировать “настоящее” событие, элемент управления может сгенерировать событие-предупреждение. Это действие называется упреждающим оповещением (prenotification). Оно дает контейнеру возможность подготовиться к тому, что должно произойти. Следует учитывать, что контейнер не может отменить событие-предупреждение. Получив его, он может лишь выполнить какие-нибудь действия.
В соответствии с соглашением, имена методов, генерирующих события-предупреждения, имеют следующий вид: Before<Action>, где <Action> - действие, которое собирается предпринять элемент управления OLE (например, метод BeforeClose()).
После того, как что-то произошло, элемент управления может сгенерировать событие-следствие. Это действие называется последующим оповещением (postnotification). По сравнению с другими типами событий события-следствия являются наиболее распространнеными. Типичными примерами таких событий являются щелчки мыши и изменение свойств. Имена методов, генерирующих события-следствия, не подчиняются соглашениям о правилах присвоения имен. Кроме того, события-следствия не могут отменяться контейнерами.
События-директивы определяются и реализуются в элементе управления, но могут быть переопределены в контейнере. Они генерируются для того, чтобы дать пользователю возможность переопределить или дополнить код обработчиков событий, реализованных в элементе управления по умолчанию.
Имена методов, генерирующих события-директивы, должны соответствовать следующему соглашению: Do<Action>. Последним параметром методов, генерирующих события-директивы, является булева переменная EnableDefault. Элемент управления устанавливает ее значение в TRUE и вызывает метод. Получив событие, контейнер может в ответ предпринять некоторые действия. Затем он устанавливает значение переменной EnableDefault в TRUE или FALSE, в зависимости от того, хочет ли он, чтобы элемент управления продолжил обработку события. Когда метод генерации сообщения завершается, элемент управления проверяет параметр EnableDefault. Если он равен TRUE, элемент управления вызывает стандартную реализацию метода, а если FALSE, то не вызывает ее.
Добавление события
Средство ClassWizard может добавлять элементу управления как базовые, так и пользовательские события. Для этого следует вызовать ClassWizard и открыть вкладку “ActiveX Events”, на которой выберать кнопку “Add Event”. В окне списка “External Name” следует ввести внешнее имя события (например, ClickIn). Параллельно в окне “Internal Name” автоматически появится имя метода FireClickIn, вызов которого будет приводить к посылке сообщения контейнеру. Говорят, что метод FireClickIn “зажигает” событие ClickIn. В окне “Parameter List” задаются параметры, передающиеся вместе с событием. Их типы выбираются из списка типов.
ClassWizard помимо создания события и присвоения ему идентификатора DISPID, а также создания “зажигающего” события метода добавит в раздел отображения события (или в схему событий) соответствующую информацию. Параллельно будет сформирован раздел интерфейса диспетчеризации событий в odl-файле, связывающий событие с идентификатором диспетчеризации.
Корректировка страниц свойств
Когда элемент управления имеет свойства, контроллеры автоматизации могут получать к ним доступ и изменять их. Но кроме этого необходимо и пользователю дать возможность делать то же самое. Элементы управления OLE, в соответствии с принятым соглашением, используют для этого страницы свойств.
Страницы свойств (property page) – это OLE-объекты, которые дают пользователю возможность просматривать и изменять свойства элемента управления. Страницы свойств содержат один или несколько диалоговых элементов управления, связанных с различными свойствами. Страницы свойств отображаются в виде модальных или немодальных диалоговых окон с закладками, причем каждой странице соответствует своя закладка.
Чтобы отобразить на экране страницы свойств элемента управления контроллеры автоматизации, например ActiveX Control Test Container, вызывают “глагол свойств” OLEIVERB_PROPERTIES элемента управления. В них обычно есть возможность сделать это через пункт меню “Properties”.
Элемент управления OLE может иметь не одну страницу свойств. Каждая страница обычно соответствует отдельной группе логически связанных свойств.
При автоматической генерации элемента управления ControlWizard автоматически генерирует одну пустую страницу свойств. Станицы свойств реализуются при помощи класса C++, порожденного от класса COlePropertyPage. Класс COlePropertyPage порожден от класса CDialog и содержит набор функций для взаимодействия с элементом управления.
Страницы свойств сами по себе являются элементами управления OLE. Это значит, что у них есть собственные CLSID, они регистрируются в системном реестре и могут быть созданы независимо от любого другого элемента управления. Это дает программисту возможность разработать т реализовать группу страниц свойств, совместно используемых различными объектами в разных программных проектах.
Страницы свойств бывают двух типов: страницы базовых свойств (stock property pages) и пользовательские страницы свойств (custom property pages).
Имеется три страницы базовых свойств, которые доступны элементам управления. Эти страницы управляют базовыми свойствами, связанными с цветами (страница Colors), с изображениями (страница Pictures), с шрифтами (страница Fonts). Как и любые другие OLE-объекты, страницы базовых свойств имеют собственные CLSID, используемые библиотеками OLE для их идентификации: CLSID_CColorPropPage, CLSID_CPicturePropPage, CLSID_CFontPropPage.
Преимущество страниц базовых свойств заключается в их “интеллектуальности”. Они реализованы так, чтобы самостоятельно находить в библиотеке типов элемента управления информацию о свойствах интересующего их типа. Например, страница Colors ищет свойства с типом OLE_COLOR и автоматически предоставляет доступ к этим свойствам.
Единственный недостаток страниц базовых свойств заключается в том, что их реализацию нельзя изменить. Для этого нужно создавать полностью новую страницу.
Все остальные страницы свойств элемента управления являются пользовательскими. Они разрабатываются точно так же, как и любые диалоговые окна. Создание страниц свойств заключается в добавлении дочерних элементов управления к шаблону диалогового окна.
При проектировании следует помнить, что страницы свойств: не имеют заголовков; по умолчанию невидимы; не имеют границ; являются дочерними окнами. Следует убедиться, что соответствующие опции в редакторе ресурсов были установлены правильно.
Добавление страниц базовых свойств
Рассмотрим, как добавить в создаваемый элемент управления страницу базовых свойств Colors. Чтобы написать код, который добавляет базовую страницу свойств (Colors) к элементу управления, необходимо выполнить следующие инструкции: найти и модифицировать таблицу следующим образом:
BEGIN_PROPPAGEIDS(CNameCtrl, 2) PROPPAGEID(CNamePropPage::guid) PROPPAGEID(CLSID_CColorPropPage) END_PROPPAGEIDS(CNameCtrl)Добавление и корректировка пользовательских страниц
Для создания новой пользовательской страницы необходимо создать диалоговую панель (отвечающую ранее перечисленным требованиям) и подключить к ней новый класс, реализующий ее работу. Для создания класса страницы свойств используется ClassWizard. При его помощи необходимо создать новый класс, наследуемый от базового класса COlePropertyPage (не СPropertyPage), и включить его в проект элемента управления. Классы, порожденные от COlePropertyPage, наследуют от него CLSID и фабрику классов, сгенерированные MFC. Затем следует добавить идентификатор новой страницы свойств в таблицу страниц элемента управления, не забыв при этом изменить общее число страниц.
Для корректировки пользовательской страницы свойств нужно работать с соответствующей диалоговой панелью, отвечающей именно за эту страницу. Например, ControlWizard сгенерировал для элемента управления Name Control одну пользовательскую страницу свойств “General”, за которую отвечает диалоговая панель с идентификатором IDD_PROPPAGE_NAME. Можно добавить в нее стандартные элементы управления для манипуляций с теми или иными свойствами OLE- элемента управления. Для реализации этих действий следует присоединить к стандартным полям диалоговой панели страницы свойств переменные (подобно тому, как это делается для обычных диалогов), отметив при этом внешние имена свойств OCX-объекта, за которые отвечают эти переменные.
Связь данных
Как уже отмечалось, OLE-элемент управления может следить за изменением значений своих свойств. При втором способе реализации строится специальная функция уведомления, вызываемая при изменении значения. При третьем и четвертом способе для изменения значения всегда вызывается метод Set. Так что почти для всех способов реализации свойства элемент управления может фиксировать изменение значения своих свойств.
Иногда бывает важно, чтобы и контейнер мог следить за этими изменениями. Свойство называется связываемым (bindable), если имеет потенциальную возможность сообщить контейнеру об изменении своего значения.
Связывание делается в два этапа. На первом свойство добавляется, как это было описано, любым способом реализации, кроме первого. На втором этапе добавленное свойство получает статус связываемого. Для этого следует:
- Вызвав ClassWizard, выбрать вкладку OLE Automation.
- В окне списка “External Name” выбрать имя свойства.
- Щелкнуть кнопку “Data Binding”.
- В появившемся окне включить флажок “Bindable Proprty” и выбрать кнопку “OK”.
Теперь свойство получило потенциальную возможность уведомление контейнера о своих изменениях. Реализовать эту возможность и превратить связываемое свойство в связанное (bound) свойство можно в процессе работы элемента управления в контейнере. Для этого нужно вызвать один из двух методов BoundPropertyChanged или BoundPropertyRequestEdit. Вызываются эти функции либо в методе Set, либо в функции уведомления.
Связывание называется оптимистическим (optimistic data binding), если вызывается метод BoundPropertyChanged. При этом изменение свойства всегда выполняются, и контейнер только уведомляется о нем. Если вызывается метод BoundPropertyRequestEdit, связывание назывется пессимистическим (pessimistic data binding). Для этого типа связывания изменение значения свойства производится, если только результат вызова функции TRUE, что означает разрешение контейнера на изменение значения.
[an error occurred while processing this directive] [an error occurred while processing this directive]