介绍
借助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