第44章 右クリックで出てくるメニュー


今回は、クライアント領域を右クリックすると出てくる ポップアップメニューを作成します。

右の図のようにマウスを右クリックした場所に メニューが出現します。前回やったアクセラレーターも ちゃんと組み込んであります。このメニューによっていろいろな 絵を出すプログラムを作ります。

オー、こりぁすごい!

これも作り方は簡単です。

簡単に手順を説明します。

1.普通通りメニューリソースを作ります。ただし第1階層目は   表示されません。(ダミーです) 2.LoadMenu関数でメニューリソースをロードします。 3.GetSubMenu関数で、ポップアップメニューのハンドルを取得します。 4.TrackPopupMenu関数でメニュー表示します。 5.DestroyMenu関数で後始末をします。

たったこれだけです。2から4までをいつ実行するかで メニューの出てくるタイミングを変えることができます。 右クリックをしたときに出てくるようにしたければ ウィンドウプロシージャのWM_RBUTTONDOWNのところで 実行すればよいですね。また、マウスの位置によってロードする メニューを変えるという手の込んだこともできます。 でも今回はクライアント領域で右クリックしたときにその場所に メニューを出すという簡単なものです。
まず、右クリックしたときにメニューが出るので WM_RBUTTONDOWNメッセージについて簡単に調べてみましょう。

WM_RBUTTONDOWN fwKeys = wParam; // キーフラグ xPos = LOWORD(lParam); // マウスカーソルのX座標 yPos = HIWORD(lParam); // Y座標

WM_RBUTTONDOWNメッセージを捕まえたとき、lParamを調べると マウスの位置がわかります。またwParamを調べると同時に シフトキーが押されているかどうかなどもわかります。

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

これで、メニューリソースをロードします。

