ここまでの冒険を記録しますか?

長岡技大ソフトウェア開発サークルの開発ブログ

DXライブラリとWindowsAPIの連携その2【メニューバー】

書き手:肥田野

昨日に引き続き、DXライブラリとWindowsAPIの関数を組み合わせる方法について解説していきます。

今回はメニューバーです。Wordやペイントなどの上に表示されている「ファイル(F)」「編集(E)」といったアレですね。

メニューバーはリソースと呼ばれるデータとして扱われます。リソースには他にダイアログボックスやツールバー、ステータスバーなどが含まれます。

Windowsのアプリに共通して使われる「部品」のことをリソースとして考えてもいいかもしれません(勿論例外はありますが)

リソースの編集方法は大きく分けて2種類あります。

一つはVisualStudioなどの開発環境が提供するリソースエディタで編集する。

もう一つはリソーススクリプトをメモ帳などのテキストエディタで直に編集する。

最終的に出来上がるものは同じなので好みの方で構いませんが、私と同じ趣味趣向の方は、テキストで直接編集したがるかもしれませんね(笑)。分かる、分かるぞその気持ち。だってカッコイイじゃん。

しかし現実は完成形がイメージしづらく、書き間違いも頻繁に発生するため、初めてリソースを扱う人には前者の方法を強くおすすめします。

リソースファイルをメモ帳などで開くと分かりますが、実は結構単純な構造をしているので、微修正くらいならテキストで直接弄った方が楽かもしれません。ただし、VisualStudioなどのお節介な手厚くサポートしてくれる開発環境では、直接ファイルを編集するとエラーが出て読み込み直す必要が出てくるので注意が必要です。

それでは具体的なリソースの編集方法から見ていきましょう。

ここではVisualStudioでの編集という前提で解説します。私はVisualStudioCommunity(無料、以下VSC)内でVisualC++を使っていますが、VSCは結構重たいソフトなので、スペックの低いPCでは操作に一々時間がかかってストレスが溜まる一方かもしれません。私もそうです。

学生であれば比較的軽い旧バージョンのVisualStudioを無料で使用できると思うので、情報系の先生に相談してみましょう。

個人なら誰でも無料で使えるVisualC++単品だと、リソースの編集機能が提供されていない場合があります。私が使っていたVisualC++2010Expressもリソースが編集できず、已むなくVSCを使っているという次第です。

ソリューションエクスプローラーを覗くと、普段使っている「ソースファイル」「ヘッダファイル」の行に「リソースファイル」がありますよね(これがない開発環境では編集できない可能性が高いです……)。これまで何に使うのか分からず何となく気になっていた方も多いのではないでしょうか。

「リソースファイル」を右クリックして、追加→リソースを選択してください。ソリューションにrcファイルとresouce.hファイルが追加され、ダイアログボックスが開かれると思います。

「リソースの種類」に色々項目がありますが、「Menu」を選択し、「新規作成」をクリックしてください。

メニューバーの編集画面が出てきましたでしょうか。出てこない方は、「リソースビュー(ソリューションエクスプローラーの近く。それでも見つからない人は『表示(V)』から選択)」の[ファイル名].rc→Menu→IDR_MENU1を選択してください。

f:id:NUT_SoftwareDevelopper:20160225202235j:plain

(↑現在の状況。プロジェクト名やソースファイル名は異なっても問題ありません)

何やら左上に「ここに入力」と書かれていますね。ここに「ファイル(&F)」と入力してみてください。

普段見慣れた、ファイルメニューが出来上がりましたね。「&F」と書くとFの下にアンダーバーが自動で入ります。また、Altキーを押しながらFを入力した時にそのメニューが選択される処理をこれで設定したことになります。

ファイルメニューが出来上がるとその下にも入力できるようになりますので、ここには「終了(&X)」と書いてみましょう。

ここをクリックすれば自動でウィンドウを閉じてくれるのか……と思いきや、流石にそこは自分でプログラムしないといけません。

終了ボタンがクリックされた時に、特定のIDがウィンドウプロシージャに送られてきます。これを受け取ってウィンドウを閉じる処理を行うのが一連の流れです。

このIDも自動で作られますが、ここはプログラマーが分かり易い名前に変えた方がいいでしょう。

リソースビューの近くに「プロパティ」タブがあると思うので、そこを表示して、先程作った終了ボタンをクリックしてください。

終了ボタンのプロパティの中に「ID」の項目があるので、そこを「IDM_END」と書き換えます。

ちなみに、先程作った「ファイル(F)」ボタンにはIDは設定できません。メニューバーから最終的にどの項目が選ばれたのかだけが重要なので、どんなに芋づる式になったメニューでもIDを設定できるのは末端部分だけです。

ここまででリソースの操作は一段落です。次はソースコードの編集に戻ります。

#include<DxLib.h>
//リソースデータ用のヘッダファイルを追加
#include"resource.h"


WNDPROC dxWndProc;
//メインウィンドウのハンドル
HWND hMainWnd;

