使用功能表

本節描述下列工作:

使用 Menu-Template 資源

您通常會在應用程式中加入功能表,方法是建立功能表範本資源,然後在運行時間載入功能表。 本節描述功能表範本的格式,並說明如何載入功能表範本資源,並在應用程式中使用它。 如需建立功能表範本資源的相關資訊,請參閱開發工具所附的說明文件。

擴充 Menu-Template 格式

擴展的選單範本格式支援更多功能。 如同標準功能表範本資源,擴充功能表範本資源具有 RT_MENU 資源類型。 系統會依版本號碼區分這兩種資源格式,這是資源標頭的第一個成員。

擴充功能表範本包含一個 MENUEX_TEMPLATE_HEADER 結構,後面接著多個 MENUEX_TEMPLATE_ITEM 項目定義結構。

舊 Menu-Template 格式

舊功能表範本(Microsoft Windows NT 3.51 和更早版本)會定義功能表,但不支援新的功能表功能。 舊的功能表範本資源具有 RT_MENU 資源類型。

舊的功能表範本包含一個 MENUITEMTEMPLATEHEADER 結構,後面接著一或多個 MENUITEMTEMPLATE 結構。

載入 Menu-Template 資源

若要載入功能表範本資源,請使用 LoadMenu 函式,指定包含資源和功能表範本標識碼之模組的句柄。 LoadMenu 函式會傳回功能表句柄,可用來將功能表指派給視窗。 此視窗會變成功能表的擁有者視窗,接收功能表所產生的所有訊息。

若要從記憶體中的功能表範本建立功能表,請使用 LoadMenuIndirect 函式,。 如果您的應用程式動態產生功能表範本,這會很有用。

若要將功能表指派給視窗,請使用 SetMenu 函式,或在建立視窗時,在 CreateWindowEx 函式的 hMenu 參數中指定功能表的句柄。 您可以將功能表指派給視窗的另一種方式,是在註冊窗口類別時指定功能表範本;範本會將指定的功能表識別為該視窗類別的類別功能表。

若要讓系統自動將特定功能表指派給視窗,請在註冊視窗的 類別時指定功能表的範本。 範本會將指定的功能表識別為該視窗類別的類別功能表。 然後,當您建立指定類別的視窗時,系統會自動將指定的功能表指派給視窗。

您無法將選單指派給作為子視窗的視窗。

若要建立類別選單,請包含選單範本資源的識別碼作為 lpszMenuName 這個 WNDCLASS 結構的成員,然後將結構的指標傳遞至 RegisterClass 函式。

建立課程菜單

下列範例示範如何建立應用程式的類別功能表、建立使用類別功能表的視窗,以及在視窗程式中處理功能表命令。

以下是應用程式標頭檔的相關部份:

// Menu-template resource identifier 
 
#define IDM_MYMENURESOURCE   3

以下是應用程式本身的相關部份:

HINSTANCE hinst; 
 
int APIENTRY WinMain(HINSTANCE hinstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) 
{ 
    MSG msg = { };  // message 
    WNDCLASS wc;    // windowclass data 
    HWND hwnd;      // handle to the main window 
 
    // Create the window class for the main window. Specify 
    // the identifier of the menu-template resource as the 
    // lpszMenuName member of the WNDCLASS structure to create 
    // the class menu. 
 
    wc.style = 0; 
    wc.lpfnWndProc = (WNDPROC) MainWndProc; 
    wc.cbClsExtra = 0; 
    wc.cbWndExtra = 0; 
    wc.hInstance = hinstance; 
    wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); 
    wc.hCursor = LoadCursor(NULL, IDC_ARROW); 
    wc.hbrBackground = GetStockObject(WHITE_BRUSH); 
    wc.lpszMenuName =  MAKEINTRESOURCE(IDM_MYMENURESOURCE); 
    wc.lpszClassName = "MainWClass"; 
 
    if (!RegisterClass(&wc)) 
        return FALSE; 
 
    hinst = hinstance; 
 
    // Create the main window. Set the hmenu parameter to NULL so 
    // that the system uses the class menu for the window. 
 
    hwnd = CreateWindow("MainWClass", "Sample Application", 
        WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 
        CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hinstance, 
        NULL); 
 
    if (hwnd == NULL) 
        return FALSE; 
 
    // Make the window visible and send a WM_PAINT message to the 
    // window procedure. 
 
    ShowWindow(hwnd, nCmdShow); 
    UpdateWindow(hwnd); 
 
    // Start the main message loop. 
 
    while (GetMessage(&msg, NULL, 0, 0)) 
    { 
        TranslateMessage(&msg); 
        DispatchMessage(&msg); 
    } 
    return msg.wParam; 
        UNREFERENCED_PARAMETER(hPrevInstance); 
} 
 
 
LRESULT APIENTRY MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) 
{ 
 
    switch (uMsg) 
    { 
        // Process other window messages. 
 
        case WM_COMMAND: 
 
            // Test for the identifier of a command item. 
 
            switch(LOWORD(wParam)) 
            { 
                case IDM_FI_OPEN: 
                    DoFileOpen();   // application-defined 
                    break; 
 
                case IDM_FI_CLOSE: 
                    DoFileClose();  // application-defined 
                    break; 
                // Process other menu commands. 
 
                default: 
                    break; 
 
            } 
            return 0; 
 
        // Process other window messages. 
 
        default: 
            return DefWindowProc(hwnd, uMsg, wParam, lParam); 
    } 
    return NULL; 
} 

建立快捷方式功能表

若要在應用程式中使用快捷方式功能表,請將其句柄傳遞至 TrackPopupMenuEx 函式。 應用程式通常會在視窗程式中呼叫 TrackPopupMenuEx,以回應使用者產生的訊息,例如 WM_LBUTTONDOWN 或 WM_KEYDOWN。

除了快顯功能表控點之外,TrackPopupMenuEx 需要您指定擁有者視窗的句柄、快顯功能表的位置(在螢幕座標中),以及使用者可用來選擇項目的滑鼠鍵。

仍然支援較舊的 TrackPopupMenu 函式,但新的應用程式應該使用 TrackPopupMenuEx 函式。 TrackPopupMenuEx 函式需要與 TrackPopupMenu相同的參數,但還允許您指定不應被功能表遮蔽的畫面部分。 在處理 WM_CONTEXTMENU 訊息時,應用程式通常會在視窗程式中呼叫這些函式。

您可以藉由提供 x 和 y 座標以及 TPM_CENTERALIGN、TPM_LEFTALIGN或 TPM_RIGHTALIGN 旗標,來指定快捷方式功能表的位置。 旗標會指定快捷方式功能表相對於 x 和 y 座標的位置。

您應該允許使用者使用相同的滑鼠按鈕來從快顯功能表中選擇項目,而該滑鼠按鈕用於顯示該功能表。 若要這樣做,請指定 TPM_LEFTBUTTON 或 TPM_RIGHTBUTTON 旗標,以指出用戶可用來選擇功能表項的滑鼠按鈕。

處理WM_CONTEXTMENU訊息

當應用程式的視窗程式將 WM_RBUTTONUP 或 WM_NCRBUTTONUP 訊息傳遞至 DefWindowProc 函式時,就會產生 WM_CONTEXTMENU 訊息。 應用程式可以處理此訊息,以顯示適合其畫面特定部分的快捷方式功能表。 如果應用程式未顯示快捷方式功能表,它應該會將訊息傳遞至 DefWindowProc 以進行預設處理。

以下是 WM_CONTEXTMENU 訊息處理的範例,因為它可能會出現在應用程式的視窗程式中。 放開滑鼠右鍵時,lParam 參數的低序和高序字組會指定滑鼠的螢幕座標(請注意,這些座標可以在具有多個監視器的系統上取得負值)。 應用程式定義的 OnContextMenu 函式如果顯示快捷功能表則傳回 TRUE,如果未顯示則傳回 FALSE。

case WM_CONTEXTMENU: 
    if (!OnContextMenu(hwnd, GET_X_LPARAM(lParam),
              GET_Y_LPARAM(lParam))) 
        return DefWindowProc(hwnd, uMsg, wParam, lParam); 
    break; 

如果指定的滑鼠位置位於視窗的工作區內,下列應用程式定義的 OnContextMenu 函式會顯示快捷方式功能表。 更高級的功能可能會根據指定的客戶區域的不同部分,顯示多個不同選單中的一個。 為了實際顯示快捷方式功能表,此範例會呼叫名為 DisplayContextMenu 的應用程式定義函數。 如需此函式的描述,請參閱 顯示快捷方式選單。

BOOL WINAPI OnContextMenu(HWND hwnd, int x, int y) 
{ 
    RECT rc;                    // client area of window 
    POINT pt = { x, y };        // location of mouse click 
 
    // Get the bounding rectangle of the client area. 
 
    GetClientRect(hwnd, &rc); 
 
    // Convert the mouse position to client coordinates. 
 
    ScreenToClient(hwnd, &pt); 
 
    // If the position is in the client area, display a  
    // shortcut menu. 
 
    if (PtInRect(&rc, pt)) 
    { 
        ClientToScreen(hwnd, &pt); 
        DisplayContextMenu(hwnd, pt); 
        return TRUE; 
    } 
 
    // Return FALSE if no menu is displayed. 
 
    return FALSE; 
} 

建立捷徑 Font-Attributes 選單

本節中的範例包含應用程式的程序代碼部分,可建立及顯示快捷方式功能表,讓使用者設定字型和字型屬性。 每當使用者按下滑鼠左鍵時,應用程式就會在其主視窗的工作區中顯示功能表。

以下是應用程式資源定義檔案中提供的快捷方式功能表範本。

PopupMenu MENU 
BEGIN 
  POPUP "Dummy Popup" 
    BEGIN 
      POPUP "Fonts" 
        BEGIN 
          MENUITEM "Courier",     IDM_FONT_COURIER 
          MENUITEM "Times Roman", IDM_FONT_TMSRMN 
          MENUITEM "Swiss",       IDM_FONT_SWISS 
          MENUITEM "Helvetica",   IDM_FONT_HELV 
          MENUITEM "Old English", IDM_FONT_OLDENG 
        END 
      POPUP "Sizes" 
        BEGIN 
          MENUITEM "7",  IDM_SIZE_7 
          MENUITEM "8",  IDM_SIZE_8 
          MENUITEM "9",  IDM_SIZE_9 
          MENUITEM "10", IDM_SIZE_10 
          MENUITEM "11", IDM_SIZE_11 
          MENUITEM "12", IDM_SIZE_12 
          MENUITEM "14", IDM_SIZE_14 
        END 
      POPUP "Styles" 
        BEGIN 
          MENUITEM "Bold",        IDM_STYLE_BOLD 
          MENUITEM "Italic",      IDM_STYLE_ITALIC 
          MENUITEM "Strike Out",  IDM_STYLE_SO 
          MENUITEM "Superscript", IDM_STYLE_SUPER 
          MENUITEM "Subscript",   IDM_STYLE_SUB 
        END 
    END 
 
END 

下列範例提供用來建立及顯示快捷方式功能表的視窗程式和支援函式。

