บทที่ 8 : เมนู

ข้อมูลจาก Iczelion

ในบทเรียนนี้เราจะมาเรียนรู้การติดตั้งเมนูเข้ากับวินโดว์ของเรา

หลักการ

เมนูเป็นส่วนประกอบหนึ่งที่สำคัญในวินโดว์ของคุณ เมนูจะแสดงรายการของงานที่โปรแกรมจะทำงานให้ผู้ใช้งานทราบ ผู้ใช้งานไม่จำเป็นต้องอ่านคู่มือการใช้งานโปรแกรมเพื่อที่จะใช้งานมัน สามารถที่จะใช้เมนูเพื่อมองภาพรวมของประสิทธิภาพของโปรแกรมและสามารถเริ่มใช้งานได้ทันที เพราะว่าเมนูเป็นเครื่องมือที่ช่วยให้ผู้ใช้งานเริ่มทำงานได้อย่างรวดเร็ว ดังนั้นคุณควรจะต้องออกแบบให้มีรูปแบบเป็นมาตรฐาน เช่น จะมีเมนูเริ่มต้นดังนี้ คือ File Edit ......Help โดยระหว่าง Edit กับ Help จะเป็นเมนูเกี่ยวกับการทำงานของโปรแกรมนั้นๆ รายการเมนูที่เปิดไดอะล็อกก็จะมีเครื่องหมาย "..." ต่อท้ายซึ่งเรียกว่า ellipsis เพื่อแสดงให้ทราบว่าจะมีหน้าต่างไดอะล็อกแสดงขึ้นมาก่อน เป็นต้น

เมนูเป็นรีซอร์สประเภทหนึ่ง (ขอใช้คำว่ารีซอร์สซึ่งมาจาก Resource แทนคำว่าทรัพยากร เพราะว่ากลัวไปจะสับสนกับทรัพยากรระบบจำพวกหน่วยความจำประเภทนั้น ซึ่งในที่นี้หมายถึงส่วนประกอบต่างๆ ของโปรแกรมซึ่งสร้างและคอมไพล์ต่างหากจากตัวโค้ดของโปรแกรม) รีซอร์สมีหลายชนิด เช่น dialog box, string table, icon, bitmap, menu และอื่นๆ รีซอร์สเหล่านี้จะถูกเก็บอยู่ในไฟล์รีซอร์สต่างหาก โดยปกติจะมีนามสกุลของไฟล์เป็น .rc และจะลิงค์รวมกับซอสโค้ดเพื่อสร้างไฟล์ executable ในภายหลัง

คุณสามารถเขียนไฟล์รีซอร์สได้ด้วยเท็กซ์เอดิเตอร์ธรรมดา โดยจะประกอบไปด้วยคำสั่งเพื่อกำหนดรูปแบบและคุณลักษณะต่างๆ ของรีซอร์สที่โปรแกรมนั้นๆ ต้องการ แม้ว่าคุณจะสามารถเขียนมันด้วยเท็กซ์เอดิเตอร์ได้แต่ว่ามันก็ไม่สะดวกนัก ควรใช้รีซอร์สเอดิเตอร์ที่มาพรัอมกับโปรแกรมประเภทวิชวลทั้งหลายเช่น Visual C++, Borland C++ จะสะดวกกว่าเพราะว่าคุณจะสามารถเห็นรูปร่างในระหว่างที่สร้างได้

รูปแบบของเมนูรีซอร์สมีดังนี้

ชื่อเมนู MENU
{
[รายการเมนู]
}

รูปแบบจะคล้ายกับตัวแปรโครงสร้างในภาษาซี โดย "ชื่อเมนู" จะเป็นชื่อที่เราจะใช้อ้างถึงในโปรแกรม ตามด้วย MENU ซึ่งเป็นคีย์เวิร์ดสำหรับบอกให้ทราบว่าเป็นเมนู ส่วนรายการเมนูต่างๆ จะอยู่ภายในบล็อกของเครื่องหมายปีกกา ยังมีอีกรูปแบบหนึ่งคือการใช้คำสั่ง BEGIN และ END แทนเครื่องหมายปีกกาเปิดและปีกกาปิดซึ่งจะมีโครงสร้างคล้ายภาษาปาสคาล และรีซอร์สเอดิเตอร์ของโปรแกรมส่วนใหญ่จะใช้รูปแบบหลังนี้

รายการเมนูสามารถเป็นได้ทั้ง MENUITEM หรือ POPUP ก็ได้

MENUITEM แสดงให้ทราบว่าเมนูนั้นจะไม่แสดงป๊อปอัพเมนูขึ้นมาเมื่อถูกเรียก มีรูปแบบดังนี้

MENUITEM "&text", ID [,options]

มันจะเริ่มด้วยคีย์เวิร์ด MENUITEM ตามด้วยข้อความที่ต้องการแสดงให้เห็น เครื่องหมาย & แสดงให้ทราบว่าตัวอักษรที่ตามหลังเมื่อแสดงบนเมนูจะมีเส้นใต้ขีดอยู่ จากนั้นจึงเป็น ID ซึ่งเป็นตัวเลขเพื่อกำหนดเลขหมายประจำตัวของเมนูนี้เพื่อใช้สำหรับการส่งเมสเสจให้วินโดว์โพรซิเยอร์เมื่อเมนูนี้ถูกเลือก ดังนั้นหมายเลขนี้จะต้องไม่ซ้ำกันในแต่ละรายการของเมนู

