33章 メニューとキーボードアクセラレーター(その5)

<html>

<head>
<meta http-equiv="Content-Type" content="text/html; charset=shift_jis"></meta>
<title>33章 メニューとキーボードアクセラレーター(その5)
</title>
</head>

<body bgcolor="WHITE">
<font size="5">33章 メニューとキーボードアクセラレーター(その5)
</font>
<hr>
<p>
 さて、今回は、メニューの状態を不活性にしたりチェックを入れたりする為の関数群について説明します。一部、13章で説明しましたが、より詳細に説明します。
<p align="CENTER"><img src="/web/20150120021321im_/http://web.kyoto-inet.or.jp:80/people/ysskondo/from29/chap33.gif">
<p>
 メニュー項目を変更さすサンプルを3つ用意しました。全部、外見は同じです。まず、<a href="chap33.lzh">ダウンロード</a>してください。今回のサンプルは、判読性を考えて、キーボードアクセラレーターを実装していません。
<hr>
<p>
 最初に、共通のメニューリソーススクリプトを提示し、最も標準的な方法と思われるサンプルのソースを提示しましょう。
<p align="CENTER">
<table border="1" bgcolor="#F0F0F0">
<caption align="BOTTOM">共通のリソーススクリプト
<tr><td>
<pre>

IDR_MENU1 MENU DISCARDABLE
BEGIN
  POPUP "サブメニュー"
   BEGIN
       MENUITEM "サブメニューのアイテム0(チェック)", ID_SUB0
       MENUITEM "サブメニューのアイテム1(不可)", ID_SUB1
       MENUITEM "サブメニューのアイテム2(グレー)", ID_SUB2
       MENUITEM "サブメニューのアイテムの状態を全て初期状態に戻す", ID_SUB3
   END
END
</pre>
</table>

<p>
<table border="1" bgcolor="#F0F0F0">
<caption align="BOTTOM">リスト33-1
<tr><td>
<pre>
/* C言語で始めるWindowsプログラミング */
/* 33章のサンプルプログラム(その1) */
/* 最も、一般的と思われる方法 */
/* Programmed by Y.Kondo */
/* 注:TABサイズは4で見てください */
/* このファイルでは、メインウインドウのウインド*/
/*ウプロシージャが定義されている */

#define STRICT
#include &lt;windows.h&gt;
#include &lt;stdio.h&gt;
#include "wndproc.h"
#include "resource.h"

/*===============================================================================*/

/* このファイル内でのみ用いられる関数のプロトタイプ宣言 */
static LRESULT Wm_CommandProc(HWND,WORD,WORD,HWND);
static LRESULT Wm_DestroyProc(void);

/* メインウインドウのウインドウプロシージャ */
LRESULT CALLBACK WindowProc(HWND hwnd,UINT message,WPARAM wparam,LPARAM lparam)
{
  switch(message)
   {
       case    WM_COMMAND:         /*  メニューに対する処理    */
           return  Wm_CommandProc(hwnd,HIWORD(wparam),LOWORD(wparam),(HWND)lparam);
       case    WM_DESTROY:         /*  ウインドウの破壊後処理  */
           return  Wm_DestroyProc();
   }
   return  DefWindowProc(hwnd,message,wparam,lparam);
}

/*===============================================================================*/

