トップWindows入門 > メニューの作成

メニューの作成

メニューの概要は「Windowsプログラミングの基礎」で触れた。 ここでは、メニューの作成について、より詳細に述べる。

1.メニューの作成

メニューは、次に示すMENUITEM構造体にデータをセットし、InsertMenuItem関数で作成する。

typedef struct tagMENUITEMINFO {                                        fMask 
   UINT    cbSize;        // 構造体のサイズ                             -- 
   UINT    fMask;         // 設定・変更用フラグ                         -- 
   UINT    fType;         // メニュー項目の種類                         MIIM_TYPE 
   UINT    fState;        // メニュー項目の状態                         MIIM_STATE 
   UINT    wID;           // アプリケーション定義の16ビット値           MIIM_ID 
   HMENU   hSubMenu;      // サブメニューのハンドル、なければNULL       MIIM_SUBMENU 
   HBITMAP hbmpChecked;   // チェック用画像のハンドル                   MIIM_CHECKMARKS 
   HBITMAP hbmpUnchecked; // チェックオフ用画像のハンドル               MIIM_CHECKMARKS 
   DWORD   dwItemData;    // 項目に結び付けられたアプリケーション定義値 MIIM_DATA 
   LPTSTR  dwTypeData;    // ヌル文字で終わる文字列                     MIIM_STRING 
   UINT    cch;           // dwTypeDataの長さ                           MIIM_STRING 
   HBITMAP hbmpItem;      // 表示するビットマップのハンドル             MIIM_BITMAP 
} MENUITEMINFO, *LPMENUITEMINFO; 

プログラム例とその実行結果を下に示す。 プログラムで &X、&A などと記述した時、X、Aはショートカットキーと呼ばれるもので、 Altキーを押すと、メニュー上の X、A に下線(アンダーバー)が現れる。 この状態で X とか A をキーインすると、マウスでそのメニュー項目をクリックしたのと同じ働きをする。

セパレーターというのは仕切り線である。 色が薄いので目立たないが、右図には仕切り線が表示されている。

 

2.階層メニュー

サブメニューに更に子メニューを持つのも簡単である。 "Menu 1.2" に子供メニュー "Menu 1.2.1" と "Menu 1.2.2" を持たせたプログラムを以下に示す。

"Menu 1.2" を "Menu 1", "Menu 2" と同様のポップアップメニューとすればよい。 "Menu 1", "Menu 2" の親メニューのハンドルは hMenu であるのに対して、 "Menu 1.2" の親メニューのハンドルは hSubMenu1 である。

 

3.チェックマーク付きメニュー

下図に示すようなチェックマーク(チェックボックス)メニューを時々目にするであろう。 チェックが付いたときと無しのときでメニューの文字列が変わるものもある。

最初からメニュー項目にチェックマークを付けるには、フラグに MIIM_STATE を加え、 mii.fState の値を MFS_CHECKED にすればよい。

void appendMenuItem(HMENU hmenu, int nId, char *lpszItemName, HMENU hmenuSub) {
    MENUITEMINFO mii = { sizeof(MENUITEMINFO), 
                MIIM_ID|MIIM_TYPE|MIIM_SUBMENU|MIIM_STATE, 
                lpszItemName!=NULL? MFT_STRING:MFT_SEPARATOR, MFS_CHECKED, nId, hmenuSub };
    mii.dwTypeData = lpszItemName;
    InsertMenuItem(hmenu, nId, FALSE, &mii);
}

しかし、チェックボックス付きのメニュー項目は少ないことが多い。 このような場合、メニューの生成時にはチェックマークをつけず、 必要に応じて特定の項目にだけチェックマークをつける。

また、通常はチェックマークはトグルスイッチ的であり、メニュー項目をクリックするごとに、 チェックが付いたり、外れたりする。

後からチェックマークを付けたり、状態を変更するには次のようにする。 下のプログラムは実行するごとに状態が変わる。

  MENUITEMINFO mii = { sizeof(MENUITEMINFO), MIIM_STATE };
  GetMenuItemInfo(hMenu, wp, FALSE, &mii);
  mii.fState = mii.fState==MFS_CHECKED ? MFS_UNCHECKED : MFS_CHECKED;
  SetMenuItemInfo(hMenu, wp, FALSE, &mii);

ここで、hMenu はメニューバーのハンドルである。 wpはメニュー項目のID(MENUITEMINFO構造体のwID要素の値)である。 GetMenuItemInfo関数で現在の状態が取り出せる。 状態を変えて、SetMenuItem関数で新しい状態をセットしている。

プログラム全体を menu03.c に示す。 最初は左図であるが、クリックすると、次にメニューを開いた時には右図になる。

 

本論から外れるが、チェックを入れたらある機能が有効になる場合、 チェックがないとき "有効" と表示されるケースをよく目にする(上の図とは逆)。 これは、現在の状態を表示しているのではなく、"有効" にしたいときクリックしなさい、 という意味である。従って、現在有効なとき "無効" と表示される。 個人的には気に入らない表記方法なため、自分は有効/無効表記はしない。

