前回質問させていただいた「子ダイアログ内の複数行エディットボックス」と
同じような質問になってしまうのですが、
今度はプッシュボタンについてですので、別スレッドで質問させていただきます。
やはりMFCで複数の子ダイアログを持つオプション設定ダイアログで、
子ダイアログにはDS_CONTROLを設定しているのですが、
フォーカスを持つプッシュボタンをEnterキーで押しても、
子ダイアログ内のイベントハンドラが呼ばれません。
Spaceキーやマウスクリックなどで押すと呼ばれます。
Spy++で追いかけてみると、子ダイアログではなく親ダイアログのほうに
BN_CLICKEDのメッセージが行っているようなのですが、
子ダイアログでは、Spaceキーで押したときとEnterキーで押したときとで、
このような違いが出てくるものなのでしょうか。
Windows自身の問題なのか、MFCの問題なのか、
それとも自分が作った親子ダイアログの問題なのかが特定できていません。
みなさんが作られた親子ダイアログでも、
このような挙動の違いが発生していましたでしょうか。
DS_CONTROLとは、それを指定されたDLG内のコントロールが、
そのDLGの親DLGの直下のコントロールであるかのように
振舞わせるオプションです。
つまり、DS_CONTROLにしたということは、そのDLGを単なる
コンテナ様の動作となるよう指定したことになります。
DLGにおいては
1.唯一の「デフォルトボタン」を持つ
2.唯一の「キーボードフォーカスを持つコントロール」を持つ
ので、上記の権利は親DLGが行使できるようになっています。
DS_CONTROLのDLGは、そのフラグの指定により、その権利を
放棄しています。
ちなみに、
3.DLGで「エンターキー」が押下された場合は、
そのDLGの「デフォルトボタン」がクリックされたとして
処理されます。
4.DLGで「スペースバー」が押下された場合は、
「キーボードフォーカスを持つコントロール」が
クリックされたとして処理されます。
従って、ご質問内で報告されている動作は「正常」であると
考えられます。
> Windows自身の問題なのか、MFCの問題なのか、
> それとも自分が作った親子ダイアログの問題なのかが特定できていません。
> みなさんが作られた親子ダイアログでも、
> このような挙動の違いが発生していましたでしょうか。
Windowsの仕様のようです。
私は親で未処理のWM_COMMANDを子ダイアログに送っています。
MFCの場合にこの策が通用するかどうかはわかりません。
お二方、ありがとうございます。
このメッセージの流れがWindows本来の動作なのですか。
VisualStudioのオプションダイアログでも調べてみたところ、
子ダイアログ内のボタンにフォーカスがある状態で、
スペースで押す→子ダイアログのみにBN_CLICKED
Enterキーで押す→親ダイアログにもダイアログにもBN_CLICKED
というメッセージが来ていました。
これは、子ダイアログに直接BN_CLICKEDが行くようなプロパティがあったり、
親ダイアログにも子ダイアログにもイベントハンドラを用意しているわけではなく、
VisualStudio自身で、BN_CLICKEDを受け取った親ダイアログが
子ダイアログにBN_CLICKEDを再送信するような処理を行っている
ということになるのですね。
少し試してみました。
PUSHBUTTONにフォーカスがあるときにSPACEキーをDOWN/UPすると
ボタンのWindowProcがWM_KEYDOWN/WM_KEYUPで
ボタンの親にWM_COMMANDをSendしているようです。
PUSHBUTTONにニーモニクが設定されている場合(例 &Button)、
そのキーを押すとボタンの親にWM_COMMANDがSendされていました。
こっちは、IsDialogMessageの動作だと思います。
ついでにVisualStudioのoption dialogをSpu++で観察してみました。
Enterキーを押したときにメッセージは
親DLG S WM_COMMAND
子DLG S WM_COMMAND
子DLG R WM_COMMAND
親DLG R WM_COMMAND
の順になっていましたので、親DLGが子にSendしているようです。
> このような挙動の違いが発生していましたでしょうか。
> このメッセージの流れがWindows本来の動作なのですか。
本来あるべき姿なのかどうかは知らないけど、所謂「This behavior is by design.」?
ただ、ボタン側で WM_GETDLGCODE に対応すればダイアログに入力を奪わせない事は可。
例えば↓の様に組めば子ダイアログ上ボタンでもフォーカス時にEnterでボタン押せた。
// MFC
BEGIN_MESSAGE_MAP(button, CButton)
ON_WM_GETDLGCODE()
ON_WM_KEYDOWN()
ON_WM_KEYUP()
END_MESSAGE_MAP()
UINT button::OnGetDlgCode() {
if (GetCurrentMessage()->wParam == VK_RETURN) {
// 自分に対して Enter が入力された時は己で処理するとダイアログに通知
return DLGC_WANTALLKEYS;
}
return CButton::OnGetDlgCode();
}
void button::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) {
switch (nChar) {
case VK_RETURN:
#if 0
GetParent()->SendMessage(WM_COMMAND, MAKEWPARAM(GetDlgCtrlID(),
BN_CLICKED), LPARAM(m_hWnd)); // 単純に←でも良いが
#else
if ((nFlags & 0x4000) == 0) {
// クライアント領域を擬似マウスダウン
SendMessage(WM_LBUTTONDOWN, 0, MAKELPARAM(0, 0));
}
#endif
break;
}
CButton::OnKeyDown(nChar, nRepCnt, nFlags);
}
void button::OnKeyUp(UINT nChar, UINT nRepCnt, UINT nFlags) {
switch (nChar) {
case VK_RETURN:
// クライアント領域を擬似マウスアップ
SendMessage(WM_LBUTTONUP, 0, MAKELPARAM(0, 0));
// とする事でOnKeyDown時の擬似マウスダウンと合わさってBN_CLICKED発生
break;
}
CButton::OnKeyUp(nChar, nRepCnt, nFlags);
}
class xxxxxxDialog : public CDialog {
button m_btn_; // サブクラス化された対象ボタン
};
情報ありがとうございます。
前回の「子ダイアログ内の複数行エディットボックス」のときに作成した
CDialogExの派生クラスで、PreTranslateMessageに以下のような処理を入れたところ、
子ダイアログ内のボタン上でのEnterキーにも対応することができました。
BOOL CBaseDialog::PreTranslateMessage(MSG* pMsg)
{
if (GetStyle() & WS_CHILD && GetStyle() & DS_CONTROL) {
if (pMsg->message == WM_KEYDOWN && pMsg->wParam == VK_RETURN) {
CWnd* pWnd = CWnd::FromHandle(pMsg->hwnd);
int dlgc = pWnd->SendMessage(WM_GETDLGCODE);
if (dlgc & (DLGC_DEFPUSHBUTTON | DLGC_UNDEFPUSHBUTTON)) {
pWnd->SendMessage(BM_CLICK);
return TRUE;
}
}
}
return CDialogEx::PreTranslateMessage(pMsg);
}
なお、ロマさんに解析いただいた情報を元に、
以下のようにすれば、VisualStudioの親子ダイアログと同じような
WM_COMMANDメッセージの流れにもできそうだったのですが、
CPaneDialogにも子ダイアログを乗せており、そうなると親となるCPaneDialogも
派生クラスを作って同じようなOnCommandを実装しないといけないため、
今回は子ダイアログだけで完結できるようにさせていただきました。
BOOL CBaseDialog::OnCommand(WPARAM wParam, LPARAM lParam)
{
if (CDialogEx::OnCommand(wParam, lParam)) {
return TRUE;
}
CWnd* pWndCtrl = CWnd::FromHandle((HWND)lParam);
if (pWndCtrl != NULL) {
CWnd* pWndOwner = pWndCtrl->GetOwner();
if (pWndOwner != NULL && pWndOwner->m_hWnd != m_hWnd) {
return (pWndOwner->SendMessage(WM_COMMAND, wParam, lParam) == 0);
}
}
return FALSE;
}
またツッコミどころがあれば指摘していただければと思います。
ありがとうございました。