ホーム ATL/WTL
DDX/DDV
ドキュメント種別 ATL/WTL に関する文書
最終更新日 2007/03/18
PR
 MFCではDDX/DDVという機能が用意されていますが、 WTLにも同様にDDX/DDV機能が用意されています。 ただし、MFCのそれとは少し使用方法が異なります。 WTLはDDX/DDV機能を実装するためにCWinDataExchangeクラステンプレートを用意しています。

DDX
 DDX(dialog data exchange)とは、ダイアログ上のコントロールの初期化と、 コントロールとのデータのやりとりを単純化するための仕組みです。

ここで言う「コントロールの初期化」とは、コントロールのサブクラス化や、 ウィンドウハンドルをコントロールクラスに設定することを意味します。 また、「コントロールとのデータのやりとり」とは、指定した変数の値をコントロールに設定したり、 逆にコントロールから取得した値を指定した変数に代入することを意味します。

 次に示すのは、WTLのDDXを使用する例です。 1 からエディットコントロールに入力した値までの総和を求めます。 (例えば10を入力した場合、1+2+3+・・・+10を計算します。) なお、入力値の上限は100とします。


プロジェクトファイル ダウンロード
// stdafx.h
#pragma once

#include <atlbase.h>
#include <atlapp.h>
extern CAppModule _Module;
#include <atlwin.h>

#include <atlcrack.h>
#include <atlmisc.h>
#include <atlctrls.h>
#include <atlddx.h>  // DDX/DDVを使用するため
			

// MainDlg.h
#pragma once

class CMainDlg : public CDialogImpl<CMainDlg>, public CWinDataExchange<CMainDlg>
{
public:
    enum { IDD = IDD_MAINDLG };

    CEdit m_edit_input;
    int m_nInput;

    // コンストラクタ
    CMainDlg() : m_nInput(1)
    {}

    // DDXマップ
    BEGIN_DDX_MAP(CMainDlg)
        DDX_CONTROL_HANDLE(IDC_EDIT_INPUT, m_edit_input)
        DDX_INT(IDC_EDIT_INPUT, m_nInput)
    END_DDX_MAP()

    BEGIN_MSG_MAP(CMainDlg)
        MSG_WM_INITDIALOG(OnInitDialog)
        COMMAND_ID_HANDLER_EX(IDC_BUTTON_SUM, OnButtonSum)
        COMMAND_ID_HANDLER_EX(IDOK, OnOK)
        COMMAND_ID_HANDLER_EX(IDCANCEL, OnCancel)
    END_MSG_MAP()

    BOOL OnInitDialog(CWindow wndFocus, LPARAM lInitParam){
        // スクリーンの中央に配置
        CenterWindow();

        // 大きいアイコン設定
        HICON hIcon = AtlLoadIconImage(IDR_MAINFRAME, LR_DEFAULTCOLOR,
            ::GetSystemMetrics(SM_CXICON), ::GetSystemMetrics(SM_CYICON));
        SetIcon(hIcon, TRUE);
        
        // 小さいアイコン設定
        HICON hIconSmall = AtlLoadIconImage(IDR_MAINFRAME, LR_DEFAULTCOLOR,
            ::GetSystemMetrics(SM_CXSMICON), ::GetSystemMetrics(SM_CYSMICON));
        SetIcon(hIconSmall, FALSE);

        // コントロール設定
        DoDataExchange(FALSE);

        m_edit_input.LimitText(3);

        return TRUE;
    }

    void OnDataExchangeError(UINT nCtrlID, BOOL bSave){
        CString strMsg;
        strMsg.Format(_T("コントロール(ID:%u)とのデータ交換に失敗。"), nCtrlID);
        MessageBox(strMsg, _T("DDXエラー"), MB_ICONWARNING);

        ::SetFocus(GetDlgItem(nCtrlID));
    }

