源代码:http://download.csdn.net/source/3522809
上一篇文章中,讲述了一些WTL的关于对话框和控件的特性,本章中将讲述的新的WTL类实现了一些高级UI特性:所有者绘制、自定义绘制、新的WTL控件、UI更新和DDV(对话框数据有效性)。
因为所有绘制和自定义绘制控件在GUI项目中非常常见,WTL提供了一些混合类来处理这些工作。首先通过AppWizard创建一个非模式对话框的WTL项目。这是为了使UI更新功能能正确的工作。
所有者绘制涉及四个消息:WM_MEASUREITEM
, WM_DRAWITEM
, WM_COMPAREITEM
, 和WM_DELETEITEM。
COwnerDraw
类,在atlframe.h中定义,为我们简化了代码,因为在此类中我们不需要为这些消息路由,而是链接消息到COwnerDraw
并且在自己的实现类中重载消息处理函数。
如何链接消息依赖于是否反射消息到控件里。下面是COwnerDraw的消息路由:
template class COwnerDraw
{
public:
BEGIN_MSG_MAP(COwnerDraw)
MESSAGE_HANDLER(WM_DRAWITEM, OnDrawItem)
MESSAGE_HANDLER(WM_MEASUREITEM, OnMeasureItem)
MESSAGE_HANDLER(WM_COMPAREITEM, OnCompareItem)
MESSAGE_HANDLER(WM_DELETEITEM, OnDeleteItem)
ALT_MSG_MAP(1)
MESSAGE_HANDLER(OCM_DRAWITEM, OnDrawItem)
MESSAGE_HANDLER(OCM_MEASUREITEM, OnMeasureItem)
MESSAGE_HANDLER(OCM_COMPAREITEM, OnCompareItem)
MESSAGE_HANDLER(OCM_DELETEITEM, OnDeleteItem)
END_MSG_MAP()
};
可以看出,消息路由的主片段处理消息WM_*;然而ALT_MSG_MAP(1)里的消息路由处理消息的反射版本
OCM_*。所有者绘制控件的消息通知,像
WM_NOTIFY,可以在它们的副控件中处理,也可以反射到控件本身,如果选择前者,消息链接直接到
COwnerDraw
// C++ class for a dialog that contains owner-drawn controls
class CSomeDlg : public CDialogImpl,
public COwnerDraw, ...
{
BEGIN_MSG_MAP(CSomeDlg)
//...
CHAIN_MSG_MAP(COwnerDraw)
END_MSG_MAP()
void DrawItem ( LPDRAWITEMSTRUCT lpdis );
};
然而,如果你想要使控件处理消息,就需要使用CHAIN_MSG_MAP_ALT
宏链接消息到ALT_MSG_MAP(1)段:
// C++ class that implements an owner-drawn button
class CMyButton : public CWindowImpl,
public COwnerDraw, ...
{
BEGIN_MSG_MAP(CMyButton)
//...
CHAIN_MSG_MAP_ALT(COwnerDraw, 1)
DEFAULT_REFLECTION_HANDLER()
END_MSG_MAP()
void DrawItem ( LPDRAWITEMSTRUCT lpdis );
};
COwnerDraw
解包消息参数,调用实现函数。我们可重载的消息处理方法有:
void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct);
void MeasureItem(LPMEASUREITEMSTRUCT lpMeasureItemStruct);
int CompareItem(LPCOMPAREITEMSTRUCT lpCompareItemStruct);
void DeleteItem(LPDELETEITEMSTRUCT lpDeleteItemStruct);
如果因某原因不想在重载中处理消息,可以调用SetMsgHandled(false),然后消息将会传递到之后的消息路由中的其他处理中。
例子中,我们创建一个所有者绘制的button,并且在Button的实现类中处理反射的消息WM_DRAWITEM
,下面是资源编辑器下的界面:
下面是Button的实现类:
class CODButtonImpl : public CWindowImpl,
public COwnerDraw
{
public:
BEGIN_MSG_MAP_EX(CODButtonImpl)
CHAIN_MSG_MAP_ALT(COwnerDraw, 1)
DEFAULT_REFLECTION_HANDLER()
END_MSG_MAP()
void DrawItem ( LPDRAWITEMSTRUCT lpdis );
};
DrawItem()调用GDI命令,为button绘制一张图:
void CODButtonImpl::DrawItem ( LPDRAWITEMSTRUCT lpdis )
{
// NOTE: m_bmp is a CBitmap init'ed in the constructor.
CDCHandle dc = lpdis->hDC;
CDC dcMem;
dcMem.CreateCompatibleDC ( dc );
dc.SaveDC();
dcMem.SaveDC();
// Draw the button's background, red if it has the focus, blue if not.
if ( lpdis->itemState & ODS_FOCUS )
dc.FillSolidRect ( &lpdis->rcItem, RGB(255,0,0) );
else
dc.FillSolidRect ( &lpdis->rcItem, RGB(0,0,255) );
// Draw the bitmap in the top-left, or offset by 1 pixel if the button
// is clicked.
dcMem.SelectBitmap ( m_bmp );
if ( lpdis->itemState & ODS_SELECTED )
dc.BitBlt ( 1, 1, 80, 80, dcMem, 0, 0, SRCCOPY );
else
dc.BitBlt ( 0, 0, 80, 80, dcMem, 0, 0, SRCCOPY );
dcMem.RestoreDC(-1);
dc.RestoreDC(-1);
}
下面是button的表现形式:
CCustomDraw的工作方式与COwnDraw相似,它处理NM_CUSTOMDRAW消息并链接它们。CCustomDraw在自定义绘制的每一个阶段都有一个可重载的函数:
DWORD OnPrePaint(int idCtrl, LPNMCUSTOMDRAW lpNMCD);
DWORD OnPostPaint(int idCtrl, LPNMCUSTOMDRAW lpNMCD);
DWORD OnPreErase(int idCtrl, LPNMCUSTOMDRAW lpNMCD);
DWORD OnPostErase(int idCtrl, LPNMCUSTOMDRAW lpNMCD);
DWORD OnItemPrePaint(int idCtrl, LPNMCUSTOMDRAW lpNMCD);
DWORD OnItemPostPaint(int idCtrl, LPNMCUSTOMDRAW lpNMCD);
DWORD OnItemPreErase(int idCtrl, LPNMCUSTOMDRAW lpNMCD);
DWORD OnItemPostEraset(int idCtrl, LPNMCUSTOMDRAW lpNMCD);
DWORD OnSubItemPrePaint(int idCtrl, LPNMCUSTOMDRAW lpNMCD);
其中,所有函数的默认处理都是返回CDRF_DODEFAULT,因此如果你需要执行自定义的绘制或者需要不同的返回值,你需要重载对应的方法。
在上张截图中,树形视图中“Drawn”的颜色是绿色的,这是通过使用一个继承于CTreeCtrl,链接消息到CCustomDraw并且重载了
OnPrePaint()
和OnItemPrePaint()的新类
CBuffyTreeCtrl。当树被填充时,“Drawn”节点的数据被设为1,
OnItemPrePaint()检测这个值并改变文字颜色。
DWORD CBuffyTreeCtrl::OnPrePaint(
int idCtrl, LPNMCUSTOMDRAW lpNMCD)
{
return CDRF_NOTIFYITEMDRAW;
}
DWORD CBuffyTreeCtrl::OnItemPrePaint(
int idCtrl, LPNMCUSTOMDRAW lpNMCD)
{
if ( 1 == lpNMCD->lItemlParam )
pnmtv->clrText = RGB(0,128,0);
return CDRF_DODEFAULT;
}
就如COwnerDraw,也可在自定义绘制类的消息处理中调用
SetMsgHandled(false),把消息传递到其他的消息路由中。
WTL拥有一些新的控件,或是其他封装的改进版(如CTreeViewCtrlEx
)或是非内置控件的新功能(如CHyperLink
)。
WTL的CBitmapButton,在atlctrlx.h中定义,比MFC中的更易用。这个WTL类使用一个图像表而不是四个单独的位图资源,这意味着我们可以把多个按钮图片放在一个位图中,从而降低GDI的使用。如果你的程序运行在win9x并且有大量的图形,这种做法是特别好的,因为使用大量的孤立的图形将会迅速耗尽GDI资源并当掉系统。CBitmapButton
派生于CWindowImpl,包含很多特性:控件自缩放,自动生成3D边框,热跟踪支持,以及根据控件的状态,一个按钮有几张图像。
在本例中,我们使用CBitmapButton放置在上章创建的所有者绘制的Button旁边,首先添加CBitmapButton对象m_wndBmpBtn作为CMainDlg的成员。然后关联控件和成员变量。加载一个位图到ImageList中,告诉按钮使用这个ImageList,同时告诉按钮哪张图片对应控件的哪个状态。下面是
OnInitDialog()
中设置按钮的代码片段:
// Set up the bitmap button
CImageList iml;
iml.CreateFromImage ( IDB_ALYSON_IMGLIST, 81, 1, CLR_NONE,
IMAGE_BITMAP, LR_CREATEDIBSECTION );
m_wndBmpBtn.SubclassWindow ( GetDlgItem(IDC_ALYSON_BMPBTN) );
m_wndBmpBtn.SetToolTipText ( _T("Alyson") );
m_wndBmpBtn.SetImageList ( iml );
m_wndBmpBtn.SetImages ( 0, 1, 2, 3 );
默认下,这个按钮占用image list的所有权,因此OnInitDialog()不能释放它创建的imagelist。
CBitmapButton是个非常有用的类,下面是它的方法:
CBitmapButtonImpl
类包含了按钮的所有实现,除非需要重载方法和消息处理函数,否则就可以直接使用CBitmapButton。
// 构造函数,设置Button的扩展风格(不要与窗口风格混淆),并可指定一个imagelist。// 通常默认的情况就足够了,我们可以用其他方法设置这两个属性
CBitmapButtonImpl(DWORD dwExtendedStyle = BMPBTN_AUTOSIZE,
HIMAGELIST hImageList = NULL)
// 重载,执行子类化并初始化内部数据
BOOL SubclassWindow(HWND hWnd)
// 获取和设置位图按钮的扩展风格DWORD GetBitmapButtonExtendedStyle()
DWORD SetBitmapButtonExtendedStyle(DWORD dwExtendedStyle,
DWORD dwMask = 0)
BMPBTN_HOVER
:激活热跟踪,当鼠标停在按钮上时,以焦点状态绘制
BMPBTN_AUTO3D_SINGLE
,
BMPBTN_AUTO3D_DOUBLE
:在图像边缘自动生成3D边框,以及鼠标得到焦点时的焦点矩形。如果你没有提供按下状态的图像,它会给你生成一个。
BMPBTN_AUTO3D_DOUBLE
提供一个稍厚的边框。
BMPBTN_AUTOSIZE
:使按钮自动调整大小以适应图像的大小。
BMPBTN_SHAREIMAGELISTS :如果设置,按钮对象不会销毁imagelist;否则,在CImageButton的析构函数中销毁imagelist。
BMPBTN_AUTOFIRE
: 如果设置,单击该按钮和按住该按钮生成重复的
WM_COMMAND消息。
当调用SetBitmapButtonExtendedStyle()
时,参数dwMask用于控制哪些风格生效,使用默认值0,表示用新的风格完全代替旧的。
HIMAGELIST GetImageList()
HIMAGELIST SetImageList(HIMAGELIST hImageList)
使用
GetImageList()
和
SetImageList()
关联imagelist和按钮,获取关联到按钮的当前的imagelist。
int GetToolTipTextLength()
bool GetToolTipText(LPTSTR lpstrText, int nLength)
bool SetToolTipText(LPCTSTR lpstrText)
CBitmapButton支持当鼠标悬停在按钮上时,显示一个ToolTip。调用GetToolTipText() 和 SetToolTipText() 获取和设置tooltip的文本。
void SetImages(int nNormal, int nPushed = -1, int nFocusOrHover = -1, int nDisabled = -1)
调用SetImages告诉按钮,imagelist中的哪个图像对应哪个状态。参数是imagelist中的0起始的图像索引。nNormal是必须的,-1表示没有与之对应的图片。
CCheckListViewCtrl,在atlctrlx.h中定义,派生于CWindowImpl,实现了带复选框的list view控件。这与MFC中的CCheckListBox不同,CCheckListBox使用的是List Box,不是List View。
CCheckListViewCtrl
相当简单,添加了很少的功能,但是它引入了一个新的帮助器类
CCheckListViewCtrlImplTraits,它像CWinTraits
但是第三个模板参数控件的List View风格。如果不定义自己的帮助器类CCheckListViewCtrlImplTraits。将会使用默认的值:LVS_EX_CHECKBOXES | LVS_EX_FULLROWSELECT
.
// 注意:必须包括LVS_EX_CHECKBOXES,否则会有断言失败
typedef CCheckListViewCtrlImplTraits<
WS_CHILD | WS_VISIBLE | LVS_REPORT,
WS_EX_CLIENTEDGE,
LVS_EX_CHECKBOXES | LVS_EX_GRIDLINES | LVS_EX_UNDERLINEHOT |
LVS_EX_ONECLICKACTIVATE> CMyCheckListTraits;
class CMyCheckListCtrl :
public CCheckListViewCtrlImpl
{
private:
typedef CCheckListViewCtrlImpl baseClass;
public:
BEGIN_MSG_MAP(CMyCheckListCtrl)
CHAIN_MSG_MAP(baseClass)
END_MSG_MAP()
};
BOOL SubclassWindow(HWND hWnd)
当你子类化一个已存在的Listview控件,
SubclassWindow()查看关联的CCheckListViewCtrlImplTraits中的扩展的Listview的风格并应用到控件中。CCheckListViewCtrlImplTraits
的前两个模板参数(windows styles and extended window styles)不使用。
BOOL GetCheckState(int nIndex)
BOOL SetCheckState(int nItem, BOOL bCheck)
获取和设置指定索引的条目的复选框状态。
void CheckSelectedItems(int nCurrItem)
此方法使用一个条目索引,切换它的状态(该条目必须是已选择的)并且改变其他被选则的条目的复选框状态。你可能不会使用这个方法,当用户点击复选框或按空格键
CCheckListViewCtrl会自动切换状态。
CTreeItem
使我们更易于使用tree control。CTreeItem
对象保存一个CTreeItem以及对应的树控件的指针。仅仅通过使用CTreeItem就可以执行针对某个item的操作。
CTreeViewCtrlEx
像CTreeViewCtrl,但是前者处理的是
CTreeItem
对象,而后者处理的是HTREEITEM 对象。
// Using plain HTREEITEMs:
HTREEITEM hti, hti2;
hti = m_wndTree.InsertItem ( "foo", TVI_ROOT, TVI_LAST );
hti2 = m_wndTree.InsertItem ( "bar", hti, TVI_LAST );
m_wndTree.SetItemData ( hti2, 37 );
// Using CTreeItems:
CTreeItem ti, ti2;
ti = m_wndTreeEx.InsertItem ( "baz", TVI_ROOT, TVI_LAST );
ti2 = ti.AddTail ( "yen", 0 );
ti2.SetData ( 42 );
CHyperLink,派生于CWindowImpl,子类化静态文本控件并使之成为可以点击的超链接。CHyperLink自动处理link的绘制(根据IE颜色选项)并支持键盘导航。它的基类CHyperLinkImpl
包含实现link的所有代码,除非你需要重载方法和消息处理函数,你可以直接使用CHyperLink。
CHyperLink
的默认行为是点击链接时在默认的IE浏览器中运行URL。如果子类化的静态控件包含
WS_TABSTOP
状态,可以tab到该控件,然后按空格或回车键相当于点击该链接。默认的
CHyperLink
使用静态控件的文本作为URL和toolTip的默认文本。
CHyperLinkImpl ( DWORD dwExtendedStyle = HLINK_UNDERLINED )
CHyperLink()
CHyperLinkImpl
的构造函数提供一个应用到控件上得扩展风格。但是CHyperLink并没有与之对应的构造函数,不过可使用
SetHyperLinkExtendedStyle()
设置该属性。
BOOL SubclassWindow(HWND hWnd)
子类化,初始化内部数据。如果使用DDX关联一个CHyperLink对象与一个静态文本控件,此函数会自动被执行,或者也可以手工调用去子类化控件。
DWORD GetHyperLinkExtendedStyle()
DWORD SetHyperLinkExtendedStyle(DWORD dwExtendedStyle, DWORD dwMask = 0)
获取和设置控件的扩展风格。你必须在SubclassWindow或
Create()之前设置扩展风格,以告诉控件如何绘制文本。
bool GetLabel(LPTSTR lpstrBuffer, int nLength)
bool SetLabel(LPCTSTR lpstrLabel)
获取和设置控件文本,如果不设置,将使用静态控件的初始文本。
bool GetHyperLink(LPTSTR lpstrBuffer, int nLength)
bool SetHyperLink(LPCTSTR lpstrLink)
获取和设置控件关联的URL文本。如果不设置,将使用静态控件的初始文本
bool GetToolTipText(LPTSTR lpstrBuffer, int nLength)
bool SetToolTipText(LPCTSTR lpstrToolTipText)
获取和设置tooltip文本。这两个函数自由在使用了
HLINK_COMMANDBUTTON
或
HLINK_NOTIFYBUTTON
风格的控件中才能使用。
HLINK_UNDERLINED
超链接文本带下划线,默认行为。
HLINK_NOTUNDERLINED
超链接文本不带下划线
HLINK_UNDERLINEHOVER
当鼠标悬停在控件上时,文本显示带下划线
HLINK_COMMANDBUTTON
当点击超链接时,控件触发
WM_COMMAND
消息 到父窗口(
BN_CLICKED
) 。
HLINK_NOTIFYBUTTON
当点击超链接是,控件触发
WM_NOTIFY
消息 (
NM_CLICK
) 到父窗口
HLINK_USETAGS
控件仅把中的文本被认为是超链接,其他文本不变。
HLINK_USETAGSBOLD
同上,但是
<a>
中的文本为黑体。当此被设置时,链接文本将永不带下划线。
HLINK_NOTOOLTIP
控件不显示tooltip。
HLINK_COMMANDBUTTON
或
HLINK_NOTIFYBUTTON
,当点击链接时,CHyperLink调用它的
Navigate(),Navigate()
调用ShellExecuteEx()
在默认的浏览器中打开URL。如果你想再点击链接后执行其他的一些行为,设置HLINK_COMMANDBUTTON
或HLINK_NOTIFYBUTTON并处理消息。
SS_CENTER
或
SS_RIGHT
,使静态文本中对齐或右对齐,但是,如果控件设置了
HLINK_USETAGS
或
HLINK_USETAGSBOLD
,文本只能左对齐。
如果你使用CHyperLink
打开一个URL(没有设置set
HLINK_COMMANDBUTTON
或
HLINK_NOTIFYBUTTON
),你不可以使用
SetToolTipText()更改ToolTip文本。但是你可以直接处理CHyperLink
的tooltip控件成员m_tip,并使用AddTool()设置文本。
m_wndLink.m_tip.AddTool ( m_wndLink, _T("Clickety!"), &m_wndLink.m_rcLink, 1 );
注意,自WTL7.0,这里有一个重大更改,WTL7.1中
CHyperLink使用tooltip的ID为1,而WTL7.0中,这个ID是窗口句柄且通过使用m_tip.UpdateTipText()更新文本。
HLINK_USETAGS
和
HLINK_USETAGSBOLD
是最好用的,当超链接文本是在一行文本中时。绘制代码查找
<a>
并将文本分割成三部分。但是如果某部分需要断字,它将会不正确地自动换行。
HLINK_UNDERLINEHOVER
不和
HLINK_USETAGSBOLD一起设置。因为这会导致链接文本后出现一些空格,如上第一个所示。
WTL中比MFC更容易更新UI。MFC中,你必须了解WM_KICKIDLE消息并且处理此消息触发UI更新。在WTL中,不需这样做,虽然在AppWizard中有一个缺陷:我们需要手工添加一行代码。
首先要做的第一件事情就是对话框必须是非模态的。这是因为CUpdateUI
要工作,你的程序需要控制消息循环。如果使对话框为模态的,系统控制了消息循环,导致空闲处理无法触发。CUpdateUI
是在空闲处理时间内工作的,因此没有空闲处理就没有UI更新。
对话框类的定义如下,与框架窗口类类似:
class CMainDlg : public CDialogImpl, public CUpdateUI,
public CMessageFilter, public CIdleHandler
{
public:
enum { IDD = IDD_MAINDLG };
BOOL PreTranslateMessage(MSG* pMsg);
BOOL OnIdle();
BEGIN_MSG_MAP_EX(CMainDlg)
MSG_WM_INITDIALOG(OnInitDialog)
COMMAND_ID_HANDLER_EX(IDOK, OnOK)
COMMAND_ID_HANDLER_EX(IDCANCEL, OnCancel)
COMMAND_ID_HANDLER_EX(IDC_ALYSON_BTN, OnAlysonODBtn)
END_MSG_MAP()
BEGIN_UPDATE_UI_MAP(CMainDlg)
END_UPDATE_UI_MAP()
//...
};
注意:CMainDlg派生于CUpdateUI,并且有一个UI更新路由。
OnInitDialog()添加消息循环和空闲处理,这与之前的框架窗口的例子相似:
// register object for message filtering and idle updates
CMessageLoop* pLoop = _Module.GetMessageLoop();
ATLASSERT(pLoop != NULL);
pLoop->AddMessageFilter(this);
pLoop->AddIdleHandler(this);
UIAddChildWindowContainer(m_hWnd);
注意,这里调用UIAddChildWindowContainer(),而不是框架窗口例子中的
UIAddToolbar()
或UIAddStatusBar()。这告诉
CUpdateUI包含需要更新的子窗口。
如果此时关注OnIdle()的代码,会发现这里少了一行代码,AppWizard没有生成,我们需要手工添加:
BOOL CMainDlg::OnIdle()
{
UIUpdateChildWindows();
return FALSE;
}
为了演示UI更新,当点击左侧的位图按钮时,右侧的按钮激活或禁止。首先,在UI更新路由中添加一个条目,使用UPDUI_CHILDWINDOW
标识这个条目是指示子窗口的:
BEGIN_UPDATE_UI_MAP(CMainDlg)
UPDATE_ELEMENT(IDC_ALYSON_BMPBTN, UPDUI_CHILDWINDOW)
END_UPDATE_UI_MAP()
然后在左侧按钮的处理中,调用UIEnable()
开启或关闭右侧按钮的活动状态:
void CMainDlg::OnAlysonODBtn ( UINT uCode, int nID, HWND hwndCtrl )
{
UIEnable ( IDC_ALYSON_BMPBTN, !m_wndBmpBtn.IsWindowEnabled() );
}
WTL的对话框数据有效性检查(Dialog Data Validation)比MFC要简单一些。在MFC中,你需要为DDX和DDV创建独立的宏,而在WTL,一个宏同时支持两个功能。在WTL中,下面的宏在DDX路由中包含基本的DDV支持:
DDX_TEXT_LEN
DDX_INT_RANGE
and
DDX_UINT_RANGE
DDX_INT
和
DDX_UINT 执行DDX,附加验证值是否在给定的最小值和最大值范围内。
DDX_FLOAT_RANGE
像DDX_FLOAT 执行DDX,并验证
值是否在给定的最小值和最大值范围内。
DDX_FLOAT_P_RANGE
(new in WTL 7.1)
DDX_FLOAT_P
执行DDX, 并验证值是否在给定的最小值和最大值范围内。
这些宏的参数与不带有效性检查的宏相比,多了一到两个指定数值接受的范围,DDX_TEXT_LEN多了一个参数,指定字符串最大可允许长度。
比如,上面IDC_FAV_SEASON的范围为[1,7],DDV宏的使用如下:
BEGIN_DDX_MAP(CMainDlg)
//...
DDX_INT_RANGE(IDC_FAV_SEASON, m_nSeason, 1, 7)
END_DDX_MAP()
OnOK调用DoDataExchange()时将检查IDC_FAV_SEASON数值的有效性,同时将数据写入
m_nSeason。
如果对话框数据有效性检查失败,CWinDataExchange
将会调用可重载的方法OnDataValidateError()并且
DoDataExchange()返回false。
OnDataValidateError()
的默认处理是扬声器发声,我们应该提供一个更友好的错误处理。
的函数原型:OnDataValidateError()
void OnDataValidateError ( UINT nCtrlID, BOOL bSave, _XData& data );
_XData是一个数据结构,它由
CWinDataExchange填充,包含当前填入的数据和可允许的范围等。
struct _XData
{
_XDataType nDataType;
union
{
_XTextData textData;
_XIntData intData;
_XFloatData floatData;
};
};
nDataType
指示联合结构体中那个有效。它的可能值为:
enum _XDataType
{
ddxDataNull = 0,
ddxDataText = 1,
ddxDataInt = 2,
ddxDataFloat = 3,
ddxDataDouble = 4
};
在我们的例子中,nDataType的值为ddxDataInt,这意味着
_XData
中的_XIntData将会被填充数据。
struct _XIntData
{
long nVal;
long nMin;
long nMax;
};
我们要重载OnDataValidateError()告诉用户可允许的范围:
void CMainDlg::OnDataValidateError ( UINT nCtrlID, BOOL bSave, _XData& data )
{
CString sMsg;
sMsg.Format ( _T("Enter a number between %d and %d"),
data.intData.nMin, data.intData.nMax );
MessageBox ( sMsg, _T("ControlMania2"), MB_ICONEXCLAMATION );
GotoDlgCtrl ( GetDlgItem(nCtrlID) );
}
在对话框类的继承列表中添加CDialogResize
并在OnInitDialog()中调用
DlgResize_Init(),然后链接消息到
CDialogResize。
原文:WTL for MFC Programmers, Part V - Advanced Dialog UI Classes