LRESULT APIENTRY MenuWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) 
{ 
    RECT rc;    // client area             
    POINT pt;   // location of mouse click  
 
    switch (uMsg) 
    { 
        case WM_LBUTTONDOWN: 
 
            // Get the bounding rectangle of the client area. 
 
            GetClientRect(hwnd, (LPRECT) &rc); 
 
            // Get the client coordinates for the mouse click.  
 
            pt.x = GET_X_LPARAM(lParam); 
            pt.y = GET_Y_LPARAM(lParam); 
 
            // If the mouse click took place inside the client 
            // area, execute the application-defined function 
            // that displays the shortcut menu. 
 
            if (PtInRect((LPRECT) &rc, pt)) 
                HandlePopupMenu(hwnd, pt); 
            break; 
        // Process other window messages.  
 
        default: 
            return DefWindowProc(hwnd, uMsg, wParam, lParam); 
    } 
    return NULL; 
} 
 
 
VOID APIENTRY HandlePopupMenu(HWND hwnd, POINT pt) 
{ 
    HMENU hmenu;            // menu template          
    HMENU hmenuTrackPopup;  // shortcut menu   
 
    //  Load the menu template containing the shortcut menu from the 
    //  application's resources. 
 
    hmenu = LoadMenu(hinst, "PopupMenu"); 
    if (hmenu == NULL) 
        return; 
 
    // Get the first shortcut menu in the menu template. This is the 
    // menu that TrackPopupMenu displays. 
 
    hmenuTrackPopup = GetSubMenu(hmenu, 0); 
 
    // TrackPopup uses screen coordinates, so convert the 
    // coordinates of the mouse click to screen coordinates. 
 
    ClientToScreen(hwnd, (LPPOINT) &pt); 
 
    // Draw and track the shortcut menu.  
 
    TrackPopupMenu(hmenuTrackPopup, TPM_LEFTALIGN | TPM_LEFTBUTTON, 
        pt.x, pt.y, 0, hwnd, NULL); 
 
    // Destroy the menu. 
 
    DestroyMenu(hmenu); 
} 

顯示捷徑選單

下列範例所示的函式會顯示快捷方式功能表。

應用程式包含字串 「ShortcutExample」 所識別的功能表資源。功能表欄只會包含功能表名稱。 應用程式會使用 TrackPopupMenu 函式來顯示與此功能表項相關聯的功能表。 (功能表欄本身不會顯示,因為 TrackPopupMenu 需要一個功能表、子功能表或快捷方式功能表的句柄。)

VOID APIENTRY DisplayContextMenu(HWND hwnd, POINT pt) 
{ 
    HMENU hmenu;            // top-level menu 
    HMENU hmenuTrackPopup;  // shortcut menu 
 
    // Load the menu resource. 
 
    if ((hmenu = LoadMenu(hinst, "ShortcutExample")) == NULL) 
        return; 
 
    // TrackPopupMenu cannot display the menu bar so get 
    // a handle to the first shortcut menu. 
 
    hmenuTrackPopup = GetSubMenu(hmenu, 0); 
 
    // Display the shortcut menu. Track the right mouse 
    // button. 
 
    TrackPopupMenu(hmenuTrackPopup, 
            TPM_LEFTALIGN | TPM_RIGHTBUTTON, 
            pt.x, pt.y, 0, hwnd, NULL); 
 
    // Destroy the menu. 
 
    DestroyMenu(hmenu); 
} 

使用 Menu-Item 位圖

系統可以使用位圖,而不是文字字串來顯示功能表項。 若要使用位圖,您必須設定選單項目的 MIIM_BITMAP 旗標,並在 MENUITEMINFO 結構的 hbmpItem 成員中指定系統應該顯示於選單項目的位圖句柄。 本節說明如何使用功能表項的點陣圖。

設定點陣圖類型旗標

MIIM_BITMAP 或 MF_BITMAP 旗標會指示系統使用點陣圖,而不是顯示功能表項的文字字串。 必須在執行時設定功能表項的 MIIM_BITMAP 或 MF_BITMAP 旗標;您無法在資源定義檔案中設定它。

針對新的應用程式,您可以使用 SetMenuItemInfo 或 InsertMenuItem 函式來設定 MIIM_BITMAP 類型旗標。 若要將選單項目從文字項目變更為位圖項目,請使用 SetMenuItemInfo。 若要將新的位圖項目新增至功能表,請使用 InsertMenuItem 函數。

針對舊版系統撰寫的應用程式可以繼續使用 ModifyMenu、InsertMenu或 AppendMenu 函式來設定 MF_BITMAP 旗標。 若要將選單項目從文字字串項目更改為點陣圖項目,請使用 ModifyMenu。 若要將新的位圖項目加入到功能表中,請使用 MF_BITMAP 標誌與 InsertMenu 或 AppendMenu 函式一起。

建立位圖

當您為功能表項設定 MIIM_BITMAP 或 MF_BITMAP 類型旗標時,您還必須指定系統應該顯示在功能表項上的點陣圖控制代碼。 您可以將點陣圖提供為位圖資源,或在運行時間建立位圖。 如果您使用位圖資源,您可以使用 LoadBitmap 函式來載入點陣圖並取得其句柄。

若要在運行時間建立位圖,請使用 Windows Graphics Device Interface (GDI) 函式。 GDI 提供數種方式在運行時間建立位圖,但開發人員通常會使用下列方法:

  1. 使用 CreateCompatibleDC 函式,建立與應用程式主視窗所使用之裝置內容相容的裝置內容。
  2. 使用 CreateCompatibleBitmap 函式來建立與應用程式主視窗相容的點陣圖,或使用 CreateBitmap 函式來建立單色位圖。
  3. 使用 SelectObject 函式,將位圖選取到相容的設備上下文中。
  4. 使用 GDI 繪圖函式,例如 Ellipse 和 LineTo,將影像繪製到位圖中。

如需詳細資訊,請參閱 位陣圖。

將線條和圖形新增至功能表

下列程式代碼範例示範如何建立包含選單項位圖的選單。 它會建立兩個功能表。 第一個是圖表功能表,其中包含三個選單項目對應的點陣圖:餅圖、折線圖和條形圖。 此範例示範如何從應用程式的資源檔載入這些點陣圖,然後使用 CreatePopupMenu 和 AppendMenu 函式來建立功能表和功能表項。

第二個選單是線條選單。 其中包含位圖,其中顯示系統中預先定義的畫筆所提供的線條樣式。 線條樣式位圖是使用 GDI 函式在執行時建立的。

以下是應用程式資源定義檔案中位圖資源的定義。

PIE BITMAP pie.bmp
LINE BITMAP line.bmp
BAR BITMAP bar.bmp

以下是應用程式頭檔的相關部分。

// Menu-item identifiers 
 
#define IDM_SOLID       PS_SOLID 
#define IDM_DASH        PS_DASH 
#define IDM_DASHDOT     PS_DASHDOT 
#define IDM_DASHDOTDOT  PS_DASHDOTDOT 
 
#define IDM_PIE  1 
#define IDM_LINE 2 
#define IDM_BAR  3 
 
// Line-type flags  
 
#define SOLID       0 
#define DOT         1 
#define DASH        2 
#define DASHDOT     3 
#define DASHDOTDOT  4 
 
// Count of pens  
 
#define CPENS 5 
 
// Chart-type flags  
 
#define PIE  1 
#define LINE 2 
#define BAR  3 
 
// Function prototypes  
 
LRESULT APIENTRY MainWndProc(HWND, UINT, WPARAM, LPARAM); 
VOID MakeChartMenu(HWND); 
VOID MakeLineMenu(HWND, HPEN, HBITMAP); 

下列範例示範如何在應用程式中建立功能表和功能表項點陣圖。

LRESULT APIENTRY MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) 
{ 
 
    static HPEN hpen[CPENS]; 
    static HBITMAP hbmp[CPENS]; 
    int i; 
 
    switch (uMsg) 
    { 
        case WM_CREATE: 
 
            // Create the Chart and Line menus.  
 
            MakeChartMenu(hwnd); 
            MakeLineMenu(hwnd, hpen, hbmp); 
            return 0; 
 
        // Process other window messages. 
 
        case WM_DESTROY: 
 
            for (i = 0; i < CPENS; i++) 
            { 
                DeleteObject(hbmp[i]); 
                DeleteObject(hpen[i]); 
            } 
 
            PostQuitMessage(0); 
            break; 
 
        default: 
            return DefWindowProc(hwnd, uMsg, wParam, lParam); 
    } 
    return NULL; 
} 
 
VOID MakeChartMenu(HWND hwnd) 
{ 
    HBITMAP hbmpPie;    // handle to pie chart bitmap   
    HBITMAP hbmpLine;   // handle to line chart bitmap  
    HBITMAP hbmpBar;    // handle to bar chart bitmap   
    HMENU hmenuMain;    // handle to main menu          
    HMENU hmenuChart;   // handle to Chart menu  
 
    // Load the pie, line, and bar chart bitmaps from the 
    // resource-definition file. 
 
    hbmpPie = LoadBitmap(hinst, MAKEINTRESOURCE(PIE)); 
    hbmpLine = LoadBitmap(hinst, MAKEINTRESOURCE(LINE)); 
    hbmpBar = LoadBitmap(hinst, MAKEINTRESOURCE(BAR)); 
 
    // Create the Chart menu and add it to the menu bar. 
    // Append the Pie, Line, and Bar menu items to the Chart 
    // menu. 
 
    hmenuMain = GetMenu(hwnd); 
    hmenuChart = CreatePopupMenu(); 
    AppendMenu(hmenuMain, MF_STRING | MF_POPUP, (UINT) hmenuChart, 
        "Chart"); 
    AppendMenu(hmenuChart, MF_BITMAP, IDM_PIE, (LPCTSTR) hbmpPie); 
    AppendMenu(hmenuChart, MF_BITMAP, IDM_LINE, 
        (LPCTSTR) hbmpLine); 
    AppendMenu(hmenuChart, MF_BITMAP, IDM_BAR, (LPCTSTR) hbmpBar); 
 
    return; 
} 
 
