Страницы
свойств
Перед тем как
мы начнем работать с окном СОМ-объекта, вводя в него реакции на управляющие
воздействия, покажем, как добавить страницу свойств (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,
которые могут не совпадать даже с предыдущей версией в этой книге, так как в
процессе разработки сервера мне приходится неоднократно повторять заново процесс
создания ко-классов:
Примечание
Каждый раз при этом идентификаторы
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; то кнопка недоступна. Она тотчас же должна стать доступной,
если пользователь страницы диалога свойств введет изменения в органы управления
на лице диалога. Конечно, этим состоянием управляет разработчик, то есть мы
с вами.