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

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

オーナードロー(オーナー描写)を使って、右クリック時のポップアップメニューを作成します。
オーナードローは、アイテムの描画処理をプログラム側(オーナー)で行います。
今回作成するアプリケーションは、メニューの表示はできますが、メニューの選択はできません。メニューの上にマウスをのせても、 色が反転しません。

メニュー - インコのWindowsSDK で作成したソースを修正します。

流れ

WM_CREATE

1.CreatePopupMenu()でドロップダウンメニュー、サブメニュー、ショートカットメニューのいずれかを作成。メニューは初期状態では空です。

2.AppendMenu()でメニューの末尾に新しいメニュー項目を追加。 InsertMenuItem()を使うことが推奨されていますが、ここでは、簡単に使える、AppendMenu()を使用します。

BOOL AppendMenu(
    HMENU  hMenu,   // メニューハンドル
    UINT   uFlags,  // オプションフラグ
    UINT   uIDItem, // アイテムIDまたはサブメニューハンドル
    PCTSTR pItem    // 表示する文字列
);

hMenu メニューのハンドル
uFlags メニューアイテムのオプション
MF_STRING
MF_ENABLED
MF_UNCHECKED
デフォルト表示
MFS_GLAYED
MFS_DISABLED
アイテムを灰色で表示し、選択不可に
MF_BITMAP ビットマップを使用
MFS_CHECKED チェックマークつき
MF_POPUP ドロップダウンメニューまたはサブメニューを開くアイテムを追加
MF_MENUBARBREAK メニューアイテムを新しい行に追加
新しい列との間に線が引かれます。(メニューバーを除く)
MF_MENUBREAK メニューアイテムを新しい行に追加
新しい列との間に線が引かれません。
MF_OWNERDRAW オーナードロー
MF_SEPARATOR セパレーター(区切り線)を表示
uIDItem アイテムのIDを指定
・uFlagsメンバでMF_POPUPが指定されているとき・・・メニューのハンドルを指定
pItem メニューアイテムに表示する文字列のアドレス
・uFlagsメンバでMF_BITMAPが指定されているとき・・・ビットマップのハンドル
・uFlagsメンバでMF_OWNERDRAWが指定されているとき・・・メニューアイテムに関する追加データを取得するために使用されるアプリケーション定義の値を指定します。 (メニューが作成・表示の更新の際に送られるWM_MEASUREITEMおよびWM_DRAWITEMメッセージのlParamパラメータが指す構造体のitemDataメンバの値)

WM_MEASUREITEM

WM_MEASUREITEMメッセージは、コントロール(ここではメニュー)が作成されたときに、 コントロール(ここではメニュー)の数だけ実行されます。

1.WM_MEASUREITEMメッセージのlParamは、MEASUREITEMSTRUCT構造体へのポインタなので、これを用意した変数にセット。

typedef struct tagMEASUREITEMSTRUCT {
    UINT CtlType;    // コントロールの種類
    UINT CtlID;      // コンボボックス・リストボックス・ボタンのコントロールID
    UINT itemID;     // メニューアイテム・可変高さのリストボックス・コンボボックスの項目ID
    UINT itemWidth;  // メニュー項目の幅
    UINT itemHeight; // リストボックス、コンボボックス、メニューの各項目の高さ
    DWORD itemData   // アイテムデータ 
} MEASUREITEMSTRUCT;

2.GetDC()で、スクリーン全体のデバイスコンテキストのハンドルを取得

HDC GetDC(
    HWND hWnd     // ウィンドウハンドル
);

3.GetTextExtentPoint32()で、文字列の幅と高さを計算

BOOL GetTextExtentPoint32(
    HDC hdc,           // デバイスコンテキストのハンドル
    LPCTSTR lpString,  // 文字列
    int cbString,      // 文字列内の文字数
    LPSIZE lpSize      // 文字列のサイズ(幅と高さ)を受け取るSIZE構造体へのポインタ
);

この関数を実行すると、lpSizeメンバに指定したSIZE構造体に文字列の幅と高さがセットされます。

typedef struct tagSIZE {
    LONG cx;
    LONG cy;
} SIZE; 