VOID MakeLineMenu(HWND hwnd, HPEN phpen, HBITMAP phbmp) 
{ 
    HMENU hmenuLines;       // handle to Lines menu      
    HMENU hmenu;            // handle to main menu              
    COLORREF crMenuClr;     // menu-item background color       
    HBRUSH hbrBackground;   // handle to background brush       
    HBRUSH hbrOld;          // handle to previous brush         
    WORD wLineX;            // width of line bitmaps            
    WORD wLineY;            // height of line bitmaps           
    HDC hdcMain;            // handle to main window's DC       
    HDC hdcLines;           // handle to compatible DC          
    HBITMAP hbmpOld;        // handle to previous bitmap        
    int i;                  // loop counter                     
 
    // Create the Lines menu. Add it to the menu bar.  
 
    hmenu = GetMenu(hwnd); 
    hmenuLines = CreatePopupMenu(); 
    AppendMenu(hmenu, MF_STRING | MF_POPUP, 
        (UINT) hmenuLines, "&Lines"); 
 
    // Create a brush for the menu-item background color.  
 
    crMenuClr = GetSysColor(COLOR_MENU); 
    hbrBackground = CreateSolidBrush(crMenuClr); 
 
    // Create a compatible device context for the line bitmaps, 
    // and then select the background brush into it. 
 
    hdcMain = GetDC(hwnd); 
    hdcLines = CreateCompatibleDC(hdcMain); 
    hbrOld = SelectObject(hdcLines, hbrBackground); 
 
    // Get the dimensions of the check-mark bitmap. The width of 
    // the line bitmaps will be five times the width of the 
    // check-mark bitmap. 
 
    wLineX = GetSystemMetrics(SM_CXMENUCHECK) * (WORD) 5; 
    wLineY = GetSystemMetrics(SM_CYMENUCHECK); 
 
    // Create the bitmaps and select them, one at a time, into the 
    // compatible device context. Initialize each bitmap by 
    // filling it with the menu-item background color. 
 
    for (i = 0; i < CPENS; i++) 
    { 
        phbmp[i] = CreateCompatibleBitmap(hdcMain, wLineX, wLineY); 
        if (i == 0) 
            hbmpOld = SelectObject(hdcLines, phbmp[i]); 
        else 
            SelectObject(hdcLines, phbmp[i]); 
        ExtFloodFill(hdcLines, 0, 0, crMenuClr, FLOODFILLBORDER); 
    } 
 
    // Create the pens.  
 
    phpen[0] = CreatePen(PS_SOLID, 1, RGB(0, 0, 0)); 
    phpen[1] = CreatePen(PS_DOT, 1, RGB(0, 0, 0)); 
    phpen[2] = CreatePen(PS_DASH, 1, RGB(0, 0, 0)); 
    phpen[3] = CreatePen(PS_DASHDOT, 1, RGB(0, 0, 0)); 
    phpen[4] = CreatePen(PS_DASHDOTDOT, 1, RGB(0, 0, 0)); 
 
    // Select a pen and a bitmap into the compatible device 
    // context, draw a line into the bitmap, and then append 
    // the bitmap as an item in the Lines menu. 
 
    for (i = 0; i < CPENS; i++) 
    { 
        SelectObject(hdcLines, phbmp[i]); 
        SelectObject(hdcLines, phpen[i]); 
        MoveToEx(hdcLines, 0, wLineY / 2, NULL); 
        LineTo(hdcLines, wLineX, wLineY / 2); 
        AppendMenu(hmenuLines, MF_BITMAP, i + 1, 
            (LPCTSTR) phbmp[i]); 
    } 
 
    // Release the main window's device context and destroy the 
    // compatible device context. Also, destroy the background 
    // brush. 
 
    ReleaseDC(hwnd, hdcMain); 
    SelectObject(hdcLines, hbrOld); 
    DeleteObject(hbrBackground); 
    SelectObject(hdcLines, hbmpOld); 
    DeleteDC(hdcLines); 
 
    return; 
} 

Menu-Item 位圖範例

本主題中的範例會建立兩個功能表,每個功能表都包含數個點陣圖功能表項。 針對每個功能表,應用程式會將對應的功能表名稱新增至主視窗的功能表欄。

第一個功能表包含三種圖表類型的選項:餅圖、折線圖和條形圖。 這些功能表項的點陣圖會定義為資源,並使用 LoadBitmap 函式載入。 與此功能表相關聯的是功能表欄上的「圖表」功能表名稱。

第二個功能表包含功能表項,其中每一個顯示與 CreatePen 函式搭配使用的五種線條樣式:PS_SOLID、PS_DASH、PS_DOT、PS_DASHDOT和 PS_DASHDOTDOT。 應用程式會使用 GDI 繪圖函式,在運行時間建立這些功能表項的點陣圖。 與此功能表相關聯的是功能表列上 行 功能表名稱。

在應用程式的視窗程式中定義了兩個靜態陣列的點陣圖控制代碼。 一個陣列包含用於 圖表 功能表的三個點陣圖控制代碼。 另一個包含使用於 行 功能表的五個位圖控制代碼。 處理 WM_CREATE 訊息時,視窗程式會載入圖表位圖、建立折線圖,然後新增對應的功能表項。 處理 WM_DESTROY 訊息時,視窗程式會刪除所有點陣圖。

以下是應用程式頭檔的相關部分。

// Menu-item identifiers 
 
#define IDM_PIE         1 
#define IDM_LINE        2 
#define IDM_BAR         3 
 
#define IDM_SOLID       4 
#define IDM_DASH        5 
#define IDM_DASHDOT     6 
#define IDM_DASHDOTDOT  7 
 
// Number of items on the Chart and Lines menus 
 
#define C_LINES         5 
#define C_CHARTS        3 
 
// Bitmap resource identifiers 
 
#define IDB_PIE         1 
#define IDB_LINE        2 
#define IDB_BAR         3 
 
// Dimensions of the line bitmaps 
 
#define CX_LINEBMP      40 
#define CY_LINEBMP      10 

以下是視窗程序的相關部分。 此視窗程式會藉由呼叫應用程式定義的 LoadChartBitmaps、CreateLineBitmaps 和 AddBitmapMenu 函式來執行大部分的初始化,本主題稍後所述。

LRESULT CALLBACK MainWindowProc( 
        HWND hwnd, 
        UINT uMsg, 
        WPARAM wParam, 
        LPARAM lParam 
        ) 
{ 
    static HBITMAP aHbmLines[C_LINES]; 
    static HBITMAP aHbmChart[C_CHARTS]; 
    int i; 
 
    switch (uMsg) 
    { 
        case WM_CREATE: 
 
             // Call application-defined functions to load the 
             // bitmaps for the Chart menu and create those for 
             // the Lines menu. 
 
            LoadChartBitmaps(aHbmChart); 
            CreateLineBitmaps(aHbmLines); 
 
             // Call an application-defined function to create 
             // menus containing the bitmap menu items. The function 
             // also adds a menu name to the window's menu bar. 
 
            AddBitmapMenu( 
                    hwnd,      // menu bar's owner window 
                    "&Chart",  // text of menu name on menu bar 
                    IDM_PIE,   // ID of first item on menu 
                    aHbmChart, // array of bitmap handles 
                    C_CHARTS   // number of items on menu 
                    ); 
            AddBitmapMenu(hwnd, "&Lines", IDM_SOLID, 
                    aHbmLines, C_LINES); 
            break; 
 
        case WM_DESTROY: 
            for (i = 0; i < C_LINES; i++) 
                DeleteObject(aHbmLines[i]); 
            for (i = 0; i < C_CHARTS; i++) 
                DeleteObject(aHbmChart[i]); 
            PostQuitMessage(0); 
            break; 
 
        // Process additional messages here. 
 
        default: 
            return (DefWindowProc(hwnd, uMsg, wParam, lParam)); 
    } 
    return 0; 
} 

應用程式定義的 LoadChartBitmaps 函式會呼叫 LoadBitmap 函式,以載入圖表功能表的點陣圖資源,如下所示。

VOID WINAPI LoadChartBitmaps(HBITMAP *paHbm) 
{ 
    paHbm[0] = LoadBitmap(g_hinst, MAKEINTRESOURCE(IDB_PIE)); 
    paHbm[1] = LoadBitmap(g_hinst, MAKEINTRESOURCE(IDB_LINE)); 
    paHbm[2] = LoadBitmap(g_hinst, MAKEINTRESOURCE(IDB_BAR)); 
} 

應用程式定義的 CreateLineBitmaps 函式會使用 GDI 繪圖函數來建立 [線條] 功能表的點陣圖。 函式會建立記憶體裝置內容 (DC),其屬性與桌面視窗的DC相同。 針對每個線條樣式,函式會建立位圖、將它選取到記憶體 DC 中,然後加以繪製。

VOID WINAPI CreateLineBitmaps(HBITMAP *paHbm) 
{ 
    HWND hwndDesktop = GetDesktopWindow(); 
    HDC hdcDesktop = GetDC(hwndDesktop); 
    HDC hdcMem = CreateCompatibleDC(hdcDesktop); 
    COLORREF clrMenu = GetSysColor(COLOR_MENU); 
    HBRUSH hbrOld; 
    HPEN hpenOld; 
    HBITMAP hbmOld; 
    int fnDrawMode; 
    int i; 
 
     // Create a brush using the menu background color, 
     // and select it into the memory DC. 
 
    hbrOld = SelectObject(hdcMem, CreateSolidBrush(clrMenu)); 
 
     // Create the bitmaps. Select each one into the memory 
     // DC that was created and draw in it. 
 
    for (i = 0; i < C_LINES; i++) 
    { 
        // Create the bitmap and select it into the DC. 
 
        paHbm[i] = CreateCompatibleBitmap(hdcDesktop, 
                CX_LINEBMP, CY_LINEBMP); 
        hbmOld = SelectObject(hdcMem, paHbm[i]); 
 
        // Fill the background using the brush. 
 
        PatBlt(hdcMem, 0, 0, CX_LINEBMP, CY_LINEBMP, PATCOPY); 
 
        // Create the pen and select it into the DC. 
 
        hpenOld = SelectObject(hdcMem, 
                CreatePen(PS_SOLID + i, 1, RGB(0, 0, 0))); 
 
         // Draw the line. To preserve the background color where 
         // the pen is white, use the R2_MASKPEN drawing mode. 
 
        fnDrawMode = SetROP2(hdcMem, R2_MASKPEN); 
        MoveToEx(hdcMem, 0, CY_LINEBMP / 2, NULL); 
        LineTo(hdcMem, CX_LINEBMP, CY_LINEBMP / 2); 
        SetROP2(hdcMem, fnDrawMode); 
 
        // Delete the pen, and select the old pen and bitmap. 
 
        DeleteObject(SelectObject(hdcMem, hpenOld)); 
        SelectObject(hdcMem, hbmOld); 
    } 
 
    // Delete the brush and select the original brush. 
 
    DeleteObject(SelectObject(hdcMem, hbrOld)); 
 
    // Delete the memory DC and release the desktop DC. 
 
    DeleteDC(hdcMem); 
    ReleaseDC(hwndDesktop, hdcDesktop); 
} 

應用程式定義的 AddBitmapMenu 函式會建立功能表,並將指定的點陣圖功能表項數目加入其中。 然後,它會將對應的功能表名稱新增至指定的視窗功能表欄。

VOID WINAPI AddBitmapMenu( 
        HWND hwnd,          // window that owned the menu bar 
        LPSTR lpszText,     // text of menu name on menu bar 
        UINT uID,           // ID of first bitmap menu item 
        HBITMAP *paHbm,     // bitmaps for the menu items 
        int cItems)         // number bitmap menu items 
{ 
    HMENU hmenuBar = GetMenu(hwnd); 
    HMENU hmenuPopup = CreatePopupMenu(); 
    MENUITEMINFO mii; 
    int i; 
 
    // Add the bitmap menu items to the menu. 
 
    for (i = 0; i < cItems; i++) 
    { 
        mii.fMask = MIIM_ID | MIIM_BITMAP | MIIM_DATA; 
        mii.wID = uID + i; 
        mii.hbmpItem = &paHbm[i]; 
        InsertMenuItem(hmenuPopup, i, TRUE, &mii); 
    } 
 
    // Add a menu name to the menu bar. 
 
    mii.fMask = MIIM_STRING | MIIM_DATA | MIIM_SUBMENU; 
    mii.fType = MFT_STRING; 
    mii.hSubMenu = hmenuPopup; 
    mii.dwTypeData = lpszText; 
    InsertMenuItem(hmenuBar, 
        GetMenuItemCount(hmenuBar), TRUE, &mii); 
} 

