介绍

本文介绍一些新类实现高级界面特性新类:控件自画和自定外观控件,新的WTL控件,UI updating和对话框数据验证(DDV)。

特别的自画和外观定制类

由于自画和定制外观控件在图形用户界面中是很常用的手段,所以WTL提供了几个嵌入类来完成这些令人厌烦的工作。我接着就会介绍它们,事实上我们在上一个例子工程ControlMania2的结尾部分已经这么做了。如果你正随着我的讲解用应用程序生成向导创建新工程,请不要忘了使用无模式对话框,为了使正常工作必须使用无模式对话框,我会在对话框中控件的UI Updating部分详细解释为什么这样作。

COwnerDraw

控件的自画需要响应四个消息:WM_MEASUREITEM, WM_DRAWITEM, WM_COMPAREITEM, 和WM_DELETEITEM,在atlframe.h头文件中定义的COwnerDraw类可以简化这些工作,使用这个类就不需要处理这四个消息,你只需将消息链入COwnerDraw,它会调用你的类中的重载函数。

如何将消息链入COwnerDraw取决与你是否将消息反射给控件,两种方法有些不同。下面是COwnerDraw类的消息映射链,它使得两种方法的差别更加明显:

template <class T> class COwnerDraw
{
public:BEGIN_MSG_MAP(COwnerDraw<T>)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_*消息,而ATL部分处理反射的消息,OCM_*。自画的通知消息就像WM_NOTIFY消息一样,你可以在父窗口处理它们,也可以将它们反射会控件,如果你使用前一种方法,消息被直接链入COwnerDraw:

class CSomeDlg : public COwnerDraw<CSomeDlg>, ...
{BEGIN_MSG_MAP(CSomeDlg)//...CHAIN_MSG_MAP(COwnerDraw<CSomeDlg>)END_MSG_MAP()void DrawItem ( LPDRAWITEMSTRUCT lpdis );
};

当然,如果你想要控件自己处理这些消息,你需要使用CHAIN_MSG_MAP_ALT宏将消息链入ALT_MSG_MAP(1)部分:

class CSomeButtonImpl : public COwnerDraw<CSomeButtonImpl>, ...
{BEGIN_MSG_MAP(CSomeButtonImpl)//...CHAIN_MSG_MAP_ALT(COwnerDraw<CSomeButtonImpl>, 1)DEFAULT_REFLECTION_HANDLER()END_MSG_MAP()void DrawItem ( LPDRAWITEMSTRUCT lpdis );
};

COwnerDraw类将对消息传递的参数展开,然后调用你的类中的实现函数。上面的例子中,我们自己的类实现DrawItem()函数,当有 WM_DRAWITEM或OCM_DRAWITEM消息被链入COwnerDraw时,这个函数就会被调用。你可以重载的方法有:

void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct);
void MeasureItem(LPMEASUREITEMSTRUCT lpMeasureItemStruct);
int  CompareItem(LPCOMPAREITEMSTRUCT lpCompareItemStruct);
void DeleteItem(LPDELETEITEMSTRUCT lpDeleteItemStruct);

如果你不想处理某个消息,你可以调用SetMsgHandled(false),消息会被传递给消息映射链中的其他响应者。 SetMsgHandled()事实上是COwnerDraw类的成员函数,但是它的作用和在BEGIN_MSG_MAP_EX()中使用 SetMsgHandled()一样。

对于ControlMania2,它从ControlMania1中的树控件开始,添加了自画按钮处理反射的WM_DRAWITEM消息,下面是资源编辑器中的新按钮:

现在我们需要一个新类实现自画按钮:

class CODButtonImpl : public CWindowImpl<CODButtonImpl, CButton>,public COwnerDraw<CODButtonImpl>
{
public:BEGIN_MSG_MAP_EX(CODButtonImpl)CHAIN_MSG_MAP_ALT(COwnerDraw<CODButtonImpl>, 1)DEFAULT_REFLECTION_HANDLER()END_MSG_MAP()void DrawItem ( LPDRAWITEMSTRUCT lpdis );
};

