Rozdział XXIII - Ikona Zasobnika Systemowego


Na tej lekcji poznamy sposoby umieszczania ikon w zasobniku systemowym (system tray) oraz jak tworzyć i korzystać z menu wyskakującego (popup).

 

 

Kod źródłowy możesz pobrać {z tego archiwum}.

 

Teoria

Zasobnik systemowy jest prostokątnym obszarem na pasku zadań, gdzie przebywa kilka ikon. Zwykle powinieneś tam zobaczyć przynajmniej cyfrowy zegar.

 

 

Ty również możesz tam umieszczać swoje ikony. Poniżej przedstawiam krok po kroku jak tego dokonać:

  1. Wypełnij strukturę NOTIFYICONDATA o następujących polach:
NOTIFYICONDATA STRUCT
    cbSize           DWORD ?
    hwnd             DWORD ?
    uID              DWORD ?
    uFlags           DWORD ?
    uCallbackMessage DWORD ?
    hIcon            DWORD ?
    szTip            BYTE 64 DUP (?)
NOTIFYICONDATA ENDS
  1. Wywołaj funkcję Shell_NotifyIcon zdefiniowaną w pliku dołączalnym shell32.inc. Funkcja ta ma następujący prototyp:
Shell_NotifyIcon PROTO dwMessage:DWORD ,pnid:DWORD

Jeśli chcesz dodać ikonę do zasobnika, użyj wiadomości NIM_ADD, jeśli chcesz ikonę usunąć, użyj NIM_DELETE.

To wszystko. Lecz częściej nie interesuje cię jedynie umieszczenie tam ikony. Chcesz mieć możliwość odpowiadania na zdarzenia myszki ponad ikoną zasobnika. Możesz tego dokonać obsługując wiadomość, którą określiłeś w polu uCallbackMessage struktury NOTIFYICONDATA.

 

Wiadomość uCallbackMessage
wParam Zawiera numer ID ikony. Jest to ta sama wartość, którą wstawiasz w pole uID struktury NOTIFYICONDATA.
lParam Młodsze słowo zawiera wiadomość myszki. Na przykład, jeżeli użytkownik kliknął prawym przyciskiem na tę ikonę, lParam będzie zawierało WM_RBUTTONDOWN.

 

Jednakże większość ikon zasobnika wyświetla rozwijane menu (kontekstowe), gdy użytkownik kliknie je prawym przyciskiem myszki. Możemy zaimplementować tę cechę tworząc rozwijane menu, a następnie wywołując TrackPopupMenu w celu wyświetlenia go. Oto niezbędne kroki:

  1. Utwórz menu wyskakujące (popup menu) wywołując CreatePopupMenu. Funkcja ta tworzy puste menu. Jeśli się powiedzie, to w rejestrze eax zwraca uchwyt menu.
  2. Przy pomocy funkcji AppendMenu, InsertMenu lub InsertMenuItem dodaj elementy menu.
  3. Gdy chcesz wyświetlić wyskakujące menu na pozycji kursora myszki, wywołaj GetCursorPos w celu otrzymania współrzędnych ekranowych kursora, a następnie wywołaj TrackPopupMenu, aby wyświetlić je na tych współrzędnych. Gdy użytkownik wybierze jakiś element z menu, system Windows wyśle wiadomość WM_COMMAND do twojej procedury okna jak przy normalnym wyborze z menu.

Uwaga: Unikaj dwóch dokuczliwych zwyczajów menu wyskakującego stosowanego z ikoną zasobnika:

  1. Gdy jest wyświetlane wyskakujące menu i klikniesz gdziekolwiek poza nim, to menu nie znika natychmiast jak powinno. Dzieje się tak dlatego, gdyż okno otrzymujące powiadomienia z menu MUSI być oknem pierwszoplanowym. Aby to naprawić, po prostu wywołaj SetForegroundWindow.
  2. Po wywołaniu SetForegroundWindow odkryjesz, iż przy pierwszym wyświetleniu menu wyskakującego wszystko działa poprawnie, lecz przy następnych razach menu pokazuje się na moment i natychmiast znika. Zachowanie to jest "celowe", co cytuję za MSDN (Microsoft Developer Network - baza wiedzy Microsoft dla twórców oprogramowania). W najbliższej przyszłości konieczne jest przełączenie zadania do programu będącego właścicielem ikony zasobnika. Możesz wymusić to przełączenie zadania wysyłając wiadomość do okna tego programu. Po prostu zastosuj PostMessage, a nie SendMessage!

Przykład

Plik TRAYICON.ASM

 

.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

WinMain PROTO :DWORD, :DWORD, :DWORD, :DWORD

.CONST

