使用菜单

本部分介绍以下任务:

使用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 参数中指定菜单的句柄。 向窗口分配菜单的另一种方法是在注册窗口类时指定菜单模板;模板将指定的菜单标识为该窗口类的类菜单。

若要让系统自动将特定菜单分配给窗口,请在注册窗口的 类时指定菜单的模板。 模板将指定的菜单标识为该窗口类的类菜单。 然后,在创建指定类的窗口时,系统会自动将指定的菜单分配给该窗口。

不能将菜单分配给作为子窗口的窗口。

若要创建类菜单,请将菜单模板资源的标识符作为 WNDCLASS 结构的 lpszMenuName 成员包含在内,然后将指向结构的指针传递给 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_LBUTTONDOWNWM_KEYDOWN

除了弹出菜单句柄之外, TrackPopupMenuEx 还要求指定所有者窗口的句柄、快捷菜单 (在屏幕坐标中的位置) ,以及用户可用于选择项的鼠标按钮。

版 TrackPopupMenu 函数仍受支持,但新应用程序应使用 TrackPopupMenuEx 函数。 TrackPopupMenuEx 函数需要与 TrackPopupMenu 相同的参数,但还允许您指定屏幕中不应遮盖菜单的部分。 处理 WM_CONTEXTMENU 消息时,应用程序通常会在窗口过程中调用这些函数。

可以通过提供 x 坐标和 y 坐标以及 TPM_CENTERALIGNTPM_LEFTALIGNTPM_RIGHTALIGN 标志来指定快捷菜单的位置。 标志指定快捷菜单相对于 x 坐标和 y 坐标的位置。

应允许用户使用用于显示菜单的同一鼠标按钮从快捷菜单中选择项。 为此,请指定 TPM_LEFTBUTTONTPM_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菜单

本部分中的示例包含应用程序的代码部分,该应用程序创建并显示快捷菜单,使用户能够设置字体和字体属性。 每当用户单击鼠标左键时,应用程序在其main窗口的工作区中显示菜单。

下面是应用程序的资源定义文件中提供的快捷菜单的菜单模板。

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_BITMAPMF_BITMAP标志指示系统使用位图而不是文本字符串来显示菜单项。 必须在运行时设置菜单项的 MIIM_BITMAPMF_BITMAP 标志;无法在资源定义文件中设置它。

对于新应用程序,可以使用 SetMenuItemInfoInsertMenuItem 函数设置 MIIM_BITMAP 类型标志。 若要将菜单项从文本项更改为位图项,请使用 SetMenuItemInfo。 若要向菜单添加新位图项,请使用 InsertMenuItem 函数。

为系统早期版本编写的应用程序可以继续使用 ModifyMenu、InsertMenuAppendMenu 函数来设置MF_BITMAP标志。 若要将菜单项从文本字符串项更改为位图项,请使用 ModifyMenu。 若要向菜单添加新位图项,请将 MF_BITMAP 标志与 InsertMenuAppendMenu 函数一 起使用。

创建位图

为菜单项设置 MIIM_BITMAPMF_BITMAP 类型标志时,还必须指定系统应为菜单项显示的位图句柄。 可以将位图作为位图资源提供,也可以在运行时创建位图。 如果使用位图资源,则可以使用 LoadBitmap 函数加载位图并获取其句柄。

若要在运行时创建位图,请使用 Windows 图形设备接口 (GDI) 函数。 GDI 提供了多种在运行时创建位图的方法,但开发人员通常使用以下方法:

  1. 使用 CreateCompatibleDC 函数创建与应用程序main窗口使用的设备上下文兼容的设备上下文。
  2. 使用 CreateCompatibleBitmap 函数创建与应用程序的main窗口兼容的位图,或使用 CreateBitmap 函数创建单色位图。
  3. 使用 SelectObject 函数选择兼容设备上下文中的位图。
  4. 使用 GDI 绘图函数(如 EllipseLineTo)将图像绘制到位图中。

有关详细信息,请参阅 位图

向菜单添加线条和图形

下面的代码示例演示如何创建包含菜单项位图的菜单。 它创建两个菜单。 第一个菜单是包含三个菜单项位图的图表菜单:饼图、折线图和条形图。 该示例演示如何从应用程序的资源文件加载这些位图,然后使用 CreatePopupMenuAppendMenu 函数创建菜单和菜单项。

第二个菜单是“线条”菜单。 它包含位图,其中显示了系统中预定义的笔提供的线条样式。 使用 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位图示例

本主题中的示例创建两个菜单,每个菜单包含多个位图菜单项。 对于每个菜单,应用程序会将相应的菜单名称添加到main窗口的菜单栏。

第一个菜单包含显示三种图表类型的菜单项:饼图、折线图和条形图。 这些菜单项的位图定义为资源,并使用 LoadBitmap 函数加载。 与此菜单关联的是菜单栏上的“图表”菜单名称。

第二个菜单包含菜单项,其中每一个都显示 与 CreatePen 函数一起使用的五种线条样式: PS_SOLIDPS_DASHPS_DOTPS_DASHDOTPS_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菜单标志 创建新的菜单项 或修改现有菜单项。

可以使用 InsertMenuItemSetMenuItemInfo 函数指定所有者绘制的菜单项。 使用 InsertMenuItem 在菜单栏或菜单中的指定位置插入新的菜单项。 使用 SetMenuItemInfo 更改菜单的内容。

调用这两个函数时,必须指定指向 MENUITEMINFO 结构的指针,该结构指定要为现有菜单项更改的新菜单项的属性或属性。 若要使项成为所有者绘制的项,请指定 fMask 成员的MIIM_FTYPE值和 fType 成员的MFT_OWNERDRAW值。

