【MFC】UpdateData(TRUE/FALSE) の仕組み

【MFC】UpdateData(TRUE/FALSE) の仕組み

MFC ダイアログで UpdateData を書く順番を間違えて、入力値がメンバ変数に入っていない、あるいは画面が上書きされてしまう、という事故は珍しくありません。

この記事では、UpdateData(TRUE)UpdateData(FALSE) の役割を、DDX の裏側・自動呼び出しのタイミング・戻り値の意味までまとめます。


目次

最初に見る軸:どちら向きか

UpdateData は引数 1 つで向きが決まる、ただそれだけの関数です。

  • UpdateData(TRUE)画面 → メンバ変数(取り込む)
  • UpdateData(FALSE)メンバ変数 → 画面(書き戻す)

覚え方のコツは、「TRUE=真実は画面にある(から読み取る)」「FALSE=画面側は古い(から上書きする)」と考えることです。逆に書くと、画面の入力がメンバ変数に入らないまま処理が進みます。


裏で動いているのは DoDataExchange

UpdateData の実体は、CDialog::DoDataExchange(CDataExchange*) を呼び出す共通関数です。その中に並ぶ DDX_Text / DDX_Check / DDX_CBIndex などが、コントロールとメンバ変数を結ぶ「金具」の役割をします。

void CMySampleDlg::DoDataExchange(CDataExchange* pDX)
{
    CDialogEx::DoDataExchange(pDX);

    // DDX: コントロールとメンバ変数を結ぶ金具
    DDX_Text(pDX, IDC_EDIT_NAME, m_strName);

    // DDV: 入力バリデーション(32 文字を超えると UpdateData(TRUE) が false を返す)
    DDV_MaxChars(pDX, m_strName, 32);
}

ここに並んでいる DDX_* の行が、UpdateData を呼ぶたびに順番に実行されます。つまり UpdateData「この関数の中にある全 DDX を一斉に走らせる」 命令で、個別のコントロールだけを同期する仕組みではありません。


自動で呼ばれるタイミング

自分で UpdateData を書かなくても、MFC が自動で呼ぶタイミングが 2 つあります。

タイミング呼ばれる向き意味
ダイアログ表示直後(OnInitDialog 内部)UpdateData(FALSE)メンバ変数の初期値を画面に載せる
OK ボタン押下時(OnOK の先頭)UpdateData(TRUE)画面の入力をメンバ変数に吸い上げ、DDV で検証

この 2 箇所だけは手で書く必要がありません。自分で書く必要があるのは、この自動呼び出しタイミング以外で画面と同期したいときだけです。たとえば「Apply ボタンで一度確定したい」「計算結果を即座に画面に反映したい」のようなケースです。


戻り値の意味:UpdateData(TRUE) だけは要確認

UpdateData(TRUE) は、DDV_* のバリデーションに失敗すると FALSE を返します。このとき MFC は問題のコントロールに自動でフォーカスを移しAfxMessageBox でエラー文言を表示します。

void CMySampleDlg::OnBnClickedFetch()
{
    if (!UpdateData(TRUE))   // DDV に失敗したら false
    {
        // ここに来たときは、既に MFC 側でフォーカス移動と
        // 警告メッセージまで済んでいるので、自前で何かを出し直す必要はない
        return;
    }

    CString msg;
    msg.Format(_T("m_strName = [%s]"), (LPCTSTR)m_strName);
    AfxMessageBox(msg);
}

一方で UpdateData(FALSE) 側は、書き戻しに「失敗」という概念がほぼないため、戻り値を見る必要はありません。どちらでも返ってくるのは BOOL ですが、気にしておくのは TRUE 側だけで十分です。


よくある事故と直し方

  • TRUE/FALSE を逆に書く:画面の入力を読みたい場面で FALSE を書くと、古いメンバ変数が画面を上書きしてしまいます。向きが合っているか、必ず画面側を主語にして確認します
  • メンバ変数に値を入れてから UpdateData(FALSE) を忘れる:代入だけでは画面は更新されません。書き戻しを忘れないようにします
  • OnInitDialogCDialogEx::OnInitDialog() を呼ぶ前に値を入れる:基底クラスが UpdateData(FALSE) を走らせる前にメンバ変数が未初期化だと、画面に空文字が出ます。基底呼び出しの後で代入する順にします
  • ワーカースレッドから UpdateData を呼ぶUpdateData は UI スレッド前提です。別スレッドからは PostMessage で UI スレッドに処理を戻してから呼びます

今回の参照サンプル

「ユーザー名」を入力するエディットボックス 1 つと、取り込む / 書き戻す の 2 ボタンを持つダイアログです。初期化で UpdateData(FALSE) が走り、ボタンを押すたびに片方向だけ同期します。

BOOL CMySampleDlg::OnInitDialog()
{
    CDialogEx::OnInitDialog();
    m_strName = _T("Hello");
    UpdateData(FALSE);        // メンバ変数 -> 画面
    return TRUE;
}

void CMySampleDlg::OnBnClickedFetch()   // 取り込む
{
    if (!UpdateData(TRUE)) return;  // 画面 -> メンバ変数(DDV 込み)
    // ここで m_strName は画面の最新値
}

void CMySampleDlg::OnBnClickedApply()   // 書き戻す
{
    m_strName = _T("MFCGuide");
    UpdateData(FALSE);        // メンバ変数 -> 画面
}
Visual Studio で 062_updatedata_sample.cpp を開き、DoDataExchange と OnBnClickedFetch / OnBnClickedApply が並んで見えている画面

まとめ

  • [ ] 判断軸は 「画面とメンバ変数のどちらを主語にするか」。画面が真実なら TRUE、メンバ変数が真実なら FALSE
  • [ ] 実体は DoDataExchange。そこにある 全 DDX 行が一斉に走るので、部分同期はできない
  • [ ] OnInitDialogOnOK では MFC が自動で UpdateData を呼ぶ。手で書くのはそれ以外の場面だけ
  • [ ] UpdateData(TRUE) の戻り値は DDV の成否。失敗時は MFC がフォーカス移動と警告まで済ませる
  • [ ] 別スレッドから直接呼ばない。PostMessage で UI スレッドに戻してから呼ぶ
目次