DrawItem()使用了像BitBlt()这样的GDI函数向按钮的表面画位图,代码应该很容易理解,因为WTL使用的类名和函数名都和MFC类似。

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) );elsedc.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 );elsedc.BitBlt ( 0, 0, 80, 80, dcMem, 0, 0, SRCCOPY );dcMem.RestoreDC(-1);dc.RestoreDC(-1);
}

我们的按钮看起来是这个样子:

CCustomDraw

CCustomDraw类使用和COwnerDraw类相同的方法处理NM_CUSTOMDRAW消息,对于自定绘制的每个阶段都有相应的重载函数:

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,如果想自画控件或返回一个不同的值,就需要重载这些函数:

你可能注意到上面的屏幕截图将“道恩”(Dawn:女名)显示成绿色,这是因为CBuffyTreeCtrl将消息链入CCustomDraw 并重载了OnPrePaint()和OnItemPrePaint()方法。向树控件中添加节点时,节点的item data字段被设置成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;
}

CCustomDraw类也有SetMsgHandled()函数,你可以像在COwnerDraw类那样使用这个函数。

WTL的新控件

WTL有几个新控件,它们要么是其他封装类的扩展(像 CTreeViewCtrlEx),要么是提供windows标准控件没有的新功能(像 CHyperLink)。

CBitmapButton

WTL的CBitmapButton类声明在atlctrlx.h中,它比MFC的同名类使用起来要简单的多。WTL的CBitmapButton类使用image list而不是单个的位图资源,你可以将多个按钮的图像放到一个位图文件中,减少GDI资源的占用。这对于使用很多图片并需要在Windows 9X系统上运行的程序很有好处,因为使用太多的单个位图将会很快耗尽GDI资源并导致系统崩溃。

CBitmapButton是一个CWindowImpl派生类,它又很多特色:自动调整控件的大小,自动生成3D边框,支持hot-tracking,每个按钮可以使用多个图像分别表示按钮的不同状态。

在ControlMania2中,我们对前面的例子创建的自画按钮使用CBitmapButton类。现在CMainDlg对话框类中添加 CBitmapButton类型的变量m_wndBmpBtn,调用SubclassWindow()函数或使用DDX将其和控件联系起来,将位图装载到 image list并告诉按钮使用这个image list,还要告诉按钮每个图像分别对应按钮的什么状态。下面是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()不能delete它所创建的image list。下面显示的是新按钮的一般状态,注意控件是如何根据图像的大小来调整自己的大小。

因为CBitmapButton是一个非常有用的类,我想介绍一下它的公有方法。

CBitmapButton methods

CBitmapButtonImpl类包含了实现一个按钮的所有代码,除非你想重载某个方法或消息处理,你可以对控件直接使用CBitmapButton类。

CBitmapButtonImpl constructor
CBitmapButtonImpl(DWORD dwExtendedStyle = BMPBTN_AUTOSIZE,HIMAGELIST hImageList = NULL)

构造函数可以指定按钮的扩展样式(这与窗口的样式不冲突)和图像列表,通常使用默认参数就足够了,因为可以使用其他的方法设定这些属性。

SubclassWindow()
BOOL SubclassWindow(HWND hWnd)

SubclassWindow()是个重载函数,主要完成控件的子类化和初始化控件类保有的内部数据。

Bitmap button extended styles
DWORD GetBitmapButtonExtendedStyle()
DWORD SetBitmapButtonExtendedStyle(DWORD dwExtendedStyle, DWORD dwMask = 0)

CBitmapButton支持一些扩展样式,这些扩展样式会对按钮的外观和操作方式产生影响:

