MFCプログラミング(4):
Enter(ESC)キーでダイアログが閉じないようにする

2021年4月10日

どもです。
今回は、キーボード入力の処理、特にEnterキー/ESCキーでダイアログが閉じられることの回避です。

1.背景(簡単に)

データ入力画面をダイアログで作成した際に、画面上でEnterキー/ESCキーを押すと、画面(ダイアログ)が閉じられます。
この動作を回避する際に、少しハマりました。

2.内容

MFCのダイアログは、画面上でEnterキー/ESCキーを押すと閉じる、というのがデフォルトの動作です。
しかし、ダイアログベースの画面で複数の入力用コントロールを持つ場合、この動作ではアプリケーションが使いにくくなります。
事実、Enterキーを押下したら画面が閉じる、というのを仕様としてユーザー側に注意してもらう、ということも考えましたが、現実的ではありませんでした。
なので、何とかしてEnterキー/ESCキー(特にEnterキー)を押してもダイアログが閉じないようにする必要がありました。

3.原因

CDialogクラスでは、Enterキー押下のイベントハンドラにOnOk()が、ESCキー押下のイベントハンドラにOnCancel()が、それぞれ設定されています。
そのため、デフォルトの状態で、各キーを押すと画面が閉じるようになっています。

Enterキー/ESCキー押下に対するイベントハンドラの設定は、CDialogクラスの設定です。
CDialogクラス自体の変更はできないので、それを継承したクラスで対応する必要があります。

4.対応

CDialogクラスでEnterキー/ESCキー押下のイベントが処理される前に、その処理を「横取り」することで対応します。
この「横取り」を実施するために、「PreTranslateMessage()」をオーバーライドします。
なお、PreTranslateMessage()は、MSDNで下記のように定義されています。

virtual BOOL PreTranslateMessage(MSG* pMsg);

MSDN
具体的な実装を示すと、下記のようになります。

BOOL CSampleDialog::PreTranslateMessage(MSG* pMsg)
{
    if (WM_KEYDOWN == pMsg->message) {
        if (VK_RETURN == pMsg->wParam) {
            return TRUE;
        }
        else if (VK_ESCAPE == pMsg->wParam) {
            return TRUE;
        }
        else {
            return CDialogEx::PreTranslateMessage(pMsg);
        }
    }
    else {
        return CDialogEx::PreTranslateMessage(pMsg);
    }
}

この実装で、まずはEnterキー/ESCキーを押してもダイアログが閉じなくなります。

5.注意点

「4.対応」で示した実装では、実は問題があります。
それは、
Enterキー/ESCキーが押された際のPreTranslateMessage()の戻り値
です。
「4.対応」のコードでは、Enterキー/ESCキーが押された場合には「TRUE」を返していますが、たとえば複数行入力可能に設定したCEditがフォーカスされていた場合、エンターキーを押しても、改行が行われなくなります。
ENTERキーを押してもダイアログが閉じず、しかし改行が行われるようにするためには、PreTranslateMessage()の戻り値を「FALSE」にします。
具体的な実装は、下記の通りです。

BOOL CSampleDialog::PreTranslateMessage(MSG* pMsg)
{
    if (WM_KEYDOWN == pMsg->message) {
        if (VK_RETURN == pMsg->wParam) {
            return FALSE;   //←ココを変更する
            //return TRUE;
        }
        else if (VK_ESCAPE == pMsg->wParam) {
            return TRUE;
        }
        else {
            return CDialogEx::PreTranslateMessage(pMsg);
        }
    }
    else {
        return CDialogEx::PreTranslateMessage(pMsg);
    }
}

即ち、MSDNでも解説されている通り、他のコントロールに対してENTERキー/ESCキー押下のイベントを通知する必要がある場合にはFALSEを、通知する必要がない場合にはTRUEをそれぞれ返すように実装します。
このように、PreTranslateMessage()によるEnterキー/ESCキーイベントを「横取り」では、他のコントロールでの当該キー入力に対する動作を考えて戻り値を決定する必要があります。

6.まとめ

今回は、MFCのダイアログにおいて、Enterキー/ESCキーが押されても画面が閉じられないようにする方法について書きました。
この方法の実装そのものは、とても簡単です。
しかし、ダイアログのEnterキー/ESCキーに対する動作のみではなく、ダイアログ上の他のコントロールの動作も考える必要があるので、少し注意が必要です。

ではっ!