6章 メニューを付けてみよう

<html>

<head>
<meta http-equiv="Content-Type" content="text/html; charset=shift_jis"></meta>
<title>6章 メニューを付けてみよう</title>
</head>

<body bgcolor="WHITE">
<p>
<font size="5">6章 メニューを付けてみよう</font>
<hr>
<p>
<center>
</center>
<p>
 5章まで、3回連続で「アプリケーションの骨格」について説明しました。この「アプリケーションの骨格」の説明は当分続きます。今回は、趣向を変えて、ウインドウにメニューを付ける方法を説明します。しかし、今回は、メニューについて完全に理解するのが目的ではありません。あくまでも、<b><i><u>次章以降の準備と思ってください</u></i></b>。
<hr>
<p>
<b>・メニューの付け方(リソース編)</b>
<p>
 ウインドウにメニューを付ける方法は、二つあります。一つは<b><i><u>リソース</u></i></b>を用いる方法。もう一つはリソースを用いず、プログラムの中にメニューを作成するルーチンを置く方法です。今回は、一般的な前者を採用します。
<p>
 今回もサンプルを用意しましたので、<a href="chap6.lzh">ここ</a>を押してダウンロードしてください。
<p>
 さて、リソースって何でしょうか?Windows上で動くアプリケーションは、しばしばリソースと呼ばれる物を持ちます。<b><i><u>リソースとは、実行形式ファイル(EXEファイルやDLLファイルなど)に含まれるバイナリー情報で、必要になったときにメモリーにロードされ利用されるデータです</u></i></b>。リソースとして実行形式ファイルにリンクされる情報は、今回のメニューの様に画面の構成要素や画面その物の情報である場合が多いようです。それゆえ、Windowsプログラミングは、もっとも原始的な開発環境ですら、VBのようなRADツールのようにユーザーインターフェースとその振る舞いを分離するという考え方を持っています。
<p>
 <b><i><u>リソースは、リソーススクリプトという言語で定義します</u></i></b>。したがって、拡張子がRCのテキストファイルに過ぎません。これをリソースコンパイラでコンパイルして、リソースファイル(拡張子はRES)を作ります。そして、従来のオブジェクトファイルやライブラリと一緒にリンクするとリソースを含んだ実行ファイルが出来ます。リソーススクリプトですが、筆者自身、その文法を詳しく知りません。なぜなら、<b><i><u>VisualC++もTurboC++も、リソーススクリプトを知らなくてもビジュアルにメニューなどを作成出来るツールが用意されている</u></i></b>からです。
<p>
 さて、第2章では以下の事を学びました。つまり、プロジェクトを作成し、ここにC言語のソースファイルを加える。今回は、これにリソーススクリプトファイルを加えるのです。では、その手順を示します。
<p>
 VisualC++バージョン5のメニュー[プロジェクト]→[プロジェクトへ追加]→[新規作成]で、新規作成のダイアログボックスが開きます。ここでファイルタブを選びます。そして、リソーススクリプトを選択し、ファイル名のエディットボックスに半角文字で、resource.rcと入れて、OKボタンを押します。これで、リソーススクリプトが格納させるファイルと、C言語のソースと定数を共有するためのヘッダファイルresource.hが作成されます。resource.hですが、リソーススクリプトを格納するファイル名をresource.rcにしたから、resource.hになったのでなく、リソーススクリプトファイルに対して出来るヘッダファイルに固定の名前です。
<p>
 次、ワークスペースのResourceViewタブの中のresourceを右クリックします。そして、[挿入]を選びます。そしてメニューを選びます。あとは、GUIならではの直感的な操作でやっていけると思います。
<p align="CENTER">
<p>
 つまり、上の図のように、プルダウンメニューを加えたい位置にマウスカーソルを持っていき、右クリック。プロパティーを選択します。そして、メニューに表示したい文字をキャプションに入れます。以下、メニューアイテムの場合は、シンボリックな数値をIDのエディットボックスに入れれば出来上がりです。この場合は、半角文字を使いましょう。実際の数値は勝手に作ってくれますので、一切、気にする必要はありません。さて、結果的に、リスト6-1の様なリソーススクリプトを作ってみました。<b><i><u>太字で装飾されている個所が、メニューを定義している個所です</u></i></b>。
<p>
<table border="1" bgcolor="#F0F0F0">
<caption align="BOTTOM">リスト6-1
<tr><td>
<pre>
#include "resource.h"

#define APSTUDIO_READONLY_SYMBOLS
#include "afxres.h"

#undef APSTUDIO_READONLY_SYMBOLS


#if !defined(AFX_RESOURCE_DLL) | | defined(AFX_TARG_JPN)
#ifdef _WIN32
LANGUAGE LANG_JAPANESE, SUBLANG_DEFAULT
#pragma code_page(932)
#endif //_WIN32