4.GetTextExtentPoint32()で、セットした構造体から、文字列の幅と高さを LPMEASUREITEMSTRUCT構造体の、itemWidthメンバと、itemHeightメンバにセット

BOOL GetTextExtentPoint32(
    HDC hdc,           // デバイスコンテキストのハンドル
    LPCTSTR lpString,  // 文字列
    int cbString,      // 文字列内の文字数
    LPSIZE lpSize      // 文字列のサイズ(幅と高さ)を受け取る構造体へのポインタ
);

5.ReleaseDC()で、デバイスコンテキストを解放。

int ReleaseDC(
    HWND hWnd,  // ウィンドウハンドル
    HDC hDC     // デバイスコンテキストのハンドル
);

WM_DRAWITEM

WM_DRAWITEMメッセージは、オーナードローをしたアイテムが、描画・再描画されるときに実行されます。
今回の場合は、メニューの1つの項目を描画・再描画されるときに実行されます。

1.WM_DRAWITEMメッセージのlParamは、LPDRAWITEMSTRUCT構造体へのポインタなので、これを用意した変数にセット。

typedef struct tagDRAWITEMSTRUCT {
    UINT CtlType;    // コントロールの種類
    UINT CtlID;      // コンボボックス、リストボックス、ボタンのコントロールID。
                     //   メニューでは、CtlIDメンバは使用されません。
    UINT itemID;     // メニューのメニュー項目ID
                     //  またはリストボックスやコンボボックスの項目のインデックス
    UINT itemAction; // 要求される描画動作を定義
    UINT itemState;  // 現在の描画動作が行われた後の項目の表示状態を指定
    HWND hwndItem;   // コンボボックス、リストボックス、ボタンのコントロールのウィンドウハンドル
    HDC hDC;         // デバイスコンテキスト
    RECT rcItem;     // 描画されるコントロールの境界を定義する四角形
    ULONG_PTR itemData;   // アイテムデータ
} DRAWITEMSTRUCT, NEAR *PDRAWITEMSTRUCT, FAR *LPDRAWITEMSTRUCT;

2.LPDRAWITEMSTRUCT構造体の、rcItemメンバ(RECT構造体)と、hDCメンバを別に用意した変数にセット。

3.LPDRAWITEMSTRUCT構造体の、itemDataメンバが指定するコントロールの項目 (今回の場合は、メニュー項目)ごとに、TextOut()で、文字列を表示します。

今回の場合、TextOut()の第2引数nXStart、第3引数nYStartには、LPDRAWITEMSTRUCT構造体のrcItemメンバを使用します。

WM_RBUTTONDOWN

1.ClientToScreen()で、 指定されたウィンドウ上の点の座標を、クライアント領域の座標からスクリーン座標に変換

2.TrackPopupMenu()でメニュー表示

ソースコードの入力

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

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

// グローバル変数:
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;

   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")};

    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);
            break;

        case WM_MEASUREITEM:
            lpMI = (LPMEASUREITEMSTRUCT)lParam;
            hdc = GetDC(hWnd);
            GetTextExtentPoint32(hdc,
                str[lpMI->itemData],
                lstrlen(str[lpMI->itemData]) - 1,
                &sz);
            lpMI->itemWidth = sz.cx;
            lpMI->itemHeight = sz.cy;
            ReleaseDC(hWnd, hdc);
            break;

        case WM_DRAWITEM:
            lpDI = (LPDRAWITEMSTRUCT)lParam;
            rc = lpDI->rcItem;
            hdc = lpDI->hDC;
            switch (lpDI->itemID)
            {
                case IDM_MENU1:
                    TextOut(hdc, rc.left, rc.top, str[0], lstrlen(str[0]));
                    break;
                case IDM_MENU2:
                    TextOut(hdc, rc.left, rc.top, str[1], lstrlen(str[1]));
                    break;
                case IDM_MENU3:
                    TextOut(hdc, rc.left, rc.top, str[2], lstrlen(str[2]));
                    break;
            }
            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_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 IDM_MENU1 111
#define IDM_MENU2 112
#define IDM_MENU3 113
#define IDC_HP 109

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

test.rc リソースファイル (変更なし)
#include "resource.h"

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

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