WM_SHELLNOTIFY EQU WM_USER + 5
IDI_TRAY       EQU    0
IDM_RESTORE    EQU 1000
IDM_EXIT       EQU 1010

.DATA

ClassName     DB "TrayIconWinClass", 0
AppName       DB "Ikona Zasobnika Systemowego", 0
RestoreString DB "&Przywróć", 0
ExitString    DB "&Zakończ program", 0

.DATA?

hInstance  D ?
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, 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
LOCAL rect: RECT

    .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 OR TPM_RIGHTBUTTON,\
                                       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

Analiza

Program wyświetli proste okno. Gdy klikniesz przycisk minimalizacji, program schowa okno i umieści ikonę w zasobniku systemowym. Gdy podwójnie klikniesz myszką tę ikonę, program odtworzy okno i usunie ikonę z zasobnika systemowego. Gdy klikniesz ją prawym przyciskiem myszki, zostanie wyświetlone menu wyskakujące. Możesz z niego wybrać opcję odtworzenia programu lub zakończenia jego pracy.

 

    .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
 

Przy tworzeniu okna powstaje wyskakujące menu i dołączane są do niego dwa elementy. Funkcja AppendMenu posiada następującą składnię:

 

AppendMenu PROTO hMenu:      DWORD,\
                 uFlags:     DWORD,\
                 uIDNewItem: DWORD,\
                 lpNewItem:  DWORD

Po utworzeniu wyskakującego menu główne okno czeka cierpliwie aż użytkownik kliknie przycisk minimalizacji.

Gdy okno jest minimalizowane, otrzymuje ono wiadomość WM_SIZE z wartością SIZE_MINIMIZED w 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

 

Korzystamy z tej okazji do wypełnienia struktury NOTIFYICONDATA. Wartość IDI_TRAY jest po prostu stałą zdefiniowaną na samym początku tekstu źródłowego programu. Nie jest ona ważna, ponieważ posiadamy tylko jedną ikonę zasobnika. Lecz jeśli w zasobniku chcemy umieszczać kilka ikon, to dla każdej z nich będziemy potrzebowali dla każdej z nich osobnego numeru ID. W polu uFlags umieszczamy wszystkie znaczniki, ponieważ określamy ikonę (NIF_ICON), prywatną wiadomość (NIF_MESSAGE) oraz tekst podpowiedzi (NIF_TIP). Wartość WM_SHELLNOTIFY jest po prostu prywatną wiadomością zdefiniowaną jako WM_USER + 5. Wartość ta nie jest istotna o ile nie koliduje z innymi wiadomościami. Tutaj zastosowałem ikonę logo Windows, lecz może to być dowolna inna ikona. Po prostu załaduj ją z pliku zasobów przy pomocy funkcji LoadIcon i umieść zwrócony przez nią uchwyt w polu hIcon. Na koniec wypełniamy pole szTip tekstem, który ma być wyświetlany przez powłokę, gdy kursor myszki zatrzymuje się ponad ikoną zasobnika.

Ukrywamy główne okno, aby dać iluzję "minimalizacji do ikony zasobnika".

Następnie wywołujemy Shell_NotifyIcon z wiadomością NIM_ADD, aby dodać tę ikonę do zasobnika systemowego.

Teraz nasze okno główne zostało ukryte a ikona umieszczona w zasobniku systemowym. Gdy umieścisz kursor myszki na tej ikonie, pojawi się tekst podpowiedzi wyświetlający nazwę naszej aplikacji, którą umieściliśmy w polu szTi. Następnie jeśli klikniesz podwójnie na tej ikonie, pojawi się główne okno a zniknie ikona zasobnika.

 

.ELSEIF uMsg==WM_SHELLNOTIFY
        .IF wParam==IDI_TRAY
            .IF lParam==WM_RBUTTONDOWN
                INVOKE GetCursorPos, ADDR pt
                INVOKE SetForegroundWindow, hWnd
                INVOKE TrackPopupMenu, hPopupMenu,\
                                       TPM_RIGHTALIGN OR TPM_RIGHTBUTTON,\
                                       pt.x, pt.y, NULL, hWnd, NULL
                INVOKE PostMessage, hWnd, WM_NULL, 0, 0
            .ELSEIF lParam==WM_LBUTTONDBLCLK
                INVOKE SetForegroundWindow, hWnd
                INVOKE SendMessage, hWnd, WM_COMMAND, IDM_RESTORE, 0
            .ENDIF
        .ENDIF

 