    void OnButtonSum(UINT uNotifyCode, int nID, CWindow wndCtl){
        if(DoDataExchange(TRUE)){
            // 値チェック
            if(m_nInput < 1 || 100 < m_nInput){
                MessageBox(_T("1 から 100 までの値を入力してください。"),
                    _T("エラー"), MB_ICONWARNING);
                return;
            }

            int nSum = 0;
            for(int i=1; i<=m_nInput; i++)
                nSum += i;
            CString strMsg;
            strMsg.Format(_T("1 から %d までの総和は %d です。"), m_nInput, nSum);
            MessageBox(strMsg);
        }
    }

    void OnOK(UINT uNotifyCode, int nID, CWindow wndCtl){
        EndDialog(nID);
    }

    void OnCancel(UINT uNotifyCode, int nID, CWindow wndCtl){
        EndDialog(nID);
    }
};
			

// SampleProject.cpp
#include "stdafx.h"
#include "resource.h"
#include "MainDlg.h"

CAppModule _Module;

int WINAPI _tWinMain(HINSTANCE hInstance, 
    HINSTANCE /*hPrevInstance*/, LPTSTR /*lpstrCmdLine*/, int /*nCmdShow*/)
{
    HRESULT hRes = ::CoInitialize(NULL);
    ATLASSERT(SUCCEEDED(hRes));

    ::DefWindowProc(NULL, 0, 0, 0L);

    AtlInitCommonControls(ICC_BAR_CLASSES);

    hRes = _Module.Init(NULL, hInstance);
    ATLASSERT(SUCCEEDED(hRes));

    CMainDlg dlg;
    int nRet = (int) dlg.DoModal();

    _Module.Term();
    ::CoUninitialize();

    return nRet;
}
			

 まず、リソースを作成します。ダイアログにエディットコントロールとボタンコントロールを配置し、 それぞれの[Caption]と[ID]を次のように設定します。 なお、エディットコントロールの[プロパティ]では[Number]を[True]に設定します。

コントロール名 Caption ID
エディット - IDC_EDIT_INPUT
ボタン 計算 IDC_BUTTON_SUM

 次に、stdafx.hヘッダでは、DDX/DDVを使用するためにatlddx.hヘッダをインクルードします。

 CMainDlgクラスでは、基底クラスにCWinDataExchangeを追加し、 CEditクラスのインスタンスと、int型の変数を、 CMainDlgクラスのメンバ変数として宣言します。 このint型変数は、エディットコントロールに入力された数値を保持するための変数であり、 CMainDlgクラスのコンストラクタで 1 に初期化します。

CEditクラスのインスタンスはDDXマップのDDX_CONTROL_HANDLEマクロによって初期化されます。 DDX_CONTROL_HANDLEマクロは、 第1引数に渡されたIDのコントロールのウィンドウハンドルを、 第2引数に渡されたオブジェクトに代入します。

WTLのDDXはコントロールの初期化用に次のようなマクロを用意しています。

  • DDX_CONTROL(ID, オブジェクト名)
    指定されたIDのコントロールを指定されたオブジェクトにサブクラス化します。

  • DDX_CONTROL_HANDLE(ID, オブジェクト名)
    指定されたIDのコントロールのウィンドウハンドルを指定されたオブジェクトに代入します。

なお、これらのマクロが実行される(つまりコントロールクラスのインスタンスが初期化される)のは、 コントロールクラスのインスタンスにNULLが設定されていて、 かつDoDataExchange(FALSE)を呼び出した時だけです。 今回の例では、WM_INITDIALOGメッセージハンドラ内でDoDataExchange(FALSE)を呼び出して、 CEditクラスのインスタンスであるm_edit_inputにエディットコントロールのウィンドウハンドルを設定します。

 CMainDlgクラスのDDXマップではさらに、 DDX_INTマクロを使ってエディットコントロールとint型変数を関連付けます。 これにより、DoDataExchange(FALSE)を呼び出した時は自動的にSetDlgItemInt()を呼び出して変数の値をエディットコントロールに設定し、 DoDataExchange(TRUE)を呼び出した時は自動的にGetDlgItemInt()を呼び出してエディットコントロールに入力された数値を変数に代入します。 DoDataExchange()は値の取得または設定に成功したときはTRUEを返し、 失敗したときはFALSEを返します。