options จะกำหนดหรือไม่ก็ได้ รูปแบบของ options มีดังนี้

คุณสามารถใช้ option ข้างบนนี้ได้หนึ่งรายการหรือถ้าต้องการใช้หลายรายการให้รวมมันดัวย "OR" แต่ INACTIVE และ GRAYED ไม่สามารถใช้ร่วมกันได้

POPUP มีรูปแบบดังนี้


 POPUP "&text" [,options] 
{ 
  [รายการเมนู] 
}

POPUP ใช้กำหนดให้รายการเมนูนั้นเมื่อถูกเลือกจะมีรายการของเมนูในรูปแบบของหน้าต่างป็อปอัพห้อยลงมา รายการเมนูอาจจะเป็น MENUITEM หรือ POPUP ก็ได้ มีรูปแบบพิเศษของ MENUITEM คือ MENUITEM SEPARATOR ซึ่งใช้แสดงเส้นขีดขวางในหน้าต่างป็อปอัพ

ขั้นตอนต่อไปหลังจากที่คุณเขียนเมนูสคริปเสร็จแล้วคือการอ้างถึงตัวเมนูภายในโปรแกรม คุณสามารถทำได้ 2 ที่ในโปรแกรมของคุณ คือ

ในเมมเบอร์ lpszMenuName ของ WNDCLASSEX เช่นถ้าคุณมีเมนูชื่อ "FirstMenu" คุณก็ใส่ชื่อของเมนูคุณเข้าไปในวินโดว์ดังนี้


.DATA 
MenuName  db "FirstMenu",0 
........................... 
........................... 
.CODE 
........................... 
mov   wc.lpszMenuName, OFFSET MenuName 
........................... 

อีกที่หนึ่งที่คุณสามารถใส่ได้คือในพารามิเตอร์ menu handle ของฟังก์ชั่น CreateWindowEx โดยจะต้องเรียกฟังก์ชั่น LoadMenu โดยใส่พารามิเตอร์ของฟังก์ชั่นคือ instance ของโปรแกรมและชื่อของเมนูตามลำดับ ฟังก์ชั่นก็จะคืนค่าของ menu handle มาที่รีจิสเตอร์ eax แล้วจึงนำไปใส่ในฟังก์ชั่น CreateWindowEx ดังนี้


.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\ 
........................... 

คุณคงอยากรู้ว่าแล้วสองวิธีนี้มันแตกต่างกันอย่างไร

ถ้าคุณใส่ไว้ใน WNDCLASSEX เมนูนี้ก็จะเป็นดีฟอลท์ของวินโดว์คลาส วินโดว์ทุกวินโดว์ของคลาสนี้จะใช้เมนูตัวเดียวกัน ถ้าคุณต้องการให้แต่ละวินโดว์ที่มาจากคลาสเดียวกันแต่มีเมนูต่างกันคุณก็ต้องเลือกวิธีที่สอง

ต่อไปเราจะมาพิจารณากันดูว่าเมนูจะแจ้งให้วินโดว์โพรซิเยอร์รู้ได้อย่างไรว่าผู้ใช้งานได้เลือกรายการของเมนู

เมื่อผู้ใช้งานเลือกรายการเมนู วินโดว์โพรซิเยอร์จะได้รับเมสเสจWM_COMMAND โดย low word ของ wParam จะเป็นค่าของ menu ID ของรายการเมนูที่ถูกเลือก

Example:

ตัวอย่างแรกนี้จะแสดงวิธีสร้างและใช้เมนูโดยการกำหนดชื่อของเมนูในวินโดว์คลาส

.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 "Our First Window",0 MenuName db "FirstMenu",0 ; ชื่อของเมนูที่อยู่ในไฟล์รีซอร์ส Test_string db "You selected Test menu item",0 Hello_string db "Hello, my friend",0 Goodbye_string db "See you again, bye",0 .data? hInstance HINSTANCE ? CommandLine LPSTR ? .const IDM_TEST equ 1 ; Menu IDs 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 ************************************************************************************************************** Menu.rc *************************************************************************************************************** #define IDM_TEST 1 #define IDM_HELLO 2 #define IDM_GOODBYE 3 #define IDM_EXIT 4 FirstMenu MENU { POPUP "&PopUp" { MENUITEM "&Say Hello",IDM_HELLO MENUITEM "Say &GoodBye", IDM_GOODBYE MENUITEM SEPARATOR MENUITEM "E&xit",IDM_EXIT } MENUITEM "&Test", IDM_TEST }

วิเคราะห์

เรามาพิจารณาที่ไฟล์รีซอร์สกันก่อน


#define IDM_TEST 1                /* ซึ่งก็คือ IDM_TEST equ 1*/ 
#define IDM_HELLO 2 
#define IDM_GOODBYE 3 
#define IDM_EXIT 4 

