Orion9

|
Posted: Wed Feb 11, 2026 20:35 Post subject: |
|
|
FallenAngel
Если вкратце.
Функция NotifyIcon принимает следующие параметры:
| Code: | sAction - тип действия ("add", "set", "delete")
uID - внутренний уникальный идентификатор иконки
nCallback - код зарегистрированного сообщения для функции обратного вызова
hIcon - дескриптор иконки (только для "add")
sTip - текст подсказки в трее
sText - текст уведомления (только для "add" и "set")
sTitle - текст заголовка уведомления
nType - флаги NIIF_* (изменение вида и поведения окна уведомлений)
Func NotifyIcon(sAction, uID, nCallback = 0, hIcon = 0, sTip = "", sText = "", sTitle = "", nType = 0) |
Чтобы добавить иконку в трей, нужен режим "add" и еще 3 параметра. Четвертый параметр sTip тоже желателен, т.к. он отвечает за текст подсказки в трее, но его в принципе можно не указывать.
Все сводится к одному вызову, например:
| Code: | NotifyIcon("add", 1005, gCode, hIco, "Иконка Autorun") |
Первый и последний параметры нам уже понятны. Осталось разобраться за что именно отвечают uID = 1005, nCallback = gCode, hIcon = hIco.
uID - это просто внутренний код иконки, взятый буквально от балды. Приложение может держать в трее несколько иконок, оболочке Windows и нам самим нужно как-то их отличать. Код может быть любым числом. Здесь он 1005.
nCallback - это код, который использует оболочка, когда она посылает сообщения о событиях в трее назад приложению. И снова этот код мы должны сами придумать, но только в определенном диапазоне. Чтобы не забивать этим голову, мы просим систему выделить нам уникальный незанятый код. Каким именно будет этот код, нам не важно, мы просто запоминаем его в переменной gCode:
| Code: | Global gCode = DllCall("RegisterWindowMessageW", "wstr", "TrayNotify1005", "uint") |
Теперь получив нужный код, мы должны внутри приложения привязать его к реальной функции, которая будет запускаться каждый раз, когда оболочка шлет нам сообщения с этим кодом.
| Code: | SetMessageAction /P %"gCode" "TestrayAction" |
Внутри функции мы должны обрабатывать те сообщения, события которых нам интересны. Это прежде всего события левого и правого клика, но можно обрабатывать и другие события.
| Code: | Func TestrayAction(hWnd, uMsg, wParam, lParam)
If lParam = WM_LBUTTONDOWN Then
MsgBox("Клик по иконке")
ElseIf lParam = WM_RBUTTONDOWN Then
ShowPopupMenu("~/D", COMMANDER_PATH & "\Bars\Vertical.bar")
EndIf
EndFunc |
Левый клик по иконке выводит MsgBox, правый отображает меню из Vertical.bar. Можно использать и другие команды, em_команды, mnu и bar файлы — здесь есть, где фантазии развернуться.
Остается только иконка. Дескриптор иконки hIcon можно получать по-разному. Самый простой способ — послать сообщение окну ТС с кодом 0x7f, тогда тотал вернет дескриптор своей иконки:
| Code: | hIcon = SendMessage(AUTORUN_TCHANDLE, 0x7f, 2, 0) |
Можно также получить дескриптор функцией ExtractIcon, вызывать которую нужно через DllCall
| Code: | sIco = GetKnownFolderPath("System") & "\shell32.dll"
hIco = DllCall("shell32\ExtractIconW", _
"ptr", AUTORUN_TCHANDLE, _
"wstr", sIco, "uint", 15, "ptr")
|
В вызовах DllCall нет ничего страшного. По сути, это просто запрос к Windows, чтобы она выполнила за нас определенные действия. В данном случае таким действием является извлечение иконки с индексом 15 из shell32.dll и передача нам дескриптора.
Нам, в свою очередь, остается передать этот дескриптор функции NotifyIcon вместе с другими параметрами. Итого, полученный код будет выглядеть так:
| Code: | # регистрация кода для функции обратного вызова
Global gCode = DllCall("RegisterWindowMessageW", "wstr", "TrayNotify1005", "uint")
# привязка функции обратного вызова к полученному коду
SetMessageAction /P %"gCode" "TestrayAction"
# функция обратного вызова
Func TestrayAction(hWnd, uMsg, wParam, lParam)
If lParam = WM_LBUTTONDOWN Then
#MsgBox("Клик по иконке")
NotifyIcon("set", 1005, gCode, hIco, "", "Test message", "", NIIF_WARNING)
ElseIf lParam = WM_RBUTTONDOWN Then
ShowPopupMenu("~/D", COMMANDER_PATH & "\Bars\Vertical.bar")
EndIf
EndFunc
# путь к иконке
sIco = GetKnownFolderPath("System") & "\shell32.dll"
# получение дескриптора иконки с индексом 15
hIco = DllCall("shell32\ExtractIconW", _
"ptr", AUTORUN_TCHANDLE, _
"wstr", sIco, "uint", 15, "ptr")
# установка в трей с идентификатором 1005
NotifyIcon("add", 1005, gCode, hIco, "Иконка Autorun") |
Вот так. Несколько строк и дело в шляпе иконка в трее
Однако, чтобы этот фрагмент кода заработал самостоятельно, без привязки к модулю Icons.aucfg, необходимо перенести из него все константы и, собственно, саму функцию NotifyIcon:
 Hidden text | Code: | # иконки окна уведомлений