4.アイコン付きメニュー

下図(再掲)に示すように、メニュー項目の文字列の前に、アイコンを置くことができる。 また、メニューバーの右端ののように、トップレベルにアイコンを置くこともできる。

これを実現するには、メニュー項目登録関数に HBITMAP hbmp 引数を追加すればよい。

void appendMenuItem(HMENU hmenu, int nId, char *lpszItemName, HMENU hmenuSub, HBITMAP hbmp) {
    MENUITEMINFO mii = { sizeof(MENUITEMINFO), MIIM_ID|MIIM_TYPE|MIIM_SUBMENU, 
                         lpszItemName!=NULL? MFT_STRING:MFT_SEPARATOR, 0, nId, hmenuSub };
    mii.dwTypeData = lpszItemName;
    if (hbmp != NULL) mii.fMask |= MIIM_BITMAP;
    mii.hbmpItem = hbmp;
    InsertMenuItem(hmenu, nId, FALSE, &mii);
}

ここまでは簡単である。 問題はこのHBITMAPが 32ビットARGB形式の bmp というWindowsにとっては比較的新しくサポートを始めた 画像フォーマットであることである。インターネットでは jpg, png, gif、 Windowsパソコンでは ico および RGB形式の bmp が主流である。

ツールソフトで何とか 32ビットARGB形式の bmpファイルに変換したとしても、LoadImage関数で

    hbmp = (HBITMAP)LoadImage(NULL,"argb32.bmp",IMAGE_BITMAP,0,0,LR_LOADFROMFILE);
として、読み込めるわけではなさそうである。ちょっとネット情報を読んだ限りでは ARGB形式のAに当たる部分がカットされてしまうようだ。(正確ではなく、誤解があるかも知れない。)

自作のランチャーでは次のように、icoファイルを読み込み、メモリ上で ARGB形式に変換している。

HBITMAP CreateBitmapARGB(int nWidth, int nHeight){
    LPVOID lpBits;
    BITMAPINFO bmi = { { sizeof(BITMAPINFOHEADER), nWidth, nHeight, 1, 32 } };
    return CreateDIBSection(NULL, (BITMAPINFO*)&bmi, DIB_RGB_COLORS, &lpBits, NULL, 0);
}

HBITMAP loadIcon(char *name) {
    char path[260];
    UINT uWidth  = GetSystemMetrics(SM_CXSMICON);       // ウィンドウタイトルバーなどの
    UINT uHeight = GetSystemMetrics(SM_CYSMICON);       // アイコンのサイズ

    int noused = sprintf(path, "/mh/mcc/ico/%s.ico", name);
    HICON hicon = LoadImage(NULL, path, IMAGE_ICON, 0, 0, LR_LOADFROMFILE);
    HBITMAP hbmp = CreateBitmapARGB(uWidth, uHeight);
    HDC hdcMem = CreateCompatibleDC(NULL);
    HBITMAP hbmpPrev = SelectObject(hdcMem, hbmp);
    DrawIconEx(hdcMem, 0, 0, hicon, uWidth, uHeight, 0, NULL, DI_NORMAL);
    SelectObject(hdcMem, hbmpPrev);
    DeleteDC(hdcMem);
    return hbmp;
}

5.オーナードローによるメニュー

描画をオーナーすなわち親ウィンドウで行うのがオーナードローである。

下のプログラムはメニューバーに置くトップレベルは標準メニューで、 その下のサブメニューをオーナードローとしている。 まず、メニューを生成するときに、タイプを MFT_OWNERDRAW とする。 それにより、親ウィンドウに WM_MESUREITEMメッセージおよびWM_OWNERDRAWメッセージが送られてくる。

WM_MESUREITEMメッセージ処理でメニュー項目のサイズを決める。 プログラムではアイコンサイズと文字列領域のサイズにマージンを加えている。

WM_OWNERDRAWメッセージで描画を行う。 ARGB形式に変換する必要はないので、前節で述べた方法よりもアイコン自体の描画は簡単である。 しかし、標準メニューの場合、マウスカーソルがメニュー項目の上にいくと、 その項目が矩形で囲まれ、背景色も薄く色が変わり、見栄えの良いインタフェースとなっている。 オーナードローで似たようなことをするにはその分行数も増える。

このため、ARGB形式に変換する必要はなくなるが、トータル的にはプログラムは嵩んでくるので、 オーナードローは標準メニューでは実現できない機能を実現する場合に限って利用する方が賢明であろう。

6.ポップアップ・メニュー

例えば、コマンドプロンプト・ウィンドウでマウスの右ボタンをクリックすると、 下図に示すようなポップアップ・メニューが現れる。

このようなポップアップ・メニューの実装は簡単である。 メニューバーを作らず、CreatePopupMenu関数で作成するメニューが最上位のメニューとする。

WM_RBUTTONDOWNメッセージ処理でマウスカーソルのクライアント・ウィンドウ座標を得て、 ClientToScreen関数でスクリーン座標に変換する。そして、 TrackPopupMenu関数でメニューを表示させればよい。