Сообщений 21    Оценка 530 [+2/-0]         Оценить  
Система Orphus

Иконки в «System Tray»

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

Автор: Nickolay Merkin
The RSDN Group
Опубликовано: 04.04.2002
Исправлено: 15.04.2009
Версия текста: 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

Первое поле – 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. Обработчик событий от иконки

Контекстное меню принято показывать в ответ на щелчок правой кнопкой, то есть на WM_RBUTTONDOWN - WM_RBUTTONUP.

Здесь есть тонкость: пользователь может нажать кнопку на иконке, затем увести мышь, отпустить кнопку, ... потом снова нажать, вернуть мышь, отпустить. Для иконки это будет выглядеть как медленный щелчок или небольшой драг-н-дроп. Обычный для других случаев выход из положения – захват мыши (::SetCapture) неприменим, поэтому все действия производятся по «переднему фронту»: WM_xBUTTONDOWN (одинарный щелчок), WM_xBUTTONDBLCLK (двойной щелчок).

Спецэффекты

Как показать контекстное меню

Делается это так же, как и в любом другом случае. Единственное отличие – в том, что координаты мыши не переданы в составе сообщения, и их придется брать напрямую.

Листинг 3. Вызов контекстного меню
ПРЕДУПРЕЖДЕНИЕ

Если не вызвать 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. Сворачивание-разворачивание в трей

Обертки для иконки

Задача оберткок — упростить работу с иконкой. Какую функциональность они могут взять на себя?

Во-первых, автоматизировать вызовы 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

Отличительная черта («фича») этого класса — способность отслеживать и моментально применять изменения свойств иконки, а также автоматически удалять ее из панели.

Кроме того, он автоматически уничтожает хэндл иконки (hIcon) при изменении (setIcon(), loadIcon() ) и в деструкторе.

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

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

Листинг 6. Объявление класса CExtShellIcon
ПРИМЕЧАНИЕ

CExtShellIcon наследует CShellIcon в защищенном режиме: базовый класс обладает избыточной функциональностью, ставящей под угрозу целостность системы (хэндл окна и сообщение нотификации поставляются исключительно носителем и не могут быть произвольно заменены).

Листинг 7. Объявление класса CExtShellIconHost

CExtShellIconHost содержит коллекцию объектов CExtShellIcon.

ПРЕДУПРЕЖДЕНИЕ

Использование STL напрямую приводит к туче предупреждений компилятора VC++ (классы STL не объявлены как экспортируемые) и способно привести к ошибкам выделения памяти со статической библиотекой C RunTime.

С другой стороны, использование абстрактной коллекции (интерфейс которой см. ниже) делает код более громоздким.

Листинг 8. Интерфейс коллекции

Реализация коллекции уже использует STL, но скрывает ее от клиента.

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

Листинг 9. Объявление класса CWinShellIconHost

Объект создает невидимое окно, которое поддерживает иконки (предоставляет хэндл окна, обрабатывает события).

ПРЕДУПРЕЖДЕНИЕ

Окно носителя пользуется помпой сообщений текущего потока.

Теоретически, можно создать сколько угодно носителей, но на практике удобно иметь один, т.е. синглетон.

Обработка событий

Как видно выше, пользователь может указать иконке либо функцию (PFNShellIconNote), либо интерфейс объекта-обработчика (_ShellIconNote). Приоритет отдается объектам.

В функцию (или метод) обработчика передается ссылка на носитель и на иконку, от которой пришло событие, а также код сообщения (uMsg) — WM_MOUSEMOVE и тому подобное.

Для того, чтобы не писать switch(uMsg), существует простая реализация класса-обработчика:

Листинг 10. Объявление класса CShellIconNote

Он анализирует код сообщения и вызывает соответствующий метод.

Таким образом, для обработки, скажем, щелчка правой кнопкой (вызов контекстного меню) достаточно перекрыть единственный метод onTrayRightButtonDown().

Утилиты

Листинг 11. Объявление класса CTrackMenu

Объект CTrackMenu позволяет загрузить несколько меню, правильным способом их показать, и разрушить по перезаписи или удалению объекта.

Листинг 12. Объявление класса CHideWindow

CHideWindow умеет сворачивать окно в трей и восстанавливать его оттуда. Сворачивание — по инициативе клиента (окно перехватывает событие минимизации). Разворачивание — по двойному щелчку на иконке.

Иконка и текст подсказки берутся из окна. Если заголовок окна поменялся, достаточно вызвать метод update() для обновления иконки в трее.

Объект использует носитель иконок CWinShellIconHost, что позволило, во-первых, динамически выделять номера иконок, а во-вторых, не принуждать клиентов самостоятельно обрабатывать события нотификации.

Пример использования. Диалог с иконкой в трее.

Текст примера: TrayTest.zip

Это простое MFC-приложение, демонстрирующее все приемы, описанные выше.

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


Любой из материалов, опубликованных на этом сервере, не может быть воспроизведен в какой бы то ни было форме и какими бы то ни было средствами без письменного разрешения владельцев авторских прав.
    Сообщений 21    Оценка 530 [+2/-0]         Оценить