Const NIIF_NONE = 0x00000000, _
NIIF_INFO = 0x00000001, _
NIIF_WARNING = 0x00000002, _
NIIF_ERROR = 0x00000003, _
NIIF_USER = 0x00000004
# без звука
Const NIIF_NOSOUND = 0x00000010
# большая иконка
Const NIIF_LARGE_ICON = 0x00000020
# сообщения окна уведомлений
Const NIN_BALLOONSHOW = 0x402, _
NIN_BALLOONHIDE = 0x403, _
NIN_BALLOONTIMEOUT = 0x404, _
NIN_BALLOONUSERCLICK = 0x405, _
NIN_POPUPOPEN = 0x406, _
NIN_POPUPCLOSE = 0x407
# флаги структуры
Const NIF_MESSAGE = 0x00000001, _
NIF_ICON = 0x00000002, _
NIF_TIP = 0x00000004, _
NIF_STATE = 0x00000008, _
NIF_INFO = 0x00000010
# системные сообщения
Const WM_MOUSEMOVE = 0x0200, _
WM_LBUTTONDOWN = 0x0201, _
WM_LBUTTONUP = 0x0202, _
WM_LBUTTONDBLCLK = 0x0203, _
WM_RBUTTONDOWN = 0x0204, _
WM_RBUTTONUP = 0x0205, _
WM_RBUTTONDBLCLK = 0x0206, _
WM_MBUTTONDOWN = 0x0207, _
WM_MBUTTONUP = 0x0208, _
WM_MBUTTONDBLCLK = 0x0209
#{
Добавление иконки в трей:
sAction - тип действия ("add", "set", "delete")
uID - внутренний уникальный идентификатор иконки
nCallback - код зарегистрированного сообщения для функции обратного вызова
hIcon - дескриптор иконки (только для "add")
sTip - текст подсказки в трее
sText - текст уведомления (только для "add" и "set")
sTitle - текст заголовка уведомления
nType - флаги NIIF_* (изменение вида и поведения окна уведомлений)
https://learn.microsoft.com/en-us/windows/win32/api/shellapi/ns-shellapi-notifyicondataw
#}
Func NotifyIcon(sAction, uID, nCallback = 0, hIcon = 0, sTip = "", sText = "", sTitle = "", nType = 0)
Local nAction, nFlags
Static TIP_MAXCHAR = 127, INFO_MAXCHAR = 255, TITLE_MAXCHAR = 63
Static buf = Buffer(auPtrSize = 4 ? 956 : 976)
buf.Zero()
nFlags = BitOR( hIcon = 0 ? 0 : NIF_ICON, _
nCallback = 0 ? 0 : NIF_MESSAGE, _
StrLen(sTip) = 0 ? 0 : NIF_TIP, _
StrLen(sText) = 0 ? 0 : NIF_INFO )
Switch sAction
Case "add"
nAction = 0
Case "set"
nAction = 1
Case "delete"
nAction = 2
Else
Return
EndSwitch
If auX64 Then
buf.SetNum(0, "dword", buf.size)
buf.SetNum(8, "hwnd", AUTORUN_TCHANDLE)
buf.SetNum(16, "uint", uID, _
"uint", nFlags, _
"uint", nCallback)
buf.SetNum(32, "ptr", hIcon)
Else
buf.SetNum(0, "dword", buf.size, _
"hwnd", AUTORUN_TCHANDLE, _
"uint", uID, _
"uint", nFlags, _
"uint", nCallback, _
"ptr", hIcon)
EndIf
Local nOffset = auX64 ? 40 : 24 # PtrSize*2 + 4*4 + padding for X64
If sTip <> 0 Then
buf.SetStr(StrLeft(sTip, TIP_MAXCHAR) & Chr(0), nOffset)
EndIf
If sText <> 0 Then
nOffset += 264
buf.SetStr(StrLeft(sText, INFO_MAXCHAR) & Chr(0), nOffset) # 256 + 4 + 4
If sTitle <> 0 Then
buf.SetStr(StrLeft(sTitle, TITLE_MAXCHAR) & Chr(0), nOffset + 516) # 512 + 4
EndIf
If nType > 0 Then
buf.SetNum(nOffset + 644, "dword", nType) # 128
EndIf
EndIf
Return DllCall("Shell32.dll\Shell_NotifyIconW", "uint", nAction, "ptr", buf.ptr, "bool")
EndFunc
# регистрация кода для функции обратного вызова
Global gCode = DllCall("RegisterWindowMessageW", "wstr", "TrayNotify1005", "uint")
# привязка функции обратного вызова к полученному коду
SetMessageAction /P %"gCode" "TestrayAction"
# функция обратного вызова
Func TestrayAction(hWnd, uMsg, wParam, lParam)
If lParam = WM_LBUTTONDOWN Then
If IsPressed(0x11) Then
NotifyIcon("set", 1005, gCode, hIco, "", "Test message", "", NIIF_WARNING)
Return
EndIf
MsgBox("Клик по иконке")
ElseIf lParam = WM_RBUTTONDOWN Then
ShowPopupMenu("~/D", COMMANDER_PATH & "\Bars\Vertical.bar")
EndIf
EndFunc
# путь к иконке
sIco = GetKnownFolderPath("System") & "\shell32.dll"
# получение дескриптора иконки с индексом 15
hIco = DllCall("shell32\ExtractIconW", _
"ptr", AUTORUN_TCHANDLE, _
"wstr", sIco, "uint", 15, "ptr")
# установка в трей с идентификатором 1005
NotifyIcon("add", 1005, gCode, hIco, "Иконка Autorun") |
Работает у вас?
В этот код я добавил обработку CTRL:
| Code: | If IsPressed(0x11) Then
NotifyIcon("set", 1005, gCode, hIco, "", "Test message", "", NIIF_WARNING)
Return
EndIf |
Теперь клик по иконке с Ctrl отображает системное уведомление. Функция NotifyIcon может не только добавлять или менять иконку в трее, но также показывать системные уведомления. Единственное условие — иконка в трее должна существовать. Именно поэтому, чтобы была возможность вызвать системные уведомления без привязки к конкретной иконке, реализована функция NotifyInfoMessage.
| Code: | #{
Вызов системных уведомлений:
InfoText - текст уведомления (255 символов)
InfoTitle - текст заголовка уведомления (63 символов)
InfoType - флаги NIIF_* меняющие вид и поведение окна уведомлений
Функция использует идентификатор "1010"
#}
Func NotifyInfoMessage(InfoText, InfoTitle = "Autorun", InfoType = 0) |
Функция заранее резервирует идентификатор иконки и не требует указания дескриптора — дескриптор берется из файла, указанного в gNotifyIcon. Если файл не существует, будет использоваться иконка тотала. Для обратного вызова также получается код и сохраняется в переменную Code1010. Функция обратного вызова определена как TrayNotifyInfo. Данная функция обрабатывает левый клик по иконке в трее, где отображается меню лога уведомлений. Это еще одна особенность NotifyInfoMessage — она сохраняет лог уведомлений в текущей сессии. Т.о. даже если уведомление было пропущено (закрыто по таймауту) в трее можно будет увидеть его след:
 Hidden text | Code: | lobal gNotifyIcon = COMMANDER_EXE
