Создание компонентов пользовательского интерфейса

advertisement
Министерство образования
Российской Федерации
Нижегородский государственный университет им. Н.И. Лобачевского
Физический факультет
Кафедра информационных технологий
в физических исследованиях
Н.С. Будников, С.А. Минеев
Создание компонентов
пользовательского интерфейса
виртуальных приборов
Методическое пособие
Нижний Новгород
2002
Н.С. Будников, С.А. Минеев
Создание компонентов
пользовательского интерфейса
виртуальных приборов
Нижний Новгород
2002
УДК 681.3
Будников
Н.
С.,
Минеев
С.А.
пользовательского интерфейса виртуальных
пособие - Н.Новгород: ННГУ, 2002. - 34 с.
Создание
приборов.
компонентов
Методическое
В пособии изложены основные принципы компонентной технологии
ActiveX и приведены практические приемы использования данной
технологии для разработки управляющих компонентов виртуальных
приборов.
Рис. 16
Cоставители:
Будников Н. С. – доцент кафедры ИТФИ, к.ф.-м. н.;
Минеев С. А. – ст. преп. кафедры ИТФИ, к.ф.-м. н.
Рецензент:
нач. отдела ННИПИ “Кварц” Колякин В.М.
Нижегородский государственный университет
им. Н.И. Лобачевского, 2002
Николай Сергеевич Будников, Сергей Алексеевич Минеев
Язык С#
_______________________________________________________
Подписано к печати
. Формат 60х84 116 .
Бумага писчая. Печать офсетная. Усл. печ. л. 1.5
Заказ
. Тираж 200 экз.
________________________________________________________
Нижегородский государственный университет им. Н.И.Лобачевского
603600 ГСП-20, Н. Новгород, просп. Гагарина, 23.
________________________________________________________
Типография ННГУ. 603000, Н. Новгород, ул. Б. Покровская, 37.
ВВЕДЕНИЕ
Последние годы в контрольно-измерительной аппаратуре все шире
применяются цифровые системы управления и контроля. Благодаря
широкому распространению и относительной дешевизне в качестве
системных контроллеров цифровой измерительной техники все чаще
используются персональные компьютеры. Разработчиков и пользователей
контрольно-измерительной аппаратуры привлекает большое количество
программного обеспечения для персональных компьютеров, особенно развитые средства разработки, визуализации и межпрограммного
взаимодействия. Широкое использование средств общего назначения
значительно увеличивает надежность и снижает стоимость разработки,
сопровождения и использования современной измерительной техники.
Очень часто, при использовании персональных компьютеров в
качестве системных контроллеров, на них возлагаются задачи финишной
обработки и визуализации результатов измерений. Большую роль, в этом
случае, играет удобство и информативность интерфейса пользователя
контрольно-измерительной аппаратуры – программных передних панелей
приборов. Производители аппаратных средств и программного обеспечения
уделяют большое внимание библиотекам используемых повторно
графических управляющих элементов – основе пользовательского
интерфейса программируемых приборов.
Основой систем графического проектирования программных панелей
виртуальных приборов в операционных средах Windows являются элементы
управления ActiveX Controls. В настоящем пособии представлены общие
сведения об ActiveX Controls, описана модель многокомпонентных объектов,
реализующая протокол взаимодействия этих объектов и средств
графического проектирования, представлена иерархия классов и
интерфейсов, используемая для разработки элементов управления и дано
подробное описание технологии использования элементов ActiveX при
создании программных панелей в системе проектирования Microsoft
Developer Studio Visual C++.
1. ОБЩИЕ СВЕДЕНИЯ ОБ ЭЛЕМЕНТАХ УПРАВЛЕНИЯ
ActiveXControls (OCX)
Элементы управления ActiveX пришли на смену элементам управления
Visual Basic (VBX), эксплуатировавшимися, в основном, программистами,
работающими с Microsoft Visual Basic (VB). Элементы VBX есть не что иное,
как готовые программные компоненты, которые программисты на VB могли
купить или написать самостоятельно. Разработка VBX-расширений стала
целой индустрией, и очень скоро их число достигло нескольких тысяч.
Создатели библиотеки MFC предусмотрели возможность использования
VBX и программистами на Visual C++.
Стандарт VBX, тесно связанный с 16-разрядной сегментной
архитектурой, в мире 32-разрядных программ оказался непригодным. На
смену элементам управления VBX пришли элементы на базе ActiveX
технологии (OCX), ставшие промышленным стандартом. Сегодня OCX
элементы доступны программистам, работающим как с MS VB, MS Visual
C++, Borland Delphi и пр.
OCX являются, по сути дела, серверами автоматизации, но в отличии
от последних обладают дополнительной возможностью экспортировать
события. Это позволяет элементам OCX непосредственно управлять
поведением контейнера, который их содержит.
1.1. OCX и обычные элементы управления
OCX – это программный модуль, подключаемый к программе так же,
как и элемент управления Windows. По крайней мере, так это кажется на
первый взгляд. Ниже проанализированы сходства и различия между OCX и
обычными элементами управления.
1.1.1. Основные характеристики обычных элементов управления
Обычные элементы управления Windows вроде полей ввода/вывода и
новые элементы управления Windows 95 работают, в основном, аналогично.
Все эти элементы – дочерние окна, применяемые, главным образом, в
диалоговых окнах. Для их представления в MFC служат классы типа CEdit
или CTreeCtrl. За создание дочернего окна элемента управления всегда
отвечает клиентская программа.
Обычные элементы управления посылают диалоговому окну
уведомляющие сообщения (стандартные Windows-сообщения), например
BN_CLICKED. Если нужно что-то сделать с элементом управления,
необходимо вызвать функцию-член соответствующего класса C++, которая
передает этому элементу Windows-сообщение. Все элементы управления по
своей природе – окна. Классы MFC для элементов управления являются
производными от класса CWnd, поэтому, чтобы получить текст из поля
ввода, нужно обратиться к функции CWnd::GetWindowText(). Но работа
даже этой функции строится на передаче сообщения элементу управления.
Элементы управления Windows – неотъемлемая часть этой
операционной системы, хотя стандартные элементы управления Windows 95
и находятся в отдельном DLL-модуле. Кроме того, имеется еще одна
разновидность обычных элементов управления Windows – так называемые
пользовательские (custom controls). Они создаются самим программистом, не
являются частью операционной системы, но работают как обычные
элементы управления, т.е. передают родительскому окну уведомления
WM_COMMAND и обрабатывают посылаемые ему сообщения.
1.1.2. Что общего у OCX и обычных элементов управления
OCX можно рассматривать как дочернее окно, подобно обычному
элементу управления. Чтобы включить OCX в диалоговое окно, Вы
вставляете его туда с помощью графического редактора, и идентификатор
OCX появляется в шаблоне ресурса. А при динамическом создании OCX («
на лету») Вы вызываете функцию Create() класса, представляющего данный
OCX. Обычно это делается в обработчике сообщения WM_CREATE
родительского окна. При необходимости выполнения каких-то операций с
OCX вызывается функция-член C++ (так же, как и для элемента управления
Windows). Окно, содержащее OCX, называется контейнером.
1.1.3. В чем отличия OCX и обычных элементов управления
Наиболее очевидной отличительной чертой OCX является наличие у
них свойств и методов. Все функции C++, вызываемые для работы с OCX,
так или иначе, используют методы и свойства. У свойств имеются
символические имена, которым соответствуют целочисленные индексы.
Разработчик OCX присваивает каждому свойству определенное внешнее
имя, скажем, BackColor или Position (для элемента управления OCX
“Indicator”, на примере которого в настоящей работе будет рассмотрена
работа с OCX), и тип, например, строка, целое или вещественное двойной
точности. Клиентская программа может устанавливать отдельные свойства
OCX, задавая их целочисленные индексы и значения. В некоторых случаях
ClassWizard позволяет определить элементы данных в классе клиентского
окна, которые сопоставляются со свойствами OCX-элементов, имеющихся в
клиентском классе. Сгенерированный DDX-код осуществляет обмен
информацией между свойствами OCX и элементами данных клиентского
класса.
Методы OCX подобны функциям. У метода есть символьное имя,
набор параметров и возвращаемое значение. Вызов метода осуществляется
вызовом функции-члена класса C++, представляющего данный OCX.
Разработчик OCX может определить любые нужные методы, например,
GetRange(), SetForeColor() и т.д.
В отличие от обычных элементов управления, OCX не посылает
своему контейнеру уведомляющих сообщений WM_…; вместо этого он
«генерирует события». Событие носит символьное имя (например, Click,
DblClick или Keydown). В сущности, это функция контейнера, которую
вызывает OCX. Как и уведомляющие сообщения от обычных элементов
управления, сообщения не возвращают OCX каких-либо данных. События
увязываются с Вашим клиентским классом так же, как и уведомляющие
сообщения от элементов управления.
В библиотеке MFC элементы OCX ведут себя как обычные дочерние
окна, однако, между окном контейнера и окном OCX весьма «толстая»
прослойка кода. Кроме того, у элемента OCX окна может и не быть вовсе.
При вызове Create() окно OCX непосредственно не создается; вместо этого
OCX загружается, и ему выдается команда для активизации на месте. После
этого OCX формирует свое окно, доступ к которому MFC обеспечивает через
указатель на CWnd. Однако использование клиентской программой
дескриптора hWnd элемента OCX было бы не самой удачной мыслью.
OCX-элементы хранятся в отдельных DLL-модулях. Фактически OCX
– это и есть DLL. Прикладная программа загружает OCX-элементы по мере
необходимости, используя изощренные приемы ActiveX-технологии,
базирующиеся на операциях с реестром Windows. Т.е. OCX-элемент,
указанный на этапе разработки программы, будет загружен только в период
выполнения этой программы. Естественно, при распространении
приложения, требующего определенных OCX-элементов, Вам надо
включить в комплект поставки файлы OCX и соответствующую программу
установки (т. е. регистрации элементов в реестре).
2. МОДЕЛЬ МНОГОКОМПОНЕНТНЫХ ОБЪЕКТОВ ДЛЯ
СРЕДСТВ ГРАФИЧЕСКОГО ПРОЕКТИРОВАНИЯ
Ядром ActiveX-технологии является модель многокомпонентных
объектов (Component Object Model - COM). COM обеспечивает стандарт
взаимодействия между программными модулями. COM – это протокол,
который соединяет один объект, сервер автоматизации ActiveX, с другим,
который называется контейнером или контроллером автоматизации. Модули
взаимодействуют друг с другом через механизм, называемый интерфейсом
(COM-интерфейс).
Базовым классом для модели компонентных объектов является класс, а
точнее структура IUnknow, объявляющая три чисто виртуальные функции.
Функция QueryInterface() возвращает указатель на интерфейс по
переданному ей идентификатору интерфейса, функция AddRef() увеличивает
число ссылок на вызванный интерфейс на единицу, функция Release()
вызывается программой-клиентом сервера при завершении работы с ним.
При этом количество ссылок на интерфейс уменьшается на единицу. Когда
число ссылок уменьшается до нуля, многокомпонентный объект
уничтожается.
Класс IСlassFactory инкапсулирует этап создания многокомпонентного
объекта с помощью функции CreateInstance(). Этот класс предназначен для
оптимизации создания многих экземпляров объекта.
Класс IDispatch поддерживает обмен данными между сервером
автоматизации и контейнером. Главной функцией этого класса, которая
собственно и занимается обменом, является функция Invoke(). Интерфейс
IDispatch является основным интерфейсом для элементов управления
ActiveX.
Ниже приведены объявления этих классов.
interface IUnknown
{
public:
BEGIN_INTERFACE
virtual HRESULT STDMETHODCALLTYPE QueryInterface(
int riid, void ** ppvObject) = 0;
virtual ULONG STDMETHODCALLTYPE AddRef( void) = 0;
virtual ULONG STDMETHODCALLTYPE Release( void) = 0;
END_INTERFACE
};
interface IClassFactory : public IUnknown
{
public:
virtual HRESULT STDMETHODCALLTYPE CreateInstance(
int riid, void * * ppvObject) = 0;
virtual HRESULT STDMETHODCALLTYPE LockServer(
BOOL fLock) = 0;
};
interface IDispatch : public IUnknown
{
public:
virtual HRESULT STDMETHODCALLTYPE GetTypeInfoCount(
UINT* pctinfo) = 0;
virtual HRESULT STDMETHODCALLTYPE GetTypeInfo(
UINT iTInfo,
LCID lcid,
void ** ppTInfo) = 0;
virtual HRESULT STDMETHODCALLTYPE GetIDsOfNames(
int riid,
char * rgszNames,
UINT cNames,
LCID lcid,
void*rgDispId) = 0;
virtual HRESULT STDMETHODCALLTYPE Invoke(
DISPID dispIdMember,
int riid,
LCID lcid,
WORD wFlags,
void *pDispParams,
VARIANT *pVarResult,
char *pExcepInfo,
char *puArgErr) = 0;
};
В библиотеке MFC классом, поддерживающим COM-интерфейс,
является класс CCmdTarget, стандартный базовый класс для всех окон и
документов.
При разработке конкретного элемента управления ActiveX
программист пишет классы, производные от классов, указанных выше. Он
замещает чистые виртуальные функции этих абстрактных базовых классов
реальными, которые обеспечивают загрузку сервера и обмен данными между
сервером и контейнером в соответствии с конкретными свойствами и
методами элемента управления ActiveX.
Developer Studio и библиотека классов MFC автоматизируют этот
процесс. При этом используются следующие макросы:
BEGIN_INTERFACE_PART,
END_INTERFACE_PART,
BEGIN_INTERFACE_MAP,
END_INTERFACE_MAP,
BEGIN_DISPATCH_MAP,
END_ DISPATCH _MAP.
Первые два макроса порождают имена вложенных классов и имена
внедренных объектов для COM-интерфейсов реальных серверов
автоматизации. Два следующих создают таблицы интерфейсов,
используемых функцией QueryInterface класса CCmdTarget. Два последних
делают тоже, но для интерфейса IDispatch.
3. ИЕРАРХИЯ КЛАССОВ И ИНТЕРФЕЙСОВ ДЛЯ СЕРВЕРОВ
АВТОМАТИЗАЦИИ ActiveX ПРИКЛАДНОГО ПО. ЭЛЕМЕНТ
УПРАВЛЕНИЯ ActiveXControls Indicator
В настоящем разделе приведена иерархия классов и интерфейсов
серверов автоматизации ActiveX, использованных нами для создания
элементов управления ActiveX Controls. Напомним, что эти элементы
являются серверами автоматизации с дополнительной возможностью
генерировать события управляющие работой контейнера.
Для апробирования и описания технологии создания и использования
ActiveX Controls при проектировании панелей виртуальных приборов нами
был разработан элемент управления “Indicator”. Этот OCX имитирует работу
индикатора уровня сигнала, поступающего на его вход. OCX “Indicator”
имеет следующие свойства:
Position – текущее значение уровня сигнала;
Range – интервал возможных значений уровня сигнала;
Solid – признак внешнего вида индикатора (сплошной
линейчатый);
BackColor – цвет заднего фона индикатора;
ForeColor – цвет переднего фона индикатора.
или
Для доступа к этим свойствам OCX “Indicator” имеет соответствующие
Get()/Set() методы.
Кроме того, OCX “Indicator” имеет метод Clean() для очистки
индикатора и способен генерировать событие Click в ответ на щелчок левой
клавишей мыши на изображении индикатора.
При разработке этого элемента управления использовались следующие
классы.
Класс CindicatorApp наследуется от COleControlModule и задает
перегрузку его функций InitInstance() и ExitInstance(). Здесь же содержится
уникальный идентификатор элемента управления _tlid.
Полная иерархия класса COleControlModule выглядит следующим
образом:
CObject
CCmdTarget
CWinThread
CWinApp
COleControlModule.
Класс CIndicatorCtrl наследуется от COleControl и задает перегрузку
функции рисования элемента управления OnDraw() и функции
DoPropExchange(), которая осуществляет инициализацию и хранение
данных.
Полная иерархия класса COleControl выглядит следующим образом:
CObject
CCmdTarget
CWnd
COleControl.
Класс CIndicatorPropPage наследуется от COlePropertyPage. В нем
перегружена функция DoDataExchange(), которая обеспечивает передачу
данных между вкладками свойств элемента управления и реальным окном
элемента, расположенным на экране пользователя.
Полная иерархия класса COlePropertyPage выглядит следующим
образом:
CObject
CCmdTarget
CWnd
CDialog
COlePropertyPage.
Для обеспечения механизма передачи данных между OCX и
контейнером использован рекомендованный Microsoft интерфейс IDispatch.
Иерархия интерфейсов была приведена в предыдущем разделе.
Объявления указанных выше классов и макросы библиотеки MFC,
определяющие интерфейсы OCX “Indicator” приведены ниже.
class CIndicatorApp : public COleControlModule
{
public:
BOOL InitInstance();
int ExitInstance();
};
const GUID CDECL BASED_CODE _tlid =
{ 0x998d8bc4, 0xa62e, 0x11d3, { 0xa3, 0x17, 0x8, 0, 0x30, 0, 0xa,
0xb1 } };
class CIndicatorCtrl : public COleControl
{
DECLARE_DYNCREATE(CIndicatorCtrl)
// Constructor
public:
CIndicatorCtrl();
// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CIndicatorCtrl)
public:
virtual void OnDraw(CDC* pdc, const CRect& rcBounds, const CRect&
rcInvalid);
virtual void DoPropExchange(CPropExchange* pPX);
virtual void OnResetState();
//}}AFX_VIRTUAL
// Implementation
protected:
~CIndicatorCtrl();
DECLARE_OLECREATE_EX(CIndicatorCtrl) // Class factory and guid
DECLARE_OLETYPELIB(CIndicatorCtrl) // GetTypeInfo
DECLARE_PROPPAGEIDS(CIndicatorCtrl) // Property page IDs
DECLARE_OLECTLTYPE(CIndicatorCtrl)
// Type name and misc
status
// Message maps
//{{AFX_MSG(CIndicatorCtrl)
// NOTE - ClassWizard will add and remove member functions here.
// DO NOT EDIT what you see in these blocks of generated code !
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
// Dispatch maps
//{{AFX_DISPATCH(CIndicatorCtrl)
short m_position;
afx_msg void OnPositionChanged();
BOOL m_solid;
afx_msg void OnSolidChanged();
short m_range;
afx_msg void OnRangeChanged();
afx_msg void Clean();
//}}AFX_DISPATCH
DECLARE_DISPATCH_MAP()
afx_msg void AboutBox();
// Event maps
//{{AFX_EVENT(CIndicatorCtrl)
//}}AFX_EVENT
DECLARE_EVENT_MAP()
// Dispatch and event IDs
public:
enum {
//{{AFX_DISP_ID(CIndicatorCtrl)
dispidPosition = 1L,
dispidSolid = 2L,
dispidRange = 3L,
dispidClean = 4L,
//}}AFX_DISP_ID
};
};
class CIndicatorPropPage : public COlePropertyPage
{
DECLARE_DYNCREATE(CIndicatorPropPage)
DECLARE_OLECREATE_EX(CIndicatorPropPage)
// Constructor
public:
CIndicatorPropPage();
// Dialog Data
//{{AFX_DATA(CIndicatorPropPage)
enum { IDD = IDD_PROPPAGE_INDICATOR };
BOOL
m_solid;
//}}AFX_DATA
// Implementation
protected:
virtual void DoDataExchange(CDataExchange* pDX);
support
// Message maps
protected:
//{{AFX_MSG(CIndicatorPropPage)
// DDX/DDV
// NOTE - ClassWizard will add and remove member functions here.
// DO NOT EDIT what you see in these blocks of generated code !
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
// Dispatch map
BEGIN_DISPATCH_MAP(CIndicatorCtrl, COleControl)
//{{AFX_DISPATCH_MAP(CIndicatorCtrl)
DISP_PROPERTY_NOTIFY(CIndicatorCtrl,
"Position",
m_position,
OnPositionChanged, VT_I2)
DISP_PROPERTY_NOTIFY(CIndicatorCtrl,
"Solid",
m_solid,
OnSolidChanged, VT_BOOL)
DISP_PROPERTY_NOTIFY(CIndicatorCtrl,
"Range",
m_range,
OnRangeChanged, VT_I2)
DISP_FUNCTION(CIndicatorCtrl,
"Clean",
Clean,
VT_EMPTY,
VTS_NONE)
DISP_STOCKPROP_BACKCOLOR()
DISP_STOCKPROP_FORECOLOR()
//}}AFX_DISPATCH_MAP
DISP_FUNCTION_ID(CIndicatorCtrl,
"AboutBox",
DISPID_ABOUTBOX, AboutBox, VT_EMPTY, VTS_NONE)
END_DISPATCH_MAP().
Здесь m_position, m_range и m_solid – внутренние элементы данных классов,
соответствующие свойствам Position, Range и Solid. Свойства BackColor и
ForeColor используются непосредственно в функции рисования OnDraw().
Набор представленных выше классов и макросов достаточен для
разработки любого ActiveX Controls из спектра необходимых для создания
программных панелей приборов.
4. ТЕХНОЛОГИЯ ИСПОЛЬЗОВАНИЯ ЭЛЕМЕНТОВ ActiveX
(OCX) ПРИ СОЗДАНИИ ПРОГРАММНЫХ ПАНЕЛЕЙ С ПОМОЩЬЮ
DEVELOPER STUDIO И MFC
При использовании элементов управления OCX в диалоговых панелях,
необходимо выполнить установку нужного элемента управления, например,
OCX “Indicator”, в Вашу операционную систему, установку его в
разрабатываемый проект и выполнить программирование контейнера (класса
диалоговой панели) включающего этот OCX.
4.1. Установка OCX
В первую очередь его нужно скопировать на жесткий диск. Его можно
разместить в любом месте, но Вам будет легче отслеживать свои OCX, если
все они будут где-нибудь в одном месте, например в каталоге
WINDOWS\SYSTEM. Некоторые OCX требуют лицензирования, и поэтому в
их состав включают отдельные LIC-файлы; кроме того, они могут нуждаться
в дополнительных записях в реестре. Лицензируемые OCX обычно
поставляются с программой установки, которая и заботится об этих
операциях.
Установка OCX может быть осуществлена с помощью Developer
Studio. Выберите из меню “Tools” команду “ActiveX Control Test Container”.
При этом открывается окно, показанное на рис.1.
Рис.1. Окно тестирующего OCX контейнера.
Из меню “File” выберите команду “Register Controls”. Откроется окно,
показанное на рис.2.
Рис.2. Окно регистрации OCX.
Нажмите кнопку “Register…”. При этом появится стандартное диалоговое
окно открытия файла (см. рис.3). Найдите с его помощью папку с
интересующим Вас элементом OCX и откройте этот файл. Установка ОСХ в
системе на этом завершится и его наименование появится в списке
элементов управления окна “Controls Registry”. Все окна после этого можно
закрыть.
Рис.3. Диалоговое окно поиска нужного OCX на диске.
После установки OCX информация о нем размещается в четырех местах
реестра Windows:
\HKEY_CLASSES_ROOT\CLSID
\HKEY_CLASSES_ROOT\TypeLib
\HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID
\HKEY_LOCAL_MACHINE\SOFTWARE\Classes\TypeLib
Фактически,
когда
Developer
Studio
вызывает
специальную
экспортированную функцию, OCX регистрирует сам себя. Другие
регистрационные утилиты типа Regserv (эта программа имеется на
прилагаемом к настоящему документу диске в каталоге Regserv), делают
абсолютно то же самое. Код, отвечающий за регистрацию OCX в программе
Regserv, выглядит примерно следующим образом:
HINSTANCE h = ::LoadLibrary(strDllPath);
FARPROC pFunc=
::GetProcAddress((HMODULE)h,"DllRegisterServer");
(*pFunc)();
Здесь strDllPath – полное имя DLL- или OCX- модуля.
Следующий шаг – установка OCX в Developer Studio в нужный проект.
Выберите из меню “Project” команду “AddToProject”. Откроется подменю, из
которого нужно выбрать команду “Components and Controls…”. В
открывшемся диалоговом окне “Components and Controls Gallery” (см. рис.4)
войдите в папку “Registered ActiveX Controls”. Вы увидите OCX,
установленные и зарегистрированные в вашей системе (см. рис.5). Если
нужный OCX уже установлен в системе, Вы увидите там его значок. (Для
этого содержимое окна, возможно, придется прокрутить.) Установка OCX в
проект осуществляется щелчком кнопки “Insert”.
Выбранный элемент OCX надо установить в каждый проект, где он
будет использоваться. Это не значит, что копируется сам OCX. ClassWizard
создаст копию класса C++, соответствующего данному OCX, и последний
появится в палитре элементов управления в графическом редакторе (для
проекта, в котором установлен OCX).
Рис.4. Диалоговое окно “Components and Controls Gallery”.
Рис.5. Диалоговое окно со списком зарегистрированных OCX-элементов.
4.2. Программирование контейнера OCX
MFC и ClassWizard поддерживают использование OCX как в
диалоговых окнах, так и в виде «дочерних окон». Чтобы использовать OCX,
надо знать, каким образом OCX обеспечивает доступ к своим свойствам и
как Ваш DDX-код взаимодействует со значениями этих свойств.
4.2.1. Доступ к свойствам
Набор свойств, доступных на этапе разработки, определяется
создателем OCX. Эти свойства отображаются на вкладках свойств OCX,
которые элемент управления при его двойном щелчке отображает в
графическом редакторе. Вкладки свойств элемента управления “Indicator”
показаны ниже.
В период выполнения доступны все свойства OCX, в том числе и те,
что были доступны на этапе разработки. Однако некоторые из них могут
быть определены «только для чтения».
Рис.6. Вкладка стандартных свойств “General” элемента управления Indicator.
Рис.7. Вкладка пользовательских (Custom) свойств “Control” элемента
управления Indicator.
Рис.8. Вкладка типовых (Stock) свойств “Цвета” элемента управления
Indicator.
Рис.9. Вкладка всех свойств “All” элемента управления Indicator.
4.2.2. C++-классы оболочки, генерируемые ClassWizard для OCX
При установке OCX в проект ClassWizard генерирует один или несколько
C++-классов оболочек, производных от CWnd, в соответствии с набором
свойств и методов данного OCX. Так, если в процессе установки элемента
OCX “Indicator” (см. параграф «Установка OCX»), согласиться с
предложенным ClassWizard именем класса (см. рис.10), будет сгенерирован
класс CIndicator.
Рис.10. Диалоговое окно с предложением о формировании класса Cindicator.
Сгенерированный класс содержит функции-члены для всех свойств и
методов, а также конструкторы, которые можно использовать для
динамического создания экземпляров соответствующего OCX.
class CIndicator : public CWnd
{
protected:
DECLARE_DYNCREATE(CIndicator)
public:
CLSID const& GetClsid()
{
static CLSID const clsid
= { 0x998d8bc7, 0xa62e, 0x11d3, { 0xa3, 0x17, 0x8, 0x0,
0x30, 0x0, 0xa, 0xb1 } };
return clsid;
}
virtual BOOL Create(LPCTSTR lpszClassName,
LPCTSTR lpszWindowName, DWORD dwStyle,
const RECT& rect,
CWnd* pParentWnd, UINT nID,
CCreateContext* pContext = NULL)
{ return CreateControl(GetClsid(), lpszWindowName, dwStyle, rect,
pParentWnd, nID); }
BOOL Create(LPCTSTR lpszWindowName, DWORD dwStyle,
const RECT& rect, CWnd* pParentWnd, UINT nID,
CFile* pPersist = NULL, BOOL bStorage = FALSE,
BSTR bstrLicKey = NULL)
{ return CreateControl(GetClsid(), lpszWindowName, dwStyle, rect,
pParentWnd, nID,
pPersist, bStorage, bstrLicKey); }
// Attributes
public:
short GetPosition();
void SetPosition(short);
BOOL GetSolid();
void SetSolid(BOOL);
OLE_COLOR GetBackColor();
void SetBackColor(OLE_COLOR);
OLE_COLOR GetForeColor();
void SetForeColor(OLE_COLOR);
short GetRange();
void SetRange(short);
// Operations
public:
void Clean();
void AboutBox();
};
Ниже приведены определения функций-членов из CPP-файла, генерируемого
ClassWizard для элемента управления “Indicator”.
short CIndicator::GetPosition()
{
short result;
GetProperty(0x1, VT_I2, (void*)&result);
return result;
}
void CIndicator::SetPosition(short propVal)
{
SetProperty(0x1, VT_I2, propVal);
}
BOOL CIndicator::GetSolid()
{
BOOL result;
GetProperty(0x2, VT_BOOL, (void*)&result);
return result;
}
void CIndicator::SetSolid(BOOL propVal)
{
SetProperty(0x2, VT_BOOL, propVal);
}
OLE_COLOR CIndicator::GetBackColor()
{
OLE_COLOR result;
GetProperty(DISPID_BACKCOLOR, VT_I4, (void*)&result);
return result;
}
void CIndicator::SetBackColor(OLE_COLOR propVal)
{
SetProperty(DISPID_BACKCOLOR, VT_I4, propVal);
}
OLE_COLOR CIndicator::GetForeColor()
{
OLE_COLOR result;
GetProperty(DISPID_FORECOLOR, VT_I4, (void*)&result);
return result;
}
void CIndicator::SetForeColor(OLE_COLOR propVal)
{
SetProperty(DISPID_FORECOLOR, VT_I4, propVal);
}
short CIndicator::GetRange()
{
short result;
GetProperty(0x3, VT_I2, (void*)&result);
return result;
}
void CIndicator::SetRange(short propVal)
{
SetProperty(0x3, VT_I2, propVal);
}
void CIndicator::Clean()
{
InvokeHelper(0x4, DISPATCH_METHOD, VT_EMPTY, NULL, NULL);
}
Особенно беспокоиться насчет кода этих функций не следует, однако
обратите внимание на то, первый параметр в вызовах GetPropety(),
SetPropety()
и
InvokeHelper(),
соответствует
диспетчерским
идентификаторам в списке свойств элемента управления “Indicator”. Как
видите, для каждого свойства существуют пары функций Get(получить) и
Set(установить). Как и в случае событий, Вы просто вызываете нужную
функцию. Например, для изменения свойств Range, Position и BackColor из
функции класса диалогового окна можно написать так:
m_indicator.SetRange(100);
m_indicator.SetPosition(10);
m_indicator.SetBackColor((OLE_COLOR)RGB(255,255,0));
Здесь m_indicator – объект класса CIndicator.
4.2.3. Поддержка OCX в AppWizard
Если при разработке проекта диалоговой панели выбрать в AppWizard
опцию ActiveX Controls (см. рис.11), в функцию InitInstance() нового
приложения будет вставлена следующая строка:
AfxEnableControlContainer( );
Кроме того, в файле StdAfx.h проекта появится:
#include <afxdisp.h>
Если Вы решили использовать OCX- элементы в уже существующем
проекте, можете сами вставить две приведенные выше строки.
Рис.11. Диалоговое окно AppWizard с выбранной опцией ActiveX Controls.
4.2.4. ClassWizard и диалоговое окно контейнера
Если использовать графический редактор для создания шаблона
диалогового окна, то после установки соответствующего элемента OCX в
проект, в диалоговом окне “Controls” появится значок, соответствующий
этому элементу. Необходимый элемент может быть размещен на диалоговой
панели также как и обычный элемент управления. На рис.12 показаны окно
“Controls” и диалоговая панель с размещенным на ней OCX “Indicator”.
Свойства этого OCX могут быть отредактированы с помощью вставок
свойств элемента “Indicator” (см. параграф «Доступ к свойствам»). С
помощью ClassWizard можно добавить элементы данных, соответствующих
свойствам OCX (см. рис.13), объект класса CIndicator (см. рис.14) и
обработчик события Click (см. рис.15).
Рис.12. Окно графического редактора с диалоговой панелью, на которой
размещен элемент управления “Indicator”.
Рис.13. Окно добавления элементов данных к классу диалогового окна,
содержащего OCX Indicator.
Рис.14. К классу диалогового окна CIndicatorTestDlg добавлен член данных
типа CIndicator.
Рис.15. К классу диалогового окна CIndicatorTestDlg добавлен обработчик
события Click элемента управления “Indicator”.
4.2.5. Элементы данных класса диалогового окна и использование
класса оболочки
Какого же типа элементы данных можно добавить к диалоговому окну
для OCX? Чтобы установить значение того или иного свойства OCX перед
вызовом функции DoModal() для данного диалогового окна, добавьте
элемент данных, соответствующий этому свойству. Если же Вам нужно
изменять значения свойств из функций-членов класса диалогового окна,
используйте другой подход: добавьте элемент данных – объект класса
оболочки OCX.
А теперь самое время обратиться к механизму работы DDX MFC.
Вернемся к классу диалогового окна CIndicatorTestDlg. Для считывания его
элементов
данных
функция
CDialog::OnInitDialog()
вызывает
CWnd::UpdateData(FALSE), а для записи в эти переменные CDialog::OnOK()
вызывает CWnd::UpdateData(TRUE). Допустим, Вы добавили по одному
элементу данных для каждого свойства OCX и хотите получить значения
свойства Range в обработчике сообщений от элемента управления. Если
вызвать UpdateData(FALSE), будут считаны значения всех свойств всех
элементов управления диалогового окна – ясно, что это пустая трата
времени. Эффективнее - не использовать элемент данных, а вместо этого
вызвать функцию Get класса оболочки. Для этого надо создать с помощью
ClassWizard элемент данных – объект класса оболочки.
Предположим, у нас есть класс оболочки CIndicatorCtrl для “Indicator”,
и в классе диалогового окна присутствует элемент данных m_indicator. Тогда
значение свойства Position можно получить так:
short Position = m_indicator.GetPosition( );
Теперь рассмотрим другой случай: прежде чем элемент управления будет
отображен на экране, надо установить признак внешнего вида индикатора.
Для этого нужно добавить с помощью ClassWizard элемент данных m_solid,
соответствующий свойству Solid элемента управления “Indicator”.
Диалоговое окно создается и отображается так:
CmyDialog dlg;
Dlg.m_solid = TRUE;
Dlg.DoModal( );
Перед отображением элемента управления на экране DDX-код позаботится
об установке значения свойства в соответствии со значением элемента
данных. Дополнительного программирования для этого не требуется. Как и
можно было ожидать, DDX-код устанавливает значение элемента данных в
соответствии со значением свойства при щелчке кнопки OK.
ClassWizard необязательно генерирует элементы данных для всех
свойств элемента управления. В частности, он не поддерживает
индексируемые свойства. Для доступа к таким свойствам надо использовать
класс оболочку.
4.2.6. Обработка событий OCX
ClassWizard позволяет увязывать события OCX с функциями-членами так же,
как это делается для Widows-сообщений и командных сообщений от
элементов управления. Если класс диалогового окна содержит один и более
OCX, ClassWizard создает и поддерживает карту стоков событий (event sink
map), которая задает соответствие между событиями и их функциямиобработчиками. Она работает подобно карте сообщений. Пример такого кода
для события Click приведен ниже.
BEGIN_EVENTSINK_MAP(CIndicatorTestDlg, CDialog)
//{{AFX_EVENTSINK_MAP(CIndicatorTestDlg)
ON_EVENT(CIndicatorTestDlg,
IDC_INDICATORCTRL,
-600 /* Click */,
OnClickIndicatorctrl, VTS_NONE)
//}}AFX_EVENTSINK_MAP
END_EVENTSINK_MAP()
Функция обработки события Click вместе с функцией обработки сообщений
таймера в тестовом примере приведены ниже. В этом примере в качестве
отображаемого на индикаторе сигнала генерируется синусоида.
void CIndicatorTestDlg::OnClickIndicatorctrl()
{
// TODO: Add your control notification handler code here
static int c=1;
if(c==0)
{
KillTimer(1);
short i=m_indicator.GetPosition();
CString text;
text.Format("%i",i);
GetDlgItem(IDC_POS)->SetWindowText(text);
c=1;
}
else
{
SetTimer(1,100,NULL);
c=0;
}
}
void CIndicatorTestDlg::OnTimer(UINT nIDEvent)
{
// TODO: Add your message handler code here and/or call default
static double x=1;
double r=24.0*sin(x)+25.0;
short i=(short)r;
m_indicator.SetPosition(i);
x+=0.1;
CDialog::OnTimer(nIDEvent);
}
4.2.7. Блокировка OCX в памяти
Обычно OCX остается спроецированным на адресное пространство
Вашего процесса только на то время, пока активно его родительское
диалоговое окно. Значит, OCX приходится загружать заново всякий раз,
когда пользователь открывает это диалоговое окно. Благодаря дисковому
кэшу, повторная загрузка проходит быстрее первоначальной. Тем не менее,
чтобы повысить производительность, можно заблокировать OCX в памяти.
Для этого в переопределенную функцию OnInitDialog() после вызова
функции базового класса добавьте следующую строку:
AfxOleLockControl(m_indicator.GetClsid( ) );
OCX останется спроецированным на адресное пространство процесса до тех
пор, пока не завершиться программа или не будет вызвана одна из функций:
AfxUnlockControl() либо AfxUnlockAllControls().
4.2.8. Создание OCX в период выполнения
Вы уже поняли, как с помощью графического редактора можно создавать
OCX-элементы в период разработки программы. А чтобы создать OCX в
период выполнения без шаблона ресурсов нужно выполнить следующие
операции.
1. Добавьте элемент данных класса оболочки OCX в Ваш класс диалогового
окна или другой C++-класс окна. Внедренный объект C++ создается и
разрушается автоматически вместе с объектом «окно».
2. При активном графическом редакторе выберите из меню View в Developer
Studio команду “Resource Symbols”. Откроется окно, показанное на
рис.16. Добавьте константу-идентификатор для нового элемента
управления.
3. Если в качестве родительского окна выступает диалоговое окно, создайте
с помощью ClassWizard обработчик сообщения WM_INITDIALOG и тем
самым переопределите CDialog::OnInitDialog(). Для окон других типов
создайте с помощью ClassWizard обработчик сообщения WM_CREATE.
Новая функция должна вызывать функцию-член Create() класса
внедренного OCX. Этот вызов неявно приведет к отображению нового
элемента управления в диалоговом окне. Элемент управления будет
разрушен при уничтожении родительского окна.
4. В классе родительского окна вручную добавьте нужные обработчики
событий для нового элемента управления. Не забудьте добавить макросы
карты стоков событий.
Рис.16. Окно “Resource Symbols”.
ClassWizard не поможет в работе с картой стоков событий, если OCX
создается в проекте динамически. Однако можно вставить данный OCX в
диалоговое окно другого, временного проекта. В итоге Вы просто
скопируете код карты стоков событий в основной проект, в класс
родительского окна.
ЗАКЛЮЧЕНИЕ
Несмотря на наличие на рынке большого количества элементов OCX
различного назначения, очень часто возникает необходимость создания
собственного, уникального элемента управления (экономически оправдана
разработка элементов управления, которые планируется использовать
многократно). Описанные в настоящем пособии технологии разработки и
использования элементов управления ActiveX Controls (OCX) могут успешно
применяться для разработки программных передних панелей виртуальных
приборов с развитым интерфейсом пользователя.
Рекомендуемая литература
1. Роберт Дж. Оберг, Технология COM+. Основы и программирование.
Микрософт Пресс, 1999.
2. Brockschmidt K., Inside OLE, Microsoft Press, 1995.
3. Дейл Роджерсон. Основы СОМ. М.: Издательский отдел “Русская
Редакция” ТОО “Channel Trading Ltd”, 1997.
Содержание
ВВЕДЕНИЕ
Стр.
2
1. ОБЩИЕ СВЕДЕНИЯ ОБ ЭЛЕМЕНТАХ УПРАВЛЕНИЯ
ActiveXControls (OCX)
2
1.1. OCX и обычные элементы управления
3
1.1.1. Основные характеристики обычных элементов
управления
3
1.1.2. Что общего у OCX и обычных
элементов управления
4
1.1.3. В чем отличия OCX и обычных
элементов управления
4
2. МОДЕЛЬ МНОГОКОМПОНЕНТНЫХ ОБЪЕКТОВ ДЛЯ СРЕДСТВ
ГРАФИЧЕСКОГО ПРОЕКТИРОВАНИЯ
5
3. ИЕРАРХИЯ КЛАССОВ И ИНТЕРФЕЙСОВ ДЛЯ СЕРВЕРОВ
АВТОМАТИЗАЦИИ ActiveX ПРИКЛАДНОГО ПО. ЭЛЕМЕНТ
УПРАВЛЕНИЯ ActiveXControls Indicator
4. ТЕХНОЛОГИЯ ИСПОЛЬЗОВАНИЯ ЭЛЕМЕНТОВ
ActiveX (OCX) ПРИ СОЗДАНИИ ПРОГРАММНЫ ПАНЕЛЕЙ
С ПОМОЩЬЮ DEVELOPER STUDIO И MFC
4.1. Установка OCX
4.2. Программирование контейнера OCX
4.2.1. Доступ к свойствам
4.2.2. C++-классы оболочки, генерируемые
ClassWizard для OCX
4.2.3. Поддержка OCX в AppWizard
4.2.4. ClassWizard и диалоговое окно контейнера
4.2.5. Элементы данных класса диалогового окна и
использование класса оболочки
4.2.6. Обработка событий OCX
4.2.7. Блокировка OCX в памяти
4.2.8. Создание OCX в период выполнения
ЗАКЛЮЧЕНИЕ
8
12
13
17
17
18
22
23
26
27
28
28
30
Декану Физического ф-та
профессору Чупрунову Е.В.
Служебная записка
Прошу оплатить расходы, связанные с опубликованием в типографии
ННГУ методического пособия: Будников Н. С., Минеев С.А. “Создание
компонентов пользовательского интерфейса виртуальных приборов”. Данное
пособие включено в план издания на IV квартал 2001 г. и будет
использоваться при проведении практических занятий и лабораторных работ
по курсу “Физические средства информационных систем”.
Зав. каф. ИТФИ
_______________ Фидельман В.Р.
Download