介绍
借助WTL
,现在可取得MFC
编码的大量易用性,而不会增加体积.本文展示了如何利用WTL
的DDX/DDV
实现,展示添加到WTL
实现中的两个自定义扩展
以扩展其覆盖区间
,并使用WTL
的属性表实现(CPropertyPageImpl)
提供从实际代码
中取的真实示例.
WTL
头文件atlddx.h
提供DDX/DDV
处理,它包含创建DDX
映射的宏(与ATL
的消息映射
概念相同)和CWinDataExchange
模板类.
因此,使用DDX/DDV
的第一步是添加
cpp
#include <ATLddx.h>
在StdAfx.h
或其他主头文件
中放入它.注意,如果你使用的是WTL
的CString
,则必须在AtlDDx.h
前包含AtlMisc.h
.
因此,我总是在StdAfx.h
中简单地添加以下内容
:
cpp
#include <atlmisc.h> //`CString`支持
#include <atlddx.h>
在atlwin.h
正下方的StdAfx.h
中,如下.
cpp
...
#include <atlbase.h>
#include <atlapp.h>
extern CAppModule _Module;
#include <atlwin.h>
#include <atlmisc.h>
#include <atlddx.h>
#include <atldlgs.h>
...
下一步是确保
你使用的对话框类
从CWinDataExchange
继承,以取得对DDX
的支持,如下:
cpp
class CCameraBase : public CPropertyPageImpl<CCameraBase>,
public CWinDataExchange<CCameraBase>
然后,现在可进入它的核心
,实际上是把你的变量
连接到各自的UI
组件.这是DDX
消息映射完成的,下面列举了简单示例
:
cpp
BEGIN_DDX_MAP(<your dialog class>)
DDX_TEXT(<对资标>,<串变量>)
DDX_TEXT_LEN(<对资标2>,
<串变量2>,
<max text length>)
END_DDX_MAP()
在消息映射
中,你可指示WTL
的DDX
类勾挂变量
到对话框
的UI
元素.有许多宏
可用来处理各种数据类型
,但简要地看一下上面的两个项
.
第一个DDX_TEXT
,简单指出在加载
时,<串变量>
中的值应该赋值给<对资标>
,一般是映射到编辑框
的串变量
.
在对话框结束
时,反向映射
,拉出<对资标>
的当前内容并放回至<串变量>
中.非常漂亮整洁
.
第二个宏如上所示,DDX_TEXT_LEN
有与DDX_TEXT
类似的函数,因为也有<对资标2>
连接<串变量2>
,但你可到还有第三个参数,<最大文本长>
.
此处指定一个值,如果用户文本输入
超过该值,则启动DDV
错误处理器.可覆盖默认处理器
,也可用默认处理器
(它将哔哔),并设置焦点
回有问题的控件
以提示
用户更正它.
你可覆盖如下函数来实现自己的处理器
,这在示例
中有演示.
cpp
void OnDataValidateError(UINT id, BOOL bSave,_XData& data)
消息映射有许多宏
,一般有个来纯连接
的宏如,(DDX_TEXT)
和连接和验证
的宏如,(DDX_TEXT_LEN)
.
这样,最后一步
是实际告诉WTL
何时实际的数据交换
.这是用假
调用DoDataExchange(BOOLfParam)
来加载包含数据值的对话
,并用真
从对话
中提取数据
来完成的.
由你决定在哪
这样干,但OnInitDialog(WM_INITDIALOG
的处理器)是DoDataExchange(FALSE)
或加载调用
的好地方.
要拉回修改后的数据
,可在对话框
的OnOK
中调用DoDataExchange(TRUE)
来提取
,对属性页
,可在OnKillActivate()
中处理它.
DDX
的实际应用
示例应用
基于商业应用
的代码子集
,该应用处理多个无线摄像机
的查看/监听
.此应用的相关部分
是,它必须允许用户
为多达16
个相机指定单个设置
,并在干净的UI
中这样.
我用WTL
的属性页
来实现,这样为每个摄像机提供一个漂亮的选项卡式对话框
,然后用DDX/DDV
来简化来回转移各个设置
.
示例应用
只允许你使用四个设置
,这样你可在调试器中看到设置是如何转移和验证
的.
cpp
struct _cameraprops
{
CString ssFriendlyName;
UINT iHouseCode;
UINT iUnitCode;
CString ssSaveDir;
BOOL fIsInSnapshotCycle;
BOOL fAddTimestamp;
};
extern _cameraprops g_cameraProps[4];
和主CPP
文件中赋值的存储(propsheetddx.cpp)
:
cpp
#include "maindlg.h"
CAppModule _Module;
_cameraprops g_cameraProps[4];
设置中央存储结构
后,下一步是创建
处理属性表
的头文件,并为属性页处理器
创建基类
.
cpp
class CCameraBase : public CPropertyPageImpl<CameraBas>,
public CWinDataExchange<CameraBas>
然后,我在VC
中创建了一个对话框
,如下:
接着,我添加了DDX
消息映射,以指定UI
和g_cameraprops
全局结构间的连接
:
cpp
BEGIN_DDX_MAP(CCameraBase)
DDX_TEXT_LEN(IDC_edit_CameraTitle, g_cameraProps[m_iIdentity].ssFriendlyName, 35)
DDX_COMBO_INDEX(IDC_cmbo_HouseCode, g_cameraProps[m_iIdentity].iHouseCode)
DDX_COMBO_INDEX(IDC_cmbo_UnitCode, g_cameraProps[m_iIdentity].iUnitCode)
DDX_TEXT(IDC_edit_FileDirectory, g_cameraProps[m_iIdentity].ssSaveDir)
DDX_BOOL_RADIO(IDC_radio_AddTimeStamp, g_cameraProps[m_iIdentity].fAddTimestamp, IDC_radio_NoTimeStamp)
END_DDX_MAP()
enum { IDD = IDD_PROP_PAGE1 };
当然,必须调用DoDataExchange
加载.
cpp
LRESULT OnInitDialog(...)
{
...
InitComboBoxes(hwndComboHouse, hwndComboUnit, m_iIdentity);
CenterWindow();
DoDataExchange(FALSE);//..
...
}
及验证和取
修改后的数据
:
cpp
BOOL OnKillActive()
{
DoDataExchange(TRUE);//..
return true;
}
因为必须扩展到16
个摄像头(尽管示例中有4个),因此我修改了CcameraBase
类的构造器
,用一个整数
来识别它正在处理的摄像头
:
cpp
CCameraBase(int _index)
{
m_iIdentity = _index;
...
}
现在,为了在的UI
中实际实现该4x
,转向继承的CpropertySheetImpl
类CcameraProperties
.没有太多内容,如下:
cpp
class CCameraProperties : public CPropertySheetImpl<CameraPropertie>
{
public:
CCameraBase m_page1;
CCameraBase m_page2;
CCameraBase m_page3;
CCameraBase m_page4;
CCameraProperties():m_page1(1),m_page2(2),m_page3(3),m_page4(4)
{
m_psh.dwFlags |= PSH_NOAPPLYNOW;
AddPage(m_page1);
AddPage(m_page2);
AddPage(m_page3);
AddPage(m_page4);
SetActivePage(0);
SetTitle(_T("Camera and Video Input Properties"));
}
注意,上面粗体显示成员初化列表
显示了实际使用各自相机来标识每个类实例的位置
.后,在选项卡式布局中调用AddPage
勾挂类,并SetActivePage
指定你的第一个页
.
上面省略了一个小消息映射
,但除此外,你现在拥有完整属性表
的处理器.
在WTL
中扩展DDX
当然,还遇见了两个直接的问题
,需要向<ATLDDX.H>
中添加新的扩展
让DDX
帧干一些额外的处理.
因为索引
表示到数组或枚举中的组合框
而不是试表示文本
值是很常见的
,因此我添加了叫DDX_COMBO_INDEX
的新宏和宏处理器
.
它处理传递和提取索引值
,而不是文本翻译
.该示例包含修改后的代码,但最终如下:
cpp
#define DDX_COMBO_INDEX(nID, var) \
if(nCtlID == (UINT)-1 || nCtlID == nID) \
{ \
if(!DDX_Combo_Index(nID, var, TRUE, bSaveAndValidate)) \
return FALSE; \
}
其次:
cpp
template <class Type>
BOOL DDX_Combo_Index(UINT nID, Type& nVal, BOOL bSigned, BOOL bSave, BOOL bValidate = FALSE, Type nMin = 0, Type nMax = 0)
{
T* pT = static_cast<>(this);
BOOL bSuccess = TRUE;
if(bSave)
{
nVal = ::SendMessage((HWND) (Type)pT->GetDlgItem(nID), CB_GETCURSEL, (WPARAM) 0, (LPARAM) 0); bSuccess = (nVal == CB_ERR ? false : true);
}
else
{
ATLASSERT(!bValidate || nVal >= nMin && nVal <= nMax);
int iRet = |::SendMessage((HWND) (Type)pT->GetDlgItem(nID), CB_SETCURSEL, (WPARAM) nVal, (LPARAM) 0);
bSuccess = (iRet == CB_ERR ? false : true);
}
|
if(!bSuccess)
{
pT->OnDataExchangeError(nID, bSave);
}
else if(bSave && bValidate)|//验证
{
ATLASSERT(nMin != nMax);
if(nVal < nMin || nVal > nMax)
{
_XData data;
data.nDataType = ddxDataInt;
data.intData.nVal = (long)nVal;
data.intData.nMin = (long)nMin;
data.intData.nMax = (long)nMax;
pT->OnDataValidateError(nID, bSave, data);
bSuccess = FALSE;
}
}
return bSuccess;
}
这样,就可成功
处理组合框UI
中的索引.另一个问题是我想用的UI
是有两个单选按钮
,代表用户的true/false
选择.
因此,我添加了另一个扩展DDX_BOOL_RADIO
,它带两个资源ID
,如下:
cpp
DDX_BOOL_RADIO(<primary radio buttonID>,
<BOOL variable>,
<Secondary radio buttonID>)
作用是确保
在加载中,根据极
变量的状态,仅选择两个按钮
中的一个.如果为真
,则选中主ID
单选按钮,单选按未选中初化按钮
,如果初始加载值
为假
,则相反.
默认数据处理器
以下是默认DDX/DDV
处理器的列表:
cpp
DDX_TEXT(nID, var)
DDX_TEXT_LEN(nID, var, len)
DDX_INT(nID, var)
DDX_INT_RANGE(nID, var, min, max)
DDX_UINT(nID, var)
DDX_UINT_RANGE(nID, var, min, max)
DDX_FLOAT(nID, var)
DDX_FLOAT_RANGE(nID, var, min, max)
//注意:你必须定义`_ATL_USE_DDX_FLOAT`才能交换`浮点`值.
DDX_CONTROL(nID, obj)
DDX_CHECK(nID, var)
DDX_RADIO(nID, var)
见,wtlddx