0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

モーダルダイアログでの ESC/Return の押下を処理したい

Last updated at Posted at 2016-01-14

問題

自作アプリの設定ダイアログで、ショートカットキーの設定をしようとしていました。
エディットコントロール上で ESC を押した時に、WM_KEYDOWN を処理して "Escape" という文字列を表示したいのです。
ですが、ESC や Enter を押した瞬間、ダイアログが閉じてしまいます。

原因

ESC や Enter の WM_KEYDOWN メッセージがシステム標準のアクセラレータテーブルによって IDCANCEL/IDOK の WM_COMMAND に変換されてしまっています。

システム標準のアクセラレータテーブルを操作する方法はありません。
TranslateAccelerator によって WM_COMMAND に変換される前に WM_KEYDOWN を処理する必要があります。

ダメな方法

MFC の CDialog::OnOK() や OnCancel() を実装しろというのが一番多く見つかりますが、これはだいたい嘘っぱちです。
ダイアログを閉じないだけで、エディットコントロールにWM_KEYDOWN が問題は解決していません。

また、OnOK() はともかく、OnCancel() は ESC のほか、Ctrl-Break でも送られてくるので見分けがつきません。

解法1

MFC の場合、CDialog::PreTranslateMessage が TranslateAccelerator の呼び出し前に呼ばれています。
このメソッドを実装して、ESC の WM_KEYDOWN を処理してしまえば良いはずです。

解法2

モーダルダイアログを Win32API の DialogBox(), DialogBoxParam() で表示している場合、解法1では解決できません。
DialogBox 関数の中でメッセージ処理が走っていて、標準的な方法では PreTranslateMessage のタイミングに割り込むことができないからです。MFC でも DialogBox を利用している所があれば同じでしょう。

結論から言うと、フックを使います。

アクセラレータテーブルでメッセージが処理されているということは、WM_KEYDOWN がメッセージ・キューに溜まっているということです。このメッセージキューからメッセージを取り出す瞬間に割り込んでやることで、TranslateAccelerator の前に WM_KEYDOWN を処理してやることができます。

HWND sm_hwndKeyEdit;
HHOOK hHook;
static LRESULT CALLBACK kbHookProc(int code, WPARAM wParam, LPARAM lParam) {
    if (-1 < code) {
        switch(wParam) {
        case VK_RETURN:
        case VK_CANCEL: //  Ctrl-Break.
        case VK_ESCAPE:
            if ((lParam & (3 << 30)) == 0) {  //  on )key down...
                if (GetFocus() == sm_hwndKeyEdit) {
                    SendMessage(sm_hwndKeyEdit, WM_KEYDOWN, wParam, lParam);
                    // cancel TranslateAccelerator call.
                    return TRUE;
                }
            }
        }
    }
    return CallNextHookEx(nullptr, code, wParam, lParam);
}
static INT_PTR CALLBACK procAcceleratorEditDlg(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) {
    HWND hwndKeyEdit = GetDlgItem(hDlg, IDC_EDIT1);
    switch (message) {
    case WM_INITDIALOG:
        if (hwndKeyEdit) {
            #pramga region hwndKeyEditのサブクラス化
              
            #pragma endregion
            sm_hwndKeyEdit = hwndKeyEdit;
            hHook = SetWindowsHookEx(WH_KEYBOARD, kbHookProc, nullptr, GetCurrentThreadId());
        }
        break;
    case WM_DESTROY:
        if (hHook) {
            UnhookWindowsHookEx(pThis->hHook);
            hHook = nullptr;
        }
        break;
    // etc...
    }
}

解法 3 (追記)

今回のような、特定コントロールでのみ ESC/Return を処理したい場合は、もっと簡単な方法がありました。

ESC/Return を処理したいコントロールで、WM_GETDLGCODE を処理します。

static LRESULT CALLBACK keyEditProc(HWND hwndKeyEdit, UINT message, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData) {
    switch (message) {
    case WM_GETDLGCODE:                 //  ★
        if (lParam) {                   //  ★
            return DLGC_WANTALLKEYS;    //  ★
        }                               //  ★
        break;                          //  ★
    //  etc.
    }
    return DefSubclassProc(hwndKeyEdit, message, wParam, lParam);
}

static INT_PTR CALLBACK procAcceleratorEditDlg(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) {
    HWND hwndKeyEdit = GetDlgItem(hDlg, IDC_EDIT1);
    switch (message) {
    case WM_INITDIALOG:
        if (hwndKeyEdit) {
            #pramga region hwndKeyEditのサブクラス化
            const UINT_PTR uIdSubclass = 0;

            if (SetWindowSubclass(hwndKeyEdit, keyEditProc, uIdSubclass, (DWORD_PTR)this) == FALSE) {
                ...
            }
            #pragma endregion
            //  フックは使いません
        }
    case WM_NCDESTROY:
        if (RemoveWindowSubclass(hwndEdit, keyEditProc, uIdSubclass) == FALSE) {
            ...
        }
        break;

    // etc...
    }
}

解法 2 はダイアログに、対象のコントロールが複数あるケースの時用ですね。

0
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?