テクニカル ノート 26: DDX ルーチンおよび DDV ルーチン
注意
次のテクニカル ノートは、最初にオンライン ドキュメントの一部とされてから更新されていません。 結果として、一部のプロシージャおよびトピックが最新でないか、不正になります。 最新の情報について、オンライン ドキュメントのキーワードで関係のあるトピックを検索することをお勧めします。
ここでは、ダイアログ データ エクスチェンジ (DDX: Dialog Data eXchange) とダイアログ データ バリデーション (DDV: Dialog Data Validation) のアーキテクチャについて説明します。 また、DDX_ プロシージャと DDV_ プロシージャを記述する方法、ClassWizard を拡張して独自のルーチンを使用する方法についても説明します。
ダイアログ データ エクスチェンジの概要
ダイアログ データ エクスチェンジを行う関数はすべて C++ コードで記述されています。 特殊なマクロやリソースは使用されていません。 DDX/DDV 機構の中心は仮想関数で、ダイアログ データの交換と検証を行うすべてのダイアログ ボックス クラスでオーバーライドされます。 これらの関数は次の形式で記述します。
void CMyDialog::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX); // call base class
//{{AFX_DATA_MAP(CMyDialog)
<data_exchange_function_call>
<data_validation_function_call>
//}}AFX_DATA_MAP
}
上のコードで使われている AFX 形式のコメントは、ClassWizard が編集するコード部分を囲んでいます。 ClassWizard と互換性のないコードは、この形式のコメントの外側に記述する必要があります。
上のコード例の <data_exchange_function_call> は、次の形式になります。
DDX_Custom(pDX, nIDC, field);
また、<data_validation_function_call> は省略可能で、次の形式になります。
DDV_Custom(pDX, field, ...);
各 DoDataExchange 関数には、複数の DDX_ 関数や DDV_ 関数を記述できます。
MFC (Microsoft Foundation Class) で使用できる DDX/DDV 関数については、「afxdd_.h」を参照してください。
ダイアログ データだけです。メンバー データには、CMyDialogクラス。 このデータは、struct 型などのデータ構造には格納されていません。
注意
ダイアログ データの機能は、ダイアログ クラスだけではなく、すべての CWnd 派生クラスで使用できます。
データの初期値は C++ 標準コンストラクターによって (通常はコメント ブロック //{{AFX_DATA_INIT、//}}AFX_DATA_INIT の中で) 設定されます。
CWnd::UpdateData は、DoDataExchange の呼び出しに関連した初期化とエラー処理を行います。
CWnd::UpdateData は、データの交換および検証を行うために任意の時点で呼び出せます。 既定では、既定の CDialog::OnOK ハンドラーの中で UpdateData (TRUE) が呼び出され、既定の CDialog::OnInitDialog の中で UpdateData (FALSE) が呼び出されます。
DDX_ ルーチンの直後に、同じ field に対する DDV_ ルーチンを呼び出します。
動作原理
ダイアログ データを使うだけの場合は、この後の内容を理解する必要はありません。 しかし、処理の背後にある動作原理を理解することによって、独自の DDX/DDV 処理を作成できるようになります。
DoDataExchange メンバー関数は Serialize メンバー関数と同じように、外部フォーム (ダイアログ中のコントロール) とクラスのメンバーとの間でデータを取得したり設定したりします。 パラメーター pDX はデータ交換のコンテキストを指定します。これは CObject::Serialize の CArchive パラメーターに相当します。 pDX (CDataExchange オブジェクト) は、CArchive と同じように、データの転送方向を示すフラグを持っています。
!m_bSaveAndValidate が指定されている場合、コントロールにデータの状態が読み込まれます。
m_bSaveAndValidate が指定されている場合、コントロールのデータがクラス メンバーに設定されます。
データ検証は、m_bSaveAndValidate フラグが設定されている場合だけ実行されます。 m_bSaveAndValidate の値は、CWnd::UpdateData に渡す BOOL パラメーターによって決定されます。
この他に、CDataExchange には次に示す 3 種類のメンバーがあります。
m_pDlgWnd:コントロールを含むウィンドウ (通常はダイアログ ボックス)。 グローバル関数 DDX_ および DDV_ の呼び出し元から、各 DDX/DDV ルーチンに 'this' ポインターを渡す必要がなくなります。
PrepareCtrl, and PrepareEditCtrl:ダイアログ コントロールのデータ交換を準備します。 データの検証が失敗したときに、コントロールにフォーカスを移せるように、そのコントロールのハンドルを格納します。 PrepareCtrl は非エディット コントロールに対して、PrepareEditCtrl はエディット コントロールに対して使用されます。
失敗:読み込んだ後で、ユーザー入力エラーを警告するメッセージ ボックスと呼ばれます。 このルーチンはフォーカスを最後のコントロール (最後に呼び出した PrepareCtrl/PrepareEditCtrl のパラメーターに指定されたコントロール) に戻し、例外をスローします。 このメンバー関数は DDX_ ルーチンと DDV_ ルーチンの両方から呼び出されます。
ユーザー拡張機能
既定の DDX/DDV 機構を拡張するには、いくつか方法があります。 次の操作を行うことができます。
新しいデータ型を追加する。
CTime
新しいデータ交換プロシージャ (DDX_???) を追加する。
void PASCAL DDX_Time(CDataExchange* pDX, int nIDC, CTime& tm);
新しい検証プロシージャ (DDV_???) を追加する。
void PASCAL DDV_TimeFuture(CDataExchange* pDX, CTime tm, BOOL bFuture); // make sure time is in the future or past
検証プロシージャに任意の式を渡す。
DDV_MinMax(pDX, age, 0, m_maxAge);
注意
ClassWizard ではこのような任意の式を含むコードを処理できないため、このようなコードは特殊コメント ブロック (//{{AFX_DATA_MAP(CMyClass)) の外側に記述します。
DoDialogExchange メンバー関数の中に、条件分岐やその他の C++ 制御ステートメントを使って、データを交換および検証する関数を条件に応じて呼び出す。
//{{AFX_DATA_MAP(CMyClass)
DDX_Check(pDX, IDC_SEX, m_bFemale);
DDX_Text(pDX, IDC_EDIT1, m_age);
//}}AFX_DATA_MAP
if (m_bFemale)
DDV_MinMax(pDX, age, 0, m_maxFemaleAge);
else
DDV_MinMax(pDX, age, 0, m_maxMaleAge);
注意
ClassWizard では上の例のようなコードを編集できないので、このようなコードは特殊コメント ブロックの外側に記述します。
ClassWizard のサポート
ClassWizard は DDX/DDV のカスタマイズを一部サポートしているので、独自の DDX_/DDV_ ルーチンを ClassWizard のユーザー インターフェイスに組み込むことができます。 これにより、1 つあるいは複数のプロジェクトで同じ DDX / DDV ルーチンを繰り返して使用する場合は費用効果が上がります。
カスタマイズするには、DDX.CLW やプロジェクトの .CLW ファイルの中に専用エントリを作成します。以前のバージョンの Visual C++ ではこの情報は APSTUDIO.INI に格納していました。 この専用エントリは、プロジェクトの .CLW ファイルの [General Info] セクションか、\Program Files\Microsoft Visual Studio\Visual C++\bin ディレクトリの DDX.CLW ファイルの [ExtraDDX] セクションに記述できます。 DDX.CLW ファイルが存在しない場合、このファイルを作成する必要があります。 カスタム DDX_/DDV_ ルーチンを 1 つのプロジェクトの中だけで使用する場合は、そのプロジェクトの .CLW ファイルの [General Info] セクションにエントリを記述します。 複数のプロジェクトで DDX_/DDV_ ルーチンを使用するときは、DDX.CLW の [ExtraDDX] セクションにエントリを記述します。
これらの専用エントリは次の形式で記述します。
ExtraDDXCount=n
n は ExtraDDX? 行の総数です。
ExtraDDX?=<keys>;<vb-keys>; <prompt>; <type>; <initValue>; <DDX_Proc>
[;<DDV_Proc>; <prompt1>; <arg1>; [<prompt2>; <fmt2>]]
? は 1 から n までの数値で、リスト中の定義する DDX 型を示します。
各フィールドはセミコロン (;) で区切ります。 各フィールドの内容を次に示します。
<keys>
= この変数型を使えるダイアログ コントロールを示す単一文字の一覧E = エディット
C = 2 ステート チェック ボックス
c = 3 ステート チェック ボックス
R = グループ内の先頭のオプション ボタン
L = 並べ替えされないリスト ボックス
l = 並べ替えされるリスト ボックス
M = エディット項目を持つコンボ ボックス
N = 並べ替えされないドロップダウン リスト
n = 並べ替えされるドロップダウン リスト
1 = DDX によってリストの先頭にデータが挿入されます (既定ではリスト末尾に挿入)。一般にこの方式は Control プロパティを転送する DDX ルーチンで使用されます。
<vb-keys>
このフィールドは 16 ビット版の VBX コントロールでしか使われません。32 ビット版では VBX コントロールはサポートされていません。<prompt>
[プロパティ コンボ] ボックスの中に表示する文字列。引用符は不要です。<type>
ヘッダー ファイルに書き出す型の識別子。 たとえば上の DDX_Time の場合は、CTime に設定されます。<vb-keys>
現在のバージョンでは使用しません。常に空にしておきます。<initValue>
初期値 (0 または空)。 空のときは、実装ファイルの //{{AFX_DATA_INIT セクションに初期化コードが記述されません。 C++ オブジェクト (CString、CTime など) では、コンストラクターで適切な初期化を行うため、空エントリを指定する必要があります。<DDX_Proc>
DDX_ プロシージャの識別子。 C++ 関数名の先頭には "DDX_" が付きますが、このフィールドでは "DDX_" を除いた部分を指定します。 上の例では <DDX_Proc> は Time になります。 ClassWizard が実装ファイルの {{AFX_DATA_MAP セクションに関数の呼び出しを記述するときに、自動的に先頭に "DDX_" が追加されます。上の例では DDX_Time になります。<comment>
この DDX の変数に関するダイアログに表示するコメント。 任意の文字列を記述できますが、通常はこの DDX/DDV 処理に関する簡単な説明を記述します。<DDV_Proc>
以降の DDV に関するフィールドは省略できます。 DDX ルーチンだけを用意して、それに対応する DDV ルーチンを作成しない場合もあります。 また、データ転送時に同時にデータを検証するという方法もあります。 DDV ルーチンにパラメーターを指定しない場合は、この方法が使用されます。ClassWizard ではパラメーターを指定しない DDV 関数がサポートされていないためです。<arg>
DDV_ プロシージャの識別子。 C++ 関数名の先頭には "DDV_" が付きますが、このフィールドでは "DDV_" を除いた部分を指定します。
この後に DDV 引数を 1 つあるいは 2 つ指定します。
<promptX>
エディット項目の上に表示される文字列。アクセラレータを指定するときは & を付けます。<fmtX>
引数型を表す文字。次の中から 1 種類を指定します。d = int
u = unsigned
D = long int (つまり long)
U = long unsigned (つまり DWORD)
f = float
F = double
s = string