WTLのDDXはコントロールと変数の関連付け用に次のようなマクロを用意しています。

  • DDX_TEXT(ID, 変数名)
    指定されたIDのコントロールと指定された文字列型変数(LPTSTR、BSTR、CComBSTR、CString)を関連付けます。

  • DDX_INT(ID, 変数名)
    指定されたIDのコントロールと指定されたint型変数を関連付けます。

  • DDX_UINT(ID, 変数名)
    指定されたIDのコントロールと指定されたUINT型変数を関連付けます。

  • DDX_FLOAT(ID, 変数名)
    指定されたIDのコントロールと指定されたfloat型変数を関連付けます。

  • DDX_FLOAT_P(ID, 変数名, 精度)
    指定されたIDのコントロールと指定されたfloat型変数を関連付けます。精度を指定できます。

  • DDX_CHECK(ID, 変数名)
    指定されたIDのチェックボタンコントロールと指定されたint型またはbool型変数を関連付けます。

  • DDX_RADIO(ID, 変数名)
    指定されたIDのラジオボタンコントロールと指定されたint型変数を関連付けます。

なお、コントロールと変数の間でデータの取得または設定が失敗したときは、 CWinDataExchangeのメンバ関数であるOnDataExchangeError()が呼び出されます。 CMainDlgクラスではこのOnDataExchangeError()をオーバーライドして、 メッセージボックスを表示します。

 最後に、リソースIDがIDC_BUTTON_SUMWM_COMMANDメッセージハンドラとしてOnButtonSum()を追加します。 このハンドラ関数では、入力された値をチェックし、 有効な値であれば 1 から入力された値までの総和を計算してメッセージボックスで表示します。

DDV
 DDV(dialog data validation)とは、 ダイアログ上のコントロールに入力されたデータの正当性をチェックするための仕組みです。

DDXの例では、エディットコントロールに入力された数値の範囲チェックを OnButtonSum()で次のように行いました。

