リッチエディットでメモ帳を作る4: 右クリックでポップアップメニュー

リッチエディット4

今回はリッチエディットでポップアップメニューを出して、コピー、貼り付け、切り取りを実行できるようにします。 今回のように、ポップアップメニューを出さなくても、Ctrl + C でコピー、Ctrl + V で貼り付け、Ctrl + X で切り取りを実行できます。

今回は リッチエディット1 - インコのWindowsSDK で作成したソースを修正します。

流れ

1.richedit.hをインクルード

2.リッチエディットコントロールのイベントマスクを取得 (EM_GETEVENTMASK)して、 マウスイベント通知許可(ENM_MOUSEEVENTS)をセット。 最後に、リッチエディットコントロールのイベントマスクを設定 (EM_SETEVENTMASK)

WM_COMMAND

メニューを選択されたときの動作を入れる。 [WM_CUT(切り取り)]、 [WM_COPY(コピー)]、 [WM_PASTE(貼り付け)]

WM_NOTIFY

WM_NOTIFYメッセージは、コモンコントロールでイベントが起こった場合、およびコモンコントロールが情報を親ウィンドウに要求する場合に、コモンコントロールの親ウィンドウに送信されます。

・WM_NOTIFYメッセージ発生時の wParam [WndProc() の第3引数]の値・・・
イベントが発生したコモンコントロールのコントロールID
・WM_NOTIFYメッセージ発生時の lParam [WndProc() の第4引数]の値・・・
NMHDR構造体のアドレス

NMHDR構造体のidFromメンバが コントロールIDと同じかどうかで、メッセージを識別します。

typedef struct tagNMHDR {
    HWND hwndFrom;   // コントロールのハンドル
    UINT idFrom;     // コントロールID
    UINT code;       // 通知コード
} NMHDR;

WM_NOTIFYメッセージのlParamである NMHDR構造体の hwndFromメンバ、もしくは idFromメンバで、コントロールを識別します。

EN_MSGFILTER(WM_NOTIFYメッセージ形式)

EN_MSGFILTERは、コントロール内にキーボードやマウスのイベントが発生したときのメッセージ。

MSGFILTER構造体

typedef struct _msgfilter {
    NMHDR nmhdr;   // NMHDR構造体
    UINT msg;      // メッセージコード
    WPARAM wParam; // wParamパラメータ
    LPARAM lParam; // lParamパラメータ
} MSGFILTER;

msgメンバが、WM_RBUTTONDOWN(右クリック)のとき、lParamはクリックされた座標になります。
msgメンバが、WM_RBUTTONDOWNなら、下記1以降の処理をします。

1.LoadMenu()でメニューリソースをロード。
メニューは、リソースファイルにあります。

HMENU LoadMenu(
    HINSTANCE hInstance,  // メニューリソースのハンドル
    LPCTSTR lpMenuName    // メニューのID
);

2.GetSubMenu()で、ポップアップメニューのハンドルを取得

HMENU GetSubMenu(
    HMENU hMenu,  // メニューのハンドル
    int nPos      // メニュー項目の位置
);

3.ClientToScreen()で、 指定されたウィンドウ上の点の座標を、クライアント領域の座標からスクリーン座標に変換
座標は、右クリックされたところにしている。

●スクリーン座標・・・スクリーンの左上隅を原点 (0,0) とするX位置とY位置の座標

●クライアント座標・・・アプリケーションの左上隅を原点として相対指定される画面の座標

BOOL ClientToScreen(
    HWND hWnd,       // ウィンドウのハンドル
    LPPOINT lpPoint  // クライアント座標
);

POINT構造体は、座標を格納する構造体です。

typedef struct tagPOINT {
    LONG x;
    LONG y;
} POINT; 

4.TrackPopupMenu()でメニュー表示
このとき、TPM_LEFTALIGN (スクリーン位置とマウスボタンフラグクリック時のX座標をメニューの左辺にする) オプションを設定

BOOL TrackPopupMenu(
    HMENU hMenu,         // メニューのハンドル
    UINT uFlags,         // オプション
    int x,               // 水平位置
    int y,               // 垂直位置
    int nReserved,       // 0固定
    HWND hWnd,           // 親ウィンドウのハンドル
    CONST RECT *prcRect  // 未使用
);

引数 uType の定数 内容
TPM_CENTERALIGN ショートカットメニューの中心を、xパラメータが指定する座標とする
TPM_LEFTALIGN ショートカットメニューの左端を、xパラメータが指定する座標とする
TPM_RIGHTALIGN ショートカットメニューの右端を、xパラメータが指定する座標とする
TPM_BOTTOMALIGN ショートカットメニューの下端を、yパラメータが指定する座標とする
TPM_TOPALIGN ショートカットメニューの上端を、yパラメータが指定する座標とする
TPM_VCENTERALIGN ショートカットメニューの中心を、yパラメータが指定する座標とする

5.DestroyMenu()で、ロードしたメニューを破棄

BOOL DestroyMenu(
    HMENU hMenu  // メニューのハンドル
);

