今回はIconViewer.cppの解説で、「あまり書く事がないな」と思っていたら、プログラムミスを発見してしまいました。ついでに面白い実験もしていますので紹介します。

 

前回「・(EZImage等他のダイアログ、更にDLL化して使えるように、モーダル(注))ダイアログベースのアプリとする」と書きました。

注:老婆心迄。

ご本家MFC:

ちょっと面白い「飼い猫」による説明:

その為、SkeltonWizardが作成したIconViewer.cppファイルはマニュアルで修正を加えています。

 

1. IconViewer.cpp

短いので全文を載せます。

//////////////////////////////////////////
// IconViewer.cpp
//Copyright (c) 04/09/2021 by BCCSkelton
//////////////////////////////////////////
#include    "IconViewer.h"
#include    "IconViewerProc.h"

////////////////
// WinMain関数
////////////////
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    LPSTR lpCmdLine, int nCmdShow) {

    //ファイルにより起動する場合(g_ByFileフラグを立てる)
    if(Arg.c() > 1)    g_ByFile = TRUE;

    //アクセラレーター登録-InclAccel("(Accelerator ID)")
    IconViewer.InclAccel("IDA_ACCEL");

    //モーダルダイアログとして起動
    IconViewer.DoModal(NULL, "IDD_ICON", IconViewerProc, hInstance);

    return 0;
}


(1)最初の「/ファイルにより起動する場合(g_ByFileフラグを立てる)」

これは「ファイルを投げ入れて起動する」場合(即ち起動コマンドに第二引数-例:"C:\Borland C++\IconViewer\Release\IconViewer.exe C:\Borland C++\SampleIcon\suchandsuch.ico"で起動した場合のファイルパスとデータファイル名(suchandsuch.ico)-がある場合)に(FALSEで初期化されている)グローバル変数g_ByFileをTRUEとしています。(その関連処理はIconViewerProc.hに記述されます。)

 

(2)次の「//アクセラレーター登録-InclAccel("(Accelerator ID)")」

BCCFormで作成されたアクセラレーターがどのようにBCCSkeltonで使われるか、の一般説明をします。

BCCSkeltonではメインとなるウィンドウ(やモードレスダイアログ)に次の形で対応しています。

①ウィンドウ(モードレスダイアログ)を登録(Init関数)

②ウィンドウ(モードレスダイアログ)の作成(Create関数)

③アクセラレーターを登録(InclAccel関数)

④メッセージ処理(Loop関数)

既にBCCSkeltonのInit()とCreate()関数はHelloWorldプロジェクトでやりましたので省略します。

BCCSkeltonのInclAccel関数(③)は単にWin32APIのLoadAcceleratorsを実行するだけです。

BCCSkeltonのLoop関数(④)は以下(例:CDLGクラス)

    while(GetMessage(&m_Msg, m_hWnd, 0, 0)) {
        if(!TranslateAccelerator(m_hWnd, m_Accel, &m_Msg) &&
        !IsDialogMessage(m_hWnd, &m_Msg)) {
            TranslateMessage(&m_Msg);
            DispatchMessage(&m_Msg);
        }
    }
    return m_Msg.wParam;
のようになっており、モードレスダイアログを持つ場合やアクセラレータを使用する場合を前提に書かれています。

実は今回アクセラレーターを作ったのですが、モードレスダイアログでなく、モーダルダイアログに変更した関係上、このLoop関数を使わないのでアクセラレーターも使われません。(驚!)

まぁ、ジョークとしてください。アクセラレーターを使う場合はcppファイルを以下サンプルを参考に元に戻します。

<サンプルモードレスダイアログ>

(略)

    //モードレスダイアログを作成Create(hParent, DlgName, DlgProc);
    if(!IconViewer.Create(NULL, hInstance, "IDD_ICON", IconViewerProc))
        return 0L;
    //アクセラレーター登録-InclAccel("(Accelerator ID)")
    IconViewer.InclAccel("IDA_ACCEL");
    //メッセージループに入る
    return IconViewer.Loop();