Gdy powstanie jakieś zdarzenie myszki ponad ikoną zasobnika, twoje okno otrzyma wiadomość WM_SHELLNOTIFY, która jest prywatną wiadomością określoną w polu uCallbackMessage. Przypomnij sobie, iż przy otrzymywaniu tej wiadomości parametr wParam zawiera numer ID ikony zasobnika, a lParam zawiera właściwą wiadomość myszki. W kodzie powyżej sprawdzamy najpierw, czy wiadomość ta pochodzi od interesującej nas ikony zasobnika. Jeśli tak, to sprawdzamy właściwą wiadomość myszki. Ponieważ interesuje nas jedynie kliknięcie ikony prawym przyciskiem myszki lub podwójne kliknięcie lewym przyciskiem, obsługujemy tylko wiadomości WM_RBUTTONDOWN oraz WM_LBUTTONDBLCLK.

Jeśli wiadomością myszki jest WM_RBUTTONDOWN, wywołujemy funkcję GetCursorPos, aby otrzymać bieżącą pozycję ekranową kursora myszki. Gdy funkcja powraca, struktura POINT jest wypełniona współrzędnymi kursora myszki na ekranie. Przez współrzędne ekranowe rozumiem współrzędne całego ekranu bez oglądania się na jakieś granice okien. Przykładowo, jeśli rozdzielczość ekranu wynosi 1024x768, to dolny prawy narożnik ekranu będzie miał współrzędne x=1023 i y=767. Jeśli chcesz zamienić współrzędne ekranowe na współrzędne w obszarze okna, użyj funkcji ScreenToClient.

Jednakże naszym celem jest wyświetlenie menu wyskakującego na bieżącej pozycji kursora myszki przy pomocy wywołania funkcji TrackPopupMenu call, a ona wymaga współrzędnych ekranowych, zatem możemy bezpośrednio wykorzystać współrzędne zwrócone przez GetCursorPos.

Funkcja TrackPopupMenu ma następującą składnię:

 

TrackPopupMenu PROTO hMenu:     DWORD,\
                     uFlags:    DWORD,\
                     x:         DWORD,\
                     y:         DWORD,\
                     nReserved: DWORD,\
                     hWnd:      DWORD,\
                     prcRect:   DWORD

Gdy użytkownik kliknie podwójnie na ikonie zasobnika, wysyłamy wiadomość WM_COMMAND do naszego własnego okna określając IDM_RESTORE w celu symulacji wyboru przez użytkownika elementu menu Przywróć w wyskakującym menu, co spowoduje odtworzenie okna oraz usunięcie ikony z zasobnika systemowego. Aby otrzymywać wiadomości o podwójnym kliknięciu myszką, okno główne musi posiadać styl CS_DBLCLKS. Przed wysłaniem tej wiadomości konieczne jest wywołanie funkcji SetForegroundWindow, która ustawi nasze okno na pierwszym planie. W przeciwnym wypadku okno po odtworzeniu mogłoby być zakryte, a kliknięcie w przycisk programu na pasku zadań odsyłało by je z powrotem do zasobnika systemowego.

 

    INVOKE Shell_NotifyIcon, NIM_DELETE, ADDR note
    mov    eax, wParam
    .IF ax==IDM_RESTORE
        INVOKE ShowWindow, hWnd, SW_RESTORE
    .ELSE
        INVOKE DestroyWindow, hWnd
    .ENDIF

 

Gdy użytkownik wybierze element menu Przywróć, usuwamy ikonę zasobnika wywołując ponownie funkcję Shell_NotifyIcon, tym razem określamy jako wiadomość NIM_DELETE. Następnie odtwarzamy główne okno do stanu pierwotnego. Jeśli użytkownik wybierze element menu Zakończ program, również usuwamy ikonę z zasobnika i usuwamy okno główne wywołując DestroyWindow.

 

Dodatek w Pascalu

Ta sama aplikacja w Pascalu:

 

{********************************
**  I Liceum Ogólnokształcące  **
**           w Tarnowie        **
**       mgr Jerzy Wałaszek    **
********************************}

program TrayIcon;

uses Windows;

const
  WM_SHELLNOTIFY = WM_USER + 5;
  NIF_MESSAGE    =    1;
  NIF_ICON       =    2;
  NIF_TIP        =    4;
  NIM_ADD        =    0;
  NIM_DELETE     =    2;
  IDI_TRAY       =    0;
  IDM_RESTORE    = 1000;
  IDM_EXIT       = 1010;
  ClassName      = 'TrayIconWinClass';
  AppName        = 'Ikona Zasobnika Systemowego';
  RestoreString  = '&Przywróć';
  ExitString     = '&Zakończ program';

var
  hInstance  : HINST;
  note       : NOTIFYICONDATA;
  hPopupMenu : longword;