建立 Owner-Drawn 選單項目

如果您需要完全控制功能表項的外觀,您可以在應用程式中使用擁有者繪製的功能表項。 本節說明建立和使用擁有者繪製功能表項所涉及的步驟。

設定 Owner-Drawn 旗標

您無法在應用程式的資源定義檔案中定義擁有者繪製的功能表項。 相反地,您必須使用 MFT_OWNERDRAW 功能表旗標來建立新的功能表項或修改現有的功能表項。

您可以使用 InsertMenuItem 或 SetMenuItemInfo 函式來指定擁有者繪製的功能表項。 使用 InsertMenuItem,在選單列或選單中的特定位置插入新的選單項目。 使用 SetMenuItemInfo 來變更功能表的內容。

在呼叫這兩個函式時,您必須指定一個指向 MENUITEMINFO 結構的指標,以設定新功能表項的屬性,或變更現有功能表項的屬性。 若要讓專案成為擁有者繪製的專案,請為 fMask 成員指定MIIM_FTYPE 值,以及 fType 成員的 MFT_OWNERDRAW 值。

藉由設定 MENUITEMINFO 結構的適當成員,您可以將應用程式定義的值與每個功能表項產生關聯,這個值稱為 項目數據。 若要這樣做,請為 fMask 成員指定 MIIM_DATA 值,併為 dwItemData 成員指定應用程式定義的值。

您可以將項目數據用於任何類型的菜單項,但對於自定義繪製項目特別有用。 例如,假設 結構包含用來繪製功能表項的資訊。 應用程式可能會使用功能表項的項目資料來儲存結構的指標。 項目數據會以 WM_MEASUREITEM 和 WM_DRAWITEM 訊息傳送至選單的擁有者視窗。 若要隨時擷取功能表的項目數據,請使用 GetMenuItemInfo 函式。

針對舊版系統撰寫的應用程式可以繼續呼叫 AppendMenu、InsertMenu,或 ModifyMenu,將 MF_OWNERDRAW 旗標指派給擁有者繪製的功能表項。

當您呼叫這三個函式中的任何一個時,您可以將值當做 lpNewItem 參數傳遞。 這個值可以代表對您應用程式有意義的任何資訊,而且當專案要顯示時,您的應用程式可以使用此資訊。 例如,值可以包含結構的指標;該結構可能包含文字字串以及應用程式用來繪製字串的邏輯字型的句柄。

Owner-Drawn 選單和WM_MEASUREITEM訊息

在系統第一次顯示自繪製的選單項目之前,它會將 WM_MEASUREITEM 訊息傳送至擁有該項目選單的視窗的視窗程序。 此訊息包含可識別項目的 MEASUREITEMSTRUCT 結構的指針,內含應用程式可能為其指派的項目資料。 窗口程式必須在返回處理訊息之前,填入結構的成員:itemWidth 和 itemHeight。 系統在建立應用程式繪製選單項目的邊界矩形時,會使用這些成員中的資訊。 它也會使用資訊來偵測使用者何時選擇專案。

Owner-Drawn 選單和WM_DRAWITEM訊息

每當必須繪製項目時(例如,第一次顯示項目或使用者選取該項目時),系統就會將 WM_DRAWITEM 訊息傳送至功能表擁有者視窗的視窗程序。 此訊息包含 DRAWITEMSTRUCT 結構的指標,其中包含專案的相關信息,包括應用程式可能為其指派的項目數據。 此外,DRAWITEMSTRUCT 包含旗標,指出項目狀態(例如它是灰色的還是被選取的),以及應用程式用來繪製項目的邊界矩形和設備上下文。

處理 WM_DRAWITEM 訊息時,應用程式必須執行下列動作:

  • 判斷必要的繪圖類型。 若要這樣做,請檢查 DRAWITEMSTRUCT 結構的 itemAction 成員。
  • 使用從 DRAWITEMSTRUCT 結構取得的周框和裝置內容,適當地繪製功能表項。 應用程式必須只在邊界矩形內繪製。 基於效能考慮,系統不會裁剪矩形外繪製之影像的部分。
  • 還原針對功能表項的裝置內容選取的所有 GDI 物件。

如果使用者選取功能表項,系統會將 DRAWITEMSTRUCT 結構的 itemAction 成員設定為 ODA_SELECT 值,並在 itemState 成員中設定 ODS_SELECTED 值。 這是應用程式重新繪製功能表項的提示,以顯示該項目已被選取。

Owner-Drawn 選單和WM_MENUCHAR訊息

非自繪功能表可以透過在選單字串中的字元旁加上底線來指定選單快速鍵。 這可讓使用者按下 ALT 並按功能表助記字元來選取功能表。 不過,在自訂功能表中,您無法以這種方式指定功能表助記鍵。 相反地,您的應用程式必須處理 WM_MENUCHAR 訊息,以提供具有功能表快捷鍵的自行繪製功能表。

當使用者輸入任何與目前功能表的預先定義助記鍵不相符的功能表助記鍵時,會傳送 WM_MENUCHAR 訊息。 wParam 中包含的值會指定 ASCII 字元,此字元會對應至使用者按下 ALT 鍵的按鍵。 wParam 的低位字指定所選選單的類型,並且可以是以下其中一個值:

  • 如果目前的功能表是子功能表,MF_POPUP。
  • 如果功能表是系統功能表,MF_SYSMENU。

wParam 的高位字包含當前菜單的菜單句柄。 具有擁有者繪製功能表的視窗可以按以下方式處理 WM_MENUCHAR:

   case WM_MENUCHAR:
      nIndex = Determine index of menu item to be selected from
               character that was typed and handle to the current
               menu.
      return MAKELRESULT(nIndex, 2);

傳回值高位字中的「二」提示系統,傳回值的低位字包含要選取之選單項目的從零開始的索引。

下列常數會對應至來自 WM_MENUCHAR 訊息的可能傳回值。

恆定 價值 意義
MNC_IGNORE 0 系統應該捨棄使用者按下的字元,並在系統喇叭上建立簡短的嗶聲。
MNC_CLOSE 1 系統應該關閉使用中的功能表。
MNC_EXECUTE 2 系統應該選擇傳回值低位字中指定的項目。 擁有者視窗會收到 WM_COMMAND 訊息。
MNC_SELECT 3 系統應該選取傳回值所指示的低位字中的項目。

 

設定 Menu-Item 文字字串的字型

本主題包含使用自繪功能表項的應用程式範例。 功能表包含設置目前字型屬性的項目,這些項目會使用適當的字型屬性顯示。

以下是在資源定義檔案中定義功能表的方式。 請注意,正則、粗體、斜體和底線功能表項的字串會在運行時間指派,因此其字串在資源定義檔案中是空的。

MainMenu MENU 
BEGIN 
    POPUP   "&Character" 
    BEGIN 
        MENUITEM    "",    IDM_REGULAR 
        MENUITEM SEPARATOR 
        MENUITEM    "",    IDM_BOLD 
        MENUITEM    "",    IDM_ITALIC 
        MENUITEM    "",    IDM_ULINE 
    END 
END 

應用程式的視窗程序會處理使用自繪式選單項目所涉及的訊息。 應用程式會使用 WM_CREATE 訊息來執行下列動作:

  • 設定用於功能表項的 MF_OWNERDRAW 旗標。
  • 設定功能表項的文字字串。
  • 取得用來繪製項目的字型句柄。
  • 取得已選取選單項的文字和背景色彩值。

文字字串和字型句柄會儲存在應用程式定義的 MYITEM 結構陣列中。 應用程式定義的 GetAFont 函數會建立對應至指定字型屬性的字型,並傳回字型的控制代碼。 句柄會在處理 WM_DESTROY 訊息期間被銷毀。

在處理 WM_MEASUREITEM 訊息期間,此範例會取得功能表項字串的寬度和高度,並將這些值複製到 MEASUREITEMSTRUCT結構。 系統會使用寬度和高度值來計算功能表的大小。

在處理 WM_DRAWITEM 訊息時,會繪製功能表項的字串,並在字串旁預留空間以便顯示核取符號的點陣圖。 如果使用者選取專案,則會使用選取的文字和背景色彩來繪製專案。