HMENU GetSubMenu( HMENU hMenu, // メニューハンドル int nPos // メニューアイテムの位置 );

これで、ポップアップメニューのハンドルを取得します。 int nPosには、ポップアップメニューのどこのメニューアイテムからかを 指定します。最初からなら0になります。(次から1,2,...となる)

BOOL TrackPopupMenu( HMENU hMenu, // メニューハンドル UINT uFlags, // スクリーン位置とマウスボタンフラグ int x, // スクリーン位置のX座標 int y, // スクリーン位置のY座標 int nReserved, // 予約済み 0を入れること HWND hWnd, // オーナーウィンドウのハンドル CONST RECT *prcRect // RECT構造体のアドレス );

この関数によってポップアップメニューを表示します。

メニューハンドルには、ポップアップメニューのハンドルを指定します。 (LoadMenuで取得したハンドルではない!GetSubMenuで取得したハンドルです)

uFlagsには、ポップアップメニューの表示する位置とxバラメータの関係を 指定します。通常はxの位置にメニューの左端が来るのが普通ですね。 従ってTPM_LEFTALIGNを指定します。

x, yはメニューの表示位置ですがこれはスクリーン座標のx, y座標です。 WM_RBUTTONDOWNのlParamから得られる座標はそのウィンドウでの座標 (クライアント座標といいます)です。 従ってlParamで得た座標を変換する必要があります。 それが次に示すClientToScreen関数です。

BOOL ClientToScreen( HWND hWnd, // ウィンドウハンドル LPPOINT lpPoint // POINT構造体のアドレス );

POINT構造体は32ビット版では、

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

のように定義されています。

16ビット版では

typedef struct tagPOINT { /* pt */ int x; int y; } POINT;

というように定義されています。現在のクライアント座標を POINT構造体に納めてClientToScreen関数を実行すると スクリーン座標に変換されます。

最後にロードしたメニューを破棄します。

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

これだけの予備知識があれば、もう簡単ですね。
それでは、さっそくサンプルのプログラムを作ってみましょう。

まず、新規プロジェクトを作成します。

プログラム中で使うメニューリソースを作ります。

ビットマップリソースを作ります。(猫、犬、ネズミ)

アクセラレーターテーブルを作ります。

リソーススクリプトを*.rcで保存します。

プロジェクトにリソーススクリプトを参加させます。

(まだ作っていないが)ソースファイルをプロジェクトに参加させます。



念のため、メニュー部分とアクセラレータ部分及びビットマップ部分のスクリプトを 抜粋しておきます。

///////////////////////////////////////////////////////////////////////////// // // Menu // MYPOPUP MENU DISCARDABLE BEGIN POPUP "表示されません" BEGIN MENUITEM "終了\tShift + Ctrl + X", IDM_END POPUP "オプション" BEGIN MENUITEM "猫の絵\tF1", IDM_CAT MENUITEM "犬の絵\tF2", IDM_DOG MENUITEM "ネズミの絵\tF3", IDM_RAT MENUITEM "消去\tEsc", IDM_CLR END END END ///////////////////////////////////////////////////////////////////////////// // // Accelerator // MYACCEL ACCELERATORS DISCARDABLE BEGIN VK_ESCAPE, IDM_CLR, VIRTKEY, NOINVERT VK_F1, IDM_CAT, VIRTKEY, NOINVERT VK_F2, IDM_DOG, VIRTKEY, NOINVERT VK_F3, IDM_RAT, VIRTKEY, NOINVERT "X", IDM_END, VIRTKEY, SHIFT, CONTROL, NOINVERT END ///////////////////////////////////////////////////////////////////////////// // // Bitmap // MYCAT BITMAP DISCARDABLE "mycat.bmp" MYDOG BITMAP DISCARDABLE "mydog.bmp" MYRAT BITMAP DISCARDABLE "myrat.bmp"

リソーススクリプトを手書きする人は、上のをコピーして使ってください。 そして、最初に

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

を忘れずにつけてください。***.hはシンボル定義のヘッダーで IDM_CLRなどの値を自分で適当に定義しておいてください。 なお、自分でリソーススクリプトを作った人は、ソースファイルの

#include "resource.h"

を自分で作った***.hで置き換えてください。

また、mycat.bmp, mydog.bmp, myrat.bmpは次のような画像です。

mycat.bmp mydog.bmp myrat.bmp

今までの準備がうまくいくとファイルビューは左のような 構成になります。(VC++5.0の場合)

リソースビューは左のようになります。

さて、準備が整ったらソースファイル(skd44.cpp)の 中身を書いていきます。

// sdk44.cpp #include <windows.h> #include "resource.h" LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); BOOL InitApp(HINSTANCE); BOOL InitInstance(HINSTANCE, int); void DrawPic(HWND, HDC, int); char szClassName[] = "sdk44"; //ウィンドウクラス HWND hParent; //親ウィンドウハンドルの保存 HACCEL hAccel; //アクセラレーターテーブルハンドル enum{clr, cat, dog, rat}; int WINAPI WinMain(HINSTANCE hCurInst, HINSTANCE hPrevInst, LPSTR lpsCmdLine, int nCmdShow) { MSG msg; if (!hPrevInst) { if (!InitApp(hCurInst)) return FALSE; } if (!InitInstance(hCurInst, nCmdShow)) { return FALSE; } hAccel = LoadAccelerators(hCurInst, "MYACCEL"); while (GetMessage(&msg, NULL, 0, 0)) { if (!TranslateAccelerator(hParent, hAccel, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } } return msg.wParam; }

アクセラレータ部分がいつもとちょっと違います。 enumについてわからない人はC言語編第46章を 参照してください。

//ウィンドウ・クラスの登録 BOOL InitApp(HINSTANCE hInst) { WNDCLASS wc; wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = WndProc; //プロシージャ名 wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInst; //インスタンスハンドル wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = GetStockObject(WHITE_BRUSH); wc.lpszMenuName = NULL; //メニュー名 wc.lpszClassName = (LPCSTR)szClassName; return (RegisterClass(&wc)); }

ここはいつもと同じです。

//ウィンドウの生成 BOOL InitInstance(HINSTANCE hInst, int nCmdShow) { HWND hWnd; hWnd = CreateWindow(szClassName, "猫でもわかるメニュー", //タイトルバーにこの名前が表示されます WS_OVERLAPPEDWINDOW, //ウィンドウの種類 CW_USEDEFAULT, //X座標 CW_USEDEFAULT, //Y座標 200, //幅 100, //高さ NULL, //親ウィンドウのハンドル、親を作るときはNULL NULL, //メニューハンドル、クラスメニューを使うときはNULL hInst, //インスタンスハンドル NULL); if (!hWnd) return FALSE; ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); hParent = hWnd; //ウィンドウハンドルの保存(忘れないでね) return TRUE; }

ここも、ほとんど同じですが親ウィンドウができたらこのハンドルを グローバル変数にコピーしておきます。 (WinMain関数の中でこれが必要になってくる)

//ウィンドウプロシージャ LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) { int id; static int sw = 0; POINT pt; HMENU hmenu, hSubmenu; HDC hdc; PAINTSTRUCT ps; switch (msg) { case WM_COMMAND: switch (LOWORD(wp)) { case IDM_END: SendMessage(hWnd, WM_CLOSE, 0L, 0L); break; case IDM_CAT: sw = cat; InvalidateRect(hWnd, NULL, TRUE); break; case IDM_DOG: sw = dog; InvalidateRect(hWnd, NULL, TRUE); break; case IDM_RAT: sw = rat; InvalidateRect(hWnd, NULL, TRUE); break; case IDM_CLR: sw = clr; InvalidateRect(hWnd, NULL, TRUE); break; default: return (DefWindowProc(hWnd, msg, wp, lp)); } break; case WM_RBUTTONDOWN: pt.x = LOWORD(lp); pt.y = HIWORD(lp); hmenu = LoadMenu((HINSTANCE)GetWindowLong(hWnd, GWL_HINSTANCE), "MYPOPUP"); hSubmenu = GetSubMenu(hmenu, 0); ClientToScreen(hWnd, &pt); TrackPopupMenu(hSubmenu, TPM_LEFTALIGN, pt.x, pt.y, 0, hWnd, NULL); DestroyMenu(hmenu); break; case WM_PAINT: hdc = BeginPaint(hWnd, &ps); DrawPic(hWnd, hdc, sw); EndPaint(hWnd, &ps); break; case WM_CLOSE: id = MessageBox(hWnd, (LPCSTR)"終了してもよいですか", (LPCSTR)"終了確認", MB_YESNO | MB_ICONQUESTION); if (id == IDYES) { DestroyWindow(hWnd); } break; case WM_DESTROY: PostQuitMessage(0); break; default: return (DefWindowProc(hWnd, msg, wp, lp)); } return 0L; }

右ボタンが押されたときの処理をよく見ておいてください。 現在のクライアント座標をPOINT構造体に格納しておいて ClientToScreen関数でスクリーン座標に変換しているのがミソです。 また、lpをそのまま使ってPOINT構造体に格納していますが、 16ビット版ではMAKEPOINTマクロというのがあって

MAKEPOINT(lp)

でPOINT構造体に格納できました。32ビット版では使えません。

void DrawPic(HWND hWnd, HDC hdc, int sw) { HDC hmdc; HBITMAP hBitmap; BITMAP bmp; HINSTANCE hInst; int BMP_W, BMP_H; hInst = (HINSTANCE)GetWindowLong(hWnd, GWL_HINSTANCE); switch (sw) { case clr: return; case cat: hBitmap = LoadBitmap(hInst, "MYCAT"); break; case dog: hBitmap = LoadBitmap(hInst, "MYDOG"); break; case rat: hBitmap = LoadBitmap(hInst, "MYRAT"); break; } GetObject(hBitmap, sizeof(BITMAP), &bmp); BMP_W = (int)bmp.bmWidth; BMP_H = (int)bmp.bmHeight; hmdc = CreateCompatibleDC(hdc); SelectObject(hmdc, hBitmap); BitBlt(hdc, 0, 0, BMP_W, BMP_H, hmdc, 0, 0, SRCCOPY); DeleteDC(hmdc); return; }

ただ、単にビットマップリソースを表示するだけの関数ですが、 swの値で場合分けしているのがミソです。 clr, cat, dog, ratというのは最初に出てきた enumです。clrの時は何もせずにもどります。


[SDK Index] [総合Index] [Previous Chapter] [Next Chapter]

Update Jun/08/1997 By Y.Kumei
当ホーム・ページの一部または全部を無断で複写、複製、 転載あるいはコンピュータ等のファイルに保存することを禁じます。