Главная » 2009 Ноябрь 6 » C++ Как создавать значки в панели управления
08:18 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(); } } Источник: http://sources.ru |
|
Всего комментариев: 0 | |