Библиотека ShellIcons (чистый WinAPI)
Пример использования: TrayTest (MFC)
Эта статья была задумана как ответы на многочисленные вопросы об иконках в System Tray (далее — трей):
В результате тестовая программа выросла в библиотеку, упрощающую работу с иконками.
ПРИМЕЧАНИЕ Здесь описана функциональность, общая для всех версий Windows (начиная с Win2000, появились дополнения — версия 5 библиотеки shellapi). |
Для того, чтобы показать иконку в трее, используется функция Shell_NotifyIcon
BOOL Shell_NotifyIcon( DWORD dwMessage, NOTIFYICONDATA* lpData ); |
Первый параметр (dwMessage)
В версии 5 появились еще две команды:
Второй параметр – ссылка на структуру NOTIFYICONDATA
typedef struct _NOTIFYICONDATA { DWORD cbSize; HWND hWnd; UINT uID; UINT uFlags; UINT uCallbackMessage; HICON hIcon; #if (_WIN32_IE < 0x0500) TCHAR szTip[64]; #else TCHAR szTip[128]; #endif #if (_WIN32_IE >= 0x0500) DWORD dwState; DWORD dwStateMask; TCHAR szInfo[256]; union { UINT uTimeout; UINT uVersion; } DUMMYUNIONNAME; TCHAR szInfoTitle[64]; DWORD dwInfoFlags; #endif #if (_WIN32_IE >= 0x600) GUID guidItem; #endif } NOTIFYICONDATA, *PNOTIFYICONDATA; |
Первое поле – cbSize – служит для передачи размера структуры, как это принято в WinAPI:
NOTIFYICONDATA nid; memset(&nid, 0, sizeof(nid)); nid.cbSize = sizeof(nid); |
Следующие поля – hWnd и uID – служат для идентификации иконки. Причем hWnd соответствует окну приложения (как правило, главному), а uID может быть произвольным и служит для различения иконок. Если включено сообщение для нотификации (uCallbackMessage, см. ниже), то оно будет посылаться этому окну.
Поле uFlags показывает, какие именно из остальных полей содержат информацию (комбинация битовых флагов):
Поле uCallbackMessage – номер оконного сообщения, которое будет посылаться окну hWnd при событиях от мышки и клавиатуры. (Подробнее об этом – в разделе «обработка событий»).
Поле hIcon – хэндл иконки.
СОВЕТ В трее показываются иконки размером 16*16. Ресурс иконки может включать несколько изображений разных размеров. Позаботьтесь добавить изображение 16*16 в ваш ресурс. |
Поле szTip – строка подсказки размером до 64 (в версии 5 – до 128) символов.
ПРЕДУПРЕЖДЕНИЕ Строка подсказки имеет тип TCHAR[], поэтому для работы с ней нужно использовать переносимые функции, а главное, правильно указывать размер в символах, а не байтах: sizeof(nid.szTip) / sizeof(nid.szTip[0]) |
lstrcpyn(nid.szTip, _T("Tool tip for my icon"), sizeof(nid.szTip)/sizeof(nid.szTip[0]); |
Поля dwState и dwStateMask – управляют состоянием иконки (битовые флаги):
Поля szInfo и szInfoTitle – большая подсказка в «воздушном шарике» (balloon). Аналогичны szTip.
Поле dwInfoFlags – стиль «воздушного шарика». Принимает одно из значений:
Поле uTimeout устанавливает задержку (в миллисекундах) для вывода «воздушного шарика». Допустимый диапазон – от 10.000 до 30.000 миллисекунд.
Поле uVersion служит для эмуляции функциональности предыдущих версий Shell. Оно принимает значения
ПРИМЕЧАНИЕ Обратите внимание, что uTimeout и uVersion конкурируют. uVersion используется только при вызове Shell_NotifyIcon с параметром NIM_SETVERSION. |
Поле guidItem зарезервировано для версии 6.
Инициализируем структуру.
NOTIFYICONDATA nid; memset(&nid, 0, sizeof(nid)); nid.cbSize = sizeof(nid); nid.hWnd = MyHWND; // хэндл имеющегося окна nid.uID = 1234; // некоторый номер (иконки, относящиеся к данному окну, должны различаться по номерам) nid.hIcon = ::LoadIcon(hInstance, MAKEINTRESOURCE(IDI_MyTrayIcon)); // что за иконка без собственно иконки, верно? |
ПРЕДУПРЕЖДЕНИЕ В дальнейшем, при удалении или изменении, не забудьте удалить hIcon (см. ниже). |
Остальные поля заполняются по мере необходимости.
lstrcpyn(nid.szTip, "Это моя иконка", sizeof(nid.szTip)/sizeof(nid.szTip[0])); // текст подсказки копируется в структуру |
Полем uFlags указываем, какие поля заполнены:
nid.uFlags = NIF_ICON | NIF_TIP; |
Вызываем функцию:
BOOL bSuccess = Shell_NotifyIcon(NIM_ADD, &nid); |
Инициализируем структуру, указав uID ранее созданной иконки, вызываем функцию:
BOOL bSuccess = Shell_NotifyIcon(NIM_DELETE, &nid); |
ПРИМЕЧАНИЕ Если окно закрыть, не удалив иконку вручную, то она остается жить до первого обращения (наведения мышки). |
Если объект иконки более не нужен, уничтожим его:
DestroyIcon(hIcon) |
Инициализируем структуру, устанавливая те поля, которые хотим изменить.
Можно менять изображение (как это делает The Bat!, машущий крыльями), текст подсказки (удаленное соединение – пишет, сколько байт передано), оконное сообщение (как правило, его устанавливают один раз при добавлении; в принципе, так можно включать/выключать обработку событий).
lstrcpyn(nid.szTip, "Новый текст", sizeof(nid.szTip)/sizeof(nid.szTip[0])); |
Устанавливаем соответствующие флаги uFlags.
nid.uFlags = NIF_TIP; |
Вызываем функцию:
BOOL bSuccess = Shell_NotifyIcon(NIM_MODIFY, &nid); |
Трей ловит события от мыши (начиная с версии 5, от клавиатуры: вызов контекстного меню) и посылает их в окно с помощью PostMessage(hWnd, uCallbackMessage, (WPARAM)uID, (LPARAM)uMsg). То есть, если навести мышь на иконку, то WM_MOUSEMOVE будет передано вторым параметром (LPARAM).
ПРИМЕЧАНИЕ Сопровождающая информация (координаты, состояния кнопок) не посылается и должна быть извлечена соответствующими функциями WinAPI (GetCursorPos). |
Приложение может выбрать любой код для сообщения (uCallbackMessage), как правило, это или число WM_USER + …, WM_APP + …, либо зарегистрированное в системе с помощью RegisterWindowMessage().
(чистый WinAPI)
Листинг 2. Обработчик событий от иконки#define WM_ShellNote (WM_APP + 1) #define id_MyIcon 123 void AddMyIcon() { NOTIFYICONDATA nid; memset(&nid, 0, sizeof(nid)); nid.cbSize = sizeof(nid); nid.hWnd = hwnd_MyWindow; nid.uID = id_MyIcon; nid.hIcon = hicon_MyIcon; nid.uCallbackMessage = WM_ShellNote; nid.dwFlags = NIF_ICON|NIF_MESSAGE; Shell_NotifyIcon(NIM_ADD, &nid); } LRESULT MyWindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { ... if(uMsg == WM_ShellNote && hWnd == hwnd_MyWindow && wParam == id_MyIcon) { switch((UINT)lParam) { case WM_MOUSEMOVE: return OnIcon_MouseMove(); case WM_LBUTTONDOWN: return OnIcon_LeftButtonDown(); case WM_LBUTTONUP: return OnIcon_LeftButtonUp(); case WM_LBUTTONDBLCLK: return OnIcon_LeftButtonDoubleClick(); case WM_RBUTTONDOWN: return OnIcon_RightButtonDown(); case WM_RBUTTONUP: return OnIcon_RightButtonUp(); case WM_RBUTTONDBLCLK: return OnIcon_RightButtonDoubleClick(); case WM_CONTEXTMENU: return OnIcon_ContextMenu(); // для версии 5 и выше } } ... } |
Контекстное меню принято показывать в ответ на щелчок правой кнопкой, то есть на WM_RBUTTONDOWN - WM_RBUTTONUP.
Здесь есть тонкость: пользователь может нажать кнопку на иконке, затем увести мышь, отпустить кнопку, ... потом снова нажать, вернуть мышь, отпустить. Для иконки это будет выглядеть как медленный щелчок или небольшой драг-н-дроп. Обычный для других случаев выход из положения – захват мыши (::SetCapture) неприменим, поэтому все действия производятся по «переднему фронту»: WM_xBUTTONDOWN (одинарный щелчок), WM_xBUTTONDBLCLK (двойной щелчок).
Делается это так же, как и в любом другом случае. Единственное отличие – в том, что координаты мыши не переданы в составе сообщения, и их придется брать напрямую.
Листинг 3. Вызов контекстного менюBOOL ShowPopupMenu(HWND hWnd, HINSTANCE hInstance, WORD nResourceID) { HMENU hMenu = ::LoadMenu(hInstance, MAKEINTRESOURCE(nResourceID)); if(!hMenu) return FALSE; HMENU hPopup = ::GetSubMenu(hMenu, 0); if(!hPopup) return FALSE; SetForegroundWindow(hWnd); POINT pt; ::GetCursorPos(&pt); BOOL bOK = ::TrackPopupMenu(hPopup, 0, pt.x, pt.y, 0, hWnd, NULL); ::DestroyMenu(hMenu); return bOK; } |
ПРЕДУПРЕЖДЕНИЕ Если не вызвать SetForegroundWindow(hWnd), то меню не сможет автоматически закрытся по щелчку мыши за его пределами. |
Как видите, ничего сложного нет.
Комментария требует только GetSubMenu().
В ресурсе хранятся «полосы меню» (menu bars), предназначенные для встраивания в окна. Если его показать функцией TrackPopupMenu, то мы увидим узкую вертикальную полоску без текста.
Поэтому нужно либо создавать «всплывающее меню» (popup menu) функцией CreatePopupMenu(), либо брать подменю (которое по определению является «всплывающим»). Соответственно, ресурс этого меню выглядит как полоса с одним элементом (номер 0), в подменю которого сложена вся функциональность.
Перефразируем задачу: как спрятать окно, убрав его кнопку с панели задач и показать иконку в трее?
СОВЕТ Вы можете воспользоваться программой TrayIt (http://www.teamcti.com/TrayIt), которая умеет сворачивать в трей окна любых приложений. |
Спрятать окно можно, вызвав ShowWindow сперва с параметром SW_MINIMIZE, а затем — SW_HIDE.
Восстановить — SW_SHOW (при этом оно появится в панели задач), а затем — SW_RESTORE (восстановить из свернутого состояния).
Когда пользователь командует «свернуть» (нажатие кнопки на заголовке окна, двойной щелчок по кнопке в панели задач, пункт системного меню), посылается сообщение WM_SIZE с параметром SIZE_MINIMIZED. Обработчик этого события может свернуть окно в трей.
Ниже приведен код на WinAPI. Перенос его на MFC или WTL — упражнение для читателя.
Листинг 4. Сворачивание-разворачивание в трей#define WM_FLIPPED_TO_TRAY (WM_APP + 1234) #define ID_FLIPPED_TO_TRAY 1234 BOOL FlipToTray(HWND hWnd, HICON hIcon, BOOL bMinimize) { // создаем иконку NOTIFYICONDATA nid; memset(&nid, 0, sizeof(nid)); nid.cbSize = sizeof(nid); nid.hWnd = hWnd; nid.uID = ID_FLIPPED_TO_TRAY; nid.uCallbackMessage = WM_FLIPPED_TO_TRAY; nid.hIcon = hIcon; GetWindowText(hWnd, nid.szToolTip, sizeof(nid.szToolTip)/sizeof(nid.szToolTip[0])); nid.dwFlags = NIF_ICON|NIF_MESSAGE|NIF_TOOLTIP; // показываем ее BOOL ok = Shell_NotifyIcon(NIM_ADD, &nid); // прячем окно if(bMinimize) ShowWindow(hWnd, SW_MINIMIZE); if(ok) // только, если удалось показать иконку ShowWindow(hWnd, SW_HIDE); return ok; } BOOL UnflipFromTray(HWND hWnd, BOOL bRestore) { // идентифицируем иконку NOTIFYICONDATA nid; memset(&nid, 0, sizeof(nid)); nid.cbSize = sizeof(nid); nid.hWnd = hWnd; nid.uID = ID_FLIPPED_TO_TRAY; // удаляем ее BOOL ok = Shell_NotifyIcon(NIM_DELETE, &nid); if(!bRestore) return ok; // восстанавливаем окно ShowWindow(hWnd, SW_SHOW); ShowWindow(hWnd, SW_RESTORE); return ok; } LRESULT CALLBACK MyWindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { . . . // минимизация if(uMsg == WM_SIZE) { if(wParam == SIZE_MINIMIZED) FlipToTray(hWnd, hTrayIcon, FALSE); } // восстановление if(uMsg == WM_FLIPPED_TO_TRAY) { if(wParam == ID_FLIPPED_TO_TRAY && lParam == WM_LBUTTONDBLCLK) UnflipFromTray(hWnd, TRUE); } . . . } |
Задача оберткок — упростить работу с иконкой. Какую функциональность они могут взять на себя?
Во-первых, автоматизировать вызовы API
Во-вторых, избавление пользователя от необходимости прикреплять иконку к пользовательским окнам:
В третьих, упрощение обработки событий
Наконец, автоматизация типовых действий
Пример такой обертки — библиотека ShellIcons (написанная мной).
ShellIcons написана на Visual C++ с без использования MFC и с минимальным использованием STL. Впрочем, из-за простоты ее легко перенести на любую технологию.
Исходные тексты: ShellIcons.zip
См. также http://www.rsdn.ru//article/files/Classes/ni.xml — класс CNotifyIcon от Игоря Вартанова, аналогичный CShellIcon.
Библиотека представляет классы
ПРИМЕЧАНИЕ Абстрагирование носителя от окна было сделано, потому что «песочница» использовала MFC, и первая реализация носителя была MFC-шная. |
В чистом виде обертка структуры NOTIFYICONDATA.
Листинг 5. Объявление класса CShellIconclass SHELLICONS_API CShellIcon { public: // constructor CShellIcon(); // destructor virtual ~CShellIcon(); public: ///////////////// // identification // window HWND getHostWindow() const; bool setHostWindow(HWND hWnd); // return false if already set up // icon id UINT getTrayID() const; bool setTrayID(UINT uID); // return false if already set up /////////////// // notification // callback message UINT getCallbackMessage() const; void setCallbackMessage(UINT uMsg); ////////////////// // icon properties // icon image HICON getIcon() const; void setIcon(HICON hIcon, bool bOwn = true); // takes/releases the handle bool loadIcon(HINSTANCE hInstance, LPCTSTR sResourceID); bool loadIcon(HINSTANCE hInstance, WORD wResourceID); // tool tips LPCTSTR getToolTip() const; void setToolTip(LPCTSTR sText); /////////// // commands bool addIcon(); bool removeIcon(); bool modifyIcon(); bool update(); bool isAdded() const; bool isIdentified() const; bool isReady() const; bool isModified() const; // automatic application bool getImmediate() const; void setImmediate(bool bImmediate); protected: NOTIFYICONDATA m_nid; bool m_bOwnIcon; // icon will be destroyed on change bool m_bImmediate; bool m_bAdded; void init(); }; |
Отличительная черта («фича») этого класса — способность отслеживать и моментально применять изменения свойств иконки, а также автоматически удалять ее из панели.
Кроме того, он автоматически уничтожает хэндл иконки (hIcon) при изменении (setIcon(), loadIcon() ) и в деструкторе.
Использование очень простое:
class CExtShellIcon; class CExtShellIconHost; ///////////////////////// // notification interface struct _ShellIconNote { virtual LRESULT note( CExtShellIconHost* pHost, CExtShellIcon* pIcon, UINT uMsg ) = 0; }; ///////////////////////////////// // notification callback function typedef LRESULT (*PFNShellIconNote) (CExtShellIconHost* pHost, CExtShellIcon* pIcon, UINT uMsg); ////////////////////// // single icon wrapper class SHELLICONS_API CExtShellIcon: protected CShellIcon { protected: friend class CExtShellIconHost; CExtShellIcon(CExtShellIconHost* pHost, UINT nTrayID); virtual ~CExtShellIcon(); LRESULT Callback(UINT uMsg); public: ///////////////////////////// // identification (read only) inline CExtShellIconHost* getHost() const { return m_pHost; } inline UINT getTrayID() const { return CShellIcon::getTrayID(); } //////////////////////// // notification callback inline PFNShellIconNote getCallbackFn() const { return m_pfnCallback; } inline void setCallbackFn(PFNShellIconNote pfn) { m_pfnCallback = pfn; } /////////////////////////// // notification sink object inline _ShellIconNote* getCallbackItf() const { return m_pCallbackItf; } inline void setCallbackItf(_ShellIconNote* pItf) { m_pCallbackItf = pItf; } /////////////////////////////////////////////// // forward to base class properties and methods // icon inline HICON getIcon() const { return CShellIcon::getIcon(); } inline void setIcon(HICON hIcon, bool bOwn = true) { CShellIcon::setIcon(hIcon, bOwn); } inline bool loadIcon(HINSTANCE hInstance, LPCTSTR sResourceID) { return CShellIcon::loadIcon(hInstance, sResourceID); } inline bool loadIcon(HINSTANCE hInstance, WORD wResourceID) { return CShellIcon::loadIcon(hInstance, wResourceID); } // tooltip inline LPCTSTR getToolTip() const { return CShellIcon::getToolTip(); } inline void setToolTip(LPCTSTR sText) { CShellIcon::setToolTip(sText); } // management inline bool show(bool bShow) { return bShow ? CShellIcon::addIcon() : CShellIcon::removeIcon(); } inline bool update() { return CShellIcon::update(); } inline bool isReady() const { return CShellIcon::isReady(); } inline bool isShown() const { return CShellIcon::isAdded(); } bool getImmediate() const { return CShellIcon::getImmediate(); } void setImmediate(bool bImmediate) { CShellIcon::setImmediate(bImmediate); } private: CExtShellIconHost* m_pHost; PFNShellIconNote m_pfnCallback; _ShellIconNote* m_pCallbackItf; }; |
ПРИМЕЧАНИЕ CExtShellIcon наследует CShellIcon в защищенном режиме: базовый класс обладает избыточной функциональностью, ставящей под угрозу целостность системы (хэндл окна и сообщение нотификации поставляются исключительно носителем и не могут быть произвольно заменены). |
/////////////////////// // icon host (abstract) class SHELLICONS_API CExtShellIconHost { public: CExtShellIconHost(); virtual ~CExtShellIconHost(); //////////////////////////// // required windows function virtual HWND getHWnd() const = 0; virtual void destroy() = 0; //////// // icons UINT getFreeTrayID() const; CExtShellIcon* addIcon( UINT nID, _ShellIconNote* pItf = NULL, PFNShellIconNote pfn = NULL ); CExtShellIcon* getIcon(UINT nID) const; int countIcons() const; bool removeIcon(UINT nID); bool removeIcon(CExtShellIcon* pIcon); int removeIcons(PFNShellIconNote pfn); // return number of removed icons int removeIcons(_ShellIconNote* pItf); // return number of removed icons void removeAllIcons(); protected: #ifdef _SHELLICONS_USE_STL typedef std::map<UINT, CExtShellIcon*> mapIcons; mapIcons m_mapIcons; #else _MapUintToPtr* m_ptrMapIcons; // interface to a simple collection #endif LRESULT notify(UINT nID, UINT uMsg); }; |
CExtShellIconHost содержит коллекцию объектов CExtShellIcon.
ПРЕДУПРЕЖДЕНИЕ Использование STL напрямую приводит к туче предупреждений компилятора VC++ (классы STL не объявлены как экспортируемые) и способно привести к ошибкам выделения памяти со статической библиотекой C RunTime. |
С другой стороны, использование абстрактной коллекции (интерфейс которой см. ниже) делает код более громоздким.
Листинг 8. Интерфейс коллекции///////////////////////////////////////// // interface of a collection UINT->LPVOID class SHELLICONS_API _MapUintToPtr { public: _MapUintToPtr() {} virtual ~_MapUintToPtr() {} virtual int count() = 0; virtual bool add(UINT nID, LPVOID pValue) = 0; virtual LPVOID take(UINT nID) = 0; virtual LPVOID drop() = 0; virtual LPVOID drop(UINT nID) = 0; virtual bool find(LPVOID pValue, UINT& nID) = 0; /////////////////////////// // interface of an iterator class _Enum { public: _Enum() {} virtual ~_Enum() {} virtual bool end() = 0; virtual UINT key() = 0; virtual LPVOID value() = 0; virtual bool next() = 0; virtual bool remove() = 0; }; virtual _Enum* start() = 0; }; |
Реализация коллекции уже использует STL, но скрывает ее от клиента.
class SHELLICONS_API CWinShellIconHost: public CExtShellIconHost { public: CWinShellIconHost(); virtual ~CWinShellIconHost(); virtual HWND getHWnd() const { return m_hWnd; } virtual void destroy(); bool CreateMe(); protected: HWND m_hWnd; static ATOM RegisterMyClass(); static LRESULT CALLBACK MyWindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); void onDestroy(); //////////// // singleton protected: struct SINGLETON { CWinShellIconHost* pGlobal; SINGLETON(): pGlobal(NULL) {} ~SINGLETON() { destroy(); } void destroy() { if(!pGlobal) return; pGlobal->destroy(); delete pGlobal; } CWinShellIconHost* make() { if(pGlobal) return pGlobal; pGlobal = new CWinShellIconHost; if(!pGlobal->CreateMe()) destroy(); return pGlobal; } }; static SINGLETON singleton; public: static CWinShellIconHost* Host() { return singleton.make(); } }; |
Объект создает невидимое окно, которое поддерживает иконки (предоставляет хэндл окна, обрабатывает события).
ПРЕДУПРЕЖДЕНИЕ Окно носителя пользуется помпой сообщений текущего потока. |
Теоретически, можно создать сколько угодно носителей, но на практике удобно иметь один, т.е. синглетон.
Как видно выше, пользователь может указать иконке либо функцию (PFNShellIconNote), либо интерфейс объекта-обработчика (_ShellIconNote). Приоритет отдается объектам.
В функцию (или метод) обработчика передается ссылка на носитель и на иконку, от которой пришло событие, а также код сообщения (uMsg) — WM_MOUSEMOVE и тому подобное.
Для того, чтобы не писать switch(uMsg), существует простая реализация класса-обработчика:
Листинг 10. Объявление класса CShellIconNoteclass SHELLICONS_API CShellIconNote : public _ShellIconNote { public: CShellIconNote(); virtual ~CShellIconNote(); virtual LRESULT note (CExtShellIconHost* pHost, CExtShellIcon* pIcon, UINT uMsg); // extended layer virtual void onTrayMouseMove (CExtShellIconHost* pHost, CExtShellIcon* pIcon); virtual void onTrayLeftButtonDown (CExtShellIconHost* pHost, CExtShellIcon* pIcon); virtual void onTrayLeftButtonUp (CExtShellIconHost* pHost, CExtShellIcon* pIcon); virtual void onTrayLeftButtonDblClk (CExtShellIconHost* pHost, CExtShellIcon* pIcon); virtual void onTrayRightButtonDown (CExtShellIconHost* pHost, CExtShellIcon* pIcon); virtual void onTrayRightButtonUp (CExtShellIconHost* pHost, CExtShellIcon* pIcon); virtual void onTrayRightButtonDblClk (CExtShellIconHost* pHost, CExtShellIcon* pIcon); // simplified layer virtual void onTrayMouseMove (UINT nID); virtual void onTrayLeftButtonDown (UINT nID); virtual void onTrayLeftButtonUp (UINT nID); virtual void onTrayLeftButtonDblClk (UINT nID); virtual void onTrayRightButtonDown (UINT nID); virtual void onTrayRightButtonUp (UINT nID); virtual void onTrayRightButtonDblClk(UINT nID); // most simple layer virtual void onTrayMouseMove (); virtual void onTrayLeftButtonDown (); virtual void onTrayLeftButtonUp (); virtual void onTrayLeftButtonDblClk (); virtual void onTrayRightButtonDown (); virtual void onTrayRightButtonUp (); virtual void onTrayRightButtonDblClk(); }; |
Он анализирует код сообщения и вызывает соответствующий метод.
Таким образом, для обработки, скажем, щелчка правой кнопкой (вызов контекстного меню) достаточно перекрыть единственный метод onTrayRightButtonDown().
enum MENU_INDEX { MENU_LEFT, MENU_RIGHT, MENU__COUNT }; class SHELLICONS_API CTrackMenu { public: CTrackMenu(); virtual ~CTrackMenu(); HWND getHWnd() const; BOOL setHWnd(HWND hWnd); HMENU getMenu(MENU_INDEX mi) const; BOOL setMenu(MENU_INDEX mi, HMENU hMenu, BOOL bOwn = true); BOOL loadMenu(MENU_INDEX mi, HINSTANCE hInstance, LPCTSTR sMenuResource); BOOL loadMenu(MENU_INDEX mi, HINSTANCE hInstance, WORD wMenuResource); BOOL trackMenu(MENU_INDEX mi, int x, int y); BOOL trackMenu(MENU_INDEX mi, const POINT* ppt); BOOL trackMenu(MENU_INDEX mi, const POINT& pt); BOOL trackMenu(MENU_INDEX mi); protected: HWND m_hWnd; HMENU m_hMenu[MENU__COUNT]; bool m_bOwn[MENU__COUNT]; bool m_bTracking; }; |
Объект CTrackMenu позволяет загрузить несколько меню, правильным способом их показать, и разрушить по перезаписи или удалению объекта.
Листинг 12. Объявление класса CHideWindowclass SHELLICONS_API CHideWindow : protected CShellIconNote { public: CHideWindow(); virtual ~CHideWindow(); HWND getHWnd() const; BOOL setHWnd(HWND hWnd); void hide(); bool update(); void show(); protected: HWND m_hWnd; UINT m_nIcon; // CTrackMenu m_TrackMenu; BOOL createIcon(); void removeIcon(); // CShellIconNote override void onTrayRightButtonDown(); void onTrayLeftButtonDblClk(); }; |
CHideWindow умеет сворачивать окно в трей и восстанавливать его оттуда. Сворачивание — по инициативе клиента (окно перехватывает событие минимизации). Разворачивание — по двойному щелчку на иконке.
Иконка и текст подсказки берутся из окна. Если заголовок окна поменялся, достаточно вызвать метод update() для обновления иконки в трее.
Объект использует носитель иконок CWinShellIconHost, что позволило, во-первых, динамически выделять номера иконок, а во-вторых, не принуждать клиентов самостоятельно обрабатывать события нотификации.
Текст примера: TrayTest.zip
Это простое MFC-приложение, демонстрирующее все приемы, описанные выше.
Вот, собственно, и все.