通过设置 MENUITEMINFO 结构的相应成员,可以将应用程序定义的值(称为 项数据)与每个菜单项相关联。 为此,请为 fMask 成员指定MIIM_DATA值,为 dwItemData 成员指定应用程序定义的值。

可以将项数据与任何类型的菜单项一起使用,但它对于所有者绘制的项特别有用。 例如,假设结构包含用于绘制菜单项的信息。 应用程序可以使用菜单项的项数据来存储指向结构的指针。 项目数据将发送到带有 WM_MEASUREITEMWM_DRAWITEM 消息的菜单所有者窗口。 若要随时检索菜单的项数据,请使用 GetMenuItemInfo 函数。

为系统早期版本编写的应用程序可以继续调用 AppendMenu、InsertMenuModifyMenu,以将MF_OWNERDRAW标志分配给所有者绘制的菜单项。

调用这三个函数中的任何一个时,可以将值作为 lpNewItem 参数传递。 此值可以表示对应用程序有意义的任何信息,并且将在显示项时可供应用程序使用。 例如, 值可以包含指向 结构的指针;结构又可能包含文本字符串和应用程序将用于绘制字符串的逻辑字体的句柄。

Owner-Drawn菜单和WM_MEASUREITEM消息

在系统首次显示所有者绘制的菜单项之前,它会将 WM_MEASUREITEM 消息发送到拥有该项菜单的窗口的窗口过程。 此消息包含指向 MEASUREITEMSTRUCT 结构的指针,该结构标识项并包含应用程序可能已分配给它的项数据。 窗口过程必须填充结构的 itemWidthitemHeight 成员,然后才能返回处理消息。 创建应用程序在其中绘制菜单项的边界矩形时,系统会使用这些成员中的信息。 它还使用信息来检测用户何时选择该项。

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 中包含的值指定与用户使用 ALT 键按下的键对应的 ASCII 字符。 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 消息的可能返回值。

常数 Value 含义
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 相应菜单项的消息。 指针包含在 MEASUREITEMSTRUCTDRAWITEMSTRUCT 结构的 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 函数创建与应用程序main窗口使用的设备上下文兼容的设备上下文。

    函数的 hdc 参数可以指定 NULL 或函数的返回值。 CreateCompatibleDC 返回兼容设备上下文的句柄。

  2. 使用 CreateCompatibleBitmap 函数创建与应用程序的main窗口兼容的位图。

    此函数的 nWidthnHeight 参数设置位图的大小;它们应指定 GetSystemMetrics 函数返回的宽度和高度信息。

    注意

    还可以使用 CreateBitmap 函数创建单色位图。

     

  3. 使用 SelectObject 函数将位图选择到兼容的设备上下文中。

  4. 使用 GDI 绘制函数(如 EllipseLineTo)将图像绘制到位图中,或使用 BitBltStretchBlt 等函数将图像复制到位图中。

有关详细信息,请参阅 位图

将位图与菜单项相关联

通过将位图的句柄传递给 SetMenuItemBitmaps 函数,可将一对检查标记位图与菜单项相关联。 hBitmapUnchecked 参数标识清除位图,hBitmapChecked 参数标识所选位图。 如果要从菜单项中删除一个或两个检查标记,可以将 hBitmapUncheckedhBitmapChecked 参数或两者都设置为 NULL

设置复选标记属性

CheckMenuItem 函数将菜单项的 检查-mark 属性设置为选中或已清除。 可以指定MF_CHECKED值以将检查-mark 属性设置为选中,并指定MF_UNCHECKED值以将其设置为清除。

还可以使用 SetMenuItemInfo 函数设置菜单项的检查状态。

有时,一组菜单项表示一组互斥选项。 通过使用 CheckMenuRadioItem 函数,可以检查一个菜单项,同时从组中的所有其他菜单项中删除检查标记。

模拟菜单中的复选框

本主题包含一个示例,演示如何模拟菜单中检查框。 该示例包含一个“字符”菜单,其项允许用户设置当前字体的粗体、斜体和下划线属性。 当字体属性生效时,检查标记显示在相应菜单项旁边的检查框中;否则,该项旁边会显示一个空检查框。

该示例将默认检查标记位图替换为两个位图:一个位图,一个是选定检查框,一个是空框的位图。 当项目的检查标记属性设置为MF_CHECKED时,所选检查框位图显示在“粗体”、“斜体”或“下划线”菜单项旁边。 当 检查-mark 属性设置为 MF_UNCHECKED 时,将显示空检查框位图。

系统提供预定义的位图,其中包含用于检查框和单选按钮的图像。 该示例隔离选定和空检查框,将它们复制到两个单独的位图,然后将它们用作“字符”菜单中项的选定和清除位图。

为了检索系统定义的检查框位图的句柄,此示例调用 LoadBitmap 函数,将 NULL 指定为 hInstance 参数,并将 OBM_CHECKBOXES 指定为 lpBitmapName 参数。 由于位图中的图像大小相同,因此此示例可以通过将位图宽度和高度除以其行和列中的图像数来隔离它们。

资源定义文件的以下部分显示了如何定义 “字符 ”菜单中的菜单项。 请注意,最初没有字体属性生效,因此常规项的 检查-mark 属性设置为选中,并且默认情况下,其余项目的 检查-mark 属性设置为清除。

#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 函数。 对于“ 段落 ”菜单上的项,窗口过程调用应用程序定义的 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); 
}