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。 |