ホーム ATL/WTL
DDX/DDV
ドキュメント種別 ATL/WTL に関する文書
最終更新日 2004/02/24
PR
 MFCではDDX/DDVという仕組みが用意されていますが、 WTLにも同様にDDX/DDV機能が用意されています。 ただし、MFCのそれとは少し使用方法が異なります。 ここではWTLのDDX/DDVについて説明します。

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

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

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


// stdafx.h内
#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内
class CMainDlg : public CDialogImpl<CMainDlg>, public CWinDataExchange<CMainDlg>
{
public:
    enum { IDD = IDD_MAINDLG };

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

    CEdit m_edit_input;
    int m_nInput;

    // 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_EX(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()

    LRESULT OnInitDialog(HWND hWnd, LPARAM lParam){
        // スクリーンの中央に配置
        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, HWND hWndCtl){
        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, HWND hWndCtl){
        EndDialog(nID);
    }

    void OnCancel(UINT uNotifyCode, int nID, HWND hWndCtl){
        EndDialog(nID);
    }
};
			

// Control.cpp内
#include "stdafx.h"

#include "resource.h"

#include "maindlg.h"

CAppModule _Module;

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

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

    AtlInitCommonControls(ICC_COOL_CLASSES | ICC_WIN95_CLASSES);

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

    int nRet = 0;
    // BLOCK: アプリケーション実行
    {
        CMainDlg dlgMain;
        nRet = dlgMain.DoModal();
    }

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

    return nRet;
}
			

 まず、リソースを作成します。ダイアログにエディットコントロールとボタンコントロールを配置し、 リソースIDを次のように指定します。 なお、エディットコントロールの[スタイル]ではデフォルトに加え[番号]にチェックを入れます。

コントロール名 リソースID
エディット IDC_EDIT_INPUT
プッシュボタン IDC_BUTTON_SUM

 次に、DDX/DDVを使用するためにstdafx.h内で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)を呼び出した時だけです。 上の例では、OnInitDialog()内でDoDataExchange(FALSE)を呼び出して、 CEditクラスのインスタンスであるm_edit_inputにエディットコントロールのウィンドウハンドルを設定しています。 その後CEdit::LimitText()を呼び出してエディットコントロールに3桁しか入力できないようにしています。

 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()をオーバーライドして、 メッセージボックスを表示するようにしています。

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

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

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

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

DDVを使用すると、このような値のチェック処理を簡略化できます。 次に示すのはDDVを使って値のチェックをする例です。 先のDDXの例のCMainDlgクラスのみを変更します。

// maindlg.h内
class CMainDlg : public CDialogImpl<CMainDlg>, public CWinDataExchange<CMainDlg>
{
public:
    enum { IDD = IDD_MAINDLG };

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

    CEdit m_edit_input;
    int m_nInput;

    // 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_EX(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()

    LRESULT OnInitDialog(HWND hWnd, LPARAM lParam){
        // スクリーンの中央に配置
        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, HWND hWndCtl){
        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, HWND hWndCtl){
        EndDialog(nID);
    }

    void OnCancel(UINT uNotifyCode, int nID, HWND hWndCtl){
        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()をオーバーライドして、 メッセージボックスを表示するようにしています。