LRESULT APIENTRY MainWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) 
{ 
 
    typedef struct _MYITEM 
    { 
        HFONT hfont; 
        LPSTR psz; 
    } MYITEM;             // structure for item font and string  
 
    MYITEM *pmyitem;      // pointer to item's font and string        
    static MYITEM myitem[CITEMS];   // array of MYITEMS               
    static HMENU hmenu;             // handle to main menu            
    static COLORREF crSelText;  // text color of selected item        
    static COLORREF crSelBkgnd; // background color of selected item  
    COLORREF crText;            // text color of unselected item      
    COLORREF crBkgnd;           // background color unselected item   
    LPMEASUREITEMSTRUCT lpmis;  // pointer to item of data             
    LPDRAWITEMSTRUCT lpdis;     // pointer to item drawing data        
    HDC hdc;                    // handle to screen DC                
    SIZE size;                  // menu-item text extents             
    WORD wCheckX;               // check-mark width                   
    int nTextX;                 // width of menu item                 
    int nTextY;                 // height of menu item                
    int i;                      // loop counter                       
    HFONT hfontOld;             // handle to old font                 
    BOOL fSelected = FALSE;     // menu-item selection flag
    size_t * pcch;
    HRESULT hResult;           
 
    switch (uMsg) 
    { 
        case WM_CREATE: 
 
            // Modify the Regular, Bold, Italic, and Underline 
            // menu items to make them owner-drawn items. Associate 
            // a MYITEM structure with each item to contain the 
            // string for and font handle to each item. 
 
            hmenu = GetMenu(hwnd); 
            ModifyMenu(hmenu, IDM_REGULAR, MF_BYCOMMAND | 
                MF_CHECKED | MF_OWNERDRAW, IDM_REGULAR, 
                (LPTSTR) &myitem[REGULAR]); 
            ModifyMenu(hmenu, IDM_BOLD, MF_BYCOMMAND | 
                MF_OWNERDRAW, IDM_BOLD, (LPTSTR) &myitem[BOLD]); 
            ModifyMenu(hmenu, IDM_ITALIC, MF_BYCOMMAND | 
                MF_OWNERDRAW, IDM_ITALIC, 
                (LPTSTR) &myitem[ITALIC]); 
            ModifyMenu(hmenu, IDM_ULINE, MF_BYCOMMAND | 
                MF_OWNERDRAW, IDM_ULINE, (LPTSTR) &myitem[ULINE]); 
 
            // Retrieve each item's font handle and copy it into 
            // the hfont member of each item's MYITEM structure. 
            // Also, copy each item's string into the structures. 
 
            myitem[REGULAR].hfont = GetAFont(REGULAR); 
            myitem[REGULAR].psz = "Regular"; 
            myitem[BOLD].hfont = GetAFont(BOLD); 
            myitem[BOLD].psz = "Bold"; 
            myitem[ITALIC].hfont = GetAFont(ITALIC); 
            myitem[ITALIC].psz = "Italic"; 
            myitem[ULINE].hfont = GetAFont(ULINE); 
            myitem[ULINE].psz = "Underline"; 
 
            // Retrieve the text and background colors of the 
            // selected menu text. 
 
            crSelText = GetSysColor(COLOR_HIGHLIGHTTEXT); 
            crSelBkgnd = GetSysColor(COLOR_HIGHLIGHT); 
 
            return 0; 
 
        case WM_MEASUREITEM: 
 
            // Retrieve a device context for the main window.  
 
            hdc = GetDC(hwnd); 
 
            // Retrieve pointers to the menu item's 
            // MEASUREITEMSTRUCT structure and MYITEM structure. 
 
            lpmis = (LPMEASUREITEMSTRUCT) lParam; 
            pmyitem = (MYITEM *) lpmis->itemData; 
 
            // Select the font associated with the item into 
            // the main window's device context. 
 
            hfontOld = SelectObject(hdc, pmyitem->hfont); 
 
            // Retrieve the width and height of the item's string, 
            // and then copy the width and height into the 
            // MEASUREITEMSTRUCT structure's itemWidth and 
            // itemHeight members.
            
            hResult = StringCchLength(pmyitem->psz,STRSAFE_MAX_CCH, pcch);
            if (FAILED(hResult))
            {
            // Add code to fail as securely as possible.
                return;
            } 
 
            GetTextExtentPoint32(hdc, pmyitem->psz, 
                *pcch, &size); 
            lpmis->itemWidth = size.cx; 
            lpmis->itemHeight = size.cy; 
 
            // Select the old font back into the device context, 
            // and then release the device context. 
 
            SelectObject(hdc, hfontOld); 
            ReleaseDC(hwnd, hdc); 
 
            return TRUE; 
 
            break; 
 
        case WM_DRAWITEM: 
 
            // Get pointers to the menu item's DRAWITEMSTRUCT 
            // structure and MYITEM structure. 
 
            lpdis = (LPDRAWITEMSTRUCT) lParam; 
            pmyitem = (MYITEM *) lpdis->itemData; 
 
            // If the user has selected the item, use the selected 
            // text and background colors to display the item. 
 
            if (lpdis->itemState & ODS_SELECTED) 
            { 
                crText = SetTextColor(lpdis->hDC, crSelText); 
                crBkgnd = SetBkColor(lpdis->hDC, crSelBkgnd); 
                fSelected = TRUE; 
            } 
 
            // Remember to leave space in the menu item for the 
            // check-mark bitmap. Retrieve the width of the bitmap 
            // and add it to the width of the menu item. 
 
            wCheckX = GetSystemMetrics(SM_CXMENUCHECK); 
            nTextX = wCheckX + lpdis->rcItem.left; 
            nTextY = lpdis->rcItem.top; 
 
            // Select the font associated with the item into the 
            // item's device context, and then draw the string. 
 
            hfontOld = SelectObject(lpdis->hDC, pmyitem->hfont);
            
            hResult = StringCchLength(pmyitem->psz,STRSAFE_MAX_CCH, pcch);
            if (FAILED(hResult))
            {
            // Add code to fail as securely as possible.
                return;
            } 
 
            ExtTextOut(lpdis->hDC, nTextX, nTextY, ETO_OPAQUE, 
                &lpdis->rcItem, pmyitem->psz, 
                *pcch, NULL); 
 
            // Select the previous font back into the device 
            // context. 
 
            SelectObject(lpdis->hDC, hfontOld); 
 
            // Return the text and background colors to their 
            // normal state (not selected). 
 
            if (fSelected) 
            { 
                SetTextColor(lpdis->hDC, crText); 
                SetBkColor(lpdis->hDC, crBkgnd); 
            } 
 
            return TRUE; 
 
        // Process other messages.  
 
        case WM_DESTROY: 
 
            // Destroy the menu items' font handles.  
 
            for (i = 0; i < CITEMS; i++) 
                DeleteObject(myitem[i].hfont); 
 
            PostQuitMessage(0); 
            break; 
 
        default: 
            return DefWindowProc(hwnd, uMsg, wParam, lParam); 
    } 
    return NULL; 
} 
 
HFONT GetAFont(int fnFont) 
{ 
    static LOGFONT lf;  // structure for font information  
 
    // Get a handle to the ANSI fixed-pitch font, and copy 
    // information about the font to a LOGFONT structure. 
 
    GetObject(GetStockObject(ANSI_FIXED_FONT), sizeof(LOGFONT), 
        &lf); 
 
    // Set the font attributes, as appropriate.  
 
    if (fnFont == BOLD) 
        lf.lfWeight = FW_BOLD; 
    else 
        lf.lfWeight = FW_NORMAL; 
 
    lf.lfItalic = (fnFont == ITALIC); 
    lf.lfItalic = (fnFont == ULINE); 
 
    // Create the font, and then return its handle.  
 
    return CreateFont(lf.lfHeight, lf.lfWidth, 
        lf.lfEscapement, lf.lfOrientation, lf.lfWeight, 
        lf.lfItalic, lf.lfUnderline, lf.lfStrikeOut, lf.lfCharSet, 
        lf.lfOutPrecision, lf.lfClipPrecision, lf.lfQuality, 
        lf.lfPitchAndFamily, lf.lfFaceName); 
} 

Owner-Drawn 選單項目的範例

本主題的範例使用了自繪製的功能表項目。 功能表項會選取特定的字型屬性,而應用程式會使用具有對應屬性的字型來顯示每個功能表項。 例如,斜體 功能表項會顯示在斜體字型中。 功能表列上的 字元 選單名稱會開啟功能表。

功能表列和下拉選單一開始是由擴充的功能表樣板資源所定義。 因為功能表範本無法指定擁有者繪製的專案,所以功能表一開始包含四個具有下列字串的文字功能表項:「一般」、「粗體」、「斜體」和「底線」。當應用程式處理 WM_CREATE 訊息時,應用程式的視窗程式會將這些專案變更為擁有者繪製的專案。 當它收到 WM_CREATE 訊息時,視窗過程會呼叫應用程式定義的 OnCreate 函式,它會針對每個功能表項執行下列步驟:

  • 配置應用程式定義的 MYITEM 結構。
  • 取得功能表項的文字,並將它儲存在應用程式定義的 MYITEM 結構中。
  • 建立用來顯示功能表項的字型,並將其句柄儲存在應用程式定義的 MYITEM 結構中。
  • 將功能表項類型變更為 MFT_OWNERDRAW,並將應用程式定義的 MYITEM 結構的指標儲存為項目數據。

因為每個應用程式定義的 MYITEM 結構的指標會儲存為項目數據,所以它會與對應功能表項的 WM_MEASUREITEM 和 WM_DRAWITEM 訊息一起傳遞至視窗程式。 指標包含在 MEASUREITEMSTRUCT 和 DRAWITEMSTRUCT 結構 成員 itemData 中。

第一次顯示時,會為每個擁有者自繪的功能表項傳送 WM_MEASUREITEM 訊息。 應用程式透過將功能表項目的字型選擇並設置到裝置內容中,然後確定以該字型顯示功能表項目文字所需的空間來處理此訊息。 字型和功能表項文字都是由功能表項的 MYITEM 結構指定(應用程式定義的結構)。 應用程式會使用 GetTextExtentPoint32 函式來判斷文字的大小。

視窗程序會處理 WM_DRAWITEM 訊息,並以適當的字型顯示功能表項文字。 字型和功能表項文字都是由功能表項的 MYITEM 結構所指定。 應用程式會選取適合功能表項狀態的文字和背景色彩。

視窗程式會處理 WM_DESTROY 訊息,以終結字型並釋放記憶體。 應用程式會刪除字型,並釋放應用程式為每個功能表項定義的 MYITEM 結構。

以下是應用程式頭檔的相關部分。

// Menu-item identifiers for the Character menu 
 
#define IDM_CHARACTER 10 
#define IDM_REGULAR   11 
#define IDM_BOLD      12 
#define IDM_ITALIC    13 
#define IDM_UNDERLINE 14 
 
// Structure associated with menu items 
 
typedef struct tagMYITEM 
{ 
    HFONT hfont; 
    int   cchItemText; 
    char  szItemText[1]; 
} MYITEM; 
 
#define CCH_MAXITEMTEXT 256 
 

以下是應用程式視窗程式及其相關函式的相關部分。

LRESULT CALLBACK MainWindowProc( 
        HWND hwnd, 
        UINT uMsg, 
        WPARAM wParam, 
        LPARAM lParam 
        ) 
{ 
    switch (uMsg) 
    { 
        case WM_CREATE: 
            if (!OnCreate(hwnd)) 
                return -1; 
            break; 
 
        case WM_DESTROY: 
            OnDestroy(hwnd); 
            PostQuitMessage(0); 
            break; 
 
        case WM_MEASUREITEM: 
            OnMeasureItem(hwnd, (LPMEASUREITEMSTRUCT) lParam); 
            return TRUE; 
 
        case WM_DRAWITEM: 
            OnDrawItem(hwnd, (LPDRAWITEMSTRUCT) lParam); 
            return TRUE; 
 
        // Additional message processing goes here. 
 
        default: 
            return DefWindowProc(hwnd, uMsg, wParam, lParam); 
    } 
    return 0; 
} 
 
 
BOOL WINAPI OnCreate(HWND hwnd) 
{ 
    HMENU hmenuBar = GetMenu(hwnd); 
    HMENU hmenuPopup; 
    MENUITEMINFO mii; 
    UINT uID; 
    MYITEM *pMyItem; 
 
    // Get a handle to the pop-up menu. 
 
    mii.fMask = MIIM_SUBMENU;     // information to get 
    GetMenuItemInfo(hmenuBar, IDM_CHARACTER, FALSE, &mii); 
    hmenuPopup = mii.hSubMenu; 
 
    // Modify each menu item. Assume that the IDs IDM_REGULAR 
    // through IDM_UNDERLINE are consecutive numbers. 
 
    for (uID = IDM_REGULAR; uID <= IDM_UNDERLINE; uID++) 
    { 
         // Allocate an item structure, leaving space for a 
         // string of up to CCH_MAXITEMTEXT characters. 
 
        pMyItem = (MYITEM *) LocalAlloc(LMEM_FIXED, 
                sizeof(MYITEM) + CCH_MAXITEMTEXT); 
 
        // Save the item text in the item structure. 
 
        mii.fMask = MIIM_STRING; 
        mii.dwTypeData = pMyItem->szItemText; 
        mii.cch = CCH_MAXITEMTEXT; 
        GetMenuItemInfo(hmenuPopup, uID, FALSE, &mii); 
        pMyItem->cchItemText = mii.cch; 
 
        // Reallocate the structure to the minimum required size. 
 
        pMyItem = (MYITEM *) LocalReAlloc(pMyItem, 
                sizeof(MYITEM) + mii.cch, LMEM_MOVEABLE); 
 
        // Create the font used to draw the item. 
 
        pMyItem->hfont = CreateMenuItemFont(uID); 
 
        // Change the item to an owner-drawn item, and save 
        // the address of the item structure as item data. 
 
        mii.fMask = MIIM_FTYPE | MIIM_DATA; 
        mii.fType = MFT_OWNERDRAW; 
        mii.dwItemData = (ULONG_PTR) pMyItem; 
        SetMenuItemInfo(hmenuPopup, uID, FALSE, &mii); 
    } 
    return TRUE; 
} 
 
