メニュー・オーナードロー

メニュー・オーナードロー1

今回作成するアプリケーションは、アイコンを付け、メニューの選択をできるようにし、メニューの上にマウスをのせると、 色が反転するようにします。

メニューのフォントと色は、Windowsの設定を取得します。

メニュー・オーナードロー1 - インコのWindowsSDK で作成したソースを修正します。

流れ

#define _WIN32_WINNT 0x0501 を追加
これがないと、Windows XP、Windows 2000では、Windowsの設定から、メニューのフォントが取得できません。
これは、後で紹介するNONCLIENTMETRICS構造体が、Windows Vistaで追加されたためです。

WM_CREATE

1.NONCLIENTMETRICS構造体のサイズを取得し、NONCLIENTMETRICS構造体のメンバcbSizeにセット。

NONCLIENTMETRICS構造体は、メニューやタイトルバーに使われている情報を管理しています。

typedef struct tagNONCLIENTMETRICS {
    UINT cbSize;             // 構造体のサイズ
    int iBorderWidth;        // 境界線の幅
    int iScrollWidth;        // 垂直スクロールバーの幅
    int iScrollHeight;       // 水平スクロールバーの幅
    int iCaptionWidth;       // タイトルバーの幅
    int iCaptionHeight;      // タイトルバーの高さ
    LOGFONT lfCaptionFont;   // タイトルバーのフォント
    int iSmCaptionWidth;     // 小さいタイトルバーの幅
    int iSmCaptionHeight;    // 小さいタイトルバーの高さ
    LOGFONT lfSmCaptionFont; // 小さいタイトルバーのフォント
    int iMenuWidth;          // メニューバーの幅
    int iMenuHeight;         // メニューバーの高さ
    LOGFONT lfMenuFont;      // メニューバーのフォント
    LOGFONT lfStatusFont;    // ステータスバーのフォント
    LOGFONT lfMessageFont;   // メッセージボックスのフォント
#if (WINVER >= 0x0600)       // Windows Vista以降
    int iPaddedBorderWidth;
#endif 
} NONCLIENTMETRICS, 
 *LPNONCLIENTMETRICS;

2.SystemParametersInfo()で、Windowsのシステム全体に関するパラメータを取得・設定する
必ず、先にNONCLIENTMETRICS構造体のメンバcbSizeに構造体のサイズをセットしてください。

3.NONCLIENTMETRICS構造体のメンバlfMenuFontから、メニューのフォントを取得。

4.GetSystemMetrics()で、システム設定や画面構成についての情報を取得。

int GetSystemMetrics(
    int nIndex
);

nIndexに、SM_CXSMICONや、SM_CYSMICONを入れると、小さいアイコンの推奨サイズを取得できます。

5.LoadIcon()で、アイコンリソースをロード

6.GetSysColor()で、システムカラーを取得します。

DWORD GetSysColor(
    int nIndex   // システムカラーのインデックス
);

nIndex 色を取得するディスプレイ要素を指定
COLOR_SCROLLBAR スクロールバーの軸の色
COLOR_BACKGROUND
COLOR_DESKTOP
デスクトップの色
COLOR_ACTIVECAPTION アクティブウィンドウのタイトルバーの色
グラデーション有効の場合はアクティブウィンドウのタイトルバーの左側の色
COLOR_INACTIVECAPTION 非アクティブウィンドウのタイトルバーの色
グラデーション有効の場合は非アクティブウィンドウのタイトルバーの左側の色
COLOR_MENU メニューの背景色
COLOR_WINDOW ウィンドウの背景色
COLOR_WINDOWFRAME ウィンドウの枠の色
COLOR_MENUTEXT メニュー内のテキストの色
COLOR_WINDOWTEXT ウィンドウ内のテキストの色
COLOR_CAPTIONTEXT アクティブウィンドウのタイトルバーのテキストの色
COLOR_ACTIVEBORDER アクティブウィンドウの境界の色
COLOR_INACTIVEBORDER 非アクティブウィンドウの境界色
COLOR_APPWORKSPACE MDI(マルチドキュメントインターフェイス)アプリケーションの背景色
COLOR_HIGHLIGHT コントロール内における選択された項目の色
COLOR_HIGHLIGHTTEXT コントロール内における選択された項目のテキストの色
COLOR_BTNFACE
COLOR_3DFACE
3Dオブジェクトの表面色
COLOR_BTNSHADOW
COLOR_3DSHADOW
3Dオブジェクトの影の色
COLOR_GRAYTEXT 無効状態のテキストの色
COLOR_BTNTEXT プッシュボタンのテキストの色
COLOR_INACTIVECAPTIONTEXT 非アクティブウィンドウのタイトルバーのテキストの色
COLOR_BTNHIGHLIGHT
COLOR_3DHIGHLIGHT
COLOR_3DHILIGHT
COLOR_BTNHILIGHT
3Dオブジェクトの最も明るい色
COLOR_3DDKSHADOW 3Dオブジェクトの暗い影の色
COLOR_3DLIGHT 3Dオブジェクトの明るい色
COLOR_INFOTEXT ツールチップコントロールのテキストの色
COLOR_INFOBK ツールチップコントロールの背景色