ソースコードの入力

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

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

// グローバル変数:
HINSTANCE hInst;                          // 現在のインターフェイス
// リッチエディットの使用するために用いる変数宣言
TCHAR strPath[MAX_PATH + 1];    // DLLのパス
HINSTANCE hRtLib;               // インスタンスハンドル
HWND hRichEdit;                 // ウィンドウハンドル

// このコード モジュールに含まれる関数の宣言を転送します:
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_CREATE  - ウインドウ作成時の処理
//  WM_SIZE    - ウインドウサイズ変更時の処理
//  WM_COMMAND - アプリケーション メニューの処理
//  WM_NOTIFY  - コントロールでイベント発生時の処理
//  WM_DESTROY - 中止メッセージを表示して戻る
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    int wmId, wmEvent;
    DWORD dwEvent;
    MSGFILTER *pmf;
    static HMENU hMenu, hSub;
    static int x, y;
    POINT pt;

    switch (message)
    {
        case WM_CREATE:
            // DLLのロード
            GetSystemDirectory(strPath , MAX_PATH + 1);
            wsprintf(strPath, TEXT("%s\\%s"), strPath, TEXT("RICHED20.DLL"));
            hRtLib = LoadLibrary((LPCTSTR)strPath);

            hRichEdit = CreateWindowEx(WS_EX_CLIENTEDGE,
                TEXT("RichEdit20A"), TEXT(""),
                WS_CHILD | WS_VISIBLE | WS_BORDER | ES_MULTILINE | WS_HSCROLL
                | WS_VSCROLL | ES_AUTOVSCROLL | ES_NOHIDESEL,
                0, 0, 0, 0,
                hWnd, (HMENU)IDC_RICHEDIT, hInst, NULL);
            // リッチエディットコントロールのイベントマスクを取得
            dwEvent = SendMessage(hRichEdit, EM_GETEVENTMASK, 0, 0);
            // マウスイベント通知許可
            dwEvent |= ENM_MOUSEEVENTS;
            // リッチエディットコントロールのイベントマスクを設定
            SendMessage(hRichEdit, EM_SETEVENTMASK, 0, (LPARAM)dwEvent);
            break;
        case WM_SIZE:
            MoveWindow(hRichEdit, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);
            break;
        case WM_COMMAND:
            wmId = LOWORD(wParam);
            wmEvent = HIWORD(wParam);
            // 選択されたメニューの解析:
            switch (wmId)
            {
                case IDM_CUT:
                    SendMessage(hRichEdit, WM_CUT, 0, 0);
                    break;
                case IDM_COPY:
                    SendMessage(hRichEdit, WM_COPY, 0, 0);
                    break;
                case IDM_PASTE:
                    SendMessage(hRichEdit, WM_PASTE, 0, 0);
                    break;
                case IDM_EXIT:
                    DestroyWindow(hWnd);
                    break;
                default:
                    return DefWindowProc(hWnd, message, wParam, lParam);
            }
            break;
        case WM_NOTIFY:
            switch (((NMHDR*)lParam)->code )
            {
                // コントロール内にキーボードやマウスのイベントが発生
                case EN_MSGFILTER:
                    pmf = (MSGFILTER *)lParam;  // lParamはMSGFILTER構造体へのポインタ
                    // MSGFILTER構造体のメンバmsgをみて、右クリックされているか判断
                    if (pmf->msg == WM_RBUTTONDOWN)
                    {
                        // MSGFILTER構造体のメンバlParamはクリックされたときの座標
                        x = LOWORD(pmf->lParam);
                        y = HIWORD(pmf->lParam);
                        hMenu = LoadMenu((HINSTANCE)GetWindowLong(hRichEdit, GWL_HINSTANCE),
                                          MAKEINTRESOURCE(IDR_MENU1));
                        hSub = GetSubMenu(hMenu, 0); // ポップアップメニューのハンドルを取得
                        pt.x = (LONG)x;
                        pt.y = (LONG)y;
                        //  クライアント領域の座標からスクリーン座標に変換
                        ClientToScreen(hRichEdit, &pt);
                        // メニュー表示
                        TrackPopupMenu(hSub,  TPM_LEFTALIGN,  pt.x, pt.y, 0, hWnd, NULL);
                        DestroyMenu(hMenu);                 // ロードしたメニューを破棄
                    }
                    break;
            }
            break;
        case WM_DESTROY:
            PostQuitMessage(0);
            break;
        default:
            return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}

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

resource.h
#define IDR_MENU1 112
#define IDM_ABOUT 104
#define IDM_EXIT 105
#define IDM_COPY 140
#define IDM_CUT  141
#define IDM_PASTE  142
#define IDC_HP 109
#define IDC_RICHEDIT 101

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

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

// 右クリックされたときのメニュー
IDR_MENU1 MENU
BEGIN
    POPUP "dummy"
    BEGIN
        MENUITEM "切り取り(&T)", IDM_CUT
        MENUITEM "コピー(&C)", IDM_COPY
        MENUITEM "貼り付け(&P)", IDM_PASTE
    END
END

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