#ifdef APSTUDIO_INVOKED

1 TEXTINCLUDE DISCARDABLE
BEGIN
  "resource.h\0"
END

2 TEXTINCLUDE DISCARDABLE
BEGIN
  "#include ""afxres.h""\r\n"
   "\0"
END

3 TEXTINCLUDE DISCARDABLE
BEGIN
  "\r\n"
   "\0"
END

#endif // APSTUDIO_INVOKED


<b>
IDR_MENU1 MENU DISCARDABLE
BEGIN
  POPUP "図形"
   BEGIN
       MENUITEM "四角形",                      ID_RECTANGLE
       MENUITEM "三角形",                      ID_TRIANGLE
   END
END
</b>
#endif // 日本語 resources



#ifndef APSTUDIO_INVOKED


#endif // not APSTUDIO_INVOKED
</pre>
</table>
<p>
 そして、C言語で書くプログラムから参照する必要のある情報は、resource.hに格納されます。これを、リスト6-2に示します。
<p>
<table border="1" bgcolor="#F0F0F0">
<caption align="BOTTOM">リスト6-2
<tr><td>
<pre>
<b>
#define IDR_MENU1 101
#define ID_RECTANGLE 40001
#define ID_TRIANGLE 40002
</b>
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 104
#define _APS_NEXT_COMMAND_VALUE 40003
#define _APS_NEXT_CONTROL_VALUE 1000
#define _APS_NEXT_SYMED_VALUE 101
#endif
#endif
</pre>
</table>
<p>
 さて、これで、メニューの準備は出来ました。あとは、C言語で定義するプログラム側の説明をします。
<hr>
<p>
<b>・メニューの付け方(表示編)</b>
<p>
 リソーススクリプトで定義されたメニューをC言語のソースから用いる方法は二つあります。一つは、<b><i><u>ウインドウクラスにメニューを付ける方法</u></i></b>。もう一つは、<b><i><u>ウインドウにメニューを付ける方法</u></i></b>です。ウインドウクラスにメニューを付ける方法では、このウインドウクラスを共有するウインドウ全てにメニューが付きます。今回は、前者のウインドウクラスにメニューを付ける方法を採っています。
<p>
 ウインドウクラスにメニューを付ける方法は、<b><i><u>WNDCLASS構造体のlpszMenuNameメンバ変数</u></i></b>にメニュー名へのアドレスをセットしてウインドウクラスを登録するだけです。
<p align="CENTER">
<table border="1" bgcolor="#F0F0F0">
<tr><td>
<pre>
typedef struct _WNDCLASS {
  UINT    style; 
   WNDPROC lpfnWndProc; 
   int     cbClsExtra; 
   int     cbWndExtra; 
   HANDLE  hInstance; 
   HICON   hIcon; 
   HCURSOR hCursor; 
   HBRUSH  hbrBackground; 
   <b>LPCTSTR lpszMenuName</b>; 
   LPCTSTR lpszClassName; 
} WNDCLASS;</pre>
</table>
<p>
しかし、VisualC++のリソースエディタは、メニュー名を整数で表現するので、これを然るべき文字列へのポインタに変換する必要があります。これを行うのが<b><i><u>MAKEINTRESOURCEマクロ</u></i></b>です。実際には以下のようにWINUSER.Hの中で以下のように定義されています。
<p align="CENTER">
<table border="1" bgcolor="#F0F0F0">
<tr><td>
<pre>
#define MAKEINTRESOURCEA(i) (LPSTR)((DWORD)((WORD)(i)))
#define MAKEINTRESOURCEW(i) (LPWSTR)((DWORD)((WORD)(i)))
#ifdef UNICODE
#define MAKEINTRESOURCE MAKEINTRESOURCEW
#else
#define MAKEINTRESOURCE MAKEINTRESOURCEA
#endif // !UNICODE
</pre>
</table>
<p>
 したがって、ウインドウクラスの登録は以下のようになります。これは、サンプルのWinMain.Cからの抜粋です。

<p align="CENTER">
<table border="1" bgcolor="#F0F0F0">
<tr><td>
<pre>
  WNDCLASS        wc;     /*  ウインドウクラス登録用の構造体          */

  wc.style        =0;
   wc.lpfnWndProc  =WindowProc;
   wc.cbClsExtra   =0;
   wc.cbWndExtra   =0;
   wc.hInstance    =hInstance;
   wc.hIcon        =LoadIcon(NULL,IDI_APPLICATION);
   wc.hCursor      =LoadCursor(NULL,IDC_ARROW);
   wc.hbrBackground=GetStockObject(LTGRAY_BRUSH);  /*  背景のブラシは灰色          */
   <b>wc.lpszMenuName=MAKEINTRESOURCE(IDR_MENU1);     /*  クラスにメニューを付ける  */</b>
   wc.lpszClassName="MainWindowClass";
   
   if(RegisterClass(&wc;)==0)               /*  ウインドウクラス登録    */
       return  0;
