Как управлять программой через значек в Tray

Для начала, статья Ильи Родичева, по этому поводу:

Если Вы в операционной ситеме Windows'95 или Windows NT 4.0 пользуетесь оболочкой Explorer, то справа на TaskBar'е Вы должны были видеть "углубленную" область в которой, обычно, помещаются часы, переключатель клавиатуры, регулятор громкости и некоторые другие утилиты. Они изображаются маленькими иконками и для них существуют ToolTip'ы как для кнопок ToolBar'ов. При щелчке или двойном щелчке по такой иконке программа обычно выполняет действие по умолчанию, а при щелчке правой кнопкой показывает Pop-Up меню. Hа уровне оболочки System Tray это приложение, поддерживающее окно, которое вы видите как "углубленную" область и некоторый сервис для работы с этим окном.

Иногда бывает, что программа должна работать [почти] все время в минимизированном состоянии. Как сделать, что бы при минимизации [старте|все время] программа представлялась иконкой в System Tray и отвечала на сообщения мыши от этой иконки?" Ответ на этот вопрос состоит из нескольких частей.

Иконка в Tray'е это просто картинка, а не окно какой-либо программы. System Tray отслеживает события мыши над иконкой и, в случае надобности, показывает ToolTip для этой иконки. Так же он отсылает сообщения о всех действиях мыши над иконкой окну, которое поместило иконку в Tray. Таким образом, нельзя поместить программу в Tray. Любая программа может добавить стоько иконок в Tray, сколько ей необходимо. При этом главное окно программы не обязано исчезать или минимизироватся.

 Для работы с SystemTray существует всего одна функция. Вот ее Си-прототип:

WINSHELLAPI BOOL WINAPI Shell_NotifyIcon(
    DWORD dwMessage,         // message identifier
    PNOTIFYICONDATA pnid    // pointer to structure
);

Эта функция описана в заголовочном файле Win32-SDK "shellapi.h", включаемом в программу при включении "windows.h". Параметр dwMessage может принимать одно из трех значений: NIM_ADD, NIM_DELETE, NIM_MODIFY. Для добавления иконки он должен быть установлен в NIM_ADD.
Параметр pnid имеет тип PNOTIFYDATA, который описан как:

typedef struct _NOTIFYICONDATA { // nid
   DWORD cbSize;
   HWND hWnd;
   UINT uID;
   UINT uFlags;
   UINT uCallbackMessage;
   HICON hIcon;
   char szTip[64];
} NOTIFYICONDATA, *PNOTIFYICONDATA;

Поля структуры NOTIFYICONDATA имеют следующий смысл:
cbSize - размер структуры, должен быть sizeof(NOTIFYICONDATA).
hWnd - дескриптор окна, которое будет получать события мыши над иконкой.
uID - уникальный идентификатор иконки. Идентификатор должен быть уникален в пределах окна - обработчика, передаваемого в hWnd.
uFlags - битовое поле, определяющее какое из следующих полей несет действительную информацию. Может быть одним из следующих значений: NIF_ICON, NIF_MESSAGE, NIF_TIP или их OR-комбинацией.
uCallbackMessage - сообщение, передаваемое окну - обработчику при событиях мыши. Желательно получать номер сообщения вызовом RegisterWindowMessage(), но допускаются и значения WM_USER+N, где N > 0.
hIcon - дескриптор иконки, помещаемой на Tray.
szTip - текст для ToolTip'а, если szTip[0] = 0x00, то ToolTip'а не будет.

Таким образом, для добавления иконки в Tray необходимо заполнить экземпляр структуры NOTIFYICONDATA и вызвать функцию Shell_NotifyIcon() с параметром NIM_ADD и указателем на заполненный экземпляр структуры. При добавлении иконки необходимо заполнить поля cbSize, hWnd, uID, uFlags, uCallbackMessage, hIcon. Поле szTip можно оставить пустым, если вам не нужен ToolTip. Поле uFlags должно содержать как минимум NIF_MESSAGE | NIF_ICON.