BMPBTN_HOVER
使用hot-tracking,当鼠标移到按钮上时按钮被画成焦点状态。
BMPBTN_AUTO3D_SINGLE, BMPBTN_AUTO3D_DOUBLE
在按钮图像周围自动产生一个三维边框,当按钮拥有焦点时会显示一个表示焦点的虚线矩形框。另外如果你没有指定按钮按下状态的图像,将会自动生成一个。BMPBTN_AUTO3D_DOUBLE样式生成的边框稍微粗一些,其他特征和BMPBTN_AUTO3D_SINGLE一样。
BMPBTN_AUTOSIZE
按钮调整自己的大小以适应图像大小,这是默认样式。
BMPBTN_SHAREIMAGELISTS
如果指定这个样式,按钮不负责销毁按钮使用的image list,如果不使用这个样式,CBitmapButton的析构函数会销毁按钮使用的image list。
BMPBTN_AUTOFIRE
如果设置这个样式,在按钮上按住鼠标左键不放将会产生连续的WM_COMMAND消息。

调用SetBitmapButtonExtendedStyle()时,dwMask参数控制着那个样式将被改变,默认值是0,意味着用新样式完全替换旧的样式。

Image list management
HIMAGELIST GetImageList()
HIMAGELIST SetImageList(HIMAGELIST hImageList)

调用SetImageList()设置按钮使用的image list。

Tooltip management
int  GetToolTipTextLength()
bool GetToolTipText(LPTSTR lpstrText, int nLength)
bool SetToolTipText(LPCTSTR lpstrText)

CBitmapButton支持显示工具提示(tooltip),调用SetToolTipText()指定显示的文字。

Setting the images to use
void SetImages(int nNormal, int nPushed = -1,int nFocusOrHover = -1, int nDisabled = -1)

调用SetImages()函数告诉按钮分别使用image list的拿一个图像表示那个状态。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。

