Иконки в «System Tray»

Практические ответы.

Автор: Nickolay Merkin
The RSDN Group
Опубликовано: 04.04.2002
Версия текста: 1.1.1

Предисловие
API для работы с иконками.
Описание
Применение
Обработка событий
Спецэффекты
Как показать контекстное меню
Как свернуть окно в трей
Обертки для иконки
Библиотека ShellIcons
Пример использования. Диалог с иконкой в трее.

Библиотека ShellIcons (чистый WinAPI)
Пример использования: TrayTest (MFC)

Предисловие

Эта статья была задумана как ответы на многочисленные вопросы об иконках в System Tray (далее — трей):

В результате тестовая программа выросла в библиотеку, упрощающую работу с иконками.

API для работы с иконками.

ПРИМЕЧАНИЕ

Здесь описана функциональность, общая для всех версий Windows (начиная с Win2000, появились дополнения — версия 5 библиотеки shellapi).

Описание

Shell_NotifyIcon

Для того, чтобы показать иконку в трее, используется функция Shell_NotifyIcon

BOOL Shell_NotifyIcon(
    DWORD dwMessage,
    NOTIFYICONDATA* lpData
    );

Первый параметр (dwMessage)

В версии 5 появились еще две команды:

Второй параметр – ссылка на структуру NOTIFYICONDATA

Структура NOTIFYICONDATA

Листинг 1. Объявление структуры 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

ShellIcons написана на Visual C++ с без использования MFC и с минимальным использованием STL. Впрочем, из-за простоты ее легко перенести на любую технологию.

Исходные тексты: ShellIcons.zip

См. также http://www.rsdn.ru//article/files/Classes/ni.xml — класс CNotifyIcon от Игоря Вартанова, аналогичный CShellIcon.

Библиотека представляет классы

ПРИМЕЧАНИЕ

Абстрагирование носителя от окна было сделано, потому что «песочница» использовала MFC, и первая реализация носителя была MFC-шная.

CShellIcon

В чистом виде обертка структуры NOTIFYICONDATA.

Листинг 5. Объявление класса CShellIcon
class 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() ) и в деструкторе.

Использование очень простое:

Расширенные обертки (СExtShellIcon, CExtShellIconHost)

Листинг 6. Объявление класса CExtShellIcon
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 в защищенном режиме: базовый класс обладает избыточной функциональностью, ставящей под угрозу целостность системы (хэндл окна и сообщение нотификации поставляются исключительно носителем и не могут быть произвольно заменены).

Листинг 7. Объявление класса CExtShellIconHost
///////////////////////
// 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, но скрывает ее от клиента.

Реализация носителя: CWinShellIconHost

Листинг 9. Объявление класса CWinShellIconHost
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. Объявление класса CShellIconNote
class 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().

Утилиты

Листинг 11. Объявление класса CTrackMenu
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. Объявление класса CHideWindow
class 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-приложение, демонстрирующее все приемы, описанные выше.

Вот, собственно, и все.


Любой из материалов, опубликованных на этом сервере, не может быть воспроизведен в какой бы то ни было форме и какими бы то ни было средствами без письменного разрешения владельцев авторских прав.