</pre>
</table>
<p>
 そういえば、まだWNDCLASS構造体の各メンバについて説明していませんでしたので、この機会に説明しましょう。
<p>
<p>
 styleというのは、ウインドウプロシージャに対するメッセージの送り方などを変えるものです。たとえば、ウインドウプロシージャにダブルクリックに対するメッセージを送りたい場合は、CS_DBLCLKSをセットします。
<p>
 lpfnWndProcはウインドウプロシージャのエントリーポイントですね。これは3章で説明しました。
<p>
 cbClsExtraとcbWndExtraは、今回も保留にしましょう。ウインドウプロシージャやウインドウクラスの共有に関係があります。
<p>
 hInstanceは、アプリケーションのインスタンスハンドルです。
<p>
 hIconはウインドウのアイコンへのハンドルです。WIN32APIのマニュアルにはhIconがNULLの時、アプリケーション自身がアイコンを描画しなければいけないように書いてありますが、Windows95のユーザーインターフェースを使っている限り、デフォルトアイコンが適用されるようです。
<p>
 hCursorは、マウスカーソルへのハンドルです。このウインドウのクライアント領域にでのマウスカーソルの形状を決定するものです。hCursorがNULLの時は、マウスカーソルがこのウインドウのクライアント領域に入っても、自動的に変わりません。実際には、WM_MOUSEMOVEメッセージに応答してSetCursor関数を用いてマウスカーソルの形状を変える必要があります。
<p>
 hbrBackGround。これは、ウインドウのクライアント領域の背景となるブラシへのハンドルです。ブラシというのは、8ピクセル×8ピクセルのビットマップの事です。このブラシ、つまり、ビットマップを用いて、ウインドウのクライアント領域を描画する時に背景を塗りつぶすのです。これに関しては、この章でもう少し説明しますが、よく分からなければ、このメンバ変数にNULLを入れてコンパイルして実行してください。ウインドウが作成され表示されても、以前にあった画面がクライアント領域に残りますので直感的に分かると思います。
<p>
 lpszMenuName。これは、もう説明しましたね。
<p>
 lpszClassName。これは3章で説明しました。ウインドウクラスの識別名へのアドレスです。
<p>
 少し脱線しましたが、これでウインドウにメニューが表示されます。次は、メニューに対する応答の仕方を説明します。
<hr>
<p>
<b>・メニューの付け方(応答編)</b>
<p>
 まず、サンプルのWndProc.C(リスト6-3)を見てみましょう。
<p>
<table border="1" bgcolor="#F0F0F0">
<caption align="BOTTOM">リスト6-3
<tr><td>
<pre>
/* C言語で始めるWindowsプログラミング */
/* 6章のサンプルプログラム */
/* 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);
static LRESULT Wm_PaintProc(HWND);

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

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

/* このファイル内でのみ用いられる型定義 */
typedef enum
{
  RECTANGLE,
   TRIANGLE
} TState;

/* このファイル内でのみ用いられる変数定義 */
static TState <b>StateFlag</b>=RECTANGLE;

static LRESULT Wm_CommandProc(HWND hwnd,WORD wNotifyCode,WORD wID,HWND hwndCtl)
{
  switch(wID)
   {
       case    ID_RECTANGLE:
           StateFlag=RECTANGLE;
           <b>InvalidateRect(hwnd,NULL,TRUE)</b>;
           <b>UpdateWindow(hwnd)</b>;
           break;
       case    ID_TRIANGLE:
           StateFlag=TRIANGLE;
           <b>InvalidateRect(hwnd,NULL,TRUE)</b>;
           <b>UpdateWindow(hwnd)</b>;
           break;
   }
   return  0;
}
static LRESULT Wm_DestroyProc(void)
{
  PostQuitMessage(0);
   return  0;
}

static LRESULT Wm_PaintProc(HWND hwnd)
{
  PAINTSTRUCT ps;
   HDC         PaintDC;
   /*  四角形のデータ  */
   POINT   RA[]={{10,10},{190,10},{190,190},{10,190},{10,10}};
   /*  三角形のデータ  */
   POINT   TA[]={{100,10},{190,190},{10,190},{100,10}};

  if(GetUpdateRect(hwnd,NULL,TRUE))
   {
       PaintDC=BeginPaint(hwnd,&ps;);
       if(StateFlag==RECTANGLE)
           Polyline(PaintDC,RA,sizeof(RA)/sizeof(POINT));
       else
           Polyline(PaintDC,TA,sizeof(TA)/sizeof(POINT));
       EndPaint(hwnd,&ps;);
   }
   return  0;
}
</pre>
</table>