下面是一个定义list view扩展样式属性的例子,加入了一个使用这个样式的新类。(注意,扩展属性必须包含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<CMyCheckListCtrl, CListViewCtrl,CMyCheckListTraits>
{
private:typedef CCheckListViewCtrlImpl<CMyCheckListCtrl, CListViewCtrl,CMyCheckListTraits> baseClass;
public:BEGIN_MSG_MAP(CMyCheckListCtrl)CHAIN_MSG_MAP(baseClass)END_MSG_MAP()
};

CCheckListViewCtrl methods

SubclassWindow()

当子类化一个已经存在的list view控件时,SubclassWindow()查看CCheckListViewCtrlImplTraits的扩展样式属性并将之应用到控件上。未用到前两个参数(窗口样式和扩展窗口样式)。

SetCheckState() and GetCheckState()

这些方法实际上是在CListViewCtrl中,SetCheckState()使用行的索引和一个布尔类型参数,该布尔参数的值表示是否 check这一行。GetCheckState()以行索引未参数,返回改行的checked状态。

CheckSelectedItems()

这个方法使用item的索引作为参数,它翻转这个item的check状态,这个item必须是被选定的,同时还将其他所有被选择的item设置成相应状态(译者加:多选状态下)。你大概不会用到这个方法,因为CCheckListViewCtrl会在check box被单击或用户按下了空格键时设置相应的item的状态。

下面是ControlMania2中的CCheckListViewCtrl的样子:

CTreeViewCtrlEx and CTreeItem

有两个类使得树控件的使用简化了很多:CTreeItem类封装了HTREEITEM,一个CTreeItem对象含有一个HTREEITEM 和一个指向包含这个HTREEITEM的树控件的指针,使你不必每次调用都引用树控件;CTreeViewCtrlEx和CTreeViewCtrl一样,只是它的方法操作CTreeItem而不是HTREEITEM。例如,InsertItem()函数返回一个CTreeItem而不是 HTREEITEM,你可以使用CTreeItem操作新添加的item。下面是一个例子:

// 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, 100 );// Using CTreeItems:
CTreeItem ti, ti2;ti = m_wndTreeEx.InsertItem ( "foo", TVI_ROOT, TVI_LAST );ti2 = ti.AddTail ( "bar", 0 );ti2.SetData ( 100 );

CTreeViewCtrl对HTREEITEM的每一个操作,CTreeItem都有与之对应的方法,正像每一个关于HWND的API都有一个CWindow方法与之对应一样。查看ControlMania2的代码可以看到更多的CTreeViewCtrlEx和CTreeItem类的方法的演示。

CHyperLink

CHyperLink是一个CWindowImpl派生类,它子类化一个static text控件,使之变成可点击的超链接。CHyperLink根据用户的IE使用的颜色画链接对象,还支持键盘导航。CHyperLink类的构造函数没有参数,下面是其它的公有方法。

CHyperLink methods

CHyperLinkImpl类内含实现一个超链接的全部代码,如果不需要重载它的方法或处理消息的话,你可以直接使用CHyperLink类。

SubclassWindow()
BOOL SubclassWindow(HWND hWnd)

重载函数SubclassWindow()完成控件子类化,然后初始化该类保有的内部数据。

Text label management
bool GetLabel(LPTSTR lpstrBuffer, int nLength)
bool SetLabel(LPCTSTR lpstrLabel)

获得或设置控件显示的文字,如果不指定显示文字,控件会显示资源编辑器指定给控件的静态字符串。

Hyperlink management
bool GetHyperLink(LPTSTR lpstrBuffer, int nLength)
bool SetHyperLink(LPCTSTR lpstrLink)

获得或设置控件关联超链接的URL,如果不指定超链接URL,控件会使用显示的文字字符串作为URL。

Navigation
bool Navigate()

导航到当前超链接的URL,该URL或者是由SetHyperLink()函数指定的URL,或者就是控件的窗口文字。

Tooltip management

没有公开的方法设置工具提示,所以需要直接使用CToolTipCtrl成员m_tip。

下图显示的就是ControlMania2对话框中的超链接控件:

在OnInitDialog()函数中设置URL:

    m_wndLink.SetHyperLink ( _T("http://www.codeproject.com/") );

对话框中控件的UI Updating

对话框中的的UI updating控制比MFC中简单得多,在MFC中,你需要响应未公开的WM_KICKIDLE消息,处理这个消息并触发控件的updating,在 WTL中,没有这个诡计,不过向导存在一个BUG,需要手工添加一行代码解决这个问题。

首先需要记住的是对话框必须是无模式的,因为CUpdateUI需要在程序的消息循环控制下工作。如果对话框是模式的,系统处理消息循环,我们程序的空闲处理函数就不会被调用,由于CUpdateUI是在空闲时间工作的,所以没有空闲处理就没有UI updating。

ControlMania2的对话框是非模式的,类定义的开始部分很像是一个框架窗口类:

class CMainDlg : public CDialogImpl<CMainDlg>, public CUpdateUI<CMainDlg>,public CMessageFilter, public CIdleHandler
{
public:enum { IDD = IDD_MAINDLG };virtual BOOL PreTranslateMessage(MSG* pMsg);virtual 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派生并含有一个update UI链。OnInitDialog()做了这些工作,这和前面介绍的框架窗口中的代码很相似:

    // register object for message filtering and idle updatesCMessageLoop* pLoop = _Module.GetMessageLoop();ATLASSERT(pLoop != NULL);pLoop->AddMessageFilter(this);pLoop->AddIdleHandler(this);UIAddChildWindowContainer(m_hWnd);

只是这次我们不是调用UIAddToolbar()或UIAddStatusBar(),而是调用 UIAddChildWindowContainer(),它告诉CUpdateUI我们的对话框含有需要updating的字窗口,只要看看 OnIdle(),你会怀疑少了写什么:

BOOL CMainDlg::OnIdle()
{return FALSE;
}

你可能猜想这里应该调用另一个CUpdateUI的方法做一些实在的updating工作,你是对的,应该是这样的,向导在OnIdle()中漏掉了一行代码,现在加上:

BOOL CMainDlg::OnIdle()
{UIUpdateChildWindows();return FALSE;
}

为了演示UI updating,我们设定鼠标点击左边的位图按钮,使得右边的按钮变得可用或禁用。先在update 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 )
{
static bool s_bBtnEnabled = true;s_bBtnEnabled = !s_bBtnEnabled;UIEnable ( IDC_ALYSON_BMPBTN, s_bBtnEnabled );
}

DDV

WTL的对话框数据验证(DDV)比MFC简单一些,在MFC中你需要分别使用DDX(对话框数据交换)宏和DDV(对话框数据验证)宏,在WTL中只需一个宏就可以了,WTL包含基本的数据验证支持,在DDV链中可以使用三个宏:

DDX_TEXT_LEN
和DDX_TEXT一样,只是还要验证字符串的长度(不包含结尾的空字符)小于或等于限制长度。
DDX_INT_RANGE and DDX_UINT_RANGE
和DDX_INT,DDX_UINT一样,还加了对数字的最大最小值的验证。
DDX_FLOAT_RANGE
除了像DDX_FLOAT一样完成数据交换之外,还验证数字的最大最小值。

ControlMania2有一个ID是IDC_FAV_SEASON的edit box,它和成员变量m_nSeason相关联。

由于有效的值是1到7,所以使用这样的数据验证宏:

    BEGIN_DDX_MAP(CMainDlg)//...DDX_INT_RANGE(IDC_FAV_SEASON, m_nSeason, 1, 7)END_DDX_MAP()

OnOK()调用DoDataExchange()获得season的数值,并验证是在1到7之间。

处理DDV验证失败

如果控件的数据验证失败,CWinDataExchange会调用重载函数OnDataValidateError(),默认到处理是驱动PC 喇叭发出声音,你可能想给出更友好的错误指示。OnDataValidateError()的函数原型是:

void OnDataValidateError ( UINT nCtrlID, BOOL bSave, _XData& data );

_XData是一个WTL的内部数据结构,CWinDataExchange根据输入的数据和允许的数据范围填充这个数据结构。下面是这个数据结构的定义:

struct _XData
{_XDataType nDataType;union{_XTextData textData;_XIntData intData;_XFloatData floatData;};
};

nDataType指示联合中的三个成员那个是有意义的,nDataType 的取值可以是:

enum _XDataType
{ddxDataNull = 0,ddxDataText = 1,ddxDataInt = 2,ddxDataFloat = 3,ddxDataDouble = 4
};

在我们的例子中,nDataType的值是ddxDataInt,这表示_XData中的_XIntData成员是有效的,_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 );::SetFocus ( GetDlgItem(nCtrlID) );
}