static LRESULT Wm_CommandProc(HWND hwnd,WORD wNotifyCode,WORD wID,HWND hwndCtl)
{
  switch(wID)
   {
       case    ID_SUB0:    /*  メニュー項目にチェックを付けたり外す    */
           if(GetMenuState(GetMenu(hwnd),ID_SUB0,MF_BYCOMMAND)&MF;_CHECKED)
           {   /*  メニュー項目がチェックされていたら、チェックを外す  */
               CheckMenuItem(GetMenu(hwnd),ID_SUB0,MF_BYCOMMAND|MF_UNCHECKED);
           }
           else
           {   /*  メニュー項目がチェックされていなければ、チェックを付ける    */
               CheckMenuItem(GetMenu(hwnd),ID_SUB0,MF_BYCOMMAND|MF_CHECKED);
           }
           break;
       case    ID_SUB1:    /*  メニュー項目を使えないようにする(外見が変らず)*/
           EnableMenuItem(GetMenu(hwnd),ID_SUB1,MF_BYCOMMAND|MF_DISABLED);
           MessageBox(hwnd,"サブメニューのアイテム1(不可)が使えなくなりました","メッセージ",MB_OK);
           break;
       case    ID_SUB2:    /*  メニュー項目をグレーにして使えないようにする    */
           EnableMenuItem(GetMenu(hwnd),ID_SUB2,MF_BYCOMMAND|MF_GRAYED);
           MessageBox(hwnd,"サブメニューのアイテム2(グレー)が使えなくなりました","メッセージ",MB_OK);
           break;
       case    ID_SUB3:    /*  サブメニューの項目の状態を全て初期状態に戻す    */
           CheckMenuItem(GetMenu(hwnd),ID_SUB0,MF_BYCOMMAND|MF_UNCHECKED);
           EnableMenuItem(GetMenu(hwnd),ID_SUB1,MF_BYCOMMAND|MF_ENABLED);
           EnableMenuItem(GetMenu(hwnd),ID_SUB2,MF_BYCOMMAND|MF_ENABLED);
           break;
   }
   return  0;
}
static LRESULT Wm_DestroyProc(void)
{
  PostQuitMessage(0);
   return  0;
}
</pre>
</table>
<p>
 最も一般的と思われるメニューの状態変更の方法は、メニューIDを用いる方法です。GetMenuState関数、CheckMenuItem関数、EnableMenuItem関数の全ての引数に<b><i><u>MF_BYCOMMAND</u></i></b>ビットを立てている事が特徴です。MF_BYCOMMANDと言うのは、メニューIDで目的のメニュー項目を識別するようにするフラグです。次に紹介する方法と比べて、<b><i><u>オペレーターに近い感覚でメニュー項目を扱えます</u></i></b>。
<p>
 では、リスト33-1で登場するメニュー操作に関する関数を説明します。
<p>
<b>
・GetMenu関数
</b>
<p>
 ウインドウに関連付けられているメニューメニューのメニューハンドルを取得する関数です。引数は、ウインドウハンドルです。
<p>
<b>
・GetMenuState関数
</b>
<p>
 指定したメニュー項目の状態を返す関数です。第一引数は、メニューハンドル。第二引数は、第三引数によって意味が変ります。第三引数が、MF_BYCOMMANDの場合は、第二引数は、メニュー項目のIDを意味します。メニュー項目の状態は、知りたい情報との論理積を用います。
<p>
 さて、メニューの状態を、変数として持っておくと、この関数を用いてメニューの状態を調べなくても良いと考える方がいると思います。しかし、この方法を採ると、メニューの状態を記録しておく変数と実際のメニューの状態に食い違いが生じるバグの温床になります。同じ状態を複数の変数やオブジェクトで持たないと言うのは重要です。
<p>
<b>
・CheckMenuItem関数
</b>
<p>
 メニュー項目の左側に、チェックマークを入れたり消したりする関数です。第一引数は、メニューハンドル。第二引数は、第三引数によって意味が変ります。MF_BYCOMMANDビットが立っている場合は、第二引数は、メニュー項目のIDです。第三引数は、MF_BYCOMMANDでメニュー項目のIDを用いるかどうかとチェックをするか外すかのフラグを論理和で指定します。
<p>
<b>
・EnableMenuItem関数
</b>
<p>
 メニュー項目を使用可能にしたり使用不可にしたりする関数です。第一引数は、メニューハンドル。第二引数は、第三引数によって意味が変ります。MF_BYCOMMANDビットが立っている場合は、第二引数は、メニュー項目のIDです。第三引数は、MF_BYCOMMADNでメニュー項目のIDを用いるかどうかと、メニューを使用可能にするか使用不可にするかを論理和で指定します。使用不可の場合、視覚的に変化が無いMF_DISABLEDと視覚的に変化があるMF_GRAYEDがあります。
<hr>
<p>
 今度は、メニューの位置情報を元に、変更を加える方法です。
<p>
<table border="1" bgcolor="#F0F0F0">
<caption align="BOTTOM">リスト33-2
<tr><td>
<pre>
/* C言語で始めるWindowsプログラミング */
/* 33章のサンプルプログラム(その2) */
/* Programmed by Y.Kondo */
/* 注:TABサイズは4で見てください */
/* このファイルでは、メインウインドウのウインド*/
/*ウプロシージャが定義されている */

#define STRICT
#include &lt;windows.h&gt;
#include &lt;stdio.h&gt;
#include "wndproc.h"
#include "resource.h"