บรรทัดข้างบนนี้จะกำหนด menu IDs ที่จะใช้ภายในเมนูสคริป คุณสามารถใส่ค่าอะไรก็ได้ตราบใดที่ค่าเหล่านี้ไม่ซ้ำกัน


FirstMenu MENU 

ประกาศเมนูของคุณด้วยคีย์เวิร์ด MENU


 POPUP "&PopUp" 
        { 
         MENUITEM "&Say Hello",IDM_HELLO 
         MENUITEM "Say &GoodBye", IDM_GOODBYE 
         MENUITEM SEPARATOR 
         MENUITEM "E&xit",IDM_EXIT 
        } 

กำหนดให้เป็นป๊อปอัพเมนูที่มี 4 รายการ รายการที่ 3 จะเป็นเส้นขวางในเมนู


MENUITEM "&Test", IDM_TEST 

สร้างเมนูอีกรายการหนึ่งในเมนูหลัก

ต่อไปลองมาพิจารณาซอสโค้ดกัน


MenuName db "FirstMenu",0                ; ชื่อเมนูของเราที่อยู่ในไฟล์รีซอร์ส
Test_string db "You selected Test menu item",0 
Hello_string db "Hello, my friend",0 
Goodbye_string db "See you again, bye",0 

MenuName คือชื่อของเมนูที่อยู่ในไฟล์รีซอร์ส คุณสามารถสร้างเมนูได้หลายเมนูในไฟล์รีซอร์สเดียวกันดังนั้นจึงต้องกำหนดว่าเมนูอันไหนที่คุณต้องการใช้ สามบรรทัดที่เหลือจะกำหนดข้อความที่จะแสดงใน message box ที่จะถูกแสดงเมื่อเมนูแต่ละรายการถูกเลือกโดยผู้ใช้งาน


IDM_TEST equ 1                    ; Menu IDs 
IDM_HELLO equ 2 
IDM_GOODBYE equ 3 
IDM_EXIT equ 4 

กำหนด menu ID ที่จะใช้ในวินโดว์โพรซิเยอร์ ค่าของมันต้องเหมือนกับที่กำหนดในไฟล์รีซอร์ส

.

.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 

เราจะจัดการเมสเสจ WM_COMMAND ในวินโดว์โพรซิเยอร์ เมื่อผู้ใช้งานเลือกรายการเมนู menu ID นั้นก็จะถูกส่งไปให้วินโดว์โพรซิเยอร์โดยอยู่ใน low word ของ wParam ที่มาพร้อมกับเมสเสจ WM_COMMAND เมื่อเราเก็บค่าของ wParam ไว้ใน eax เราจึงเปรียบเทียบค่า menu ID ที่เรากำหนดไว้ตอนต้นโปรแกรมกับ ax ( ax นะครับ เพราะว่าเราต้องการเช็คที่ low word ) โปรแกรมจะแสดงเมสเสจบ๊อกซ์ตามเมนูที่เราเลือกในสามเมนูแรก

ถ้าผู้ใช้งานเลือกเมนู Exit ก็จะเป็นการปิดหน้าต่างของเราเพราะว่าเราส่งค่า hWnd ซึ่งเป็นวินโดว์แฮนเดิ้ลของเราไปให้ฟังก์ชั่น DestroyWindow (สังเกตว่าจะอยู่ในส่วนของ .ELSE เพราะว่าเราไม่มีเมนูอื่นอีกแล้ว ดังนั้นเมนูที่เหลือก็ต้องเป็นของ Exit อยู่แล้ว)

คุณคงจะเห็นแล้วว่าการกำหนดชื่อของเมนูในวินโดว์คลาสจะทำได้ง่าย แต่คุณก็สามารถเลือกใช้วิธีอื่นได้เพื่อที่จะให้วินโดว์ของคุณมีเมนูใช้งาน ซึ่งในที่นี้จะไม่แสดงซอร์สโค้ดทั้งหมด จะแสดงเฉพาะส่วนที่เปลี่ยนแแปลงดังตัวอย่างข้างล่างนี้ ไฟล์รีซอร์สใช้ของเดิมไม่จำเป็นต้องแก้ไข


.data? 
hInstance HINSTANCE ? 
CommandLine LPSTR ? 
hMenu HMENU ?                    ; แฮนเดิ้ลของเมนูของเรา

กำหนดตัวแปร hMenu เป็นประเภท HMENU เพื่อเก็บค่าเมนูแฮนเดิ้ลของเรา


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 

ก่อนที่จะเรียกฟังก์ชั่น CreateWindowEx เราจะใช้ฟังก์ชั่น LoadMenu ซึ่งมี instance handle และพอยเตอร์ที่ชี้ไปยังชื่อของเมนูของเราเป็นพารามิเตอร์ของฟังก์ชั่นตามลำดับเพื่อหาค่าแฮนเดิ้ลของเมนูที่อยู่ในไฟล์รีซอร์ส จึงค่อยส่งเมนูแฮนเดิ้ลนี้ไปยังฟังก์ชั่น CreateWindowEx


กลับหน้าแรก