MFCでPreTranslateMessage関数を使う話

つい先日、仕事で行き詰っていた処理がありました。

今回はそれを解決してくれたPreTranslateMessage
事例について備忘録です。

準備

プロジェクト新規作成です。

PreTranslateMessageTestと名付けましょう。

プロジェクトの作り方はこちらから。

コントロールは、エディットボックスとボタンを一つ用意します。
また、既存のOKボタンは不要なので削除します。
キャンセルボタンは使うのでそのままに。

位置は適当に。

用意するボタンは特に処理を加えません。
なので正直、
エディットボックスでもチェックボックスでもコンボボックスでもいいのですが。

こんな感じ。

そしてもう一つ。
Ctrl + D でタブでコントロールが動く順を確認しておきます。

この順番にしておく必要はないですが、
今回はこの順を前提に話を進めます。
要は、エディットボックスからTabを押すとキャンセルに、
Tab + Shiftでダミーボタンに動くことが分かればいいです。

最後にエディットボックスのプロパティについて。
事象の再現状、数字しか入力できないようにしておきます。
また、IDはIDC_EDIT_SAMPLEにします。

ひとまずビルド。

処理① エディットボックスキルフォーカス

先ほどのコントロール配置画面から、
エディットボックスを選択後右クリックにて「イベントハンドラーの追加」を選択。

画像のように、クラスの一覧をCPreTranslateMessageTestDlg、
メッセージの種類をEN_KILLFOCUSにセットします。
これでOK。

OnEnKillfocusEditSample関数が作られます。

エディットボックスからフォーカスが外れるたび、呼ばれるようになります。

この関数内に処理を記述。
今回は、桁数が5桁以上か判定を。

/**
 * @fn
 * @brief	エディットボックスキルフォーカス処理
 * @param	なし
 * @return	なし
 */
void CPreTranslateMessageTestDlg::OnEnKillfocusEditSample()
{
	// TODO: ここにコントロール通知ハンドラー コードを追加します。
	CEdit* pEdit = (CEdit*)GetDlgItem(IDC_EDIT_SAMPLE);
	CString strText;
	pEdit->GetWindowText(strText);		// エディットボックス内のテキストを取得します

	if (strText.GetLength() >= 5)
	{
		AfxMessageBox(_T("5桁以上入力されています。"));
	}

}

これだけ。
実際にビルドしてみます。

4桁以下だとメッセージは出てきませんが、
5桁以上入力して他のボタンを押したりTabを押すと。

はい、チェック処理が働きました。

処理② キャンセル押されたら何もしない

先の処理の場合、当然キャンセルボタンを押してチェック処理が働きます。

キャンセル押したなら
もうチェックしなくていいじゃん!

そこで、キャンセルが押されたら例外的に処理を行わないようにします。

先ほどのキルフォーカス関数で、
今どこにフォーカスが当たっているかを考え、
キャンセルならreturnさせます。

/**
 * @fn
 * @brief	エディットボックスキルフォーカス処理
 * @param	なし
 * @return	なし
 */
void CPreTranslateMessageTestDlg::OnEnKillfocusEditSample()
{
	// TODO: ここにコントロール通知ハンドラー コードを追加します。
	CWnd* pWndCancel = GetDlgItem(IDCANCEL);
	CWnd* pWndFocus = GetFocus();
	if (pWndCancel == pWndFocus)
	{
		// キャンセルにフォーカスが当たっているのでチェックしない
		return;
	}

	// それ以外はチェック処理を進める
	CEdit* pEdit = (CEdit*)GetDlgItem(IDC_EDIT_SAMPLE);
	CString strText;
	pEdit->GetWindowText(strText);		// エディットボックス内のテキストを取得します

	if (strText.GetLength() >= 5)
	{
		AfxMessageBox(_T("5桁以上入力されています。"));
	}

}

11行目で現在フォーカス当たっている箇所を特定、
キャンセルボタンならば処理を抜けるようにしました。

これで再度、ビルドして動作を見てみます。

5桁以上入力された状態でダミーボタンを押すと今まで通りメッセージが出てきます。

しかし、キャンセルボタンを押すと
メッセージが出てこずにダイアログを閉じることができました。

処理③ Tabキーが押されたか

ここからが、本題です。

確かにキャンセルを押されたらチェックせずに閉じるのはいいです。

しかしこれだと、
Tabキーでフォーカスがキャンセルに移動した場合もチェックが行えません。
15行目のreturnに入ってしまいます。

これが、仕事で行き詰ってた話でした。
Tabキーで次のコントロールに行くのはよく行う動作ですし、
それはキャンセルで閉じる話じゃないんだから、チェックしてほしいなぁ…。

そこで今回の主役、PreTranslateMessageの出番です。

Ctrl + Shift + Cで、クラスビューを開きます。

いま処理を行っているのはCPreTranslateMessageTestDlgクラスなので、そこを選択。

プロパティからオーバーライドを選択し、
PreTranslateMessageを追加します。

これで、関数が自動で追加されます。

この関数は、イベントが起こるたび(ボタンが押される、クリックされる等々…)呼ばれる関数
…です(不安)。
詳しくは公式か別のブログを見てください。