_XData中的另外两个结构_XTextData和_XFloatData的定义在atlddx.h中,感兴趣的话可以打开这个文件查看一下。

改变对话框的大小

WTL引起我的注意的第一件事是对可调整大小对话框的内建的支持。在这之前我曾写过一篇关于这个主题的文章,详情请参考这篇文章。简单的说就是将CDialogResize类添加到对话框的集成列表,在OnInitDialog()中调用 DlgResize_Init(),然后将消息链入CDialogResize。

继续

下一章,我将介绍如何在对话框中使用ActiveX控件和如何处理控件触发的事件。

参考

Using WTL's Built-in Dialog Resizing Class - Michael Dunn

Using DDX and DDV with WTL - Less Wright

WTL入门(五) 自定义控件相关推荐

  1. openresty 前端开发入门五之Mysql篇

    2019独角兽企业重金招聘Python工程师标准>>> openresty 前端开发入门五之Mysql篇 这章主要演示怎么通过lua连接mysql,并根据用户输入的name从mysq ...

  2. MySQL入门 (五) : CRUD 与资料维护

    1 取得表格资讯 1.1 DESCRIBE指令 「DESCRIBE」是MySQL资料库提供的指令,它只能在MySQL资料库中使用,这个指令可以取得某个表格的结构资讯,它的语法是这样的: 你在MySQL ...

  3. openresty json mysql_openresty 前端开发入门五之Mysql篇

    openresty 前端开发入门五之Mysql篇 这章主要演示怎么通过lua连接mysql,并根据用户输入的name从mysql获取数据,并返回给用户 操作mysql主要用到了lua-resty-my ...

  4. Python爬虫入门五之URLError异常处理

    Python爬虫入门五之URLError异常处理 参考文章: (1)Python爬虫入门五之URLError异常处理 (2)https://www.cnblogs.com/pxuan/p/647362 ...

  5. 小学生学计算机步骤,小学生计算机入门“五步走”

    小学生计算机入门"五步走" 新学期开始了,小学信息技术入门课在学生学习计算机的过程中非常关键,但由于理论知识颇多,学生听起来会感到很枯燥.笔者结合自己几年来的教学实践,谈一谈自己的 ...

  6. 罗马音平假名中文可复制_日语零基础入门五十音,日语零基础五十音图表

    日语零基础入门五十音,日语入门的最基本要求就是记住五十音图,但是这个记住不仅是你能背下来或是默写下来.而是你需对号入座! 下面是一张五十音图表. 即每个假名单独拿出来你要立马反应出来怎么读.其重要性甚 ...

  7. ROS入门五 TF坐标变换

    ROS入门五 TF坐标变换 坐标变换简介 TF功能包 是什么? TF功能包干什么 ? TF坐标变换如何实现? TF工具 乌龟例程中的TF 安装功能包turtle_tf 运行 实现TF的广播和监听功能 ...

  8. conan入门(五):conan 交叉编译引用第三方库示例

    conan 交叉编译引用第三方库示例 Conan 是 C 和 C++ 语言的依赖项和包管理器.它是免费和开源的,适用于所有平台(Windows.Linux.OSX.FreeBSD.Solaris 等) ...

  9. 脑残式网络编程入门(五):每天都在用的Ping命令,它到底是什么?

    本文引用了公众号纯洁的微笑作者奎哥的技术文章,感谢原作者的分享. 1.前言 老于网络编程熟手来说,在测试和部署网络通信应用(比如IM聊天.实时音视频等)时,如果发现网络连接超时,第一时间想到的就是使用 ...

  10. Kaggle入门五大步,跟着做就对了

    Kaggle是一个流行的数据科学竞赛平台,这个平台非常庞大,如果你是一个初学者,很可能会不知所措,不知道如何开始成为一个优秀的数据科学家的旅程.都会犹豫不决,这并不奇怪.很多人都会有相似的担心,比如: ...

