Rozdział VIII - Menu


W tym rozdziale nauczymy się dołączać do naszego okna listę poleceń menu..

 

obrazek

 

Załaduj pliki {przykładu 1} i {przykładu 2}

 

Teoria

Lista poleceń menu jest jednym z najważniejszych komponentów w naszym oknie. Lista ta przedstawia usługi oferowane użytkownikowi przez program. Użytkownik nie musi studiować instrukcji dołączonej do programu, aby móc go używać, może on przeczytać uważnie menu, aby wyrobić sobie pogląd co do możliwości określonego programu i natychmiast zacząć z nim zabawę. Ponieważ lista poleceń menu jest narzędziem umożliwiającym użytkownikowi szybki start z programem, powinieneś stosować się do standardów. Ujmując zwięźle pierwsze dwa elementy menu powinny być elementami Plik i Edycja, a ostatni powinien być elementem Pomoc. Własne elementy możesz umieszczać pomiędzy elementami Edycja i Pomoc. Jeśli element menu wywołuje okienko dialogowe, to do nazwę powinieneś zakończyć trzema kropkami (...).

Lista poleceń menu jest typem zasobu. Istnieje kilka typów zasobów, takich jak okno dialogowe, tablica łańcuchów tekstowych, ikona, mapa bitowa, lista menu itp. Zasoby są opisane w oddzielnym pliku zwanym plikiem zasobów, który posiada rozszerzenie .RC. Zasoby łączysz z kodem źródłowym podczas etapu konsolidacji. Końcowy produkt jest plikiem wykonywalnym posiadającym zarówno instrukcje jak i zasoby.

Skrypty zasobów możesz tworzyć przy pomocy dowolnego edytora tekstu. Składają się one ze zdań opisujących wygląd oraz inne atrybuty zasobów wykorzystywanych w określonym programie. Jednakże tworzenie takich skryptów w procesorze tekstu jest czynnością uciążliwą. Lepszą alternatywą będzie zapewne użycie edytora zasobów, który w sposób wizualny pozwala ci z łatwością projektować zasoby. Edytory zasobów zwykle są dołączane do pakietów kompilatorów takich jak Visual C++, Borland C++, itp. (od tłumacza - polecam pakiet RadASM, który zawiera dobry, zintegrowany edytor zasobów).

Zasób menu opisuje się w sposób następujący:

 

MojeMenu MENU 
{ 
   [tutaj lista menu] 
}


Programiści C/C++ mogą zauważyć, iż bardzo to przypomina deklarację struktury. MojeMenu jest nazwą menu, za którą występuje słowo kluczowe MENU oraz lista menu w nawiasach klamrowych. Zamiast nich możesz stosować słowa BEGIN i END, jeśli tak sobie życzysz. Taka składnia będzie bardziej strawna dla programujących w języku Pascal.

Lista menu może składać się albo z poleceń MENUITEM albo z POPUP.

Polecenie MENUITEM definiuje opcję menu, która przy wyborze nie rozwija nowej listy poleceń. Składnia tego polecenia jest następująca:


MENUITEM "&tekst", ID [,opcje]
 

Rozpoczyna się od słowa kluczowego MENUITEM, za którym następuje tekst używany do wyświetlenia danej opcji menu. Zwróć uwagę na znak "&". Powoduje on podkreślenie na ekranie literki, która następuje bezpośrednio za nim. Za tekstem umieszcza się numer identyfikacyjny ID elementu menu. ID jest numerem, który będzie użyty do identyfikacji elementu menu w wiadomości wysyłanej do procedury okna, gdy ten element menu zostanie wybrany przez użytkownika. Dlatego elementy menu nie mogą mieć powtarzających się numerów ID.

Opcje można pominąć. Dostępne są następujące opcje:

Możesz użyć jedną z podanych wyżej opcji lub połączyć kilka operatorem OR. Uważaj, razem nie można jedynie łączyć INACTIVE oraz GRAYED.

Polecenie POPUP posiada następującą składnię:


POPUP "&tekst" [,opcje] 
{ 
    [lista menu] 
}
 

Polecenie POPUP definiuje element menu, który przy wybraniu rozwija listę elementów menu w małym oknie. Lista ta może się składać z poleceń POPUP lub MENUITEM. Istnieje specjalne polecenie MENUITEM SEPARATOR, które rysuje poziomą linię w oknie z poleceniami menu.

Następnym krokiem po zakończeniu pracy nad skryptem zasobów jest odwołanie się do niego w twoim programie źródłowym. Możesz tego dokonać w dwóch różnych miejscach swojego programu.