さて、処理の順としては
PreTranslateMessage⇒OnEnKillfocusEditSampleです。
なので、PreTranslateMessageでタブが押されたかを保持し、
その情報をもとにOnEnKillfocusEditSampleでチェック処理をするか否かを判定します。

PreTranslateMessageTestDlg.hにて、メンバ変数を要します。

class CPreTranslateMessageTestDlg : public CDialogEx
{
//略

private:
	BOOL m_bTabFlag;	// Tabキーが押されましたか?
};

続いてPreTranslateMessageTestDlg.cppに戻り、
コンストラクタで、m_bTabFlagをFALSEに初期化。

CPreTranslateMessageTestDlg::CPreTranslateMessageTestDlg(CWnd* pParent /*=nullptr*/)
	: CDialogEx(IDD_PRETRANSLATEMESSAGETEST_DIALOG, pParent)
{
	m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
	m_bTabFlag = FALSE;
}

そしていよいよ、タブが押されたか判定。

BOOL CPreTranslateMessageTestDlg::PreTranslateMessage(MSG* pMsg)
{
	// TODO: ここに特定なコードを追加するか、もしくは基底クラスを呼び出してください。
	if (pMsg->message == WM_KEYDOWN)	
	{
		// キーが押されました

		if (pMsg->wParam == VK_TAB)
		{
			// タブキーが押されたので、フラグを立てます
			m_bTabFlag = TRUE;
		}
	}

	return CDialogEx::PreTranslateMessage(pMsg);
}

8行目の条件で、Tabが押されたかの判定をします。

最後にキルフォーカス処理を修正。

/**
 * @fn
 * @brief	エディットボックスキルフォーカス処理
 * @param	なし
 * @return	なし
 */
void CPreTranslateMessageTestDlg::OnEnKillfocusEditSample()
{
	// TODO: ここにコントロール通知ハンドラー コードを追加します。
	CWnd* pWndCancel = GetDlgItem(IDCANCEL);
	CWnd* pWndFocus = GetFocus();
	if (pWndCancel == pWndFocus)
	{
		if (m_bTabFlag == TRUE)
		{
			// これは、Tabキーが押された場合
			// ⇒チェックはしたいので関数を抜けない
			m_bTabFlag = FALSE;	// もとに戻しておく
		}
		else
		{
			// キャンセルボタンが押されたのでチェックせず終わる
			return;
		}
	}

	// それ以外はチェック処理を進める
	CEdit* pEdit = (CEdit*)GetDlgItem(IDC_EDIT_SAMPLE);
	CString strText;
	pEdit->GetWindowText(strText);		// エディットボックス内のテキストを取得します

	if (strText.GetLength() >= 5)
	{
		AfxMessageBox(_T("5桁以上入力されています。"));
	}

}

14~24行目を追加です。
これでTabキーが押されたら以降の処理が行われ、
キャンセルが押されたら関数を抜ける仕様です。

実際に試したところ、
期待通りの結果を得ることができました。

活用例

pMsg->wParam == VK_TAB
の判定はもちろん、VK_TABを変更すると
どのキー押されたかを色々チェックできます。

一例ですが

  • VK_BACK⇒Back Space
  • VK_RETURN⇒Enter
  • VK_SHIFT⇒Shift
  • VK_CONTROL⇒Ctrl
  • VK_LEFT⇒「←」キー
  • VK_UP⇒「↑」キー
  • VK_RIGHT⇒「→」キー
  • VK_DOWN⇒「↓」キー
  • VK_F1⇒F1 (他のファンクションキーも同様)

またキーボードとは違いますが

  • VK_LBUTTON⇒マウス左クリック
  • VK_RBUTTON⇒マウス右クリック

なども判定できます。

例えば先ほどのPreTranslateMessageにこのような処理を記述してみます。

BOOL CPreTranslateMessageTestDlg::PreTranslateMessage(MSG* pMsg)
{
	// TODO: ここに特定なコードを追加するか、もしくは基底クラスを呼び出してください。
	if (pMsg->message == WM_KEYDOWN)	
	{
		if (pMsg->wParam == VK_RIGHT)
		{
			// 「→」キーを押すと次のコントロールへフォーカス
			NextDlgCtrl();
		}
		else if (pMsg->wParam == VK_LEFT)
		{
			// 「←」キーを押すと前のコントロールへフォーカス
			PrevDlgCtrl();
		}
	}

	return CDialogEx::PreTranslateMessage(pMsg);
}

本来、左右矢印を押してもコントロールは移動できませんが、
これにより移動可能となります。

他にも、
Escキーを押すと新しいダイアログを出す、
F1を押すたびコントロールの色が切り替わるなど、
考えられる処理はたくさんありますね。

終わりに

今回はPreTranslateMessage関数について紹介しました。

正直、今の今までこの関数の存在自体を知りませんでした…。
今回業務で初めて出会いました。

結構便利なモノだと分かったので、
例えばショートカット処理を作るときに今後活用してみようかと思います。

今回はここまで。

SampleSrc/CPlusPlus/PreTranslateMessageTest at main ?? Tatsumath/SampleSrc
Various sources, mostly C++. Contribute to Tatsumath/SampleSrc development by creating an account on GitHub.

コメント

タイトルとURLをコピーしました