WTL入门(5)--- 高级的对话框UI类

源代码:http://download.csdn.net/source/3522809 

上一篇文章中,讲述了一些WTL的关于对话框和控件的特性,本章中将讲述的新的WTL类实现了一些高级UI特性:所有者绘制、自定义绘制、新的WTL控件、UI更新和DDV(对话框数据有效性)。

Specialized Owner Draw and Custom Draw Classes

因为所有绘制和自定义绘制控件在GUI项目中非常常见,WTL提供了一些混合类来处理这些工作。首先通过AppWizard创建一个非模式对话框的WTL项目。这是为了使UI更新功能能正确的工作。

COwnerDraw

所有者绘制涉及四个消息: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

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),把消息传递到其他的消息路由中。

New WTL Controls

WTL拥有一些新的控件,或是其他封装的改进版(如CTreeViewCtrlEx)或是非内置控件的新功能(如CHyperLink)。

CBitmapButton

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是个非常有用的类,下面是它的方法:

CBitmapButton methods

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

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()
};

CCheckListViewCtrl methods

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会自动切换状态。

CTreeViewCtrlEx and CTreeItem

这两个类通过封装 CTreeItem 使我们更易于使用tree control。CTreeItem 对象保存一个CTreeItem以及对应的树控件的指针。仅仅通过使用CTreeItem就可以执行针对某个item的操作。
CTreeViewCtrlExCTreeViewCtrl,但是前者处理的是 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

CHyperLink,派生于CWindowImpl,子类化静态文本控件并使之成为可以点击的超链接。CHyperLink自动处理link的绘制(根据IE颜色选项)并支持键盘导航。它的基类CHyperLinkImpl 包含实现link的所有代码,除非你需要重载方法和消息处理函数,你可以直接使用CHyperLink。
CHyperLink 的默认行为是点击链接时在默认的IE浏览器中运行URL。如果子类化的静态控件包含 WS_TABSTOP 状态,可以tab到该控件,然后按空格或回车键相当于点击该链接。默认的 CHyperLink 使用静态控件的文本作为URL和toolTip的默认文本。

CHyperLink methods

下面仅介绍常用的方法,对于其他的,比如计算控件大小,解析链接文本等均在atlctrlx.h中
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 风格的控件中才能使用。


CHyperLink extended styles

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并处理消息。

Other CHyperLink details

我们可以为静态文本控件设置 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_USETAGSHLINK_USETAGSBOLD 是最好用的,当超链接文本是在一行文本中时。绘制代码查找 <a> 并将文本分割成三部分。但是如果某部分需要断字,它将会不正确地自动换行。

你应该确保 HLINK_UNDERLINEHOVER 不和 HLINK_USETAGSBOLD一起设置。因为这会导致链接文本后出现一些空格,如上第一个所示。

UI Updating Dialog Controls

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() );
}

DDV

WTL的对话框数据有效性检查(Dialog Data Validation)比MFC要简单一些。在MFC中,你需要为DDX和DDV创建独立的宏,而在WTL,一个宏同时支持两个功能。在WTL中,下面的宏在DDX路由中包含基本的DDV支持:

DDX_TEXT_LEN
像DDX_TEXT 执行DDX并验证字符串的长度(不计入空终结符)不大于限值。
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。

Handling DDV failures

如果对话框数据有效性检查失败,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) );
}

Resizing Dialogs

在对话框类的继承列表中添加CDialogResize 并在OnInitDialog()中调用DlgResize_Init(),然后链接消息到CDialogResize。


原文:WTL for MFC Programmers, Part V - Advanced Dialog UI Classes

你可能感兴趣的:(ATL/WTL)