WM_MEASUREITEM

WM_DRAWITEM

1.CreateFontIndirect()で、フォントハンドルにセット

2.SelectObject()で、指定されたデバイスコンテキストのオブジェクトを選択。

WM_DRAWITEM

3.LPDRAWITEMSTRUCT構造体の、itemDataメンバが指定するコントロールの項目 が、ODS_SELECTED(項目が選択状態のとき)のビットが立っているときは、 CreateSolidBrush()で、指定された色(メニュー選択時の色)の論理ブラシを作成。

HBRUSH CreateSolidBrush(
    COLORREF crColor   // ブラシの色を表す値
);

4.SetBkColor()で、現在の背景色を、指定された色の値に設定

5.SetTextColor()で、指定されたデバイスコンテキストのテキストの色を指定された色に設定

6.FillRect()で、指定されたブラシを使って、長方形を塗りつぶします。

int FillRect(
    HDC hDC,           // デバイスコンテキストのハンドル
    CONST RECT *lprc,  // 長方形
    HBRUSH hbr         // ブラシのハンドル
);

7.DeleteObject()で、作成したオブジェクトを削除。

8.DrawIconEx()で、指定されたアイコンまたはマウスカーソルを指定されたデバイスに描画。

BOOL DrawIconEx(
    HDC    hDC,         // 描画先のデバイスコンテキストのハンドル
    int    x,           // アイコンまたはマウスカーソルの左上端のx座標
    int    y,           // アイコンまたはマウスカーソルの左上端のy座標
    HICON  hIcon,       // アイコンまたはマウスカーソルのハンドル
    int    width,       // アイコンまたはマウスカーソルの実際の描画幅
    int    height,      // アイコンまたはマウスカーソルの実際の描画高さ
    UINT   iStepAniCur, // (アニメーションカーソル時)フレームインデックス
    HBRUSH hbrFlicker,  // ブラシのハンドル
    UINT   uFlags       // フラグ
);

引数 uFlags の定数機能
DI_MASKマスクを使ってアイコン、マウスカーソルを描画
DI_IMAGEイメージを使ってアイコン、マウスカーソルを描画
DI_NORMALDI_IMAGE と DI_MASK
DI_DEFAULTSIZEwidthメンバとheightメンバに 0 が指定されている場合、アイコンまたはマウスカーソルをデフォルトのサイズで描画

ソースコードの入力

ソースコードは下記のように入れてください。

test.cpp

#define _WIN32_WINNT 0x0501

#include <windows.h>
#include "resource.h"

#define MENU_WIDTH_PLUS 5

// グローバル変数:
HINSTANCE hInst;                          // 現在のインターフェイス
HMENU hMenu;                              // メニューのハンドル
LPMEASUREITEMSTRUCT lpMI;                 // LPMEASUREITEMSTRUCT構造体
LPDRAWITEMSTRUCT lpDI;                    // DRAWITEMSTRUCT構造体

// このコード モジュールに含まれる関数の宣言を転送します:
ATOM MyRegisterClass(HINSTANCE hInstance);
BOOL InitInstance(HINSTANCE, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR lpCmdLine,
                     int nCmdShow)
{
    MSG msg;
    MyRegisterClass(hInstance);

    // アプリケーションの初期化を実行します:
    if (!InitInstance (hInstance, nCmdShow))
    {
        return FALSE;
    }
    // メイン メッセージ ループ:
    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return (int) msg.wParam;
}

