|
На этом уроке мы узнаем, как помещать иконки в system tray и как создавать/использовать всплывающее меню.
Пример можете скачать здесь.
ТЕОРИЯ
System tray - это прямоугольная область панели задач, в которой располагаются несколько иконок.
Скорее всего, вы обнаружите там как минимум цифровые часы. Вы можете самостоятельно помещать иконки в
system tray. Далее приводятся шаги, которые нужно для этого выполнить:
Заполните структуру NOTIFYICONDATA, содержащую следующие поля:
cbSize - pазмер данной структуры.
hwnd - хэндл окна, которое будет получать уведомление, когда
над иконкой в tray'e произойдёт событие мыши.
uID - константа, используемая в качестве индентификатора иконки. Вы сами выбираете
значение этой константе. В случае, если вы поместили в system tray несколько иконок, вы сможете узнать,
над какой именно из них произошло событие мыши.
uFlags - указывает, какие поля данной структуры заполнены
NIF_ICON Поле hIcon заполнено.
NIF_MESSAGE Поле uCallbackMessage заполнено.
NIF_TIP Поле szTip заполнено.
uCallbackMessage - пользовательское сообщение, которое Windows отошлёт указанному в
поле hwnd окну, в случае, когда над иконкой произойдёт событие мыши. Сообщение вы создаете сами.
hIcon - хэндл иконки, которую вы хотите поместить в system tray.
szTip - 64-байтовый массив, содержащий строку для использования в качестве всплывающей подсказки
к иконке.
Вызовите Shell_NotifyIcon, определённую в shell32.inc. Данная функция имеет следующий прототип:
Shell_NotifyIcon PROTO dwMessage:DWORD, pnid:DWORD
dwMessage - это тип сообщения, которое нужно отправить оболочке.
NIM_ADD Добавляет иконку в system tray.
NIM_DELETE Удаляет иконку из system tray.
NIM_MODIFY Изменяет иконку в system tray.
pnid - это указатель на корректно заполненную структуру NOTIFYICONDATA.
Если вы хотите добавить иконку в system tray, используйте сообщение NIM_ADD, если хотите удалить иконку,
применяйте NIM_DELETE.
Вот, собственно, и всё. Но чаще всего просто поместить иконку в system tray недостаточно. Вам нужно как-то
реагировать на событий мыши, происходящие над этой иконкой. Это можно сделать, обрабатывая сообщение, указанное
в поле uCallbackMessage структуры NOTIFYICONDATA. Это сообщение содержит следующие значения в wParam и lParam
(отдельное спасибо s__d за эту информацию):
wParam содержит ID иконки. Это то же самое значение, что вы поместили в поле uID структуры NOTIFYICONDATA.
lParam Младшее слово содержит сообщение мыши. Например, если пользователь сделал правый щелчок по иконке,
то lParam будет содержать WM_RBUTTONDOWN.
Обычно иконка в system tray показывает всплывающее меню при правом щелчке по ней. Этого можно добиться, если сначала
создать само всплывающее меню, а затем вызывать TrackPopupMenu для его отображения. Шаги приведены ниже:
Создайте всплывающее меню, вызвав CreatePopupMenu. Эта функция создаёт пустое меню, и при успешном создании
возвращает его хэндл в eax.
Добавьте пункты в меню с помощью AppendMenu, InsertMenu или InsertMenuItem.
Когда вам будет нужно отобразить всплывающее меню на месте курсора мыши, вызовите GetCursorPos, чтобы
узнать текущие координаты курсора, а затем вызовите TrackPopupMenu, чтобы вывести меню на экран.
Когда пользователь щёлкнет на одном из пунктов меню, Windows отправит сообщение WM_COMMAND вашей оконной процедуре,
точно так же, как и при работе с обычным меню.
Внимание: остерегайтесь следующих проблем, часто возникающих при работе со всплывающими меню.
Когда меню отображено на экране, щелчок вне меню не приводит к его немедленному исчезновению.
Это происходит потому, что окно, которое будет получать уведомления от меню, ДОЛЖНО быть на переднем плане.
Просто вызовите SetForegroundWindow, чтобы исправить эту проблему.
После вызова SetForegroundWindow вы обнаружите, что в первый раз всплывающее меню сработает нормально, но при
последующем появлении оно будет отображаться, а затем тут же исчезать. Как написано в MSDN, это сделано "намеренно".
Необходимо переключить задачу на программу, являющуюся владельцем иконки в system tray. Этого можно добиться,
отправив любое сообщение окну вашей программы. Но только используйте PostMessage, а не SendMessage!
ПРИМЕР
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\shell32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\shell32.lib
WM_SHELLNOTIFY equ WM_USER+5
IDI_TRAY equ 0
IDM_RESTORE equ 1000
IDM_EXIT equ 1010
WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD
.data
ClassName db "TrayIconWinClass",0
AppName db "TrayIcon Demo",0
RestoreString db "&Restore",0
ExitString db "E&xit Program",0
.data?
hInstance dd ?
note NOTIFYICONDATA <>
hPopupMenu dd ?
.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke WinMain, hInstance,NULL,NULL, SW_SHOWDEFAULT
invoke ExitProcess,eax
WinMain proc
hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW or CS_DBLCLKS
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_APPWORKSPACE
mov wc.lpszMenuName,NULL
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov* wc.hIcon,eax
mov* wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov* wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPED+WS_CAPTION+WS_SYSMENU+WS_MINIMIZEBOX+WS_MAXIMIZEBOX+WS_VISIBLE,\
CW_USEDEFAULT,350,200,NULL,NULL,\
hInst,NULL
mov hwnd,eax
.while TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.endw
mov eax,msg.wParam
ret
WinMain endp
WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
LOCAL pt:POINT
.if uMsg==WM_CREATE
invoke CreatePopupMenu
mov hPopupMenu,eax
invoke AppendMenu,hPopupMenu,MF_STRING,IDM_RESTORE,addr RestoreString
invoke AppendMenu,hPopupMenu,MF_STRING,IDM_EXIT,addr ExitString
.elseif uMsg==WM_DESTROY
invoke DestroyMenu,hPopupMenu
invoke PostQuitMessage,NULL
.elseif uMsg==WM_SIZE
.if wParam==SIZE_MINIMIZED
mov note.cbSize,sizeof NOTIFYICONDATA
push hWnd
pop note.hwnd
mov note.uID,IDI_TRAY
mov note.uFlags,NIF_ICON+NIF_MESSAGE+NIF_TIP
mov note.uCallbackMessage,WM_SHELLNOTIFY
invoke LoadIcon,NULL,IDI_WINLOGO
mov note.hIcon,eax
invoke lstrcpy,addr note.szTip,addr AppName
invoke ShowWindow,hWnd,SW_HIDE
invoke Shell_NotifyIcon,NIM_ADD,addr note
.endif
.elseif uMsg==WM_COMMAND
.if lParam==0
invoke Shell_NotifyIcon,NIM_DELETE,addr note
mov eax,wParam
.if ax==IDM_RESTORE
invoke ShowWindow,hWnd,SW_RESTORE
.else
invoke DestroyWindow,hWnd
.endif
.endif
.elseif uMsg==WM_SHELLNOTIFY
.if wParam==IDI_TRAY
.if lParam==WM_RBUTTONDOWN
invoke GetCursorPos,addr pt
invoke SetForegroundWindow,hWnd
invoke TrackPopupMenu,hPopupMenu,TPM_RIGHTALIGN,pt.x,pt.y,NULL,hWnd,NULL
invoke PostMessage,hWnd,WM_NULL,0,0
.elseif lParam==WM_LBUTTONDBLCLK
invoke SendMessage,hWnd,WM_COMMAND,IDM_RESTORE,0
.endif
.endif
.else
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.endif
xor eax,eax
ret
WndProc endp
end start
АНАЛИЗ
Программа отобразит на экране обычное окно. По нажатию кнопки "Свернуть" оно свернётся до иконки в system tray
По двойному щелчку по иконке программа восстановит своё окно и удалит иконку из system tray. По правому щелчку
будет выведено всплывающее меню, из которого можно восстановить программу или выйти из неё.
.if uMsg==WM_CREATE
invoke CreatePopupMenu
mov hPopupMenu,eax
invoke AppendMenu,hPopupMenu,MF_STRING,IDM_RESTORE,addr RestoreString
invoke AppendMenu,hPopupMenu,MF_STRING,IDM_EXIT,addr ExitString
Когда будет создано главное окно, также создастся всплывающее меню, к которому затем будут добавлены два пункта. Функция
AppendMenu имеет следующий синтаксис:
AppendMenu PROTO hMenu:DWORD, uFlags:DWORD, uIDNewItem:DWORD, lpNewItem:DWORD
hMenu это хэндл меню, к которому вы хотите добавить пункт
uFlags информирует Windows о добавляемом пункте меню - изображение ли это, строка или отрисовываемый владельцем
объект; включен ли он, неопределён или отключен, и т.д. Полный список есть в win32 api reference. В
нашем случае мы используем флаг MF_STRING, который означает, что пункт меню - это строка.
uIDNewItem это ID пункта меню. Это значение определяется пользователем, и используется для обращения к
пункту меню.
lpNewItem хранит содержание пункта меню, в зависимости от значения поля uFlags. Так как мы указали MF_STRING в
поле uFlags, то lpNewItem должен содержать указатель на строку для отображения в пункте меню.
После того, как всплывающее меню создано, главное окно будет терпеливо ждать до тех пор, пока пользователь не нажмёт
на кнопку "Свернуть".
Когда окно сворачивается, оно получает сообщение WM_SIZE со значением SIZE_MINIMIZED в wParam.
.elseif uMsg==WM_SIZE
.if wParam==SIZE_MINIMIZED
mov note.cbSize,sizeof NOTIFYICONDATA
push hWnd
pop note.hwnd
mov note.uID,IDI_TRAY
mov note.uFlags,NIF_ICON+NIF_MESSAGE+NIF_TIP
mov note.uCallbackMessage,WM_SHELLNOTIFY
invoke LoadIcon,NULL,IDI_WINLOGO
mov note.hIcon,eax
invoke lstrcpy,addr note.szTip,addr AppName
invoke ShowWindow,hWnd,SW_HIDE
invoke Shell_NotifyIcon,NIM_ADD,addr note
.endif
Мы используем этот момент, чтобы заполнить структуру NOTIFYICONDATA. IDI_TRAY это просто константа,
определённая в начале исходного кода. Ей можно задать любое значение. Это не очень важно, так как у нас только
одна иконка в system tray. Но если вы захотите поместить туда сразу несколько иконок, то вам потребуется задать
уникальный ID для каждой из них. Мы выставляем сразу все флаги в поле uFlags, так как мы указываем иконку (NIF_ICON),
мы указываем пользовательское сообщение (NIF_MESSAGE), а также текст всплывающей подсказки (NIF_TIP). WM_SHELLNOTIFY это
просто пользовательское сообщение, определённое как WM_USER+5. Само значение не так важно, пока оно сохраняет
свою уникальность. Я использовал логотип Windows в качестве иконки для этой программы, но вы можете использовать и любую
другую иконку ;) Просто загрузите её из файла ресурсов вызовом LoadIcon и сохраните возвращаемое значение в поле hIcon.
После всего этого поместим в поле szTip текст, который мы хотим видеть в качестве всплывающей подсказки
к иконке.
Мы скрываем главное окно, чтобы создать эффект "сворачивания в иконку".
Затем мы вызываем Shell_NotifyIcon с сообщением NIM_ADD, чтобы добавить иконку в system tray.
Теперь наше главное окно скрыто, а иконка успешно помещена в system tray. Если вы наведёте на неё курсор, то
увидите подсказку с текстом, который вы поместили в поле szTip. Далее, если вы дважды щелкните по иконке, восстановится
главное окно, а сама иконка исчезнет.
.elseif uMsg==WM_SHELLNOTIFY
.if wParam==IDI_TRAY
.if lParam==WM_RBUTTONDOWN
invoke GetCursorPos,addr pt
invoke SetForegroundWindow,hWnd
invoke TrackPopupMenu,hPopupMenu,TPM_RIGHTALIGN,pt.x,pt.y,NULL,hWnd,NULL
invoke PostMessage,hWnd,WM_NULL,0,0
.elseif lParam==WM_LBUTTONDBLCLK
invoke SendMessage,hWnd,WM_COMMAND,IDM_RESTORE,0
.endif
.endif
Когда над иконкой происходит событие мыши, ваше окно получает сообщение
WM_SHELLNOTIFY, то есть пользовательское сообщение, указанное в поле uCallbackMessage.
Напомню, что по приёму этого сообщения wParam содержит ID иконки, а lParam содержит событие мыши.
В вышеприведенном коде сначала проверяется, пришло ли сообщение от интересующей нас иконки. Если да, то тогда
мы смотрим на событие мыши. Так как нам нужны только правый щелчок и левый двойной щелчок, то мы обрабатываем
лишь сообщения WM_RBUTTONDOWN и WM_LBUTTONDBLCLK.
Если сообщение от мыши это WM_RBUTTONDOWN, мы вызываем GetCursorPos, чтобы узнать текущие координаты курсора
мыши. После возврата из функции, структура POINT содержит абсолютные координаты курсора. Под абсолютными координатами
я подразумеваю координаты, привязанные ко всему экрану, не берущие во внимание границы окна. Например,
если разрешение экрана 640*480, то правый нижний угол это x==639, y==479. Если вы желаете перевести абсолютные
координаты в оконные, используйте функцию ScreenToClient.
Однако мы хотим отобразить всплывающее меню в точке, где сейчас расположен курсор мыши, с помощью функции TrackPopupMenu,
которой требуются именно абсолютные координаты. Поэтому мы просто используем координаты, полученные от GetCursorPos.
TrackPopupMenu имеет следующий синтаксис:
TrackPopupMenu PROTO hMenu:DWORD, uFlags:DWORD, x:DWORD, y:DWORD, nReserved:DWORD, hWnd:DWORD, prcRect:DWORD
hMenu это хэндл всплывающего меню, которое нужно отобразить.
uFlags указывает опции отображения. Например, как располагать меню относительно указанных ниже
координат, и какая из кнопок мыши используется для отслеживания меню. В нашем примере мы используем
флаг TPM_RIGHTALIGN, чтобы разместить меню слева от указанной точки.
x и y указывают местоположение меню в абсолютных координатах.
nReserved должно содержать NULL.
hWnd это хэндл окна, которое будет получать сообщения от меню.
prcRect это прямоугольная область экрана, щелчки в пределах которой НЕ будут приводить к исчезновению меню.
Обычно сюда помещается NULL, чтобы меню исчезало при любом щелчке вне его.
Когда пользователь дважды щелкнёт по иконке, мы отправим нашему окну сообщение WM_COMMAND с указанием IDM_RESTORE,
чтобы создать иллюзию выбора пользователем пункта "Восстановить" в меню, и таким образом восстановить окно, а также
удалить иконку из system tray. Чтобы иметь возможность получать сообщения двойного щелчка, главное окно должно иметь
стиль CS_DBLCLKS.
invoke Shell_NotifyIcon,NIM_DELETE,addr note
mov eax,wParam
.if ax==IDM_RESTORE
invoke ShowWindow,hWnd,SW_RESTORE
.else
invoke DestroyWindow,hWnd
.endif
Когда пользователь выберет пункт "Восстановить" в меню, мы удаляем иконку повторным вызовом
Shell_NotifyIcon, только на этот раз указывая NIM_DELETE в качестве сообщения. Затем мы возвращаем
первозданный вид главному окну. Если пользователь выберет пункт "Закрыть", мы тоже удаляем
иконку из system tray и уничтожаем главное окно вызовом DestroyWindow.
|
|