第72回 プログラミングについて『ウィンドウにメニューを追加する方法』

今回も前回に続いてWindowsのプログラムのあまり楽しくないところのお話をします。
まず前回のソースコード。
#include <afxwin.h>
#include "resource.h"
class MyApp : public CWinApp
{
public:
virtual BOOL InitInstance();
};
class MyWnd : public CFrameWnd
{
public:
MyWnd();
};
class MyApp MyApp;
BOOL MyApp::InitInstance()
{
m_pMainWnd = new class MyWnd;
m_pMainWnd->SetIcon(LoadIcon(ID_MYAPP_ICON),TRUE);
return TRUE;
}
MyWnd::MyWnd()
{
Create(NULL,"",WS_OVERLAPPEDWINDOW | WS_VISIBLE ,rectDefault);
}
今回はウインドウにメニューを追加する方法をお話します。ウインドウにメニューを追加する方法にもいくつかありますが、簡単な方法は CWnd から派生したクラスのコンストラクタ(上の例では MyWnd::MyWnd )でメニューリソースを指定するやり方です。
まず、メニューリソースを作りましょう。Visual C++の開発環境で、『ファイル』をクリックし、『開く』を指定します。ここで前回作成した myapp.rc ファイルを開きます。myapp.rc の階層が表示されますので、myaoo.rc 上にカーソルを移動して右ボタンをクリックして『挿入 ...』を指定し、『Menu』を選択します。そうすると、メニューを定義する画面に切り替わりますので、左上の破線で囲まれた部分にカーソルを移動して右ボタンをクリックし、プロパティを表示させます。
このダイアログのキャプションに File と入力して下さい(本当は何でもいいのですが、ここでは File としておきます)。すると、左上にそのままその文字が表示され、その下にサブメニューが追加されます。次にこのサブメニュー上にカーソルを移動して、再び右ボタンを押してサブメニューのプロパティーを表示させます。このキャプションには Open、IDには IDM_OPEN を指定して下さい。この操作を繰り返して、キャプションが Save、IDが IDM_SAVE、更にキャプションが EXIT、IDが IDM_EXIT というサブメニューを追加します。ここまで作成できたら、メニューの定義を終了して下さい。
再び myapp.rc の階層に戻ったら、このメニューの名称を変更しておきましょう。カーソルを今作成したメニュー上に移動して右ボタンをクリックし、プロパティを表示させます。IDに "MYAPP_MENU" と入力して、このリソースを上書き保存して下さい。保存ができたら、rc コマンドでバイナリのリソースファイルに変換します。
このままでは、リンクしてもウインドウにはメニューは追加されません。次にウインドウにメニューを追加するプログラムに変更しましょう。MyApp:MyApp での Create 関数を、
MyWnd::MyWnd()
{
Create(NULL,"",WS_OVERLAPPEDWINDOW | WS_VISIBLE ,rectDefault,NULL,
"MYAPP_MENU");
}
にし、コンパイル、リンクして実行してみて下さい。これでウインドウにメニューが追加されたはずです。でも多分あまり楽しくはないと思います。その上、サブメニューは、すべて灰色で使用できない状態になっているはずです。
実は、どうして灰色になってしまうのかを調べるのに私は一日費やした経験がありますが、最後に分かった理由は、このプログラムにはこのメニューをクリックしたときに対応する(コマンドを処理する)関数がまだ用意されていないからなのです。
上のプログラムではまだWindowsから送られてくるメッセージを任意に処理することができず、あくまでもウインドウのディフォルトの動作に頼っています。もし、追加したメニューを選択できたとしても、そのメニューに対応する処理はディフォルトでは何も用意はされていません。そこで、メッセージを処理する機能を追加しましょう。
まず、MyWnd クラスの宣言に DECLARE_MESSAGE_MAP() マクロを追加します。あまり深く考えないで、おまじない程度に思っていて下さい。
#include <afxwin.h>
#include "resource.h"
class MyApp : public CWinApp
{
public:
virtual BOOL InitInstance();
};
class MyWnd : public CFrameWnd
{
public:
MyWnd();
DECLARE_MESSAGE_MAP() //ここに追加。
};
class MyApp MyApp;
BOOL MyApp::InitInstance()
{
m_pMainWnd = new class MyWnd;
m_pMainWnd->SetIcon(LoadIcon(ID_MYAPP_ICON),TRUE);
return TRUE;
}
MyWnd::MyWnd()
{
Create(NULL,"",WS_OVERLAPPEDWINDOW | WS_VISIBLE ,rectDefault,NULL,
"MYAPP_MENU");
}
このプログラムをコンパイル、リンクしてみると、
"protected: virtual struct AFX_MSGMAP const *
__thiscall MyWnd::GetMessageMap(void)const "
(?GetMessageMap@MyWnd@@MBEPBUAFX_MSG MAP@@XZ)" は未解決です
というなんだかよく分からないエラーになるはずです。要するに、関数が1つ足りないということです。そこで、プログラムの最後にでも次の行を追加します。これもマクロで『CFrameWnd クラスから派生した MyWnd のメッセージマップの定義だぞ』といった程度に考えてもらえれば十分です。このマクロがコンパイル時に展開されると、上のエラーになってしまった不足している関数になるのです。
BEGIN_MESSAGE_MAP(MyWnd,CFrameWnd)
END_MESSAGE_MAP()
この BEGIN_MESSAGE_MAP と END_MESSAGE_MAP の間に処理するメッセージとそれに対応するメンバー関数を記述していくことになります。この状態ではコンパイルとリンクはエラーなしで終了しますが、サブメニューはまだ使用できませんので、メニュー Openを処理する機能を付けてみましょう。ここでは、メッセージマップを次のようにします。
BEGIN_MESSAGE_MAP(MyWnd,CFrameWnd)
ON_COMMAND(IDM_OPEN,OnOpen)
END_MESSAGE_MAP()
ON_COMMAND というマクロはメニューを選択したり、ボタンを押したりしたときに発生するメッセージ(この場合はメニュー Open に付けたIDの IDM_OPEN)を処理したいときに使用します。2番目の引数には MyWnd のメンバー関数名を指定します。このままですと MyWnd::OnOpen が定義されていないということになりますので、MyWnd クラスにこのメンバー関数の宣言を追加します。
class MyWnd : public CFrameWnd
{
public:
MyWnd();
afx_msg void OnOpen();
DECLARE_MESSAGE_MAP()
};
ON_COMMAND に処理させる関数(この場合は OnOpen)は void 型で引数なしのものでなければなりません。 afx_msg というのを void の前に指定していますが、これはあまり意味がないみたいです。
更に、OnOpen の実体を作りましょう。とりあえずは、このメンバー関数が呼び出されたことが表示されるようにメッセージボックスを表示することにします。
afx_msg void MyWnd::OnOpen()
{
MessageBox("OnOpen was called !","Open file",MB_OK);
}
それでは再びプログラム全体。
#include <afxwin.h>
#include "resource.h"
class MyApp : public CWinApp
{
public:
virtual BOOL InitInstance();
};
class MyWnd : public CFrameWnd
{
public:
MyWnd();
afx_msg void OnOpen();
DECLARE_MESSAGE_MAP()
};
class MyApp MyApp;
BOOL MyApp::InitInstance()
{
m_pMainWnd = new class MyWnd;
m_pMainWnd->SetIcon(LoadIcon(ID_MYAPP_ICON),TRUE);
return TRUE;
}
MyWnd::MyWnd()
{
Create(NULL,"",WS_OVERLAPPEDWINDOW | WS_VISIBLE ,rectDefault,NULL,
"MYAPP_MENU");
}
BEGIN_MESSAGE_MAP(MyWnd,CFrameWnd)
ON_COMMAND(IDM_OPEN,OnOpen)
END_MESSAGE_MAP()
afx_msg void MyWnd::OnOpen()
{
MessageBox("OnOpen was called !","Open file",MB_OK);
}
このプログラムをコンパイル、リンクしてみると、サブメニュー Open の色が黒に変わっていて、選択してみるとOKボタンのあるダイアログボックスが表示されたと思います。
実はこれまでメニューに Open とか Save にしたのはこのプログラムを簡単なテキストエディタにしようという下心があったのです。簡単にテキストエディタを作るにはMFCクラスライブラリに CWnd から派生した CEdit というクラスがあります。このクラスは殆どの場合、ダイアログボックスで1行のキー入力に使用されるものです。ダイアログボックスでエディタでエディットボックスを配置すると、このクラスが使用されるようになっています。
このエディットボックスは複数行の入力も可能で、スクロールバーも使用できますので、アプリケーション内でテキストを入力するには最も簡単に実現できるものです。ただ、本格的なテキストエディタにするには、編集機能は基本的なものしかなく、扱えるテキストのバイト数が最大で64Kバイトですので厳しいかも知れません。よくフリーソフトで簡単なテキストエディタがありますが、このクラスを利用しているものがかなり多い様です。
テキストエディタの機能を加えるのは次回以降にしますが、今回はメッセージの処理の基本を理解できたと思います。
それではまた次回。