После добавления иконки в Tray можно менять саму иконку, ToolTip и сообщение, посылаемое окну. Для этого необходимо заполнить экземпляр структуры NOTIFYICONDATA и вызвать функцию Shell_NotifyIcon() с параметром NIM_MODIFY и указателем на заполненный экземпляр структуры.

При изменении иконки необходимо заполнить поля cbSize, hWnd, uID, uFlags и поля, отвечающие за параметры иконки, которые вы хотите менять. При этом uFlags должен содержать комбинацию флагов, описывающую поля, которые необходимо менять.

Для удаления иконки вы должны знать ее ID и дескриптор окна-обработчика сообщений.
Для удаления иконки с Tray надо вызвать функцию Shell_NotifyIcon() с параметром NIM_DELETE и указателем на экземпляр структуры NOTIFYDATAICON, у которого должны быть заполнены следующие поля: cbSize, hWnd, uID.

При добавлении иконки в Tray мы указывали окно - обработчик сообщения и сообщение (CallbackMessage). Теперь окно, указанное вами будет при любых событиях мыши, происходящих над иконкой получать сообщение, указанное при добавлении иконки. При этом параметры lParam и wParam будут задействованы следующим образом:

(UINT)wParam - содержит ID иконки, над которой произошло событие
(UINT)lParam - содержит стандартное событие мыши, такое как WM_MOUSEMOVE или WM_LBUTTONDOWN.

При этом, информация о клавишах смены регистра, так же как и местоположения события, передаваемые при стандартных "настоящих" сообщениях мыши, теряются. Hо положение курсора можно узнать функцией GetCursorPos(), а состояние клавиш смены регистра - функцией GetKeyState(), описанных в winuser.h.

Как сделать, чтобы программа показывала Pop-Up меню при щелчке на иконке, помещенной в Tray?

Вы должны обрабатывать сообщение, указанное вами при добавлении иконки на Tray. При значении (UINT)lParam, равном WM_RBUTTONDOWN (это обычно для Pop-Up меню по правой кнопке), или любому другому необходимому вам, вы должны вызовом функции GetCursorPos() получить позицию курсора в момент события (вряд ли пользователь успеет убрать мышь за время обработки сообщения, особенно если он ожидает меню), получить вескриптор Pop-Up меню одним из многих способов (LoadMenu(), GetSubMenu(), CreateMenu(), и т.д.) и выполнить следующий код:

SetForegroundWindow(hWnd);
TrackPopupMenuEx(hMenu, TPM_HORIZONTAL | TPM_LEFTALIGN, x, y, hWnd, NULL);
DestroyMenu(hMenu);
PostMessage(hWnd, WM_USER, 0, 0);

где hWnd - дескриптор окна, которое будет обрабатывать команду меню, hMenu - дескриптор меню, x и y - позиция курсора. Для подробностей смотрите Win32 SDK Help по функции TrackPopupMenuEx.

Как сделать, чтобы программа минимизировалась в Tray?

Hа самом деле, не "программа оказывается на Tray", а только иконка помещается на Tray, а главное окно программы скрывается. Для достижения такого результата вам надо обрабатывать сообщение WM_SIZE, и при значении wParam, равном SIZE_MINIMIZED вы должны выполнить примерно следующую последовательность действий: добавить иконку на Tray и скрыть окно - вызвать ShowIndow(hWnd, SW_HIDE).

Когда произойдет действие, которое должно активировать вашу программу - WM_LBUTTONDBLCLK или WM_LBUTTONDOWN (или то, что нравится вам), вы должны удалить иконку и вызвать ShowWindow(hWnd,SW_SHOW) или ShowWindow(hWnd,SW_SHOWMAXIMIZED).

Всегда ли все вышесказанное будет работать?