<p>
 今回のウインドウプロシージャは、<b><i><u>WM_COMMAND</u></i></b>というメッセージを処理していますね。このメッセージは、この講座では初めて登場するものです。今回の様にメニューを操作してメニュー項目を選んだ場合や、キーボードアクセラレータ(ショートカットキー)が押された場合、また、いずれ説明するボタンなどが押された場合に、WM_COMMANDは、アプリケーションのメッセージキューに置かれます。
<p>
 では、WM_COMMANDメッセージの付属情報を見てみましょう。以下、WIN32APIのマニュアルからの引用です。
<p align="CENTER">
<table border="1" bgcolor="#F0F0F0">
<tr><td>
<pre>
WM_COMMAND

wNotifyCode = HIWORD(wParam); // notification code
wID = LOWORD(wParam); // item, control, or accelerator identifier
hwndCtl = (HWND) lParam; // handle of control
</pre>
</table>
<p>
 この章で重要なのは、LOWORD(wParam)です。この値を用いて選択されたメニュー項目を識別します。
<hr>
<p>
<b>・メニューの付け方(アプリケーションの骨格編)</b>
<p>
 さて、リスト6-3を読めば分かると思いますが、<b><i><u>今回のサンプルプログラムの特徴は、WM_PAINTに対する処理でのみ描画を行い、WM_COMMANDに対する処理では、描画を行っていません</u></i></b>。奇異に感じるかもしれませんが、これがWindowsアプリケーション構築の定石です。しかし、5章の様なお絵描きプログラムなど、より高度なレスポンスが要求される場合は、その限りではありません。
<p>
 WM_COMMANDに対しては、ウインドウプロシージャを定義するモジュールの状態を変えるに止め、この状態に応じて、WM_PAINTを処理する時に描画する図形を変えるのです。しかし、WM_PAINTは本来、OSがアプリケーションに再描画の機会を通知するものですから、再描画の必要性が出るまでは、WM_PAINTは発生しません。そこで、WM_PAINTを発生さすのが、<b><i><u>InvalidateRect関数</u></i></b>です。これにより、OSから再描画の必要性を迫られます。
<p>
 では、これに続く<b><i><u>UpdateWindow関数</u></i></b>はどのような作用があるのでしょうか?WM_PAINTメッセージは、プライオリティーが低いメッセージです。つまり、アプリケーションのメッセーキューにまだ処理するメッセージが残っておれば、こちらが優先的にGetMessage関数に吸い上げられます。そこで、UpdateWindow関数を用いると、再描画の必要性があれば、ダイレクトにウインドウプロシージャにWM_PAINTメッセージを送り付けます。結果としてレスポンスの向上が期待できます。
<hr>
<p>
<b>・InvalidateRect関数について</b>
<p>
 この関数は、クライアント領域内の特定の矩形領域を無効にする関数です。特定の領域が無効されると、再描画の必要性が生じます。
<p>
 以下はWIN32APIからの引用です。
<p align="CENTER">
<table border="1" bgcolor="#F0F0F0">
<tr><td>
<pre>
BOOL InvalidateRect(

  HWND  hWnd,             // handle of window with changed update region  
   CONST RECT  * lpRect,   // address of rectangle coordinates 
   BOOL  bErase            // erase-background flag 
  );
</pre>
</table>
<p>
 そこで、第3引数が理解しにくいと思います。背景(バックグラウンド)というのはウインドウのクライアント領域の色やパターンの事ですね。ウインドウクラスを登録する時に、きちんと背景となるブラシを登録しておれば、最小化した後、元に戻すと、背景は自動的に再描画されます。つまり、全部完全に消去されてから、WM_PAINTで新たに描き直す訳です。しかし、アプリケーション側からクライアント領域を無効にする場合、別に他のウインドウによって内容が消去されているのではないので、背景まで描き直すかどうか選択できるわけです。これが、第3引数の役目です。これをTRUEにすると背景は消去され、FALSEにすると以前に描画した物が残されます。
<hr>
<p>
 この講座の更新が滞っている間に、メニューやウインドウのサブクラス化などの質問のメールが届きました。なんかこんな講座でも期待されているのかなと喜ばしい限りです。
<p>
 さて、今回は、メニューに関して簡単な説明をしましたが、あくまでも今後の講座の為です。次回は、オーナー付きウインドウと子ウインドウについて説明しようと思います。では、お楽しみに。
<p align="RIGHT">
1999年3月26日<br>
加筆1999年11月21日<br>
画像追加2001年8月19日<br>
<hr>
<p align="RIGHT">
<a href="index.html">目次</a><br>
<a href="chap7.html">次へ</a>
<hr>
<p align="RIGHT">
著作権者:近藤妥

</body>
</html>

  • 最終更新:2018-03-11 04:23:56

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

認証パスワード