void OnButtonSum(UINT uNotifyCode, int nID, CWindow wndCtl){
    if(DoDataExchange(TRUE)){
        // 値チェック
        if(m_nInput < 1 || 100 < m_nInput){
            MessageBox(_T("1 から 100 までの値を入力してください。"),
                _T("エラー"), MB_ICONWARNING);
            return;
        }
        ...
        ...
			

DDVを使用すると、このような値のチェック処理を簡略化できます。 次に示すのは、前述のDDXを使用したプログラムで値のチェックにDDVを使用する例です。

プロジェクトファイル ダウンロード
// MainDlg.h
#pragma once

class CMainDlg : public CDialogImpl<CMainDlg>, public CWinDataExchange<CMainDlg>
{
public:
    enum { IDD = IDD_MAINDLG };

    CEdit m_edit_input;
    int m_nInput;

    // コンストラクタ
    CMainDlg() : m_nInput(1)
    {}

    // DDXマップ
    BEGIN_DDX_MAP(CMainDlg)
        DDX_CONTROL_HANDLE(IDC_EDIT_INPUT, m_edit_input)
        DDX_INT_RANGE(IDC_EDIT_INPUT, m_nInput, 1, 100)
    END_DDX_MAP()

    BEGIN_MSG_MAP(CMainDlg)
        MSG_WM_INITDIALOG(OnInitDialog)
        COMMAND_ID_HANDLER_EX(IDC_BUTTON_SUM, OnButtonSum)
        COMMAND_ID_HANDLER_EX(IDOK, OnOK)
        COMMAND_ID_HANDLER_EX(IDCANCEL, OnCancel)
    END_MSG_MAP()

    BOOL OnInitDialog(CWindow wndFocus, LPARAM lInitParam){
        // スクリーンの中央に配置
        CenterWindow();

        // 大きいアイコン設定
        HICON hIcon = AtlLoadIconImage(IDR_MAINFRAME, LR_DEFAULTCOLOR,
            ::GetSystemMetrics(SM_CXICON), ::GetSystemMetrics(SM_CYICON));
        SetIcon(hIcon, TRUE);
        
        // 小さいアイコン設定
        HICON hIconSmall = AtlLoadIconImage(IDR_MAINFRAME, LR_DEFAULTCOLOR,
            ::GetSystemMetrics(SM_CXSMICON), ::GetSystemMetrics(SM_CYSMICON));
        SetIcon(hIconSmall, FALSE);

        // コントロール設定
        DoDataExchange(FALSE);

        m_edit_input.LimitText(3);

        return TRUE;
    }

    void OnDataExchangeError(UINT nCtrlID, BOOL bSave){
        CString strMsg;
        strMsg.Format(_T("コントロール(ID:%u)とのデータ交換に失敗。"), nCtrlID);
        MessageBox(strMsg, _T("DDXエラー"), MB_ICONWARNING);

        ::SetFocus(GetDlgItem(nCtrlID));
    }

    void OnDataValidateError(UINT nCtrlID, BOOL bSave, _XData& data){
        CString strMsg; 
        strMsg.Format(_T("%d から %d までの値を入力してください。"),
            data.intData.nMin, data.intData.nMax);
        MessageBox(strMsg, _T("DDVエラー"), MB_ICONEXCLAMATION);

        ::SetFocus(GetDlgItem(nCtrlID));
    }

    void OnButtonSum(UINT uNotifyCode, int nID, CWindow wndCtl){
        if(DoDataExchange(TRUE)){
            int nSum = 0;
            for(int i=1; i<=m_nInput; i++)
                nSum += i;
            CString strMsg;
            strMsg.Format(_T("1 から %d までの総和は %d です。"), m_nInput, nSum);
            MessageBox(strMsg);
        }
    }

    void OnOK(UINT uNotifyCode, int nID, CWindow wndCtl){
        EndDialog(nID);
    }

    void OnCancel(UINT uNotifyCode, int nID, CWindow wndCtl){
        EndDialog(nID);
    }
};
			

まず、DDXマップのDDX_INTマクロを、DDX_INT_RANGEマクロに変更します。 このマクロの第1、第2引数はDDX_INTマクロと同様、コントロールのIDとint型変数を指定します。 第3引数には値の最小値を、第4引数には値の最大値を指定します。

これにより、DoDataExchange()を呼び出して コントロールに値を設定したりコントロールから値を取得する時に、 その値の範囲チェックが行われます。 正当な値のときは、DoDataExchange()はTRUEを返します。 不正な値のときはFALSEを返します。

WTLは次のようなDDV機能付きDDXマクロを用意しています。

  • DDX_TEXT_LEN(ID, 変数名, 文字数)
    指定されたIDのコントロールと指定された文字列型変数(LPTSTR、BSTR、CComBSTR、CString)を関連付けます。文字数チェックをします。

  • DDX_INT_RANGE(ID, 変数名, 最小値, 最大値)
    指定されたIDのコントロールと指定されたint型変数を関連付けます。値の範囲チェックをします。

  • DDX_UINT_RANGE(ID, 変数名, 最小値, 最大値)
    指定されたIDのコントロールと指定されたUINT型変数を関連付けます。値の範囲チェックをします。

  • DDX_FLOAT_RANGE(ID, 変数名, 最小値, 最大値)
    指定されたIDのコントロールと指定されたfloat型変数を関連付けます。値の範囲チェックをします。

  • DDX_FLOAT_P_RANGE(ID, 変数名, 最小値, 最大値, 精度)
    指定されたIDのコントロールと指定されたfloat型変数を関連付けます。精度を指定できます。値の範囲チェックをします。

DDVによって値が不正であると判断された場合は、 CWinDataExchangeのメンバ関数であるOnDataValidateError()が呼び出されます。 CMainDlgクラスではこのOnDataValidateError()をオーバーライドして、 メッセージボックスを表示します。

 最後に、OnButtonSum()から不要となった数値の範囲チェック処理を削除します。