HFONT CreateMenuItemFont(UINT uID) 
{ 
    LOGFONT lf;
    HRESULT hr; 
 
    ZeroMemory(&lf, sizeof(lf)); 
    lf.lfHeight = 20; 
    hr = StringCchCopy(lf.lfFaceName, 32, "Times New Roman");
    if (FAILED(hr))
    {
    // TODO: writer error handler
    } 
 
    switch (uID) 
    { 
        case IDM_BOLD: 
            lf.lfWeight = FW_HEAVY; 
            break; 
 
        case IDM_ITALIC: 
            lf.lfItalic = TRUE; 
            break; 
 
        case IDM_UNDERLINE: 
            lf.lfUnderline = TRUE; 
            break; 
    } 
    return CreateFontIndirect(&lf); 
} 
 
VOID WINAPI OnDestroy(HWND hwnd) 
{ 
    HMENU hmenuBar = GetMenu(hwnd); 
    HMENU hmenuPopup; 
    MENUITEMINFO mii; 
    UINT uID; 
    MYITEM *pMyItem; 
 
    // Get a handle to the menu. 
 
    mii.fMask = MIIM_SUBMENU;     // information to get  
    GetMenuItemInfo(hmenuBar, IDM_CHARACTER, FALSE, &mii); 
    hmenuPopup = mii.hSubMenu; 
 
    // Free resources associated with each menu item. 
 
    for (uID = IDM_REGULAR; uID <= IDM_UNDERLINE; uID++) 
    { 
        // Get the item data. 
 
        mii.fMask = MIIM_DATA; 
        GetMenuItemInfo(hmenuPopup, uID, FALSE, &mii); 
        pMyItem = (MYITEM *) mii.dwItemData; 
 
        // Destroy the font and free the item structure. 
 
        DeleteObject(pMyItem->hfont); 
        LocalFree(pMyItem); 
    } 
} 
 
VOID WINAPI OnMeasureItem(HWND hwnd, LPMEASUREITEMSTRUCT lpmis) 
{ 
    MYITEM *pMyItem = (MYITEM *) lpmis->itemData; 
    HDC hdc = GetDC(hwnd); 
    HFONT hfntOld = (HFONT)SelectObject(hdc, pMyItem->hfont); 
    SIZE size; 
 
    GetTextExtentPoint32(hdc, pMyItem->szItemText, 
            pMyItem->cchItemText, &size); 
 
    lpmis->itemWidth = size.cx; 
    lpmis->itemHeight = size.cy; 
 
    SelectObject(hdc, hfntOld); 
    ReleaseDC(hwnd, hdc); 
} 
 
VOID WINAPI OnDrawItem(HWND hwnd, LPDRAWITEMSTRUCT lpdis) 
{ 
    MYITEM *pMyItem = (MYITEM *) lpdis->itemData; 
    COLORREF clrPrevText, clrPrevBkgnd; 
    HFONT hfntPrev; 
    int x, y; 
 
    // Set the appropriate foreground and background colors. 
 
    if (lpdis->itemState & ODS_SELECTED) 
    { 
        clrPrevText = SetTextColor(lpdis->hDC, 
                GetSysColor(COLOR_HIGHLIGHTTEXT)); 
        clrPrevBkgnd = SetBkColor(lpdis->hDC, 
                GetSysColor(COLOR_HIGHLIGHT)); 
    } 
    else 
    { 
        clrPrevText = SetTextColor(lpdis->hDC, 
                GetSysColor(COLOR_MENUTEXT)); 
        clrPrevBkgnd = SetBkColor(lpdis->hDC, 
                GetSysColor(COLOR_MENU)); 
    } 
 
    // Determine where to draw and leave space for a check mark. 
 
    x = lpdis->rcItem.left; 
    y = lpdis->rcItem.top; 
    x += GetSystemMetrics(SM_CXMENUCHECK); 
 
    // Select the font and draw the text. 
 
    hfntPrev = (HFONT)SelectObject(lpdis->hDC, pMyItem->hfont); 
    ExtTextOut(lpdis->hDC, x, y, ETO_OPAQUE, 
            &lpdis->rcItem, pMyItem->szItemText, 
            pMyItem->cchItemText, NULL); 
 
    // Restore the original font and colors. 
 
    SelectObject(lpdis->hDC, hfntPrev); 
    SetTextColor(lpdis->hDC, clrPrevText); 
    SetBkColor(lpdis->hDC, clrPrevBkgnd); 
} 

使用自訂複選標記點陣圖

系統會提供預設的核取標記點陣圖,以顯示於選取的功能表項旁。 您可以提供一對點陣圖來替換預設的核取記號點陣圖,從而自定義個別功能表項。 系統會在選取項目時顯示一個點陣圖,另一個點陣圖則在沒有選取時顯示。 本節說明建立和使用自定義複選標記點圖時所涉及的步驟。

建立自訂勾選標記點陣圖

自定義複選標記點陣圖的大小必須與預設複選標記點圖相同。 您可以呼叫 GetSystemMetrics 函式,以取得位圖的預設複選標記大小。 此函式傳回值的低序字會指定寬度;高序字會指定高度。

您可以使用點陣圖資源來提供核取符號的點陣圖。 不過,由於所需的位圖大小會根據顯示類型而有所不同,因此您可能需要使用 StretchBlt 函式,在運行時間調整位圖的大小。 根據位圖的不同,調整大小所造成的失真可能會產生無法接受的結果。

您可以使用 GDI 函式,在執行時間建立位圖,而不是使用位圖資源。

在運行時間建立位圖

  1. 使用 CreateCompatibleDC 函式來建立與應用程式主視窗使用的裝置環境相容的裝置內容。

    函式的 hdc 參數可以指定 NULL 或函式的傳回值。 CreateCompatibleDC 會將句柄傳回至相容的裝置內容。

  2. 使用 CreateCompatibleBitmap 函式來建立與應用程式主視窗相容的位圖。

    此函式的 nWidth 和 nHeight 參數會設定位圖的大小;它們應該指定由 getSystemMetrics函式傳回的寬度和高度資訊。

    注意

    您也可以使用 CreateBitmap 函式來建立單色位圖。

     

  3. 使用 SelectObject 函式,將位圖選取至相容的裝置內容。

  4. 使用 GDI 繪圖函式,例如 Ellipse 和 LineTo,將影像繪製到位圖中,或使用 BitBlt 和 StretchBlt 等函式將影像複製到位圖中。

如需詳細資訊,請參閱 位陣圖。

將點陣圖與功能表項產生關聯

您可以將一對複選標記點陣圖與功能表項目產生關聯,方法是將這些點陣圖的句柄傳遞給 SetMenuItemBitmaps 函式。 hBitmapUnchecked 參數會識別清楚的點陣圖,而 hBitmapChecked 參數會識別選取的位陣圖。 如果您要從功能表項中移除一或兩個勾選框,您可以將 hBitmapUnchecked 或 hBitmapChecked 參數,或兩者都設定為 NULL。

設定勾選標記屬性

CheckMenuItem 函式設定功能表項的勾選狀態屬性為已選取或已清除。 您可以指定 MF_CHECKED 值,將複選標記屬性設定為已選取,並將 MF_UNCHECKED 值設定為清除。

您也可以使用 SetMenuItemInfo 函式來設定選單項的檢查狀態。

有時一組功能表項代表一組互斥選項。 藉由使用 CheckMenuRadioItem 函式,您可以選擇一個功能表項目,同時取消群組中所有其他功能表項目的選取標記。

模擬功能表中的複選框

本主題包含示範如何在功能表中模擬複選框的範例。 此範例包含字元功能表,其項目允許使用者設定當前字型的粗體、斜體和底線屬性。 當字型屬性生效時,對應的功能表項旁的複選框會顯示複選標記;否則,專案旁邊會顯示空白複選框。

此範例將預設的複選標記位圖替換為兩個位圖:一個為選取的複選框位圖,另一個為空白方框位圖。 當項目的勾選標記屬性設定為 MF_CHECKED時,選取的勾選框位圖會顯示在粗體、斜體或底線功能表項旁邊。 當複選標記屬性設定為 MF_UNCHECKED時,將顯示清除或未選取的複選框位圖。

系統提供預先定義的點陣圖,其中包含用於複選框和單選按鈕的影像。 此範例會隔離選取的和空白複選框、將它們複製到兩個不同的位圖,然後使用它們做為 字元 功能表中項目的選取和清除位陣圖。

為了擷取系統定義複選框位圖的句柄,此範例會呼叫 LoadBitmap 函式,將 NULL 指定為 hInstance 參數,並將 OBM_CHECKBOXES 指定為 lpBitmapName 參數。 由於點陣圖中的影像大小都相同,因此範例可以藉由將點陣圖寬度和高度除以其數據列和數據行中的影像數目來隔離它們。

在以下資源定義檔中顯示了如何定義 字元 功能表中的選單項目。 請注意,一開始沒有任何字型屬性生效,因此 一般 項目的複選標記屬性會設定為已選取,而且根據預設,其餘專案的複選標記屬性會設定為清除。

#include "men3.h" 
 
MainMenu MENU 
BEGIN 
    POPUP   "&Character" 
    BEGIN 
        MENUITEM    "&Regular",     IDM_REGULAR, CHECKED 
        MENUITEM SEPARATOR 
        MENUITEM    "&Bold",        IDM_BOLD 
        MENUITEM    "&Italic",      IDM_ITALIC 
        MENUITEM    "&Underline",   IDM_ULINE 
    END 
END

以下是應用程式頭檔的相關內容。

// Menu-item identifiers  
 
#define IDM_REGULAR 0x1 
#define IDM_BOLD    0x2 
#define IDM_ITALIC  0x4 
#define IDM_ULINE   0x8 
 
// Check-mark flags  
 
#define CHECK   1 
#define UNCHECK 2 
 
// Font-attribute mask  
 
#define ATTRIBMASK 0xe 
 
// Function prototypes  
 
LRESULT APIENTRY MainWndProc(HWND, UINT, WPARAM, LPARAM); 
HBITMAP GetMyCheckBitmaps(UINT); 
BYTE CheckOrUncheckMenuItem(BYTE, HMENU); 

下列範例顯示建立複選標記點圖之窗口程式的部分;設定 粗體、斜體及 底線 選單項的複選標記屬性:和終結複選標記點圖。