.DATA
MenuName DB "FirstMenu",0
...
.CODE 
    ...
    mov wc.lpszMenuName, OFFSET MenuName 
    ...
.DATA
MenuName DB    "FirstMenu",0
hMenu    HMENU ?
    ...
.CODE
    ...
    INVOKE LoadMenu, hInst, OFFSET MenuName
    mov hMenu, eax
    INVOKE CreateWindowEx, NULL, OFFSET ClsName,\
                           OFFSET Caption, WS_OVERLAPPEDWINDOW,\
                           CW_USEDEFAULT, CW_USEDEFAULT,\
                           CW_USEDEFAULT, CW_USEDEFAULT,\
                           NULL,\
                           hMenu,\
                           hInst,\
                           NULL
    ...

 

Zatem możesz zapytać, czym różnią się te dwa sposoby?

Jeśli odwołujesz się do menu w strukturze WNDCLASSEX, to dane menu stanie się "standardowym" menu dla klasy okna. Każde okno tej klasy będzie wyposażone w to samo menu.

Jeśli chcesz, aby każde okno utworzone na podstawie tej samej klasy posiadało różne zestawy poleceń menu, to musisz wybrać drugi sposób. W tym przypadku każde okno utworzone przez funkcję CreateWindowEx z ustawionym parametrem uchwytu menu będzie posiadało menu przykrywające standardowe, określone w strukturze WNDCLASSEX.

Następnie zbadamy, w jaki sposób menu powiadamia procedurę okna o wyborze opcji dokonanym przez użytkownika.

Gdy użytkownik wybierze element menu, procedura okna otrzyma wiadomość WM_COMMAND. Młodsze słowo wParam zawiera numer ID wybranego przez użytkownika elementu menu.

Teraz mamy wystarczająco dużo wiadomości do utworzenia i używania menu. Do dzieła.

Przykład

Pierwszy przykład pokazuje sposób utworzenia i użycia menu przez określenie nazwy menu w klasie okna.


.386

.MODEL FLAT, STDCALL

OPTION CASEMAP:NONE

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

INCLUDE    \masm32\include\windows.inc
INCLUDE    \masm32\include\user32.inc
INCLUDE    \masm32\include\kernel32.inc
INCLUDELIB \masm32\lib\user32.lib
INCLUDELIB \masm32\lib\kernel32.lib

.DATA

ClassName      DB "SimpleWinClass",0
AppName        DB "Nasze Pierwsze Okno",0
MenuName       DB "FirstMenu",0
Test_string    DB "Wybrałeś element Test z menu",0
Hello_string   DB "Witaj, mój przyjacielu",0
Goodbye_string DB "Do zobaczenia, cześć",0

.DATA?

hInstance   HINSTANCE ?
CommandLine LPSTR     ?

.CONST

IDM_TEST    EQU 1
IDM_HELLO   EQU 2
IDM_GOODBYE EQU 3
IDM_EXIT    EQU 4

.CODE

start:
	INVOKE GetModuleHandle, NULL
	mov    hInstance, eax
	INVOKE GetCommandLine
	mov    CommandLine, eax
	INVOKE WinMain, hInstance, NULL, CommandLine, 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
    mov    wc.lpfnWndProc, OFFSET WndProc
    mov    wc.cbClsExtra, NULL
    mov    wc.cbWndExtra, NULL
    push   hInst
    pop    wc.hInstance
    mov    wc.hbrBackground, COLOR_WINDOW+1
    mov    wc.lpszMenuName, OFFSET MenuName
    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, NULL, ADDR ClassName, ADDR AppName,\
           WS_OVERLAPPEDWINDOW, CW_USEDEFAULT,\
           CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL,\
           hInst, NULL
    mov    hwnd, eax
    INVOKE ShowWindow, hwnd, SW_SHOWNORMAL
    INVOKE UpdateWindow, hwnd
    .WHILE TRUE
        INVOKE GetMessage, ADDR msg, NULL, 0, 0
        .BREAK .IF (!eax)
        INVOKE DispatchMessage, ADDR msg
    .ENDW
    mov  eax, msg.wParam
    ret
   
WinMain ENDP

WndProc PROC hWnd: HWND, uMsg: UINT, wParam: WPARAM, lParam: LPARAM

    .IF uMsg==WM_DESTROY
        INVOKE PostQuitMessage, NULL
    .ELSEIF uMsg==WM_COMMAND
        mov eax, wParam
        .IF ax==IDM_TEST
            INVOKE MessageBox, NULL, ADDR Test_string, OFFSET AppName, MB_OK
        .ELSEIF ax==IDM_HELLO
            INVOKE MessageBox, NULL, ADDR Hello_string, OFFSET AppName, MB_OK
        .ELSEIF ax==IDM_GOODBYE
            INVOKE MessageBox, NULL, ADDR Goodbye_string, OFFSET AppName, MB_OK
        .ELSE
            INVOKE DestroyWindow, hWnd
        .ENDIF
    .ELSE
        INVOKE DefWindowProc, hWnd, uMsg, wParam, lParam
        ret
    .ENDIF
    xor eax, eax
    ret
   