/*===============================================================================*/

/* このファイル内でのみ用いられる関数のプロトタイプ宣言 */
static LRESULT Wm_CommandProc(HWND,WORD,WORD,HWND);
static LRESULT Wm_DestroyProc(void);

/* メインウインドウのウインドウプロシージャ */
LRESULT CALLBACK WindowProc(HWND hwnd,UINT message,WPARAM wparam,LPARAM lparam)
{
  switch(message)
   {
       case    WM_COMMAND:         /*  メニューに対する処理    */
           return  Wm_CommandProc(hwnd,HIWORD(wparam),LOWORD(wparam),(HWND)lparam);
       case    WM_DESTROY:         /*  ウインドウの破壊後処理  */
           return  Wm_DestroyProc();
   }
   return  DefWindowProc(hwnd,message,wparam,lparam);
}

/*===============================================================================*/

static LRESULT Wm_CommandProc(HWND hwnd,WORD wNotifyCode,WORD wID,HWND hwndCtl)
{
  switch(wID)
   {
       case    ID_SUB0:    /*  メニュー項目にチェックを付けたり外す    */
           if(GetMenuState(GetSubMenu(GetMenu(hwnd),0),0,MF_BYPOSITION)&MF;_CHECKED)
           {   /*  メニュー項目がチェックされていたら、チェックを外す  */
               CheckMenuItem(GetSubMenu(GetMenu(hwnd),0),0,MF_BYPOSITION|MF_UNCHECKED);
           }
           else
           {   /*  メニュー項目がチェックされていなければ、チェックを付ける    */
               CheckMenuItem(GetSubMenu(GetMenu(hwnd),0),0,MF_BYPOSITION|MF_CHECKED);
           }
           break;
       case    ID_SUB1:    /*  メニュー項目を使えないようにする(外見が変らず)*/
           EnableMenuItem(GetSubMenu(GetMenu(hwnd),0),1,MF_BYPOSITION|MF_DISABLED);
           MessageBox(hwnd,"サブメニューのアイテム1(不可)が使えなくなりました","メッセージ",MB_OK);
           break;
       case    ID_SUB2:    /*  メニュー項目をグレーにして使えないようにする    */
           EnableMenuItem(GetSubMenu(GetMenu(hwnd),0),2,MF_BYPOSITION|MF_GRAYED);
           MessageBox(hwnd,"サブメニューのアイテム2(グレー)が使えなくなりました","メッセージ",MB_OK);
           break;
       case    ID_SUB3:    /*  サブメニューの項目の状態を全て初期状態に戻す    */
           CheckMenuItem(GetSubMenu(GetMenu(hwnd),0),0,MF_BYPOSITION|MF_UNCHECKED);
           EnableMenuItem(GetSubMenu(GetMenu(hwnd),0),1,MF_BYPOSITION|MF_ENABLED);
           EnableMenuItem(GetSubMenu(GetMenu(hwnd),0),2,MF_BYPOSITION|MF_ENABLED);
           break;
   }
   return  0;
}
static LRESULT Wm_DestroyProc(void)
{
  PostQuitMessage(0);
   return  0;
}
</pre>
</table>
<p>
 リスト33-2の特徴は、リスト33-1のMF_BYCOMMANDが<b><i><u>MF_BYPOSITION</u></i></b>になっている事です。これに従い、<b><i><u>GetSubMenu関数が登場</u></i></b>しています。
<p>
 メニュー項目をIDで識別するか位置で識別するかで、メニューに対して、考え方を大きく変更する必要があります。メニュー項目をIDで取り扱った場合(つまり、MF_BYCOMMAND)、枝分かれしたメニューを1つのメニューと捉える事が出来ました。しかし、メニュー項目を位置で取り扱った場合(つまり、MF_BYPOSITION)、横方向1本で1メニュー、縦方向1本で1メニューと捉える必要があります。そして、GetMenu関数から得られるメニューを幹に、GetSubMenu関数で、1本のメニューハンドルを取得し、個々のメニューの起点からの変位でメニュー項目の位置を表現します。横方向のメニューの場合、左端の項目が0。縦方向のメニューの場合、上端の項目が0です。リスト33-1とリスト33-2を比較してください。メニュー項目のIDであった引数が、全て、左端・上端からの変位になっています。