//
//  関数: MyRegisterClass()
//
//  目的: ウィンドウ クラスを登録します。
//
ATOM MyRegisterClass(HINSTANCE hInstance)
{
    WNDCLASSEX wcex;

    wcex.cbSize = sizeof(WNDCLASSEX);

    wcex.style = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc = WndProc;
    wcex.cbClsExtra = 0;
    wcex.cbWndExtra = 0;
    wcex.hInstance = hInstance;
    wcex.hIcon = LoadIcon(NULL , IDI_APPLICATION);
    wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
    wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
    wcex.lpszMenuName = MAKEINTRESOURCE(IDC_HP);
    wcex.lpszClassName = TEXT("HP");
    wcex.hIconSm = LoadIcon(NULL , IDI_APPLICATION);

    return RegisterClassEx(&wcex);
}

//
//   関数: InitInstance(HINSTANCE, int)
//
//   目的: メイン ウィンドウを作成します。
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
   HWND hWnd;

   hInst = hInstance; // グローバル変数にインスタンス処理を格納します。

   hWnd = CreateWindow(TEXT("HP"), TEXT("HP"), WS_OVERLAPPEDWINDOW,
      CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);

   if (!hWnd)
   {
      return FALSE;
   }

   ShowWindow(hWnd, nCmdShow);
   UpdateWindow(hWnd);

   return TRUE;
}