WndProc ENDP

END start

 

Zawartość pliku zasobów MENU.RC jest następująca:


#define IDM_TEST    1
#define IDM_HELLO   2
#define IDM_GOODBYE 3
#define IDM_EXIT    4

FirstMenu MENU
{
    POPUP "&Lista"
    {
        MENUITEM "&Przywitaj się", IDM_HELLO
        MENUITEM "P&ożegnaj się", IDM_GOODBYE
        MENUITEM SEPARATOR
        MENUITEM "&Koniec", IDM_EXIT
    }
    MENUITEM "&Test", IDM_TEST
}

Analiza

Przeanalizujmy najpierw plik zasobu:


#define IDM_TEST    1 /* równe IDM_TEST EQU 1 w pliku ASM */ 
#define IDM_HELLO   2 
#define IDM_GOODBYE 3 
#define IDM_EXIT    4


Powyższe wiersze definiują numery ID elementów menu używane przez nasz skrypt.

Możesz przypisywać dowolne wartości numerom ID pod warunkiem, iż w danym menu różnią się one od siebie.


FirstMenu MENU


Zdefiniuj swoje menu przy pomocy słowa kluczowego MENU.


    POPUP "&Lista"
    {
        MENUITEM "&Przywitaj się", IDM_HELLO
        MENUITEM "P&ożegnaj się", IDM_GOODBYE
        MENUITEM SEPARATOR
        MENUITEM "&Koniec", IDM_EXIT
    }


Zadeklaruj menu rozwijane z czterema elementami, jeden z nich to separator.


MENUITEM "&Test", IDM_TEST


Zadeklaruj nowy element w menu głównym.

W następnej kolejności zbadamy kod źródłowy programu.


MenuName       DB "FirstMenu",0
Test_string    DB "Wybrałeś element Test z menu",0
Hello_string   DB "Witaj, mój przyjacielu",0
Goodbye_string DB "Do zobaczenia, cześć",0


MenuName jest nazwą menu w pliku zasobów. Zwróć uwagę, iż w takim pliku można zdefiniować kilka list poleceń menu, zatem musisz określić, które z nich chcesz użyć. Pozostałe trzy wiersze definiują łańcuchy tekstowe, które będą wyświetlane przez okienka wiadomości wywoływane, gdy użytkownik dokona wyboru odpowiedniej opcji z menu.


IDM_TEST    EQU 1 ;numery ID poleceń menu
IDM_HELLO   EQU 2
IDM_GOODBYE EQU 3
IDM_EXIT    EQU 4


Zdefiniuj numery ID elementów menu do użytku w procedurze okna. Wartości te MUSZĄ być identyczne z wartościami w pliku zasobów.


.ELSEIF uMsg==WM_COMMAND
    mov eax, wParam
    .IF ax==IDM_TEST
        INVOKE MessageBox, NULL, ADDR Test_string, OFFSET AppName, MB_OK
    .ELSEIF ax==IDM_HELLO
        INVOKE MessageBox, NULL, ADDR Hello_string, OFFSET AppName, MB_OK
    .ELSEIF ax==IDM_GOODBYE
        INVOKE MessageBox, NULL, ADDR Goodbye_string, OFFSET AppName, MB_OK
    .ELSE
        INVOKE DestroyWindow, hWnd
.ENDIF


W procedurze okna przetwarzamy wiadomości WM_COMMAND. Gdy użytkownik wybiera element menu, do procedury okna zostaje wysłana wiadomość WM_COMMAND i w młodszym słowie parametru wParam system Windows umieszcza numer ID wybranego elementu menu. Zatem po pobraniu do rejestru eax wartości wParam porównujemy jego młodsze słowo ax z poprzednio zdefiniowanymi numerami ID elementów menu i podejmujemy odpowiednie działania. W pierwszych trzech przypadkach gdy użytkownik wybierze element menu Test, Przywitaj się lub Pożegnaj się, po prostu wyświetlamy krótki tekst w okienku wiadomości.

Jeśli użytkownik wybierze element menu Koniec, wywołujemy DestroyWindow z uchwytem naszego okna jako parametr, co spowoduje zamknięcie naszego okna.

