DDX和DDV方法
本文主要介绍DDX和DDV的体系结构,本文也帮助您了解DDX或DDV的过程以及如何扩展ClassWizard,使它可以使用用户的过程。下面我们首先来看一下对话框数据交换。请注意,在上面的两个过程中,关键的问题在于虚函数的重载。我们经常会在我们用ClassWizard生成的代码中看到下面的代码:
void CMyDialog::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX); // call base class
//{{AFX_DATA_MAP(CMyDialog)
<data_exchange_function_call>
//}}AFX_DATA_MAP
}
其中AFX是由ClassWizard控制的,只有包括在AFX中间的代码才可以由ClassWizard管理,如果用户不希望让一些东西让ClassWizard管理了,那就把这些东西移出AFX就可以了。但是,通常的情况下还是不这样做的好。上面代码中还有一段没有写出来,那就是<data_exchange_function_call>,它的形式可以是DDX_Custom(pDX, nIDC, field),或是DDV_Custom(pDX, field, ...);,所有的关于DDX和DDV的定义在文件'afxdd_.h'中定义。函数初始化的定义在类的构造函数中的//{{AFX_DATA_INIT和//}}AFX_DATA_INIT之间。
CWnd::UpdateData函数是我们经常使用的,我们经常在它后面的值中取一些true或者false之类的值,那让我们来看看,如果是true,它都做了些什么。它实际上进行的就是数据交换。我们一般不用了解下面的所说的数据交换过程一样可以使用对话框,但是如果我们能够了解一点,那当然要好多了。我们可以把DoDataExchange成员函数看成是Serialize(串行化)成员函数,反正两都都是将数据送入类中,或把数据从类中送出。我们在上面代码中一定看到了一个pDX的东西,它是一个参数,它是用于进行数据交换的参数,我们上面已经知道了在UpdateData中要传送一个参数,表示方向,那我们看看pDX中表示数据传输方向(这个方向就是表示数据究竟是从类中传输出来呢,还是向类传送进去呢):
如果m_bSaveAndValidate为假,将数据传输到相应的控件上去,数据传输出类了。
如果m_bSaveAndValidate为真,将数据传输到类中来,数据传输进类了!
在设置了m_bSaveAndValidate之后,验证(数据有效性的)过程才得以进行。
下面有几个函数可是不能不说:
m_pDlgWnd: 它指是包括当前控件的窗口,它用来防止DDX_和DDV_的全局调用;
PrepareCtrl:是用于准备进行数据交换的对话框控件,它返回一个窗口句柄。如果验证过程失败,就得用这个句柄使原来的窗口继续获得焦点。请注意:PrepareCtrl用于准备非编辑性的控件,对于可以进行输入和编辑的控件请使用PrepareEditCtrl;
Fail: 我们验证过程失败时,调用Fail过程,这个函数的主要作用就是恢复对原来窗口的焦点,保持原来窗口中的输入与选择内容,发出一个Exception。
我们可以通过下面几个方法扩展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);
下面的验证就是验证输入的年纪是不是在0到最大年纪(对人来说可能是500岁)之间:
DDV_MinMax(pDX, age, 0, m_maxAge);
上面的这些代码不能由ClassWizard管理,所以要放在//{{AFX_DATA_MAP(CMyClass))之外。
下面是一些有条件的验证,它的意思,我想在我就不多嘴了,看这篇文章的也多少知道几个英文,大家看英文自己也会明白的。
//{{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中管理DDX/DDV,那不更好吗。ClassWizard支持DDX和DDV的子集,通过让ClassWizard管理DDX和DDV,我们会得到一些方便。特别在重复使用的时候更是如此。如果希望实现这个功能,可以在DDX.CLW中或工程的.CLW中进行编译,加入相应的函数入口。在工程的.CLW中,将用户自定义的函数入口写在[General Info]段中,也可以在DDX.CLW的[ExtraDDX]段中加入相应的代码,但是请注意,一定要把DDX.CLW放在\Program Files\DevStudio\SharedIDE\bin目录下。当然,如果DDX.CLW不存在,那只有用户自己创建它了。如果你只希望在你的工程中使用特定的函数入口,将相应的函数入口加入工程的.CLW中就可以了。如果你希望在多个工程中和以后的工作中使用,则可以在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 = edit
C = two-state check box
c = tri-state check box
R = first radio button in a group
L = non-sorted list box
l = sorted list box
M = combo box (with edit item)
N = non-sorted drop list
n = sorted drop list
如果DDX需要插入在列头上,则要使用1。它一般用于传输控制属性的DDX过程。
<vb-keys> 此域用于传输16位VBX控件的一些情况,对于32位的产品,根本就不支持VBX;
<prompt> = 放置在属性组合框中的字符串;
<type> = 在头文件中出现的单个标记。在上例中,它应该被设置为CTime;
<vb-keys> = 为空
<initValue> = 初值为0或为空。如果为空,不需要在初始化行//{{AFX_DATA_INIT内进行初始化。只有当成员为类的时候才好使用这个选项,因为对于类,有类的初始化函数进行初始化,可是对于其它类型还是要进行初始化的;
<DDX_Proc> = DDX过程的标识符,DDX过程的C++函数名必须以DDX开始,但这个DDX不应该包括在<DDX_Proc>标识符中。上例中的<DDX_Proc>应该为Time,ClassWizard可以在将函数写入{{AFX_DATA_MAP段时加上DDX。
<comment> = 注释。用户愿意放什么就可以放什么。
<DDV_Proc> DDV过程标识符,不是所有的DDX过程后面都要有DDV过程,为了方便起见,可以将DDV过程作为集成化传输的一部分。你可以写上这一项,但是不加参数,ClassWizard不会将不带参数的DDV加入代码中的。DDV过程也要有DDV作为前缀,但是不必加上,因为ClassWizard可以自己加上。下面两个是DDV参数:
<promptX> = 放置在编程项目上的字符串,可以使用&作为加速键
<fmtX> = 参数类型的格式字符,它们有:
d = int
u = unsigned
D = long int (that is, long)
U = long unsigned (that is, DWORD)
f = float
F = double
s = string
例子大家可以参考VC自己带的例子chkbook。