Страницы свойств
Перед тем как мы начнем работать с окном СОМ-объекта, вводя в него реакции на управляющие воздействия, покажем, как добавить страницу свойств (property page) в уже существующий блок страниц объекта, который активизируется с помощью контекстного меню. Страница свойств является отдельным элементом управления, называемым Property Page, интерфейсы которого должны быть реализованы в рамках отдельного ко-класса. Такая структура позволяет нескольким ко-классам одновременно пользоваться страницами свойств, размещенными в общем СОМ DLL-сервере. Новый класс для поддержки страницы свойств помещается в сервер с помощью той же процедуры, которую мы использовали при вставке класса COpenGL, но при этом следует выбрать другой тип элемента управления. Вновь воспользуемся услугами мастера Studio.Net ATL Add Class.
Просмотрите результаты. Прежде всего убедитесь, что в проекте появился новый класс CPropDlg, который поддерживает функциональность страницы свойств и окна диалога. Однако, запустив сервер и вызвав из контекстного меню его свойства, вы не увидите новой страницы. Там будут только те две страницы, которые были и до момента, как вы подключили поддержку страницы свойств. Для того чтобы новая страница действительно попала в блок страниц элемента, надо ввести новый элемент в карту свойств разрабатываемого элемента COpenGL. Откройте файл OpenGL.h и найдите в нем карту свойств. Она начинается строкой:
BEGIN_PROP_MAP(COpenGL)
Введите в нее новый элемент:
PROP_ENTRY("Свет", 1, CLSID_PropDlg)
который привязывает (binds) новую страницу к существующему блоку страниц свойств. Как видите, страница создается и связывается с объектом COpenGL по правилам СОМ, то есть с помощью уникального идентификатора ко-класса CLSlD_PropDlg. Единица определяет индекс DISPID (dispatch identifier) — 32-битный идентификатор, который используется упоминавшейся выше функцией invoke для идентификации методов, свойств и аргументов. Карта свойств теперь должна выглядеть следующим образом:
BEGIN_PROP_MAP(COpenGL)
PROP_DATA_ENTRY("_cx", m_sizeExtent.ex, VT_UI4)
PROP_DATA_ENTRY("_cy", m_sizeExtent.cy, VT_UI4)
PROP_ENTRY("FillColor", DISPID_FILLCOLOR, CLSID_StockColorPage)
PROP_ENTRY("CBeT", 1, CLSID_PropDlg) END_PROP_MAP()
Здесь важно уяснить, что каждая строка типа PROP_ENTRY соответствует какой-то функциональности, скрытой в каркасе сервера. Например, стандартное свойство Fill Color реализовано с помощью одной переменной m_clrFillColor и пары функций FillColor, упоминания о которых вы видели в IDL-файле. Тела этих функций остались за кулисами. То же справедливо относительно страницы свойств.
Важным моментом является появление нового ко-класса в составе библиотеки типов, генерируемой DLL-сервером. В коде, приведенном ниже, отметьте появление строк, связанных с ко-классом PropDlg и, конечно, не обращайте внимание на идентификаторы CLSID, которые могут не совпадать даже с предыдущей версией в этой книге, так как в процессе разработки сервера мне приходится неоднократно повторять заново процесс создания ко-классов:
Каждый раз при этом идентификаторы CLSID обновляются, и ваш реестр распухает еще больше. Хорошим правилом для запоминания в этом случае является следующее. Убирайте регистрацию всего сервера каждый раз, когда вы целиком убираете какой-либо неудачный ко-класс. Это, как мы отмечали, делается с помощью команды Start > Run > regsvr32 -u "C:\My Projects\ATLGL\ Debug\ATLGL.dll.". Перед тем как нажать кнопку ОК, внимательно проверьте правильность файлового пути к вашему серверу.
library ATLGLLib
{
importlib("stdole32.tlb");
importlib("stdole2.tlb") ;
[
uuid(6DEBB446-C43A-4AB5-BEEl-110510C7AC89)
helpstring("_IOpenGLEvents Interface")
]
dispinterface _IOpenGLEvents
{
properties:
methods:
};
[
uuid(5B3EF182-CD91-426F-9309-2E4869C353DB),
helpstringC'OpenGL Class")
]
coclass COpenGL
{
[default] interface IQpenGL;
[default, source] dispinterface _IOpenGLEvents;
};
//====== Новые элементы в библиотеке типов сервера
[
uuid(3AE16CD6-4558-460F-8A7E-5AB83D40DE9A),
helpstring("_IGraphPropEvents Interface")
]
dispinterface _IGraphPropEvents
{
properties:
methods:
};
[
uuid(lAOC756A-DA17-4630-91BO-72722950B8F7) ,
helpstring("GraphProp Class")
]
coclass PropDlg
{
interface lUnknown;
[default, source] dispinterface _IGraphPropEvents;
};
Убедитесь, что в составе проекта появились новые файлы (PropDlg. h, PropDlg. cpp и PropDlg. rgs). Откройте первый файл описаний и отметьте, что класс CPropDlg происходит от четырех родителей (классов ATL и одного интерфейса). Два из них (ccomObjectRootEx и CGomCoClass) мы уже встречали ранее, а два других (iPropertyPagelmpl и CDialoglmpl), как нетрудно догадаться, поддерживают функциональность диалоговой вкладки (страницы), размещаемой в блоке страниц (property sheet), и самого диалога, то есть механизм обмена данными. Оба родителя являются шаблонами, которые уже настроены на наш конкретный класс CPropDlg. Конструктор класса:
CPropDlg()
{
m_dwTitleID = IDSJTITLEPropDlg;
m_dwHelpFileID = IDS_HELPFILEPropDlg;
m_dwDocStringID = IDS_DOCSTRINGPropDlg;
}
устанавливает унаследованные переменные m_dwTitleio и идентификаторы строковых ресурсов в те значения, которые им присвоил мастер Studio.Net. Сами строки вы можете увидеть в ресурсах, если откроете узел дерева String Table. В классе изначально присутствует реакция на кнопку Apply, которая, как вы знаете, всегда сопровождает блок диалоговых вкладок (property sheet):
//====== Реакция на нажатие кнопки Apply
STDMETHOD(Apply)(void)
{
ATLTRACE(_T("CPropDlg::Apply\n"));
for (UINT i = 0; i < m_nObjects; i++)
{
// Do something interesting here
// ICircCtl* pCirc;
//m_ppUnk[i]->QueryInterface(IID_ICircCtl, (void**)SpCirc)
// pCirc->put_Caption(CComBSTR("smth special"));
// pCirc->Release();
}
m_bDirty = FALSE;
return S__OK;
}
В комментарий мастер поместил подсказку, которая дает намек о том, как следует пользоваться новым классом. Как вы видите, общение между двумя классами нашего сервера (copenGL и CPropDlg) должно происходить по правилам СОМ, то есть с помощью указателя на интерфейс. Этот факт производит впечатление излишней усложненности. Если оба класса расположены в рамках одной DLL, они могли бы общаться друг с другом с помощью прямого указателя, несмотря на то, что сама DLL загружается в пространство чужого процесса.
Имя ICircCtl, которое присутствует в подсказке, не имеет отношения к нашему проекту. Оно связано с учебным примером по созданию элементов управления с помощью библиотеки ATL. Вы можете увидеть этот пример в MSDN (Visual C++ Tutorials > Creating the Circle Control).
Переменная m_bDirty используется каркасом в качестве флага доступности кнопки Apply. Если m_bDirt у == FALSE; то кнопка недоступна. Она тотчас же должна стать доступной, если пользователь страницы диалога свойств введет изменения в органы управления на лице диалога. Конечно, этим состоянием управляет разработчик, то есть мы с вами.