//DxLib_Initの前に行う設定
int SettingBeforeInit(bool);
int SettingAfterInit();
LRESULT CALLBACK MyProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInst, LPSTR lpsCmdLine, int nCmdShow) {
    SettingBeforeInit(true);
    DxLib_Init();
    SettingAfterInit();

    while (!ProcessMessage() && !ScreenFlip() && !ClearDrawScreen()) {
        
    }
    DxLib_End();
    return 0;
}

int SettingBeforeInit(bool wndMode) {
    ChangeWindowMode(wndMode);
    if (wndMode) {
        //メニューIDの読み込み
        LoadMenuResource(IDR_MENU1);
    }
    return 0;
}

int SettingAfterInit() {
    SetDrawScreen(DX_SCREEN_BACK);
    hMainWnd = GetMainWindowHandle();
    dxWndProc = (WNDPROC)GetWindowLong(hMainWnd, GWL_WNDPROC);
    SetWindowLong(hMainWnd, GWL_WNDPROC, (LONG)MyProc);
    
    return 0;
}

LRESULT CALLBACK MyProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) {
    switch (msg) {
    case WM_COMMAND:
        switch (LOWORD(wp)) {
        case IDM_END:
            SendMessage(hWnd, WM_CLOSE, NULL, NULL);
            break;
        }
        break;
    }
    return CallWindowProc(dxWndProc, hWnd, msg, wp, lp);
}

まずはリソースファイルと同時に生成されたresource.hをインクルードします。もしかしたら環境によって名前が違うかも知れないので確認してください。

今回からSettingBeforeInit関数を追加しました。DxLib_Init関数の前に行う設定はここにまとめます。

ちなみにDXライブラリの数ある設定系関数の内、DxLib_Initの前に行えばいいのか後に行えばいいのかって分かりにくいですよね。この判断はWindowsAPIでイチからプログラムを作ってみると大体区別できるようになります。

要はDxLib_Init関数でウィンドウを生成しているので、ウィンドウ生成時に知っておかなければならない情報はDxLib_Initの前、ウィンドウが作られたあとで変更できる情報は後に設定すればいいのですが、ウィンドウの生成についてはこの連載では扱わない予定ですので興味のある方は各自で調べてみてください。

SettingBeforeInit関数では、引数から得た値でウィンドウモード、フルスクリーンモードを選択します。引数がTRUE、つまりウィンドウモードの時だけメニューバーを表示します。当たり前と言えば当たり前ですが、フルスクリーンの設定でメニューを表示しようとするとエラーになるようです。

SettingAfterInit関数は特に変更はありません。前回の記事をアップしたあとに、hMainWndをグローバル変数に修正したので、もしローカル変数のままの人は書き換えておいてください。今すぐ役に立つ訳ではないですが、追々使うことになるかもしれません。

自作プロシージャMyProcでは、WM_COMMANDメッセージが来た時のみ処理をするようにしています。

WM_COMMANDメッセージは様々なタイミングで発生しますが、今回はメニューバーから項目が選択された時に発生するメッセージを拾います。

前回の記事で少し触れていますが、WM_COMMANDはそのメッセージそのものに意味があるのではなく、メッセージの内部に更に細かい情報が詰め込まれています。

その詳細なメッセージはプロシージャの第三引数wpに収まっていますので、WM_COMMANDの中で更にswitch文を使い、場合分けをします。

このswitch文に書かれた「LOWORD()」は、関数ではなくマクロです。ここにwpを放り込むと、wpの上位ワードだけを吐き出してくれます。役割も関数に似ているので、そこまで神経質に違いを気にすることはないかもしれません。メニューバーのどのボタンが押されたかの情報は、wpの上位ワードに収められています。

そして、具体的に送られてくるメッセージというのが、メニューバーを作った時に設定したIDになります。

IDM_ENDが送られてきたら、SendMessage関数で自分自身にメッセージを送ります。そう、メッセージはシステムからだけでなく自分で出すこともできるのです。

第一引数に自分自身のウィンドウハンドル、第二引数にはWM_CLOSEを指定します。第三、第四引数は使用しないのでNULLを入れておきます。

このWM_CLOSEは、ウィンドウの×ボタンを押した時にも発生するメッセージです。

実はWM_CLOSEを呼び出しただけではウィンドウが閉じるだけで内部ではまだデータが残っている状態で、通常のWinodwsアプリではWM_CLOSEを受け取ったあとの動作も定義しないといけないのですが、どうやらDXライブラリの方で上手く片付けてくれているようです。おそらく×ボタンが押されるとProcessMessage関数がエラーを吐いてwhile文を抜け出し、DxLib_End関数で展開したファイルやら確保したメモリやらを片付けてから、安全にウィンドウを閉じてくれるのでしょう(その辺りの細かい動きは見たことがないので何とも言えないですが……)。

ビルドして、無事ファイルメニューから閉じるを選択することができたでしょうか。以上で今回の内容は終わりです。メニューの項目はいくらでも増やせるので、興味があれば自分で新しい項目を作って動きを定義してみるのも面白いですね。

項目を作る→IDを設定する→プロシージャでIDを受け取る→動きを定義するの流れを理解できていれば、いくらでも拡張の余地はあるでしょう。

次回はダイアログを使ってみたいと思います。