Как создавать значки в панели управления.
Автор: 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 "Intergalactic\n
Intergalactic settings for space
cadets\n\n"
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:\utils\MyPanel\MyPanel.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();
}
}
|