//
//  関数: WndProc(HWND, UINT, WPARAM, LPARAM)
//
//  目的:  メイン ウィンドウのメッセージを処理します。
//
//  WM_COMMAND - アプリケーション メニューの処理
//  WM_DESTROY - 中止メッセージを表示して戻る
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    int wmId, wmEvent;
    HDC hdc;
    RECT rc;
    POINT pt;
    SIZE sz;
    LPCTSTR str[] = {TEXT("メニュー1"), TEXT("メニュー2"), TEXT("メニュー3")};
    NONCLIENTMETRICS NCMetrics;
    HFONT hFont, hFontOld;
    static LOGFONT g_menufont;
    static int g_icon_vx, g_icon_vy;
    static DWORD g_menu_backcolor;
    static DWORD g_menu_textcolor;
    static HICON hIcon;
    HBRUSH hBrush;

    switch (message)
    {
        case WM_CREATE:
            hMenu = CreatePopupMenu();
            AppendMenu(hMenu, MF_OWNERDRAW, IDM_MENU1, (LPCTSTR)0);
            AppendMenu(hMenu, MF_OWNERDRAW, IDM_MENU2, (LPCTSTR)1);
            AppendMenu(hMenu, MF_OWNERDRAW, IDM_MENU3, (LPCTSTR)2);
            NCMetrics.cbSize = sizeof(NONCLIENTMETRICS);
            SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(NONCLIENTMETRICS),
                                   &NCMetrics, 0);
            g_menufont = NCMetrics.lfMenuFont;
            g_icon_vx = (int)GetSystemMetrics(SM_CXSMICON);
            g_icon_vy = (int)GetSystemMetrics(SM_CYSMICON);
            hIcon = LoadIcon(hInst, MAKEINTRESOURCE(IDI_TEST));
            g_menu_backcolor = GetSysColor(COLOR_HIGHLIGHT);
            g_menu_textcolor = GetSysColor(COLOR_HIGHLIGHTTEXT);
            break;

        case WM_MEASUREITEM:
            lpMI = (LPMEASUREITEMSTRUCT)lParam;
            hdc = GetDC(hWnd);
            hFont = CreateFontIndirect(&g_menufont);
            hFontOld = (HFONT)SelectObject(hdc, hFont);
            GetTextExtentPoint32(hdc,
                str[lpMI->itemData],
                lstrlen(str[lpMI->itemData]) - 1,
                &sz);
            lpMI->itemWidth = sz.cx + g_icon_vx + MENU_WIDTH_PLUS;
            lpMI->itemHeight = sz.cy + 5;
            ReleaseDC(hWnd, hdc);
            break;

        case WM_DRAWITEM:
            lpDI = (LPDRAWITEMSTRUCT)lParam;
            rc = lpDI->rcItem;
            hdc = lpDI->hDC;
            hFont = CreateFontIndirect(&g_menufont);
            hFontOld = (HFONT)SelectObject(hdc, hFont);
            switch (lpDI->itemID)
            {
                case IDM_MENU1:
                    if (lpDI->itemState & ODS_SELECTED)
                    {
                        hBrush = CreateSolidBrush(g_menu_backcolor);
                        SetBkColor(hdc, g_menu_backcolor);
                        SetTextColor(hdc, g_menu_textcolor);
                    }
                    else
                    {
                        hBrush = CreateSolidBrush(GetBkColor(hdc));
                    }
                    FillRect(hdc, &rc, hBrush);
                    DeleteObject(hBrush);
                    DrawIconEx(hdc, rc.left, rc.top + 1, hIcon, g_icon_vx, g_icon_vy, 0,
                      NULL, DI_IMAGE | DI_MASK);
                    TextOut(hdc, rc.left + g_icon_vx + MENU_WIDTH_PLUS, rc.top,
                              str[0], lstrlen(str[0]));
                    break;
                case IDM_MENU2:
                    if (lpDI->itemState & ODS_SELECTED)
                    {
                        hBrush = CreateSolidBrush(g_menu_backcolor);
                        SetBkColor(hdc, g_menu_backcolor);
                        SetTextColor(hdc, g_menu_textcolor);
                    }
                    else
                    {
                        hBrush = CreateSolidBrush(GetBkColor(hdc));
                    }
                    FillRect(hdc, &rc, hBrush);
                    DeleteObject(hBrush);
                    DrawIconEx(hdc, rc.left, rc.top + 1, hIcon, g_icon_vx, g_icon_vy, 0,
                      NULL, DI_IMAGE | DI_MASK);
                    TextOut(hdc, rc.left + g_icon_vx + MENU_WIDTH_PLUS, rc.top,
                              str[1], lstrlen(str[1]));
                    break;
                case IDM_MENU3:
                    if (lpDI->itemState & ODS_SELECTED)
                    {
                        hBrush = CreateSolidBrush(g_menu_backcolor);
                        SetBkColor(hdc, g_menu_backcolor);
                        SetTextColor(hdc, g_menu_textcolor);
                    }
                    else
                    {
                        hBrush = CreateSolidBrush(GetBkColor(hdc));
                    }
                    FillRect(hdc, &rc, hBrush);
                    DeleteObject(hBrush);
                    DrawIconEx(hdc, rc.left, rc.top + 1, hIcon, g_icon_vx, g_icon_vy, 0,
                      NULL, DI_IMAGE | DI_MASK);
                    TextOut(hdc, rc.left + g_icon_vx + MENU_WIDTH_PLUS, rc.top,
                              str[2], lstrlen(str[2]));
                    break;
            }
            SelectObject(hdc, hFontOld);
            DeleteObject(hFont);
            break;

        case WM_RBUTTONDOWN:
            pt.x = LOWORD(lParam); 
            pt.y = HIWORD(lParam);
            ClientToScreen(hWnd, &pt);
            TrackPopupMenu(hMenu, TPM_LEFTALIGN, pt.x, pt.y, 0, hWnd, NULL);
            break;

        case WM_COMMAND:
            wmId = LOWORD(wParam);
            wmEvent = HIWORD(wParam);
            // 選択されたメニューの解析:
            switch (wmId)
            {
                case IDM_MENU1:
                    MessageBox(hWnd, TEXT("メニュー1が押されました"), TEXT("確認"), MB_OK);
                    break;
                case IDM_MENU2:
                    MessageBox(hWnd, TEXT("メニュー2が押されました"), TEXT("確認"), MB_OK);
                    break;
                case IDM_MENU3:
                    MessageBox(hWnd, TEXT("メニュー3が押されました"), TEXT("確認"), MB_OK);
                    break;
                case IDM_EXIT:
                    DestroyWindow(hWnd);
                    break;
                default:
                    return DefWindowProc(hWnd, message, wParam, lParam);
            }
            break;

        case WM_DESTROY:
            PostQuitMessage(0);
            break;

        default:
            return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}

上記の太線で示している箇所のみ追加です。

resource.h
#define IDM_ABOUT 104
#define IDM_EXIT 105
#define IDI_TEST 107
#define IDM_MENU1 111
#define IDM_MENU2 112
#define IDM_MENU3 113
#define IDC_HP 109

上記の太線で示している箇所のみ追加です。

test.rc リソースファイル
#include "resource.h"

/////////////////////////////////////////////////////////////////////////////
//
// アイコン
//

IDI_TEST       ICON         "test.ico"


/////////////////////////////////////////////////////////////////////////////
//
// メニュー
//

IDC_HP MENU
BEGIN
    POPUP "ファイル(&F)"
    BEGIN
        MENUITEM "アプリケーションの終了(&X)", IDM_EXIT
    END
    POPUP "ヘルプ(&H)"
    BEGIN
        MENUITEM "バージョン情報(&A)...", IDM_ABOUT
    END
END

上記の太線で示している箇所のみ追加です。