LRESULT APIENTRY MainWndProc(HWND hwndMain, UINT uMsg, WPARAM wParam, LPARAM lParam) 
{ 
 
    static HBITMAP hbmpCheck;   // handle to checked bitmap    
    static HBITMAP hbmpUncheck; // handle to unchecked bitmap  
    static HMENU hmenu;         // handle to main menu         
    BYTE fbFontAttrib;          // font-attribute flags        
 
    switch (uMsg) 
    { 
        case WM_CREATE: 
 
            // Call the application-defined GetMyCheckBitmaps 
            // function to get the predefined checked and 
            // unchecked check box bitmaps. 
 
            hbmpCheck = GetMyCheckBitmaps(CHECK); 
            hbmpUncheck = GetMyCheckBitmaps(UNCHECK); 
 
            // Set the checked and unchecked bitmaps for the menu 
            // items. 
 
            hmenu = GetMenu(hwndMain); 
            SetMenuItemBitmaps(hmenu, IDM_BOLD, MF_BYCOMMAND, 
                hbmpUncheck, hbmpCheck); 
            SetMenuItemBitmaps(hmenu, IDM_ITALIC, MF_BYCOMMAND, 
                hbmpUncheck, hbmpCheck); 
            SetMenuItemBitmaps(hmenu, IDM_ULINE, MF_BYCOMMAND, 
                hbmpUncheck, hbmpCheck); 
 
            return 0; 
 
        case WM_COMMAND: 
            switch (LOWORD(wParam)) 
            { 
                // Process the menu commands.  
 
                case IDM_REGULAR: 
                case IDM_BOLD: 
                case IDM_ITALIC: 
                case IDM_ULINE: 
 
                    // CheckOrUncheckMenuItem is an application- 
                    // defined function that sets the menu item 
                    // checkmarks and returns the user-selected 
                    // font attributes. 
 
                    fbFontAttrib = CheckOrUncheckMenuItem( 
                        (BYTE) LOWORD(wParam), hmenu); 
 
                    // Set the font attributes.  
 
                    return 0; 
 
                // Process other command messages.  
 
                default: 
                    break; 
            } 
 
            break; 
 
        // Process other window messages.  
 
        case WM_DESTROY: 
 
            // Destroy the checked and unchecked bitmaps.  
 
            DeleteObject(hbmpCheck); 
            DeleteObject(hbmpUncheck); 
 
            PostQuitMessage(0); 
            break; 
 
        default: 
            return DefWindowProc(hwndMain, uMsg, wParam, lParam); 
    } 
    return NULL; 
} 
 
HBITMAP GetMyCheckBitmaps(UINT fuCheck) 
{ 
    COLORREF crBackground;  // background color                  
    HBRUSH hbrBackground;   // background brush                  
    HBRUSH hbrTargetOld;    // original background brush         
    HDC hdcSource;          // source device context             
    HDC hdcTarget;          // target device context             
    HBITMAP hbmpCheckboxes; // handle to check-box bitmap        
    BITMAP bmCheckbox;      // structure for bitmap data         
    HBITMAP hbmpSourceOld;  // handle to original source bitmap  
    HBITMAP hbmpTargetOld;  // handle to original target bitmap  
    HBITMAP hbmpCheck;      // handle to check-mark bitmap       
    RECT rc;                // rectangle for check-box bitmap    
    WORD wBitmapX;          // width of check-mark bitmap        
    WORD wBitmapY;          // height of check-mark bitmap       
 
    // Get the menu background color and create a solid brush 
    // with that color. 
 
    crBackground = GetSysColor(COLOR_MENU); 
    hbrBackground = CreateSolidBrush(crBackground); 
 
    // Create memory device contexts for the source and 
    // destination bitmaps. 
 
    hdcSource = CreateCompatibleDC((HDC) NULL); 
    hdcTarget = CreateCompatibleDC(hdcSource); 
 
    // Get the size of the system default check-mark bitmap and 
    // create a compatible bitmap of the same size. 
 
    wBitmapX = GetSystemMetrics(SM_CXMENUCHECK); 
    wBitmapY = GetSystemMetrics(SM_CYMENUCHECK); 
 
    hbmpCheck = CreateCompatibleBitmap(hdcSource, wBitmapX, 
        wBitmapY); 
 
    // Select the background brush and bitmap into the target DC. 
 
    hbrTargetOld = SelectObject(hdcTarget, hbrBackground); 
    hbmpTargetOld = SelectObject(hdcTarget, hbmpCheck); 
 
    // Use the selected brush to initialize the background color 
    // of the bitmap in the target device context. 
 
    PatBlt(hdcTarget, 0, 0, wBitmapX, wBitmapY, PATCOPY); 
 
    // Load the predefined check box bitmaps and select it 
    // into the source DC. 
 
    hbmpCheckboxes = LoadBitmap((HINSTANCE) NULL, 
        (LPTSTR) OBM_CHECKBOXES); 
 
    hbmpSourceOld = SelectObject(hdcSource, hbmpCheckboxes); 
 
    // Fill a BITMAP structure with information about the 
    // check box bitmaps, and then find the upper-left corner of 
    // the unchecked check box or the checked check box. 
 
    GetObject(hbmpCheckboxes, sizeof(BITMAP), &bmCheckbox); 
 
    if (fuCheck == UNCHECK) 
    { 
        rc.left = 0; 
        rc.right = (bmCheckbox.bmWidth / 4); 
    } 
    else 
    { 
        rc.left = (bmCheckbox.bmWidth / 4); 
        rc.right = (bmCheckbox.bmWidth / 4) * 2; 
    } 
 
    rc.top = 0; 
    rc.bottom = (bmCheckbox.bmHeight / 3); 
 
    // Copy the appropriate bitmap into the target DC. If the 
    // check-box bitmap is larger than the default check-mark 
    // bitmap, use StretchBlt to make it fit; otherwise, just 
    // copy it. 
 
    if (((rc.right - rc.left) > (int) wBitmapX) || 
            ((rc.bottom - rc.top) > (int) wBitmapY)) 
    {
        StretchBlt(hdcTarget, 0, 0, wBitmapX, wBitmapY, 
            hdcSource, rc.left, rc.top, rc.right - rc.left, 
            rc.bottom - rc.top, SRCCOPY); 
    }
 
    else 
    {
        BitBlt(hdcTarget, 0, 0, rc.right - rc.left, 
            rc.bottom - rc.top, 
            hdcSource, rc.left, rc.top, SRCCOPY); 
    }
 
    // Select the old source and destination bitmaps into the 
    // source and destination DCs, and then delete the DCs and 
    // the background brush. 
 
    SelectObject(hdcSource, hbmpSourceOld); 
    SelectObject(hdcTarget, hbrTargetOld); 
    hbmpCheck = SelectObject(hdcTarget, hbmpTargetOld); 
 
    DeleteObject(hbrBackground); 
    DeleteObject(hdcSource); 
    DeleteObject(hdcTarget); 
 
    // Return a handle to the new check-mark bitmap.  
 
    return hbmpCheck; 
} 
 
 
BYTE CheckOrUncheckMenuItem(BYTE bMenuItemID, HMENU hmenu) 
{ 
    DWORD fdwMenu; 
    static BYTE fbAttributes; 
 
    switch (bMenuItemID) 
    { 
        case IDM_REGULAR: 
 
            // Whenever the Regular menu item is selected, add a 
            // check mark to it and then remove checkmarks from 
            // any font-attribute menu items. 
 
            CheckMenuItem(hmenu, IDM_REGULAR, MF_BYCOMMAND | 
                MF_CHECKED); 
 
            if (fbAttributes & ATTRIBMASK) 
            { 
                CheckMenuItem(hmenu, IDM_BOLD, MF_BYCOMMAND | 
                    MF_UNCHECKED); 
                CheckMenuItem(hmenu, IDM_ITALIC, MF_BYCOMMAND | 
                    MF_UNCHECKED); 
                CheckMenuItem(hmenu, IDM_ULINE, MF_BYCOMMAND | 
                    MF_UNCHECKED); 
            } 
            fbAttributes = IDM_REGULAR; 
            return fbAttributes; 
 
        case IDM_BOLD: 
        case IDM_ITALIC: 
        case IDM_ULINE: 
 
            // Toggle the check mark for the selected menu item and 
            // set the font attribute flags appropriately. 
 
            fdwMenu = GetMenuState(hmenu, (UINT) bMenuItemID, 
                MF_BYCOMMAND); 
            if (!(fdwMenu & MF_CHECKED)) 
            { 
                CheckMenuItem(hmenu, (UINT) bMenuItemID, 
                    MF_BYCOMMAND | MF_CHECKED); 
                fbAttributes |= bMenuItemID; 
            }
            else 
            { 
                CheckMenuItem(hmenu, (UINT) bMenuItemID, 
                    MF_BYCOMMAND | MF_UNCHECKED); 
                fbAttributes ^= bMenuItemID; 
            } 
 
            // If any font attributes are currently selected, 
            // remove the check mark from the Regular menu item; 
            // if no attributes are selected, add a check mark 
            // to the Regular menu item. 
 
            if (fbAttributes & ATTRIBMASK) 
            { 
                CheckMenuItem(hmenu, IDM_REGULAR, 
                    MF_BYCOMMAND | MF_UNCHECKED); 
                fbAttributes &= (BYTE) ~IDM_REGULAR; 
            }
            else 
            { 
                CheckMenuItem(hmenu, IDM_REGULAR, 
                    MF_BYCOMMAND | MF_CHECKED); 
                fbAttributes = IDM_REGULAR; 
            } 
 
            return fbAttributes; 
    } 
} 

使用自訂勾選標記位圖的範例

本主題中的範例將自定義勾選符號位圖指派給兩個選單中的項目。 第一個選單中的選單項目會指定字元屬性:粗體、斜體和底線。 每個選單項目可以被選取或清除。 針對這些選單項目,範例使用的複選標記點陣圖類似於核取方塊控制項的選取和清除狀態。

第二個選單中的選單項目指定了段落對齊設定:左對齊、置中和右對齊。 在任何時刻,只會選取其中一個選單項目。 針對這些功能表項,此範例會使用複選標記點陣圖,類似於單選按鈕控件的選取和清除狀態。

視窗程式會藉由呼叫應用程式定義的 OnCreate 函式來處理 WM_CREATE 訊息。 OnCreate 會建立四個核取標記位圖,然後使用 SetMenuItemBitmaps 函式,將它們指派給適當的選單項目。

若要建立每個點陣圖,OnCreate 會呼叫應用程式定義的 CreateMenuBitmaps 函式,並指定位圖特定繪圖函式的指標。 CreateMenuBitmaps 會建立所需大小的單色位圖、將它選取到記憶體裝置內容中,並清除背景。 然後它會呼叫指定的繪圖函式,以填入前景。

四個應用程式定義的繪圖函式是 DrawCheck、DrawUncheck、DrawRadioCheck和 DrawRadioUncheck。 它們分別繪製了一個帶有 X 的矩形、一個空矩形、一個包含較小填滿橢圓的橢圓和一個空橢圓。

視窗程序會藉由刪除勾選標記位圖來處理 WM_DESTROY 訊息。 它會使用 getMenuItemInfo 函式來擷取每個點陣圖句柄,然後將句柄傳遞至函式。

