今回は、DDVルーチンはなぜDDXルーチンの直後に呼び出さないといけないかについてご紹介いたします。
MSDNの言葉
DDXとは「ダイアログ データ エクスチェンジ(Dialog Data Exchange)」のことで、ダイアログボックスのコントロールの初期化・値の取得を容易にする仕組みです。
DDVとは「ダイアログ データ 検証(Dialog Data Validation)」のことで、データ交換に加えて、指定した規則に従った検証を行う仕組みです。
MSDNで以下のように言及されている通り、DDVルーチンはDDXルーチンの直後に呼び出さなければいけません。
A DDV function for a given control must be called immediately after the DDX function for the same control.
The DDV_ routine should immediately follow the DDX_ routine for that field.
本記事では、この「直後に呼び出さなければいけなせん。」に焦点を当てます。
DDX/DDVの順番
はじめに
ではまず、DDVルーチンがDDXルーチンの直後に呼び出されないとどのような挙動をするのでしょうか。
ここでは、それぞれエディットボックスに紐づける二つの変数を用いて検証してみます。
ダイアログクラスがCWndクラスからオーバーライドした「void DoDataExchange(CDataExchange* pDX)」関数から、DDXルーチンとしてDDX_Text関数を、DDVルーチンとしてDDV_MaxChars関数を呼び出し、文字数上限を検証規則として設定します。
※継承関係は「ダイアログクラス>CDialogEx>CDialog>CWnd」。
検証は、ダイアログの初期化処理完了時の状態を対象とします。
(CDialog::OnInitDialog関数内のUpdateData(FALSE)の呼び出しにて実行されるDoDataExchange()の結果を確認する)
成功とするのは、「文字数上限を超えるとエディットボックスに入力ができない」状態です。
ーーーーーーーーーーーーーーーーーー
エディットボックス①
ID:IDC_EDIT_1
変数:m_strText1(型はCString)
入力上限:10文字にする
ーーーーーーーーーーーーーーーーーー
エディットボックス②
ID:IDC_EDIT_2
変数:m_strText2(型はCString)
入力上限:15文字にする
ーーーーーーーーーーーーーーーーーー
case⑴:成功(DDX①→DDV①→DDX②→DDV②)
コード
DDX_Text(pDX, IDC_EDIT_1, m_strText1); // DDX①
DDV_MaxChars(pDX, m_strText1, 10); // DDV①
DDX_Text(pDX, IDC_EDIT_2, m_strText2); // DDX②
DDV_MaxChars(pDX, m_strText2, 15); // DDV②
結果
それぞれのDDXルーチンの直後にそれぞれのDDVルーチンを呼び出した場合、
エディットボックス①、②ともに、「文字数上限を超えると入力ができない」状態になりました。
case⑵:失敗(DDV①→DDX①→DDV②→DDX②)
コード
DDV_MaxChars(pDX, m_strText1, 10); // DDV①
DDX_Text(pDX, IDC_EDIT_1, m_strText1); // DDX①
DDV_MaxChars(pDX, m_strText2, 15); // DDV②
DDX_Text(pDX, IDC_EDIT_2, m_strText2); // DDX②
結果
それぞれのDDXルーチンの前にDDVルーチンを呼び出した場合、
エディットボックス①は「15文字しか入力できない」状態で、エディットボックス②は「上限を超えても入力可能」になりました。
case⑶:失敗(DDX①→DDX②→DDV①→DDV②)
コード
DDX_Text(pDX, IDC_EDIT_1, m_strText1); // DDX①
DDX_Text(pDX, IDC_EDIT_2, m_strText2); // DDX②
DDV_MaxChars(pDX, m_strText1, 10); // DDV①
DDV_MaxChars(pDX, m_strText2, 15); // DDV②
結果
DDXルーチンをまとめて呼び出した後にDDVルーチンを呼び出した場合、
エディットボックス①は「上限を超えても入力可能」で、エディットボックス②は「15文字しか入力できない」状態になりました。
では、なぜこのような挙動になるのでしょうか?
解答パート
DDVルーチンが目的のコントロールを知る方法
鍵を握っているのは、DDXルーチン・DDVルーチンの双方に共通する引数pDX(CDataExchange*型)です。
CDataExchangeクラスはデータの保存検証有無や、親ウィンドウへのポインタを保持しています。
DDXルーチンが呼び出されると、内部で「指定されたIDのコントロール」のハンドルが取得されます。
ここで取得したコントロールに対して、データの設定・取得を行うことで、ダイアログデータ交換が実現されています。
またハンドルを取得した際には、「最後に指定されたコントロールID」として、指定したIDがCDataExchangeクラス内に保持されます。
※CDataExchangeクラスのメンバ変数「m_idLastControl」が保持する。
他方でDDVルーチンは、コントロールを取得するために、先ほどCDataExchangeクラス内に保持した「最後に指定されたコントロールID」を利用します。
このように、DDVルーチンはDDXで更新されたCDataExchangeクラスの情報をもとに、データの検証を行います。
そのため、DDVルーチンはDDXルーチンの直後に呼び出されなければなりません。
DDX/DDVの順番の検証結果の理由
上記の検証から、DDX/DDVの順番について以下のことが分かります。
case⑵
DDX①にて IDC_EDIT_1が「最後に指定されたコントロールID」として保持され、DDV②にてその情報を基に文字数上限が「15字」に設定された。
case⑶
DDV①、DDV②の呼び出し時ともに、 IDC_EDIT_2が通知対象のコントロールとして扱われている。
ゆえに、エディットボックス②に対して、DDV①、DDV②の検証が行われていることがわかる。
DDV_MaxCharsの文字数上限の設定方法は、SendMessage関数による文字数上限の通知なので、エディットボックス②の文字数上限が「10字」→「15字」と更新された。
振り返り
case⑶にて初期化完了後、エディットボックス①に10字以上入力してからUpdateData(TRUE)を呼び出すと、以下のようなメッセージボックスが表示されます。
これは、DDV_MaxChars関数内で条件に応じてメッセージボックスを出す処理が行われており、UpdateData(TRUE)関数内の処理でDoDataExchange()が実行される際にこの条件を満たしたために起こります。
ただ、MSDNをきちんと読み、内部での処理をきちんと理解していなければ、突然出たメッセージボックスに驚くこともあると思います(ありました)。
ただMSDNの「~してはいけない」を鵜吞みにせず、なぜしてはいけないのかまで理解することで、このようにうっかり「~してはいけない」ことをしてしまうことも少なくなると思います。
また、内部処理を理解することで、それを拡張して活用することも可能になります。
業務で用いるコードには今まで先輩方が蓄積してきた便利な仕組みが多々ありますので、それを間違いなく使いこなし、活かせるようになるためにも、本質的な理解は怠らないようにしたいと思います。
DDX/DDVの順番については以上です。
さいごに
今回は、「DDX/DDVの順番はなぜ順番通りでないといけないのか」について確認しました。
これはダイアログデータ交換を扱う上で基本的な事柄ですが、提供コードを追っていくことで、ダイアログデータ交換の本質的な理解により近づけたように感じています。
また、基本的な部分こそ万事に通ずるところでもありますので、今後も基本の裏付けは重視していきたいと思います。
以上です。
最後まで読んでいただきありがとうございました。