<p>
<b>
・GetSubMenu関数
</b>
<p>
 枝分かれしたサブメニューのハンドルを取得する関数です。第一引数は、メニューハンドル。第二引数は、枝分かれしているサブメニューの絶対位置です。返値は、サブメニューのハンドルです。
<hr>
<p>
 さて、最後の方法。これは、最初の方法と概念的に全く同じです。しかし、GetMenu関数以外、他のメニューを操作する関数は1つも使っていません。代わりに、<b><i><u>GetMenuItemInfo関数</u></i></b>と<b><i><u>SetMenuItemInfo関数</u></i></b>で全ての処理を行っています。
<p>
 最初の方法の多くのメニュー操作の関数は、リスト33-3で用いている2つの関数を使いやすくするラップ関数の様です。WindowsAPIは、目が眩むほど多くの関数がありますが、意外と本質的な関数は少なく、本質的な関数の機能のうちよく使う物を別個にラップした様な関数が多くあるだけです。
<p>
<table border="1" bgcolor="#F0F0F0">
<caption align="BOTTOM">リスト33-3
<tr><td>
<pre>
/* C言語で始めるWindowsプログラミング */
/* 33章のサンプルプログラム(その3) */
/* Programmed by Y.Kondo */
/* 注:TABサイズは4で見てください */
/* このファイルでは、メインウインドウのウインド*/
/*ウプロシージャが定義されている */

#define STRICT
#include &lt;windows.h&gt;
#include &lt;stdio.h&gt;
#include "wndproc.h"
#include "resource.h"

/*===============================================================================*/

/* このファイル内でのみ用いられる関数のプロトタイプ宣言 */
static LRESULT Wm_CommandProc(HWND,WORD,WORD,HWND);
static LRESULT Wm_DestroyProc(void);

/* メインウインドウのウインドウプロシージャ */
LRESULT CALLBACK WindowProc(HWND hwnd,UINT message,WPARAM wparam,LPARAM lparam)
{
  switch(message)
   {
       case    WM_COMMAND:         /*  メニューに対する処理    */
           return  Wm_CommandProc(hwnd,HIWORD(wparam),LOWORD(wparam),(HWND)lparam);
       case    WM_DESTROY:         /*  ウインドウの破壊後処理  */
           return  Wm_DestroyProc();
   }
   return  DefWindowProc(hwnd,message,wparam,lparam);
}

/*===============================================================================*/