當使用者選擇功能表項時,會將 WM_COMMAND 訊息傳送至擁有者視窗。 針對 字元 選單上的選單項,視窗程序會呼叫應用程式定義的 CheckCharacterItem 函數。 視窗程序會針對 [Paragraph] 功能表中的專案,呼叫應用程式自定義的 CheckParagraphItem 函式。

字元 選單上的每個項目都可以獨立選取和清除。 因此,CheckCharacterItem 只會切換指定的功能表項檢查狀態。 首先,函式會呼叫 GetMenuItemInfo 函式,以取得目前的功能表項狀態。 然後,它會切換 MFS_CHECKED 狀態旗標,並呼叫 SetMenuItemInfo 函式來設定新狀態。

不同於字元屬性,一次只能選取一個段落對齊方式。 因此,CheckParagraphItem 會勾選選取的功能表項,並從功能表上的所有其他項目中移除勾選標記。 若要這樣做,它會呼叫 CheckMenuRadioItem 函式。

以下是應用程式頭檔的相關部分。

// Menu-item identifiers for the Character menu 
 
#define IDM_CHARACTER 10 
#define IDM_BOLD      11 
#define IDM_ITALIC    12 
#define IDM_UNDERLINE 13 
 
// Menu-item identifiers for the Paragraph menu 
 
#define IDM_PARAGRAPH 20 
#define IDM_LEFT      21 
#define IDM_CENTER    22 
#define IDM_RIGHT     23 
 
// Function-pointer type for drawing functions 
 
typedef VOID (WINAPI * DRAWFUNC)(HDC hdc, SIZE size); 
 

以下是應用程式視窗程式和相關函式的相關部分。

LRESULT CALLBACK MainWindowProc( 
        HWND hwnd, 
        UINT uMsg, 
        WPARAM wParam, 
        LPARAM lParam 
        ) 
{ 
    switch (uMsg) 
    { 
        case WM_CREATE: 
            if (!OnCreate(hwnd)) 
                return -1; 
            break; 
 
        case WM_DESTROY: 
            OnDestroy(hwnd); 
            PostQuitMessage(0); 
            break; 
 
        case WM_COMMAND: 
            switch (LOWORD(wParam)) 
            { 
                case IDM_BOLD: 
                case IDM_ITALIC: 
                case IDM_UNDERLINE: 
                    CheckCharacterItem(hwnd, LOWORD(wParam)); 
                    break; 
 
                case IDM_LEFT: 
                case IDM_CENTER: 
                case IDM_RIGHT: 
                    CheckParagraphItem(hwnd, LOWORD(wParam)); 
                    break; 
 
                // Process other commands here. 
 
            } 
            break; 
 
        // Process other messages here. 
 
        default: 
            return DefWindowProc(hwnd, uMsg, wParam, lParam); 
    } 
    return 0; 
} 
 
VOID WINAPI CheckCharacterItem(HWND hwnd, UINT uID) 
{ 
    HMENU hmenuBar = GetMenu(hwnd); 
    HMENU hmenuPopup; 
    MENUITEMINFO mii; 
 
    // Get a handle to the Character menu. 
 
    mii.fMask = MIIM_SUBMENU;  // information to get 
    GetMenuItemInfo(hmenuBar, IDM_CHARACTER, FALSE, &mii); 
    hmenuPopup = mii.hSubMenu; 
 
    // Get the state of the specified menu item. 
 
    mii.fMask = MIIM_STATE;    // information to get 
    GetMenuItemInfo(hmenuPopup, uID, FALSE, &mii); 
 
    // Toggle the checked state. 
 
    mii.fState ^= MFS_CHECKED; 
    SetMenuItemInfo(hmenuPopup, uID, FALSE, &mii); 
} 
 
VOID WINAPI CheckParagraphItem(HWND hwnd, UINT uID) 
{ 
    HMENU hmenuBar = GetMenu(hwnd); 
    HMENU hmenuPopup; 
    MENUITEMINFO mii; 
 
    // Get a handle to the Paragraph menu. 
 
    mii.fMask = MIIM_SUBMENU;  // information to get 
    GetMenuItemInfo(hmenuBar, IDM_PARAGRAPH, FALSE, &mii); 
    hmenuPopup = mii.hSubMenu; 
 
    // Check the specified item and uncheck all the others. 
 
    CheckMenuRadioItem( 
            hmenuPopup,         // handle to menu 
            IDM_LEFT,           // first item in range 
            IDM_RIGHT,          // last item in range 
            uID,                // item to check 
            MF_BYCOMMAND        // IDs, not positions 
            ); 
} 
 
BOOL WINAPI OnCreate(HWND hwnd) 
{ 
    HMENU hmenuBar = GetMenu(hwnd); 
    HMENU hmenuPopup; 
    MENUITEMINFO mii; 
    UINT uID; 
    HBITMAP hbmChecked; 
    HBITMAP hbmUnchecked; 
 
    // Get a handle to the Character menu. 
 
    mii.fMask = MIIM_SUBMENU;     // information to get 
    GetMenuItemInfo(hmenuBar, IDM_CHARACTER, FALSE, &mii); 
    hmenuPopup = mii.hSubMenu; 
 
    // Create the checked and unchecked bitmaps. 
 
    hbmChecked = CreateMenuBitmap(DrawCheck); 
    hbmUnchecked = CreateMenuBitmap(DrawUncheck); 
 
    // Set the check-mark bitmaps for each menu item. 
 
    for (uID = IDM_BOLD; uID <= IDM_UNDERLINE; uID++) 
    { 
        SetMenuItemBitmaps(hmenuPopup, uID, MF_BYCOMMAND, 
                hbmUnchecked, hbmChecked); 
    } 
 
    // Get a handle to the Paragraph pop-up menu. 
 
    mii.fMask = MIIM_SUBMENU;     // information to get 
    GetMenuItemInfo(hmenuBar, IDM_PARAGRAPH, FALSE, &mii); 
    hmenuPopup = mii.hSubMenu; 
 
    // Create the checked and unchecked bitmaps. 
 
    hbmChecked = CreateMenuBitmap(DrawRadioCheck); 
    hbmUnchecked = CreateMenuBitmap(DrawRadioUncheck); 
 
    // Set the check-mark bitmaps for each menu item. 
 
    for (uID = IDM_LEFT; uID <= IDM_RIGHT; uID++) 
    { 
        SetMenuItemBitmaps(hmenuPopup, uID, MF_BYCOMMAND, 
                hbmUnchecked, hbmChecked); 
    } 
 
    // Initially check the IDM_LEFT paragraph item. 
 
    CheckMenuRadioItem(hmenuPopup, IDM_LEFT, IDM_RIGHT, 
            IDM_LEFT, MF_BYCOMMAND); 
    return TRUE; 
} 
 
HBITMAP WINAPI CreateMenuBitmap(DRAWFUNC lpfnDraw) 
{ 
    // Create a DC compatible with the desktop window's DC. 
 
    HWND hwndDesktop = GetDesktopWindow(); 
    HDC hdcDesktop = GetDC(hwndDesktop); 
    HDC hdcMem = CreateCompatibleDC(hdcDesktop); 
 
    // Determine the required bitmap size. 
 
    SIZE size = { GetSystemMetrics(SM_CXMENUCHECK), 
                  GetSystemMetrics(SM_CYMENUCHECK) }; 
 
    // Create a monochrome bitmap and select it. 
 
    HBITMAP hbm = CreateBitmap(size.cx, size.cy, 1, 1, NULL); 
    HBITMAP hbmOld = SelectObject(hdcMem, hbm); 
 
    // Erase the background and call the drawing function. 
 
    PatBlt(hdcMem, 0, 0, size.cx, size.cy, WHITENESS); 
    (*lpfnDraw)(hdcMem, size); 
 
    // Clean up. 
 
    SelectObject(hdcMem, hbmOld); 
    DeleteDC(hdcMem); 
    ReleaseDC(hwndDesktop, hdcDesktop); 
    return hbm; 
} 
 
VOID WINAPI DrawCheck(HDC hdc, SIZE size) 
{ 
    HBRUSH hbrOld; 
    hbrOld = SelectObject(hdc, GetStockObject(NULL_BRUSH)); 
    Rectangle(hdc, 0, 0, size.cx, size.cy); 
    MoveToEx(hdc, 0, 0, NULL); 
    LineTo(hdc, size.cx, size.cy); 
    MoveToEx(hdc, 0, size.cy - 1, NULL); 
    LineTo(hdc, size.cx - 1, 0); 
    SelectObject(hdc, hbrOld); 
} 
 
VOID WINAPI DrawUncheck(HDC hdc, SIZE size) 
{ 
    HBRUSH hbrOld; 
    hbrOld = SelectObject(hdc, GetStockObject(NULL_BRUSH)); 
    Rectangle(hdc, 0, 0, size.cx, size.cy); 
    SelectObject(hdc, hbrOld); 
} 
 
VOID WINAPI DrawRadioCheck(HDC hdc, SIZE size) 
{ 
    HBRUSH hbrOld; 
    hbrOld = SelectObject(hdc, GetStockObject(NULL_BRUSH)); 
    Ellipse(hdc, 0, 0, size.cx, size.cy); 
    SelectObject(hdc, GetStockObject(BLACK_BRUSH)); 
    Ellipse(hdc, 2, 2, size.cx - 2, size.cy - 2); 
    SelectObject(hdc, hbrOld); 
} 
 
VOID WINAPI DrawRadioUncheck(HDC hdc, SIZE size) 
{ 
    HBRUSH hbrOld; 
    hbrOld = SelectObject(hdc, GetStockObject(NULL_BRUSH)); 
    Ellipse(hdc, 0, 0, size.cx, size.cy); 
    SelectObject(hdc, hbrOld); 
} 
 
VOID WINAPI OnDestroy(HWND hwnd) 
{ 
    HMENU hmenuBar = GetMenu(hwnd); 
    HMENU hmenuPopup; 
    MENUITEMINFO mii; 
 
    // Get a handle to the Character menu. 
 
    mii.fMask = MIIM_SUBMENU;     // information to get 
    GetMenuItemInfo(hmenuBar, IDM_CHARACTER, FALSE, &mii); 
    hmenuPopup = mii.hSubMenu; 
 
    // Get the check-mark bitmaps and delete them. 
 
    mii.fMask = MIIM_CHECKMARKS; 
    GetMenuItemInfo(hmenuPopup, IDM_BOLD, FALSE, &mii); 
    DeleteObject(mii.hbmpChecked); 
    DeleteObject(mii.hbmpUnchecked); 
 
    // Get a handle to the Paragraph menu. 
 
    mii.fMask = MIIM_SUBMENU;     // information to get 
    GetMenuItemInfo(hmenuBar, IDM_PARAGRAPH, FALSE, &mii); 
    hmenuPopup = mii.hSubMenu; 
 
    // Get the check-mark bitmaps and delete them. 
 
    mii.fMask = MIIM_CHECKMARKS; 
    GetMenuItemInfo(hmenuPopup, IDM_LEFT, FALSE, &mii); 
    DeleteObject(mii.hbmpChecked); 
    DeleteObject(mii.hbmpUnchecked); 
}