1. 对话框里控件上的数据应该如何保存?
1) 一般需要在CDialog的派生类中定义一些数据成员用来保存控件中用户输入的数据;
2) 那么最大的问题就是如何让这些定义的数据成员和控件中的数据进行及时的交流——使用SetDlgItemText和GetDlgItemText两个函数即可:
i. 在OnInitDialog中使用SetDlgItemText将数据成员作为初始值来初始化控件,前提是要对数据成员先进行初始化;
ii. 在OnOK、OnCancel等退出按钮的处理函数中使用GetDlgItemText将控件中的数据回写到相应的数据成员中;
iii. 例如:SetDlgItemText(IDC_NAME, m_strName)、GetDlgItemText(IDC_NAME, m_strName);
!第一个参数是目标控件的ID,第二个是数据成员,用于和控件交换数据;
3) 以上交换数据的缺陷:
i. 数据类型限制较大:只能交换字符串型的数据,有时候用数值类型保存更加方便;
ii. 数据正确与否无法检验:用户输入的数据是否合理(超出规定范围等)不能自动检验,如果数据多,则需要使用大量的语句进行校验,效率低下;
4) MFC给出的解决方案:在CDialog的虚函数DoDataExchange中使用DXX函数和DDV函数分别进行数据交换和数据校验即可,非常方便,无需在OnInitDialog和退出按钮函数中调用Set和Get函数,即可自动进行交换(数据的流动方向也可以自动判断)和校验;
2. DoDataExchange函数和CDataExchange类:
1) DoDataExchange的原型:virtual void CWnd::DoDataExchange(CDataExchange* pDX);
i. 可以看到该函数是从CWnd继承而来的,因此普通的窗口也可以进行这样的数据交换;
ii. 就是在该函数中使用DDX函数(Do Data Exchange的缩写)进行数据交换,使用DDV函数(Do Data Validate的缩写)进行数据校验;
2) pDX即pointer of CDataExchange的缩写,CDataExchange类主要功能就是记录数据的流向,pDX将作为DDX和DDV函数的参数,当收到WM_INITDIALOG时pDX记录的流向是从数据成员流向控件,当对话框退出时pDX记录的流向是从控件流向数据成员,在这两个时间点都会自动调用DoDataExchange函数而pDX则自动指出了数据的流向;
!因此无需手动调用SetDlgItemXXX和GetDlgItemXXX函数了,所有的一切都可以自动完成;
3. DDX系列函数:
1) DDX有多个版本,分别对应不同类型的数据成员,包括字符串、浮点型、整形等等,这样就使得控件和不同类型的数据成员进行交换;
2) DDX函数的一般化原型:void AFXAPI DDX_XXX(CDataExchange* pDX, int nIDC, type& value);
3) 可以看到DDX是一种全局框架函数(AFXAPI修饰)而不是成员函数;
4) pDX指出了数据的流向,而数据交换的两个目的地就是nIDC(ID Control的缩写)代表的控件和value代表的数据成员;
!!可以看到,由于数据成员会接受控件中的数据,因此会发生改变,所以使用引用作为参数;
5) DDX_Text:这就代表控件的数据是以字符串的形式呈现给用户的,如编辑器CEdit等,由于文本数据可以表示普通的文本,也可以表示编号等数字,或是金额的浮点数,因此DDX_Text重载了很多版本,这些版本之间其它都相同,除了type不一样,type涵盖了所有的整型(BYTE、int、DWORD、long等),也有字符串型(CString、string),也包括浮点型(float、double),也包括其它类型(COleDateTime、COleCurrency);
6) DDX_Check:type是int,它将用一个整型数来表示复选框的选中状态,即BST_CHECKED、BST_UNCHECKED、BST_INDETERMINATE;
7) DDX_Radio:type是int,用0索引来表示选中的单选按钮,0表示第一个选中、1表示第二个选中...以此类推;
8) DDX_LBIndex:type是int,用0索引表示选中的是第几项列表框项;
9) DXX_LBString:type是CString,表示选中的是能和value前缀匹配的那个列表框项;
10) DDX_LBStringExact:type也是CString,和LBString不同的是必须是完全匹配;
11) DDX_LB系列相同的还有DDX_CB系列,即组合框,其也有CBIndex、CBString和CBStringExact三个版本;
12) DDX_Scroll:type是int,用一个整型变量来确定滚条上滑块的位置;
4. DDV系列函数:
1) 特别注意的一点就是DDV校验的对象是数据成员,而不是直接校验控件中的数据,因此必须等到控件中的数据加载进数据成员后才能进行校验,也就是说必须等到按完退出按钮之后才能进行校验(先载入数据,校验如果不正确则将弹出警告消息框让你修改成符合要求的数据,修改正确之后按退出按钮才能正常关闭对话框);
2) DDV系列函数主要分为两类,一类用来校验数值型数据,检查数据是否落在某个范围之内,另一类用来校验字符串型数据,检查其长度是否超过某个特定值;
3) 数值校验DDV函数的一般化原型:void AFXAPI DDV_MinMaxType(CDataExchange* pDX, Type m_typeMember, Type minVal, Type maxVal);
i. 这也是一个全局框架函数,并且需要pDX指导数据流向;
ii. m_typeMember是被校验的数据成员,而minVal和maxVal则是min ~ max的范围,即正确区间必须为[min, max]的闭区间;
iii. 由于MFC并不支持动态数据类型,因此重载了各种类型的DDV函数,而不能用一个函数接受多种数据类型,DDV数值校验支持的Type有byte、int、long、UINT、DWORD、float、double,基本涵盖所有整型;
4) 验证字符串不超过一定长度——DDV_MaxChars
i. 原型:void AFXAPI DDV_MaxChars(CDataExchange* pDX, CString const& m_member, int nChars);
ii. 用于检验m_member成员字符串的长度不超过nChars;
5) DDX和DDV的顺序:
i. DDX和DDV都写在DoDataExchange里,而在执行DoDataExchange函数时不例外会按顺序执行里面的语句;
ii. 因此一定要每DDX一个数据就DDV该相应的数据,因为DDV时如果校验失败,则会弹出错误消息框并将输入焦点落在校验错误的数据对应的控件上,如果DDX和DDV的编写顺序混乱,在错误发生时可能会导致焦点落在错误的控件上!!
iii. 推荐的写法例如:
DDX_Text(pDX, IDC_COUNT, m_nCount); DDV_MinMaxInt(pDX, m_nCount, 0, 100); // 对m_nCount的校验紧接着m_nCount的校验之后
!!为什么在覆盖OnInitDialog以及OnOK等函数时需要调用基类相应的函数——数据交换校验的具体流程:
1) 首先在基类CDialog的OnInitDialog中会调用参数为FALSE的UpdateData函数,其中FALSE指示了数据流是从数据成员流向控件;
2) 接着UpdateData函数中会创建一个CDataExchange对象(将流向FALSE作为创建CDataExchange的参数),然后以该对象指针作为参数调用DoDataExchange;
3) DoDataExchange接着执行各个DDX和DDV函数;
4) 在执行OnOK等退出按钮的处理函数时则是在基类的OnOK函数中调用参数为TRUE的UpdateData函数(TRUE指明流向是从控件到数据成员),接着UpdateData的执行流程和前面讲的一样;
!!因此,如果不调用基类的相应的函数则无法进行数据交换和校验!!