static LRESULT Wm_CommandProc(HWND hwnd,WORD wNotifyCode,WORD wID,HWND hwndCtl)
{
  /*  MENUITEMINFO構造体
   typedef struct tagMENUITEMINFO {
   UINT    cbSize; 
   UINT    fMask; 
   UINT    fType;          fMaskで有効になるメンバ MIIM_TYPE 
   UINT    fState;         fMaskで有効になるメンバ MIIM_STATE 
   UINT    wID;            fMaskで有効になるメンバ MIIM_ID 
   HMENU   hSubMenu;       fMaskで有効になるメンバ MIIM_SUBMENU 
   HBITMAP hbmpChecked;    fMaskで有効になるメンバ MIIM_CHECKMARKS  
   HBITMAP hbmpUnchecked;  fMaskで有効になるメンバ MIIM_CHECKMARKS 
   DWORD   dwItemData;     fMaskで有効になるメンバ MIIM_DATA 
   LPTSTR  dwTypeData;     fMaskで有効になるメンバ MIIM_TYPE 
   UINT    cch; 
   } MENUITEMINFO;
   */
   MENUITEMINFO    MenuItemInfo;
   switch(wID)
   {
       case    ID_SUB0:    /*  メニュー項目にチェックを付けたり外す    */
           ZeroMemory(&MenuItemInfo;,sizeof(MENUITEMINFO));
           MenuItemInfo.cbSize=sizeof(MENUITEMINFO);
           MenuItemInfo.fMask=MIIM_STATE;
           GetMenuItemInfo(GetMenu(hwnd),ID_SUB0,FALSE,&MenuItemInfo;);
           if(MenuItemInfo.fState&MFS;_CHECKED)
           {   /*  メニュー項目がチェックされていたら、チェックを外す  */
               ZeroMemory(&MenuItemInfo;,sizeof(MENUITEMINFO));
               MenuItemInfo.cbSize=sizeof(MENUITEMINFO);
               MenuItemInfo.fMask=MIIM_STATE;
               MenuItemInfo.fState=MFS_UNCHECKED;
               SetMenuItemInfo(GetMenu(hwnd),ID_SUB0,FALSE,&MenuItemInfo;);
           }
           else
           {   /*  メニュー項目がチェックされていなければ、チェックを付ける    */
               ZeroMemory(&MenuItemInfo;,sizeof(MENUITEMINFO));
               MenuItemInfo.cbSize=sizeof(MENUITEMINFO);
               MenuItemInfo.fMask=MIIM_STATE;
               MenuItemInfo.fState=MFS_CHECKED;
               SetMenuItemInfo(GetMenu(hwnd),ID_SUB0,FALSE,&MenuItemInfo;);
           }
           break;
       case    ID_SUB1:    /*  メニュー項目を使えないようにする(外見が変らず)*/
           ZeroMemory(&MenuItemInfo;,sizeof(MENUITEMINFO));
           MenuItemInfo.cbSize=sizeof(MENUITEMINFO);
           MenuItemInfo.fMask=MIIM_STATE;
           /*
           MenuItemInfo.fState=MFS_DISABLED;
           ヘッダファイル(WINRESRC.H,WINUSER.H)のバグ
           */
           MenuItemInfo.fState=MF_DISABLED;
           SetMenuItemInfo(GetMenu(hwnd),ID_SUB1,FALSE,&MenuItemInfo;);
           MessageBox(hwnd,"サブメニューのアイテム1(不可)が使えなくなりました","メッセージ",MB_OK);
           break;
       case    ID_SUB2:    /*  メニュー項目をグレーにして使えないようにする    */
           ZeroMemory(&MenuItemInfo;,sizeof(MENUITEMINFO));
           MenuItemInfo.cbSize=sizeof(MENUITEMINFO);
           MenuItemInfo.fMask=MIIM_STATE;
           MenuItemInfo.fState=MFS_GRAYED;
           SetMenuItemInfo(GetMenu(hwnd),ID_SUB2,FALSE,&MenuItemInfo;);
           MessageBox(hwnd,"サブメニューのアイテム2(グレー)が使えなくなりました","メッセージ",MB_OK);
           break;
       case    ID_SUB3:    /*  サブメニューの項目の状態を全て初期状態に戻す    */
           ZeroMemory(&MenuItemInfo;,sizeof(MENUITEMINFO));
           MenuItemInfo.cbSize=sizeof(MENUITEMINFO);
           MenuItemInfo.fMask=MIIM_STATE;
           MenuItemInfo.fState=MFS_UNCHECKED;
           SetMenuItemInfo(GetMenu(hwnd),ID_SUB0,FALSE,&MenuItemInfo;);
           ZeroMemory(&MenuItemInfo;,sizeof(MENUITEMINFO));
           MenuItemInfo.cbSize=sizeof(MENUITEMINFO);
           MenuItemInfo.fMask=MIIM_STATE;
           MenuItemInfo.fState=MFS_ENABLED;
           SetMenuItemInfo(GetMenu(hwnd),ID_SUB1,FALSE,&MenuItemInfo;);
           ZeroMemory(&MenuItemInfo;,sizeof(MENUITEMINFO));
           MenuItemInfo.cbSize=sizeof(MENUITEMINFO);
           MenuItemInfo.fMask=MIIM_STATE;
           MenuItemInfo.fState=MFS_ENABLED;
           SetMenuItemInfo(GetMenu(hwnd),ID_SUB2,FALSE,&MenuItemInfo;);
           break;
   }
   return  0;
}
static LRESULT Wm_DestroyProc(void)
{
  PostQuitMessage(0);
   return  0;
}
</pre>
</table>
<p>
 GetMenuItemInfo関数とSetMenuItemInfo関数の理解には、<b><i><u>MENUITEMINFO構造体</u></i></b>の理解が必須です。これはまた、メニューの動的な作成の理解にも必須です。
<p>
 次章では、<b><i><u>MENUITEMINFO構造体</u></i></b>の理解をして、メニューの動的な作成をします。
<p>
 では、お楽しみに。
<p align="RIGHT">
2002年12月21日<br>
<hr>
<p align="RIGHT">
<a href="/web/20150120021321/http://web.kyoto-inet.or.jp:80/people/ysskondo/index.html">目次</a><br>
<hr>
<p align="RIGHT">
著作権者:近藤妥
</body>
</html>

  • 最終更新:2018-03-11 05:28:06

このWIKIを編集するにはパスワード入力が必要です

認証パスワード