Hет! Все вышенаписанное работает только при использовании в операционных системах Windows 95 и Windows NT 4.0 оболочки Explorer, и при разрешенном System Tray. В случае, если не происходит запуска systray.exe (запускается автоматически Explorer'ом при старте) или используется другая оболочка (Dashboard, Program Manager, File Manager), функция Shell_NotifyIcon() будет возвращать при вызове FALSE и не выполнять ни каких действий.
Еще раз повторю: System Tray - это возможность оболочки, а не операционной системы!

Официальная информация по System Tray:

Есть маленький пример в Win32 SDK: SDKRoot\Samples\Win32\Win95\TrayNot\*.*

Hу и конечно описание в документации функции Shell_NotifyIcon() и структуры NOTIFYICONDATA.

Так же можно посмотреть Microsoft Knowledge Base:
PSS ID Number: Q128129
PSS ID Number: Q134237
PSS ID Number: Q139408

Copyright © 1999 Ilya Rodichev. Последнее обновление 07 октября 1999г.
Contents of this page is a part of System Tray FAQ by Lev Serebryakov


А теперь,  как это реализуется на Clarion:

Прототипы используемых функций Windows API

include('winapi.clw', 'Equates' )

ShowWindow(UNSIGNED,SIGNED),SIGNED,PASCAL

OMIT('****',_WIDTH32_)
CallWindowProc(LONG,USHORT,SHORT,SHORT,LONG),LONG,RAW,PASCAL
SetWindowLong(USHORT,SHORT,LONG),LONG,RAW,PASCAL
****
COMPILE('****',_WIDTH32_) CallWindowProc(LONG,UNSIGNED,UNSIGNED,UNSIGNED,LONG),LONG,RAW,PASCAL,NAME('CallWindowProcA')
SetWindowLong(USHORT,SHORT,LONG),LONG,RAW,PASCAL,NAME('SetWindowLongA')
Shell_NotifyIcon(ULONG,LONG),BOOL,PASCAL,NAME('Shell_NotifyIconA')
LoadIcon(UNSIGNED,LONG),UNSIGNED,PASCAL,NAME('LoadIconA')
SetForegroundWindow(UNSIGNED),BOOL,PASCAL
****

After Global Includes:

! Tray Icon Declarations

SC::WindowMain LONG
TrayCString    CSTRING(64)

NotifyIconData GROUP,PRE(NID)
cbSize        ULONG
hWnd          UNSIGNED
uID           UNSIGNED
uFlags         UNSIGNED
uCBmessage UNSIGNED
hIcon      UNSIGNED
ToolTip    CSTRING(64)
END

NID:Active BYTE     ! Global "Is Tray Icon Active" Status byte
Event:NIM                            EQUATE(440h)     ! "USER" Event Number
Event:NIM:MouseLeft    EQUATE(441h)     ! Left mouse button
Event:NIM:MouseRight  EQUATE(442h)    ! Right mouse buttons
Event:NIM:MouseLeft2  EQUATE(443h)    ! Left mouse double-click
Event:NIM:MouseRight2 EQUATE(444h)  ! Right mouse double-click

NIM_ADD     EQUATE(0)   ! Mode
NIM_MODIFY  EQUATE(1)
NIM_DELETE  EQUATE(2)
NIF_MESSAGE EQUATE(1)   ! Information to update
NIF_ICON    EQUATE(2)
NIF_TIP EQUATE(4)

LOC:WM_QUERYENDSESSION EQUATE(0011h)
LOC:WM_ENDSESSION      EQUATE(0016h)
LOC:WM_MOUSEMOVE     EQUATE(200h)     ! Tray events
LOC:WM_LBUTTONDOWN   EQUATE(201h)
LOC:WM_LBUTTONUP     EQUATE(202h)     ! Main tray event
LOC:WM_LBUTTONDBLCLK EQUATE(203h)
LOC:WM_RBUTTONDOWN   EQUATE(204h)
LOC:WM_RBUTTONUP     EQUATE(205h)     ! Typically a popup
LOC:WM_RBUTTONDBLCLK EQUATE(206h)
GWL_WndProc EQUATE(-4)

Обработка событий Windows, в т.ч. событий из Subclass-процедуры

Точка вставки:
Шаблоны Clarion: Other window ewent handling
Шаблоны ABC: Window Manager.TakeWindowEvent()

IF EVENT()=EVENT:Iconized ! Tray Icon
  IF ~NID:Active  ! If not already active
     DO AddIconToTray
  END
END

CASE EVENT()
OF Event:NIM:MouseLeft   ! Левая кнопка мыши
MouseLeftProc()                  ! Процедура реакции на нажатие левой кнопки мыши
OF Event:NIM:MouseLeft2 ! Даблклик Левой кнопки мыши
     ;
OF Event:NIM:MouseRight2 ! Даблклик Правой кнопки мыши
    ;
OF Event:NIM:MouseRight ! Отпускание правой кнопки мыши
     MouseRightProc() ! Процедура реакции на нажатие правой кнопки мыши
END   ! case event()

Установить subclass-процедуру и включить TrayIcon

Точка вставки:
Clarion: After opening Window
ABC: Windows Events.OpenWindow

! Установить subclass-процедуру
SC::WindowMain = SetWindowLong(0{Prop:Handle},GWL_WndProc, ADDRESS(SubClassMain))
do PrepareNIDMain
if ~NID:Active ! If not already active
   do AddIconToTray
end

Убрать TrayIcon и отключить subclass-процедуру

Точка вставки:
Clarion: Before closing the Window
ABC: Windows Events.CloseWindow и Windows Events.CloseDown

! Убрать TrayIcon
if Shell_NotifyIcon( NIM_DELETE, ADDRESS(NotifyIconData) )
    NID:Active=False ! Reset global status flag
END

! Отключить subclass-процедуру
SC::WindowMain = SetWindowLong( 0{Prop:Handle}, GWL_WndProc, SC::WindowMain )

Procedure Routines:

PrepareNIDMain ROUTINE  ! Подготовить данные NotifyIconData для включения TrayIcon

TrayCString = 'Tray_ico'   ! Должна быть включена в проект как Tray.ico
NID:cbSize = SIZE(NotifyIconData)
NID:hWnd = 0{Prop:Handle}  ! Client Handle
NID:uID = 100
NID:uFlags = NIF_ICON + NIF_MESSAGE + NIF_TIP
NID:uCBmessage = Event:NIM ! this is the event we have to trap
NID:hIcon = LoadIcon( System{prop:appinstance}, Address(TrayCstring) )
NID:ToolTip = 'Нажми меня!'

AddIconToTray ROUTINE ! Установить TrayIcon

if Shell_NotifyIcon(NIM_ADD, ADDRESS(NotifyIconData)) ! Add the icon to the tray
   NID:Active = True ! Set global status flag
END

Субкласс процедура:

Прототип: SubClassMain (UNSIGNED,UNSIGNED,UNSIGNED,LONG),LONG,PASCAL

SubClassMain PROCEDURE (hWnd_,usMsg_,WParam_,IParam_)

CODE
CASE usMsg_
  OF LOC:WM_QUERYENDSESSION
     RETURN(True)
OF LOC:WM_ENDSESSION
     POST(Event:CloseDown)
     RETURN(True)
OF Event:NIM       ! as specified in NID:uCBmessage earlier
     CASE BAND(IParam_, 0FFFFh) ! What happened
     OF LOC:WM_LBUTTONUP ! Left mouse click
        POST(Event:NIM:MouseLeft)
     OF LOC:WM_LBUTTONDBLCLK ! Left mouse double click
        POST(Event:NIM:MouseLeft2)
     OF LOC:WM_RBUTTONDOWN ! Right mouse click - pressed
        ;
     OF LOC:WM_RBUTTONUP ! Right mouse click - released
        POST(Event:NIM:MouseRight)
    OF LOC:WM_RBUTTONDBLCLK ! Right mouse double click
        POST(Event:NIM:MouseRight2)
     END
    RETURN(0)
else ! case usMsg_
    RETURN( CallWindowProc(SC::WindowMain, hWnd_, usMsg_, WParam_, IParam_) )
END ! case usMsg_

Hosted by uCoz