(略)

今回の「//モーダルダイアログとして起動」と比べると、モーダルダイアログとモードレスダイアログの違いがよく分かります。(なお、モーダルダイアログとしてビルドするも、プログラムサイズが気になる方はrcファイルのIDA_ACCELとアクセラレーター登録の部分を削除してください。)

 

さて、SkeltonWizardが(HelloWorld.cppのように)モードレスダイアログとしてコーディングしたものを、今回モーダルダイアログにマニュアルで書き換えたのですが、そうする場合、プログラムの終了はEndModal関数で終わらなければなりません。所がプログラミング中にコロッとこれを忘れ、「終了処理がモードレスダイアログのまま(即ちWM_CLOSE-OnClose()とWM_DESTROY-OnDestroy()による終了処理)」になっていることが判明しました。(汗;-しかし実際何ら表面上は正常に作動していたので全く気が付きませんでした。)

 

ではどこでEndModalを使うのでしょうか?

 

(簡単なダイアログのIDOKボタンのように)メニューやツールボタンでモーダルダイアログ処理を終了させる割り込み、即ちIDM_EXITの処理=OnExit()関数の処理(IconViewer.hで確認してください)でしょうか?

 

例えば従来のOnExitでは"SendMsg(WM_CLOSE, 0, 0);"とだけしていましたが、これを削除して"(クラス内定義なのでインスタンス名IconViewer.は不要)EndModal(戻り値-今回の場合なんでも結構です);"とします。

再ビルドして動かすと(当たり前ですが)正常に動作(プログラムの終了)します。し、か、し、「終了確認」ダイアログが現れないことに気が付きます。何故でしょう?

 

そうです、EndModal()(Win32APIのEndDialog()関数)を実行する場合、ダイアログはWM_CLOSEメッセージを出さないのでOnClose()関数を通らないからです。(私も今回初めて知りました。)

 

ではOnExit関数のところにOnClose関数にあった「終了確認」メッセージボックス処理を移し、使わなくなったOnCloseとOnDestroy関数を削除してビルドしましょう。今度は正常に終了確認が求められます。と、こ、ろ、が、ウィンドウ(ダイアログ)の「X」ボタン(これはwParamにSC_CLOSEをいれてWM_SYSCOMMANDメッセージを発生し、受け取ったDefWindowProc() がWM_CLOSEを送ります)を押すと、矢張りストンと終了してしまいます。これではEndModal()関数が実行されません。

 

次にOnDestroy関数を復活させて、EndModal()とWM_SYSCOMMAND+SC_CLOSEを夫々実行させてみると、この関数を通ること(WM_DESTROYメッセージ処理が実行される)ことが分かります。しかし、終了確定のWM_DESTROYで終了確認を求めるのは適切ではないでしょう。

 

ということで、結論的には現在アップされているコードのOnClose関数の定義にEndModal関数を追加することが一番スマートで手間がかからないことが分かります。

【修正コード】

bool ICONVIEWER::OnClose(WPARAM wParam, LPARAM lParam) {

    if(MessageBox(m_hWnd, "終了しますか", "終了確認",
                    MB_YESNO | MB_ICONINFORMATION) == IDYES) { (括弧を追加)
        //処理をするとDestroyWindow、PostQuitMessageが呼ばれる
        EndModal(TRUE); (ここにEndModalを追加)
        return TRUE;
    } (括弧を追加)
    else
        //そうでなければウィンドウではDefWindowProc関数をreturn、ダイアログではreturn FALSEとなる。
        return FALSE;
}

 

しかし、このブログを書いているおかげで思わぬところでミスを見つけられ、ダイアログの終了プロセスを確認でき、一番有効なソリューションを見つけられたので勉強になりました。

 

では、今回はここまで。(なお、このIconViewerサンプルの修正版は次の修正アップロードの際に載せますので、学習目的のためにも、それまでは↑の【修正コード】をマニュアルで修正してください。)