Jak widać określenie nazwy menu w klasie okna jest nieskomplikowane. Jednakże możesz również zastosować alternatywną metodę załadowania menu do swojego okna. Nie pokażę tutaj całego kodu źródłowego tego przykładu, który w większości jest taki sam. Plik zasobów jest identyczny z opisanym powyżej w obu metodach. Zmiany w kodzie pokazuję poniżej:


.DATA?

hInstance   HINSTANCE ?
CommandLine LPSTR     ?
hMenu       HMENU     ?


Zdefiniuj zmienną typu HMENU do przechowywania uchwytu naszej listy poleceń menu.


    INVOKE LoadMenu, hInst, OFFSET MenuName
    mov hMenu, eax
    INVOKE CreateWindowEx, NULL, ADDR ClassName, ADDR AppName,\
                           WS_OVERLAPPEDWINDOW, CW_USEDEFAULT,\
                           CW_USEDEFAULT, CW_USEDEFAULT,\
                           CW_USEDEFAULT, NULL, hMenu,\
                           hInst, NULL


Przed wywołaniem CreateWindowEx wywołujemy funkcję LoadMenu z uchwytem do egzemplarza naszego programu oraz wskazaniem nazwy menu. Funkcja LoadMenu zwraca uchwyt do naszego menu w pliku zasobów, który z kolei przekazujemy do CreateWindowEx.

 

Dodatek w Pascalu

Ta sama aplikacja w Pascalu:

Aplikacja wykorzystuje identyczny plik zasobów, co program asemblerowy. W przypadku DevPascala plik ten należy umieścić w katalogu projektu pod nazwą rsrc.rc.

 

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

program Menu;

uses Windows;

const
  ClassName      = 'SimpleWinClass';
  AppName        = 'Nasze Pierwsze Okno';
  MenuName       = 'FirstMenu';
  Test_string    = 'Wybrałeś element Test z menu';
  Hello_string   = 'Witaj, mój przyjacielu';
  Goodbye_string = 'Do zobaczenia, cześć';
  IDM_TEST       = 1;
  IDM_HELLO      = 2;
  IDM_GOODBYE    = 3;
  IDM_EXIT       = 4;

function WndProc(h:HWND;uMsg:UINT;wP:WPARAM;lP:LPARAM) : longint;
begin
  case uMsg of
    WM_DESTROY : begin
                   PostQuitMessage(0);
                   WndProc := 0;
                 end;
    WM_COMMAND : begin
                   case (wP and $ffff) of
                     IDM_TEST    : MessageBox(0,Test_string,AppName,MB_OK);
                     IDM_HELLO   : MessageBox(0,Hello_string,AppName,MB_OK);
                     IDM_GOODBYE : MessageBox(0,Goodbye_string,AppName,MB_OK);
                     else DestroyWindow(h);
                   end;
                 end;
    else WndProc := DefWindowProc(h,uMsg,wP,lP);
  end;
end;

function WinMain(hI,hPI:HINST;CmdLine:LPSTR;cmdShow:longint) : longint;
var
  wc : WndClassEx;
  ms : msg;
  h  : HWnd;
begin
  wc.cbSize        := SizeOf(WndClassEx);
  wc.style         := CS_HREDRAW or CS_VREDRAW;
  wc.lpfnWndProc   := @WndProc;
  wc.cbClsExtra    := 0;
  wc.cbWndExtra    := 0;
  wc.hInstance     := hI;
  wc.hbrBackground := COLOR_WINDOW + 1;
  wc.lpszMenuName  := MenuName;
  wc.lpszClassName := ClassName;
  wc.hIcon         := LoadIcon(0,IDI_APPLICATION);
  wc.hIconSm       := wc.hIcon;
  wc.hCursor       := LoadCursor(0,IDC_ARROW);
  RegisterClassEx(wc);
  h := CreateWindowEx(0,ClassName,AppName,
                      WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,CW_USEDEFAULT,
                      CW_USEDEFAULT,CW_USEDEFAULT,0,0,hI,0);
  ShowWindow(h,CmdShow);
  UpdateWindow(h);
  while GetMessage(ms,0,0,0) do
  begin
    TranslateMessage(ms);
    DispatchMessage(ms);
  end;
  WinMain := ms.wParam;
end;

begin
  ExitProcess(WinMain(GetModuleHandle(0),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.


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

©2025 mgr Jerzy Wałaszek

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

Pytania proszę przesyłać na adres email: i-lo@eduinf.waw.pl

W artykułach serwisu są używane cookies. Jeśli nie chcesz ich otrzymywać,
zablokuj je w swojej przeglądarce.
Informacje dodatkowe