div.main {margin-left: 20pt; margin-right: 20pt} Как создавать значки в панели управления.
Автор: Paul DiLascia
Скачать исходник к статье - 179 Кб (Компилятор: Visual C++)
Значки, которые мы привыкли видеть в панели управления, это так
называемые апплеты, которые представляют из себя обычные DLL-ки, имеющие
расширение .cpl и содержащие в себе специфическую функцию CPlApplet.
Каждый раз, когда запускается приложение панели управления (CONTROL.EXE),
то сперва оно ищет в системной директории все файлы XXX.cpl, затем
загружает каждую DLL и вызывает функцию CPlApplet с различными
сообщениями. Например, когда Панель управления запускается первый раз, то
функция CPlApplet вызывается с сообщением msg=CPL_INIT. Затем, если
пользователь дважды кликнет по иконке аплета, то CPlApplet будет вызвана с
сообщением msg=CPL_DBLCLK.
Каждая DLL-ка панели управления может поддерживать несколько иконок или
апплетов. Для этого панель управления посылает сообщение CPL_GETCOUNT, и
от нас требуется сообщить ей точное количество. После этого панель
управления запросит информацию о каждом апплете при помощи сообщений
CPL_INQUIRE или CPL_NEWINQUIRE. На рисунке показан процесс посылки
сообщений панелью управления:
Рисунок 1.
Процедура общения Вашей DLL с панелью управления довольно универсальна
и легко воплощается в классах. Поэтому я создал два класса
CControlPanelApp и CCPApplet, которые собственно и занимаются процессом
общения. Чтобы показать, как это работает, я написал собственную DLL
панели управления MyPanel. Она включает в себя два апплета (Рисунок 2),
один диалог (Рисунок 3) и одно окошко с закладками (Рисунок 4)
Рисунок 2.
MyPanel.cpp представляет обычное MFC Документ/Вид приложение за
исключением того, что класс наследован от CControlPanelApp вместо CWinApp.
А вместо InitInstance (которая обычно используется для добавления шаблонов
документов) я вызываю OnInit, в которой создаю два апплета: BOOL CMyControlPanelApp::OnInit()
{
AddApplet(newCCPApplet(
IDR_MYAPPLET1,
RUNTIME_CLASS(
CMyDialog1)));
AddApplet(new CCPApplet(
IDR_MYAPPLET3,
RUNTIME_CLASS(
CMyPropSheet)));
return CControlPanelApp::OnInit();
}
Класс апплета CCPApplet настолько универсален, что даже нет
необходимости в MyPanel наследовать от него собственный. Единственное, что
прийдётся дописать соственно код для диалогов. В моём случае, MyPanel
включает диалог (CMyDialog) и property sheet (CMyPropSheet). Чтобы
добавить свои диалоги, достаточно написать и и переопределить
CControlPanelApp::OnInit как показано выше. Класс сделает всё остальное
самостоятельно.
Рисунок 3.
Классы CControlPanelApp и CCPAppletBut так же заботятся о иконках,
описании, функции CPlApplet а так же о всех CPL сообщениях. CPanel.cpp
содержит в себе функцию CPlApplet, которая передаёт CPL сообщения в
виртуальную функцию. Когда панель управления вызывает CPlApplet с
сообщением CPL_INIT, то CPlApplet вызывает CControlPanelApp:: OnCplMsg,
которая в свою очередь вызывает CControlPanelApp::OnInit. OnCplMsg это
аналог CWnd::WindowProc, а OnInit - аналогичен обработчику сообщения
OnCreate. Некоторые CPL сообщения, типа CPL_INQUIRE и CPL_ DBLCLK, имеют
параметр lParam1, который содержит номер апплета (индекс), для которого
предназначено сообщение. Как я уже говорил, DLL-ка панели управления может
обслуживать несколько иконок или апплетов, поэтому в таких случаях
CControlPanelApp::OnCplMsg направляет сообщение в виртуальную функцию в
CCPApplet, а не CControlPanelApp.
Рисунок 4.
А теперь предлагаю более подробно разобраться с моим апплетом. Для
создания апплета, вызывается конструктор, в который необходимо передать ID
ресурса и MFC runtime class. AddApplet(new CCPApplet(IDR_MYAPPLET3, RUNTIME_CLASS(CMyPropSheet)));
Этой информации достаточно для создания апплета. После вызова этой
функции, Ваш апплет добавится к списку m_lsApplets. Дефолтовый обработчик
для CPL_ GETCOUNT возвращает число апплетов, беря информацию именно из
этого списка. Как только панель управления пошлёт CPL_INQUIRE или CPL_
NEWINQUIRE, то CCPApplet воспользуется идентификатором (ID) ресурса, чтобы
получить иконку, имя и описание. Имя и описание, разделены на подстроки в
основной строке ресурса.
STRINGTABLE PRELOAD DISCARDABLE
BEGIN
IDR_MYAPPLET3 "Intergalacticn
Intergalactic settings for space
cadetsnn"
END
Теперь, если кликнуть по иконке апплета, то панель управления пошлёт
сообщение CPL_DBLCKT, которое будет обработано функцией
CCPApplet::OnLaunch, которая использует runtime class для создания
экземпляра диалогового окошка или окна с закладками, а затем просто
вызывает DoModal. LRESULT CCPApplet::OnLaunch(CWnd* pWndCpl,
LPCSTR lpCmdLine)
{
CWnd* pw = (CWnd*)m_pDialogClass->CreateObject();
if (pw) {
if (pw->IsKindOf(RUNTIME_CLASS(CPropertySheet))) {
CPropertySheet* ps = (CPropertySheet*)pw;
ps->SetActivePage(lpCmdLine ? atoi(lpCmdLine) : 0);
ps->DoModal();
} else if (pw->IsKindOf(RUNTIME_CLASS(CDialog))) {
CDialog* pd = (CDialog*)pw;
pd->DoModal();
}
}
return pw==NULL;
}
Не забудьте объявить своё диалоговое окошко в DECLARE_DYNCREATE, иначе
оно не создастся. Так же не забудьте переопределить OnPostNcDestroy чтобы
"удалить его". Почему ? Объясняю. Обычно мы создаём диалог в стеке CMyDialog dlg;
dlg.DoModal();
поэтому нет необходимости его удалять. Однако, CCPApplet создаёт Ваш
диалог в куче, поэтому необходимо удалять его после того, как он будет
уничтожен. Иначе будет утечка памяти.
После того, как апплет будет откомпилирован, не забудьте переименовать
его в .cpl и поместить в системную директорию. Однако, DLL-ку можно
оставить и в своей директории, тогда необходимо в CONTROL.INI в секции
MMCPL добавить следующую строчку: [MMCPL]
MyPanel=c:utilsMyPanelMyPanel.cpl
Существует маленькая проблемка, которая возникает, если Вы вдруг
захотите добавить новый апплет или изменить имя или иконку. Изменения
сразу не появятся в панели управления. Дело в том, что панель управления,
после того как считает информацию (CPL_INQUIRE) из Вашего апплета, сражу
же закэширует её на диск. Верный способ заставить панель управления
поновой считать информацию из апплета, это переименовать DLL. Можно
канечно просто нажать F5 (Обновить), но у меня это не дало результатов. В
процессе разработки можно установить CCPApplet::m_bDynamic в TRUE, тем
самым указав классу использовать CPL_NEWINQUIRE (информация не кэшируется)
вместо CPL_INQUIRE (информация кэшируется). А после того, как все отладки
будут закончены опять вернуть m_bDynamic=FALSE (по умолчанию).
Один из немаловажных вопросов, которые могут возникнуть при создании
апплета панели управления, это как его отлаживать ? Есть два пути решения
данной проблемы. Можно запустить панель управления под отладчиком, а можно
воспользоваться rundll32: rundll32 shell32.dll,Control_RunDLL mypanel.cpl
Control_RunDLL, это специальная функция в shell32.dll, которая
запускает приложение панели управления. Чтобы запустить определённый
апплет в Вашей DLL, наберите следующее
rundll32
shell32.dll,Control_RunDLL
mypanel.cpl,@n
где n, это номер Вашего апплета. Если добавить в конец строку,
то она будет передана в CPL_ STARTWPARAMS типа командной строки (command
line), которая передаётся в стандартном приложении Windows. Обычно
такая строка используется для апплетов, основанных на property sheet,
чтобы сразу показать определённую страницу. Например, чтобы показать
закладку Настройка (Settings) в свойствах экрана (Display Properties)
наберите следующее:
rundll32
shell32.dll,Control_RunDLL
desk.cpl,,3
В моей программе, нет необходимости делать дополнительную разборку
параметров. Если Ваш апплет будет основан на property sheet, то CCPApplet
автоматически интерпретирует дополнительный аргумент как номер
страницы. // В CCPApplet::OnLaunch
CWnd* pw = (CWnd*)m_pDialogClass->CreateObject();
if (pw) {
if (pw->IsKindOf(RUNTIME_CLASS(CPropertySheet))) {
CPropertySheet* ps = (CPropertySheet*)pw;
ps->SetActivePage(lpCmdLine ?
atoi(lpCmdLine) : 0);
ps->DoModal();
}
}
|