#Global gNotifyIcon = COMMANDER_PATH & "\Ini\Newsbar\notify-yes.ico"
# регистрация уникального кода
Global Code1010 = DllCall("RegisterWindowMessageW", "wstr", "TrayNotify1010", "uint")
SetMessageAction /P %"Code1010" "TrayNotifyInfo"
# иконка уведомлений
Func TrayNotifyInfo(hWnd, uMsg, wParam, lParam)
Switch lParam
Case WM_LBUTTONDOWN
ShowPopupMenu /D /F "TrayNotifyInfoMenu"
Case WM_RBUTTONDOWN
#NotifyIcon("delete", 1010)
Case NIN_BALLOONUSERCLICK
Case NIN_BALLOONTIMEOUT
EndSwitch
EndFunc
# меню уведомлений
Func TrayNotifyInfoMenu()
Local txt, nIco, sIco, sTime, sInfo, sType, sFunc, i = 0, j = 0
For i = TrayLog.Count - 1 To 0 Step -1
nIco = -1
sIco = "-1"
sTime = StrPart(TrayLog[i], Chr(0), 1)
sInfo = StrPart(TrayLog[i], Chr(0), 2)
sType = StrPart(TrayLog[i], Chr(0), 4)
If BitAND(sType, 3) = 1 Then
nIco = 76
ElseIf BitAND(sType, 3) = 2 Then
nIco = 79
ElseIf BitAND(sType, 3) = 3 Then
nIco = 93
EndIf
If nIco <> -1 Then sIco = "imageres.dll," & nIco
sFunc = "TrayNotifyItem " & i & " " & nIco
txt &= 'MENUITEM "' & sTime & " " & sInfo & '", em_aucmd ' & sIco & " " & sFunc & auCRLF
j += 1
If j = 30 Then Break
Next
If TrayLog.Count = 0 Then txt &= 'MENUITEM "<Пусто>", em_aucmd' & auCRLF
txt &= 'MENUITEM SEPARATOR' & auCRLF
txt &= 'MENUITEM "Копировать", em_aucmd -1 TrayNotifyCopy' & auCRLF
txt &= 'MENUITEM "Скрыть иконку", em_aucmd -1 NotifyIcon "delete" 1010' & auCRLF
Return txt
EndFunc
Func TrayNotifyItem(nItem, nIco)
Local nFlag
Local sInfo = StrPart(TrayLog[nItem], Chr(0), 2)
Local sText = StrPart(TrayLog[nItem], Chr(0), 3)
Switch nIco
Case 76
nFlag = 64
Case 79
nFlag = 48
Case 93
nFlag = 16
Else
nFlag = ''
EndSwitch
MsgBox(sText, sInfo, nFlag)
EndFunc
Func TrayNotifyCopy()
Local txt = TrayLog.Text
txt = StrReplace(txt, Chr(0), ";")
ClipPut(txt)
ShowTransHint("Скопировано в буфер")
EndFunc
#{
Вызов системных уведомлений:
InfoText - текст уведомления (255 символов)
InfoTitle - текст заголовка уведомления (63 символов)
InfoType - флаги NIIF_* меняющие вид и поведение окна уведомлений
Функция использует идентификатор "1010"
#}
Func NotifyInfoMessage(InfoText, InfoTitle = "Autorun", InfoType = 0)
Local Offset = auX64 ? 40 : 24, sIcon
Static buf = Buffer(auPtrSize = 4 ? 956 : 976)
Static nFlags = BitOR(NIF_MESSAGE, NIF_ICON, NIF_INFO, NIF_TIP)
If FileExist(gNotifyIcon) Then
sIcon = gNotifyIcon
Else
sIcon = COMMANDER_EXE
EndIf
Static hIco = DllCall("shell32\ExtractIconW", _
"ptr", AUTORUN_TCHANDLE, _
"wstr", sIcon, "uint", 0, "ptr")
NotifyIcon("delete", 1010)
buf.Zero()
If auX64 Then
buf.SetNum(0, "dword", buf.size)
buf.SetNum(8, "hwnd", AUTORUN_TCHANDLE)
buf.SetNum(16, "uint", 1010, _
"uint", nFlags, _
"uint", Code1010)
buf.SetNum(32, "ptr", hIco)
buf.SetNum(296, "dword", 1, _
"dword", 1)
Else
buf.SetNum(0, "dword", buf.size, _
"hwnd", AUTORUN_TCHANDLE, _
"uint", 1010, _
"uint", nFlags, _
"uint", Code1010, _
"ptr", hIco)
buf.SetNum(280, "dword", 1, _
"dword", 1)
EndIf
buf.SetStr("Notify Messages" & Chr(0), Offset)
buf.SetStr(StrLeft(InfoText, 255) & Chr(0), Offset + 264) # 256 + 4 + 4
Offset += 264
If InfoTitle <> "" Then
buf.SetStr(StrLeft(InfoTitle, 63) & Chr(0), Offset + 516) # 512 + 4
EndIf
Offset += 516
If InfoType > 0 Then
buf.SetNum(Offset + 128, "dword", InfoType) # 128
EndIf
TrayLog.Add(Time() & Chr(0) & InfoTitle & Chr(0) & InfoText & Chr(0) & InfoType)
Tray1010 = 1
Return DllCall("Shell32.dll\Shell_NotifyIconW", "uint", 0, "ptr", buf.ptr)
EndFunc |
Это только сами функции и их вызов. В реальности код разрастается из-за различных условий и проверок. Например, вряд ли есть необходимость всегда держать иконку в трее, а значит надо как-то учитывать это при запуске — считывать ключ из ини-файла, смотреть его значение, загружать или не загружать иконку в зависимости от этого значения и т.д. Именно поэтому модуль получился больше, чем эти две функции, ведь это мой рабочий модуль, т.е. модуль, который я использую у себя в ТС. Кое-что там лишнее и переплетается с другими модулями, поэтому сам модуль не универсальный, универсальны только эти две функции.
Как-то так  |
|