function WndProc(hWnd:HWND;uMsg:UINT;wParam:WPARAM;lParam:LPARAM) : longint;
var
  pt   : POINT;
  rect: RECT;

begin
  Result := 0;
  case uMsg of
    WM_CREATE:
    begin
      hPopupMenu := CreatePopupMenu;
      AppendMenu(hPopupMenu,MF_STRING,IDM_RESTORE,RestoreString);
      AppendMenu(hPopupMenu,MF_STRING,IDM_EXIT,ExitString);
    end;
    WM_DESTROY:
    begin
      DestroyMenu(hPopupMenu);
      PostQuitMessage(0);
    end;
    WM_SIZE:
      if wParam = SIZE_MINIMIZED then
      begin
        note.cbSize           := sizeof(NOTIFYICONDATA);
        note.wnd              := hWnd;
        note.uID              := IDI_TRAY;
        note.uFlags           := NIF_ICON + NIF_MESSAGE + NIF_TIP;
        note.uCallbackMessage := WM_SHELLNOTIFY;
        note.hIcon            := LoadIcon(0,IDI_WINLOGO);
        note.szTip            := AppName;
        ShowWindow(hWnd,SW_HIDE);
        Shell_NotifyIcon(NIM_ADD,@note);
      end;
    WM_COMMAND:
      if lParam = 0 then
      begin
        Shell_NotifyIcon(NIM_DELETE,@note);
        if (wParam and $ffff) = IDM_RESTORE then
          ShowWindow(hWnd,SW_RESTORE)
        else
          DestroyWindow(hWnd);
      end;
    WM_SHELLNOTIFY:
      if wParam = IDI_TRAY then
        case lParam of
          WM_RBUTTONDOWN:
          begin
            GetCursorPos(pt);
            SetForegroundWindow(hWnd);
            TrackPopupMenu(hPopupMenu,TPM_RIGHTALIGN or TPM_RIGHTBUTTON,pt.x,pt.y,0,hWnd,rect);
            PostMessage(hWnd,WM_NULL,0,0);
          end;
          WM_LBUTTONDBLCLK:
            SendMessage(hWnd,WM_COMMAND,IDM_RESTORE,0);
        end;
    else Result := DefWindowProc(hWnd,uMsg,wParam,lParam);
  end;
end;

function WinMain(hInst,hPrevInst:HINST;CmdLine:LPSTR;CmdShow:DWORD) : longint;
var
  wc   : WNDCLASSEX;
  msg : MSG;
  hwnd: HWND;

begin
  with wc do
  begin
    cbSize        := sizeof(WNDCLASSEX);
    style         := CS_HREDRAW or CS_VREDRAW or CS_DBLCLKS;
    lpfnWndProc   := @WndProc;
    cbClsExtra    := 0;
    cbWndExtra    := 0;
    hInstance     := hInst;
    hbrBackground := COLOR_APPWORKSPACE;
    lpszMenuName  := 0;
    lpszClassName := ClassName;
    hIcon         := LoadIcon(0,IDI_APPLICATION);
    hIconSm       := hIcon;
    hCursor       := LoadCursor(0,IDC_ARROW);
  end;
  RegisterClassEx(wc);
  hwnd := CreateWindowEx(WS_EX_CLIENTEDGE,ClassName,AppName,
          WS_OVERLAPPED + WS_CAPTION + WS_SYSMENU + WS_MINIMIZEBOX +
          WS_MAXIMIZEBOX + WS_VISIBLE,CW_USEDEFAULT,CW_USEDEFAULT,
          350,200,0,0,hInst,0);
  while GetMessage(msg,0,0,0) do
  begin
    TranslateMessage(msg);
    DispatchMessage(msg);
  end;
  Result := msg.wParam;
end;

begin
  hInstance := GetModuleHandle(0);
  ExitProcess(WinMain(hInstance,0,0,SW_SHOWDEFAULT));
end.

 

Autorem kursu jest Iczelion. Kurs programowania Windows znalazł się na serwerze I LO w Tarnowie za pisemną zgodą autora.
Tłumaczenie z języka angielskiego, opracowanie HTML i konwersję przykładów programów wykonał mgr Jerzy Wałaszek.



List do administratora Serwisu Edukacyjnego Nauczycieli I LO

Twój email: (jeśli chcesz otrzymać odpowiedź)
Temat:
Uwaga: ← tutaj wpisz wyraz  ilo , inaczej list zostanie zignorowany

Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048

 

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień szeroko opisywanych w podręcznikach.



   I Liceum Ogólnokształcące   
im. Kazimierza Brodzińskiego
w Tarnowie

©2017 mgr Jerzy Wałaszek

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji
GNU Free Documentation License.