ホーム WTL Mobile
DDX/DDV
ドキュメント種別 ATL/WTL に関する文書
最終更新日 2009/12/05
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

#define WINVER 0x0420
#include <atlbase.h>
#if _ATL_VER == 0x900
#define _SECURE_ATL 1
#endif

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

#include <tpcshell.h>
#include <aygshell.h>
#pragma comment(lib, "aygshell.lib")

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

// SampleProjectDialog.h
#pragma once

class CSampleProjectDialog : 
    public CAppStdDialogImpl<CSampleProjectDialog>,
    public CUpdateUI<CSampleProjectDialog>,
    public CMessageFilter, public CIdleHandler, 
    public CWinDataExchange<CSampleProjectDialog>
{
public:
    DECLARE_APP_DLG_CLASS(NULL, IDR_MAINFRAME, L"Software\\WTL")

    enum { IDD = IDD_MAINDLG };

    CEdit m_edit_input;
    int m_nInput;

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

    virtual BOOL PreTranslateMessage(MSG* pMsg){
        return CWindow::IsDialogMessage(pMsg);
    }

    virtual BOOL OnIdle(){
        return FALSE;
    }

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

    BEGIN_UPDATE_UI_MAP(CSampleProjectDialog)
    END_UPDATE_UI_MAP()

    BEGIN_MSG_MAP(CSampleProjectDialog)
        MSG_WM_INITDIALOG(OnInitDialog)
        MSG_WM_DESTROY(OnDestroy)
        COMMAND_ID_HANDLER_EX(IDC_BUTTON_SUM, OnButtonSum)
        CHAIN_MSG_MAP(CUpdateUI<CSampleProjectDialog>)
        CHAIN_MSG_MAP(CAppStdDialogImpl<CSampleProjectDialog>)
    END_MSG_MAP()

    BOOL OnInitDialog(CWindow wndFocus, LPARAM lInitParam){
        AtlCreateEmptyMenuBar(m_hWnd);

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

        m_edit_input.LimitText(3);

        // メッセージループにメッセージフィルタとアイドルハンドラを追加
        CMessageLoop* pLoop = _Module.GetMessageLoop();
        pLoop->AddMessageFilter(this);
        pLoop->AddIdleHandler(this);

        SetMsgHandled(false);

        return TRUE;
    }

    void OnDestroy(){
        // メッセージループからメッセージフィルタとアイドルハンドラを削除
        CMessageLoop* pLoop = _Module.GetMessageLoop();
        pLoop->RemoveMessageFilter(this);
        pLoop->RemoveIdleHandler(this);
    }

    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);
        }
    }
};
			

// SampleProject.cpp
#include "stdafx.h"
#include "resourceppc.h"
#include "SampleProjectDialog.h"

CAppModule _Module;

int WINAPI _tWinMain(HINSTANCE hInstance,
    HINSTANCE /*hPrevInstance*/, LPTSTR lpstrCmdLine, int nCmdShow)
{
    HRESULT hRes =
        CSampleProjectDialog::ActivatePreviousInstance(hInstance, lpstrCmdLine);

    if(FAILED(hRes) || S_FALSE == hRes){
        return hRes;
    }

    hRes = ::CoInitializeEx(NULL, COINIT_MULTITHREADED);
    ATLASSERT(SUCCEEDED(hRes));

    AtlInitCommonControls(ICC_DATE_CLASSES);
    SHInitExtraControls();

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

    int nRet = CSampleProjectDialog::AppRun(lpstrCmdLine, nCmdShow);

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

    return nRet;
}
			

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

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

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

 CSampleProjectDialogクラスでは、基底クラスにCWinDataExchangeを追加し、 CEditクラスのインスタンスと、int型の変数を、 CSampleProjectDialogクラスのメンバ変数として宣言します。 このint型変数は、エディットコントロールに入力された数値を保持するための変数であり、 CSampleProjectDialogクラスのコンストラクタで 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にエディットコントロールのウィンドウハンドルを設定します。

 CSampleProjectDialogクラスの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()が呼び出されます。 CSampleProjectDialogクラスではこの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を使用する例です。

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

class CSampleProjectDialog : 
    public CAppStdDialogImpl<CSampleProjectDialog>,
    public CUpdateUI<CSampleProjectDialog>,
    public CMessageFilter, public CIdleHandler, 
    public CWinDataExchange<CSampleProjectDialog>
{
public:
    DECLARE_APP_DLG_CLASS(NULL, IDR_MAINFRAME, L"Software\\WTL")

    enum { IDD = IDD_MAINDLG };

    CEdit m_edit_input;
    int m_nInput;

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

    virtual BOOL PreTranslateMessage(MSG* pMsg){
        return CWindow::IsDialogMessage(pMsg);
    }

    virtual BOOL OnIdle(){
        return FALSE;
    }

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

    BEGIN_UPDATE_UI_MAP(CSampleProjectDialog)
    END_UPDATE_UI_MAP()

    BEGIN_MSG_MAP(CSampleProjectDialog)
        MSG_WM_INITDIALOG(OnInitDialog)
        MSG_WM_DESTROY(OnDestroy)
        COMMAND_ID_HANDLER_EX(IDC_BUTTON_SUM, OnButtonSum)
        CHAIN_MSG_MAP(CUpdateUI<CSampleProjectDialog>)
        CHAIN_MSG_MAP(CAppStdDialogImpl<CSampleProjectDialog>)
    END_MSG_MAP()

    BOOL OnInitDialog(CWindow wndFocus, LPARAM lInitParam){
        AtlCreateEmptyMenuBar(m_hWnd);

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

        m_edit_input.LimitText(3);

        // メッセージループにメッセージフィルタとアイドルハンドラを追加
        CMessageLoop* pLoop = _Module.GetMessageLoop();
        pLoop->AddMessageFilter(this);
        pLoop->AddIdleHandler(this);

        SetMsgHandled(false);

        return TRUE;
    }

    void OnDestroy(){
        // メッセージループからメッセージフィルタとアイドルハンドラを削除
        CMessageLoop* pLoop = _Module.GetMessageLoop();
        pLoop->RemoveMessageFilter(this);
        pLoop->RemoveIdleHandler(this);
    }

    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);
        }
    }
};
			

まず、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()が呼び出されます。 CSampleProjectDialogクラスではこのOnDataValidateError()をオーバーライドして、 メッセージボックスを表示します。

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