Страницы свойств
Страницы свойств
Перед тем как мы начнем работать с окном СОМ-объекта, вводя в него реакции на управляющие воздействия, покажем, как добавить страницу свойств (property page) в уже существующий блок страниц объекта, который активизируется с помощью контекстного меню. Страница свойств является отдельным элементом управления, называемым Property Page, интерфейсы которого должны быть реализованы в рамках отдельного ко-класса. Такая структура позволяет нескольким ко-классам одновременно пользоваться страницами свойств, размещенными в общем СОМ DLL-сервере. Новый класс для поддержки страницы свойств помещается в сервер с помощью той же процедуры, которую мы использовали при вставке класса COpenGL, но при этом следует выбрать другой тип элемента управления. Вновь воспользуемся услугами мастера Studio.Net ATL Add Class.
- Установите фокус на элемент ATLGL в дереве Solution Explorer и в контекстном меню выберите команду Add > Add Class, при этом важно, чтобы фокус стоял на имени проекта ATLGL
- В окне диалога Add Class выберите категорию ATL, шаблон ATL Property Page и нажмите кнопку Open.
- В окне мастера ATL Property Page выберите вкладку Names и в поле Short Name введите PropDlg.
- Перейдите на вкладку Attributes и просмотрите допустимые установки, ничего в них не меняя.
- Перейдите на вкладку Strings и в поле Title введите имя страницы Light, которое будет обозначено на вкладке (page tab). В поле Doc String введите строку Graphics Properties.
- Нажмите кнопку Finish.
Просмотрите результаты. Прежде всего убедитесь, что в проекте появился новый класс 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, которые могут не совпадать даже с предыдущей версией в этой книге, так как в процессе разработки сервера мне приходится неоднократно повторять заново процесс создания ко-классов:
Примечание 1
Примечание 1
Каждый раз при этом идентификаторы 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 загружается в пространство чужого процесса.
Примечание 2
Примечание 2
Имя ICircCtl, которое присутствует в подсказке, не имеет отношения к нашему проекту. Оно связано с учебным примером по созданию элементов управления с помощью библиотеки ATL. Вы можете увидеть этот пример в MSDN (Visual C++ Tutorials > Creating the Circle Control).
Переменная m_bDirty используется каркасом в качестве флага доступности кнопки Apply. Если m_bDirt у == FALSE; то кнопка недоступна. Она тотчас же должна стать доступной, если пользователь страницы диалога свойств введет изменения в органы управления на лице диалога. Конечно, этим состоянием управляет разработчик, то есть мы с вами.