最新文章

  1. Juniper EX3400堆叠配置 + 链路捆绑配置
  2. 10.NoSQL数据库第2部分
  3. 女儿问了我一个问题:什么是抽象?
  4. 【Excel】日常记录
  5. 中秋佳节,这个网络安全 7 年经验的大佬来发福利了!
  6. 挂接CreateProcessW实现对进程创建的完全控制
  7. MyBatis学习总结[5]-动态 SQL
  8. ETH-trunk(链路聚合协议)lacp
  9. 修改(移交)微信支付商户平台超级管理员账号流程
  10. ECSHOP模板修改
  11. 第六届”蓝帽杯“全国大学生网络安全技能大赛WriteUp
  12. (不定期更新)《虚拟现实应用技术》(Yanlz+Unity+XR+VR+AR+MR+AVE+Oculus+SteamVR+眩晕症+5G+云计算+边缘计算+人机交互+立钻哥哥+==)
  13. js判断浏览器类型,判断ie内核,webkit
  14. 第一次接触,两眼一抹黑。我只是想下载个小小小的文件而已啊,脑壳疼。。
  15. 计算机房间墙壁的布置,房间小怎么布置 小房间布置有妙招
  16. 搞明白这八个问题 Linux系统就好学多了
  17. 开始使用pgAdmin III
  18. Go 程序设计语言书中 源码
  19. 提交微信小程序代码到微信代码管理git上
  20. Tampermonkey(油猴)脚本编写快速入门

热门文章

  1. 商务与经济统计学习 --概率
  2. 【学习笔记】统计学入门(5/7)——二项分布
  3. 【老生谈算法】matlab实现Dijkstra最短路算法源码——Dijkstra算法
  4. Artifact XXX:war exploded: Artifact is being deployed, please wait...解决方法
  5. donet学习笔记10(c#基础)
  6. 银行业客户全面老龄化,该如何应对?
  7. 腾讯云短信功能初步使用
  8. 14-基于51单片机的声音分贝测量与显示仿真
  9. 第115页的gtk+编程例子——最简单的进度条2附带使用Anjuta写gtk程序的步骤
  10. 电磁流量计流速信号特征