ATL
ATL中所使用的基本技术包括以下几个方面:
- COM技术
- C++模板类技术(Template)
- C++多继承技术(Multi-Inheritance)
ATL / AUX库
一组VC ++帮助程序和模式,以帮助自动执行一些例行编码任务
辅助工具 WTL Helper
https://www.codeproject.com/Articles/8858/WTL-Helper
https://sourceforge.net/projects/wtlhelper9/
使用 WTL Helper 可以快速的生成 控件映射 (Variables mapping) 和 消息映射 (Message mapping) 的代码。 WTL Helper 的使用可以参考这篇文章 WTL Helper
VFC开源工具
VisualFC是VisualStudio下的WTL应用程序开发插件,其中包括了WTL的应用程序向导,这个向导即可以包括在VisualFC中,也可以作为应用程序单独运行使用。
下载地址:https://sourceforge.net/projects/visualfc/
其他地址:https://code.google.com/archive/p/visualfc/
WTL and WinxGUI addin for eVC4/VS60/VS2003/VS2005/VS2008/VS2010/VCExpress
支持VS60, EVC4, VS2005 and VS2008
VisualFC简介(WTL、WinxGUI可视化开发环境)
VisualFC 0.82 (VFCTools and WTLAppwizard support VS2010 and VCExpress)
WTL
https://sourceforge.net/projects/wtl/
安装WTL向导
AppWizard目录执行
wscript Setup.js
WTL for MFC programmer 系列文章(翻译)
WTL10_9163已经支持VS2019-将WTL应用向导添加到VS2019
递归继承列表
template <class T>
class B1
{
public:
void SayHi()
{
T* pT = static_cast<T*>(this); // HUH?? 我将在下面解释
pT->PrintClassName();
}
protected:
void PrintClassName() { cout << "This is B1"; }
};
class D1 : public B1<D1>
{
// No overridden functions at all
};
class D2 : public B1<D2>
{
protected:
void PrintClassName() { cout << "This is D2"; }
};
main()
{
D1 d1;
D2 d2;
d1.SayHi(); // prints "This is B1"
d2.SayHi(); // prints "This is D2"
}
ATL代码
CWindowImpl,CDialogImpl 和 CAxDialogImpl类
在ATL类中对窗口过程的实现是CWindowImpl。CWindowImpl 含有所有窗口实现代码,例如:窗口类的注册,窗口的子类化,消息映射以及基本的WindowProc()函数,可以看出这与MFC的设计有很大的不同,MFC将所有的代码都放在一个CWnd类中。
还有两个独立的类包含对话框的实现,它们分别是CDialogImpl 和 CAxDialogImpl,CDialogImpl 用于实现普通的对话框而CAxDialogImpl实现含有ActiveX控件的对话框。
任何非对话框窗口都是从CWindowImpl 派生的
ATL类CWinTraits定义窗口类型
ATL提供了几个预先定义的特殊的类型,其中之一就是CFrameWinTraits,一个非常棒的框架窗口:
typedef CWinTraits<WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, 0> CControlWinTraits;
typedef CWinTraits<WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, WS_EX_APPWINDOW | WS_EX_WINDOWEDGE> CFrameWinTraits;
typedef CWinTraits<WS_OVERLAPPEDWINDOW | WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, WS_EX_MDICHILD> CMDIChildWinTraits;
添加按钮事件
对话框中添加button,id为IDC_BUTTON1
BEGIN_MSG_MAP(xx)
COMMAND_ID_HANDLER(IDC_BUTTON1, OnButton1)
END_MSG_MAP()
WTL代码
WTL的类大致可以分为几种类型:
- 主框架窗口的实现CFrameWindowImpl, CMDIFrameWindowImpl
- 控件的封装CButton, CListViewCtrl
- GDI 对象的封装CDC, CMenu
- 一些特殊的界面特性 CSplitterWindow, CUpdateUI, CDialogResize, CCustomDraw
实用的工具类和宏CString, CRect, BEGIN_MSG_MAP_EX
#define STRICT #define WIN32_LEAN_AND_MEAN #define _WTL_USE_CSTRING #include <atlbase.h> // 基本的ATL类 #include <atlapp.h> // 基本的WTL类,定义了有关消息处理的类和CAppModule extern CAppModule _Module; // WTL 派生的CComModule版本 #include <atlwin.h> // ATL 窗口类 #include <atlframe.h> // WTL 主框架窗口类 #include <atlmisc.h> // WTL 实用工具类,例如:CString,使用CString类,需要手工定义_WTL_USE_CSTRING标号 #include <atlcrack.h> // WTL 增强的消息宏
DECLARE_FRAME_WND_CLASS有两个参数,窗口类名(类名可以是NULL,ATL会替你生成一个类名)和资源ID,创建窗口时WTL用这个ID装载图标,菜单和加速键表。
WTL 对消息映射的增强
WTL的增强消息映射宏定义在atlcrack.h中。(这个名字来源于“消息解密者”,是一个与windowsx.h的宏所使用的相同术语)首先将BEGIN_MSG_MAP改为BEGIN_MSG_MAP_EX,带_EX的版本产生“解密”消息的代码。
将Win32 API通过消息传递过来的WPARAM和LPARAM数据还原出来
WTL的消息处理使用MSG_作为前缀,后面是消息名称,例如MSG_WM_CREATE。
CFrameWindowImpl 是直接从CWindow类派生的, 所以它继承了所有CWindow类的方法,如SetTimer()。这使得对窗口API的调用有点象MFC的代码,只是MFC使用CWnd类包装这些API。
使用BEGIN_MSG_MAP_EX代替BEGIN_MSG_MAP后,ATL的消息映射宏可以和WTL的宏混合使用
SetMsgHandled()
SetMsgHandled(false)
让消息通过CHAIN_MSG_MAP宏链入基类,这个调用代替了 ATL宏使用的bHandled参数。
(调用SetMsgHandled (false)让消息流入基类是个好的习惯,因为这样我们就不必总是记着哪个消息需要基类处理那些消息不需要基类处理,这和VC的类向导产生的代码相似,多数的派生类的消息处理函数的开始或结尾都会调用基类的消息处理函数)
SetMsgHandled(TRUE)
让消息不同流入基类进行处理。
全局函数Run()
Run()函数创建主窗口并开始消息循环,Run()调用CMessageLoop::Run(),消息泵实际上是位于CMessageLoop::Run()内
一些类
CMessageFilter是一个嵌入类,它提供PreTranslateMessage()函数,CIdleHandler也是一个嵌入类,它提供了OnIdle()函数。CMessageLoop, CIdleHandler 和 CUpdateUI三个类互相协同完成界面元素的状态更新(UI update),就像MFC中的ON_UPDATE_COMMAND_UI宏一样。
CUpdateUI能够操作5种不同的界面元素:顶级菜单项(就是菜单条本身),弹出式菜单的菜单项,工具条按钮,状态条的格子和子窗口(如对话框中的控件)。每一种界面元素都对应CUpdateUIBase类的一个常量:
- 菜单条项: UPDUI_MENUBAR
- 弹出式菜单项: UPDUI_MENUPOPUP
- 工具条按钮: UPDUI_TOOLBAR
- 状态条格子: UPDUI_STATUSBAR
- 子窗口: UPDUI_CHILDWINDOW
CMessageLoop
CMessageLoop,它掌管消息泵和空闲处理。
CMessageLoop为我们的应用程序提供一个消息泵,除了一个标准的DispatchMessage/TranslateMessage循环外,它还通过调用PreTranslateMessage()函数实现了消息过滤机制,通过调用OnIdle()实现了空闲处理功能。下面是Run()函数的伪代码:
int Run()
{
MSG msg;
for(;;)
{
while ( !PeekMessage(&msg) )
DoIdleProcessing();
if ( 0 == GetMessage(&msg) )
break; // WM_QUIT retrieved from the queue
if ( !PreTranslateMessage(&msg) )
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return msg.wParam;
}
那些需要过滤消息的类只需要象CMainFrame::OnCreate()函数那样调用CMessageLoop::AddMessageFilter()函数就行了,CMessageLoop就会知道该调用那个PreTranslateMessage()函数,同样,如果需要空闲处理就调用CMessageLoop::AddIdleHandler()函数。
需要注意得是在这个消息循环中没有调用TranslateAccelerator() 或 IsDialogMessage() 函数,因为CFrameWindowImpl在这之前已经做了处理,但是如果你在程序中使用了非模式对话框,那你就需要在CMainFrame::PreTranslateMessage()函数中添加对IsDialogMessage()函数的调用。
CFrameWindowImpl
CFrameWindowImpl 和它的基类 CFrameWindowImplBase提供了对toolbars,rebars, status bars,工具条按钮的工具提示和菜单项的掠过式帮助,这些也是MFC的CFrameWnd类的基本特征。
主窗口的工具条和状态条
CFrameWindowImpl只支持一个工具条,也没有像MFC那样的可多点停靠的工具条,如果你想使用多个工具条又不想修改CFrameWindowImpl的内部代码,你就需要使用Rebar。我将介绍它们二者并演示如何使用应用程序向导添加工具条和Rebar。
CFrameWindowImpl::OnSize()消息响应函数调用了UpdateLayout(),UpdateLayout()做两件事:从新定位所有控制条和改变视图窗口的大小使之填充整个客户区。实际工作是由UpdateBarsPosition()完成的,UpdateLayout()只是调用了该函数。实现的代码相当简单,向工具条和状态条发送WM_SIZE消息,由这些控制条的默认窗口处理过程将它们定位到主窗口的顶部或底部。
通用控件的封装类
下面是Windows内建控件的封装类:
用户控件: CStatic, CButton, CListBox, CComboBox, CEdit, CScrollBar, CDragListBox
通用控件: CImageList, CListViewCtrl (CListCtrl in MFC), CTreeViewCtrl (CTreeCtrl in MFC), CHeaderCtrl, CToolBarCtrl, CStatusBarCtrl, CTabCtrl, CToolTipCtrl, CTrackBarCtrl (CSliderCtrl in MFC), CUpDownCtrl (CSpinButtonCtrl in MFC), CProgressBarCtrl, CHotKeyCtrl, CAnimateCtrl, CRichEditCtrl, CReBarCtrl, CComboBoxEx, CDateTimePickerCtrl, CMonthCalendarCtrl, CIPAddressCtrl
MFC中没有的封装类: CPagerCtrl, CFlatScrollBar, CLinkCtrl (clickable hyperlink, available on XP only)
还有一些是WTL特有的类:CBitmapButton, CCheckListViewCtrl (带检查选择框的list控件), CTreeViewCtrlEx 和 CTreeItem (通常一起使用, CTreeItem 封装了HTREEITEM), CHyperLink (类似于网页上的超链接对象,支持所有操作系统)
使用控件的封装类
ATL 方式 1 - 连接一个CWindow对象
HWND hwndList = GetDlgItem(IDC_LIST);
CListViewCtrl wndList1 (hwndList); // use constructor
CListViewCtrl wndList2, wndList3;
wndList2.Attach ( hwndList ); // use Attach method
wndList3 = hwndList; // use assignment operator
ATL 方式 2 - 包容器窗口(CContainedWindow)
CContainedWindow是介于CWindow和CWindowImpl之间的类,它可以子类化控件,在控件的父窗口中处理控件的消息,这使得所有的消息处理都放在对话框类中,不需要为为每个控件生成一个单独的CWindowImpl派生类对象。需要注意的是不能用CContainedWindow 处理WM_COMMAND, WM_NOTIFY和其他通知消息,因为这些消息是发给控件的父窗口的。
CContainedWindow只是CContainedWindowT定义的一个数据类型,CContainedWindowT才是真正的类,它是一个模板类,使用window接口类的类名作为模板参数。这个特殊的CContainedWindowT
CContainedWindow只是它定义的一个简写名称,要使用不同的window接口类只需将该类的类名作为模板参数就行了,例如CContainedWindowT
钩住一个CContainedWindow对象需要做四件事:
在对话框中创建一个CContainedWindowT 成员变量。
将消息处理添加到对话框消息映射的ALT_MSG_MAP小节。
在对话框的构造函数中调用CContainedWindowT 构造函数并告诉它哪个ALT_MSG_MAP小节的消息需要处理。
在OnInitDialog()中调用CContainedWindowT::SubclassWindow() 方法与控件建立关联。
在ControlMania1中,我对三个按钮分别使用了一个CContainedWindow,对话框处理发送到每一个按钮的WM_SETCURSOR消息,并改变鼠标指针形状。
现在仔细看看这一步,首先,我们在CMainDlg中添加了CContainedWindow成员。
class CMainDlg : public CDialogImpl<CMainDlg>
{
// ...
protected:
CContainedWindow m_wndOKBtn, m_wndExitBtn;
};
其次,我们添加了ALT_MSG_MAP小节,OK按钮使用1小节,Exit按钮使用2小节。这意味着所有发送给OK按钮的消息将由ALT_MSG_MAP(1)小节处理,所有发给Exit按钮的消息将由ALT_MSG_MAP(2)小节处理。
class CMainDlg : public CDialogImpl<CMainDlg>
{
public:
BEGIN_MSG_MAP_EX(CMainDlg)
MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
COMMAND_ID_HANDLER(ID_APP_ABOUT, OnAppAbout)
COMMAND_ID_HANDLER(IDOK, OnOK)
COMMAND_ID_HANDLER(IDCANCEL, OnCancel)
ALT_MSG_MAP(1)
MSG_WM_SETCURSOR(OnSetCursor_OK)
ALT_MSG_MAP(2)
MSG_WM_SETCURSOR(OnSetCursor_Exit)
END_MSG_MAP()
LRESULT OnSetCursor_OK(HWND hwndCtrl, UINT uHitTest, UINT uMouseMsg);
LRESULT OnSetCursor_Exit(HWND hwndCtrl, UINT uHitTest, UINT uMouseMsg);
};
接着,我们调用每个CContainedWindow的构造函数,告诉它使用ALT_MSG_MAP的哪个小节。
CMainDlg::CMainDlg() : m_wndOKBtn(this, 1),
m_wndExitBtn(this, 2)
{
}
构造函数的参数是消息映射链的地址和ALT_MSG_MAP的小节号码,第一个参数通常使用this,就是使用对话框自己的消息映射链,第二个参数告诉对象将消息发给ALT_MSG_MAP的哪个小节。
最后,我们将每个CContainedWindow对象与控件关联起来。
LRESULT CMainDlg::OnInitDialog(...)
{
// ...
// Attach CContainedWindows to OK and Exit buttons
m_wndOKBtn.SubclassWindow ( GetDlgItem(IDOK) );
m_wndExitBtn.SubclassWindow ( GetDlgItem(IDCANCEL) );
return TRUE;
}
下面是新的WM_SETCURSOR消息处理函数:
LRESULT CMainDlg::OnSetCursor_OK (HWND hwndCtrl, UINT uHitTest, UINT uMouseMsg )
{
static HCURSOR hcur = LoadCursor ( NULL, IDC_HAND );
if ( NULL != hcur )
{
SetCursor ( hcur );
return TRUE;
}
else
{
SetMsgHandled(false);
return FALSE;
}
}
LRESULT CMainDlg::OnSetCursor_Exit ( HWND hwndCtrl, UINT uHitTest, UINT uMouseMsg )
{
static HCURSOR hcur = LoadCursor ( NULL, IDC_NO );
if ( NULL != hcur )
{
SetCursor ( hcur );
return TRUE;
}
else
{
SetMsgHandled(false);
return FALSE;
}
}
如果你还想使用按钮类的特性,你需要这样声明变量:
CContainedWindowT<CButton> m_wndOKBtn;
ATL 方式 3 - 子类化(Subclassing)
第三种方法创建一个CWindowImpl派生类并用它子类化一个控件。这和第二种方法有些相似,只是消息处理放在CWindowImpl类内部而不是对话框类中。
class CButtonImpl : public CWindowImpl<CButtonImpl, CButton>
{
BEGIN_MSG_MAP_EX(CButtonImpl)
MSG_WM_SETCURSOR(OnSetCursor)
END_MSG_MAP()
LRESULT OnSetCursor(HWND hwndCtrl, UINT uHitTest, UINT uMouseMsg)
{
static HCURSOR hcur = LoadCursor ( NULL, IDC_SIZEALL );
if ( NULL != hcur )
{
SetCursor ( hcur );
return TRUE;
}
else
{
SetMsgHandled(false);
return FALSE;
}
}
};
在OnInitDialog()种子类化About按钮。
m_wndAboutBtn.SubclassWindow ( GetDlgItem(ID_APP_ABOUT) );
WTL 方式 - 对话框数据交换(DDX)
WTL的DDX(对话框数据交换)很像MFC,可以使用很简单的方法将变量和控件关联起来。
接着在对话框类中添加DDX链,这和MFC的类向导使用的DoDataExchange()函数功能相似。对于不同类型的数据可以使用不同的DDX宏,我们使用DDX_CONTROL用来连接变量和控件
class CEditImpl : public CWindowImpl<CEditImpl, CEdit>
{
BEGIN_MSG_MAP_EX(CEditImpl)
MSG_WM_CONTEXTMENU(OnContextMenu)
END_MSG_MAP()
void OnContextMenu ( HWND hwndCtrl, CPoint ptClick )
{
MessageBox("Edit control handled WM_CONTEXTMENU");
}
};
class CMainDlg : public CDialogImpl<CMainDlg>,
public CWinDataExchange<CMainDlg>
{
//...
BEGIN_DDX_MAP(CMainDlg)
DDX_CONTROL(IDC_EDIT, m_wndEdit)
END_DDX_MAP()
protected:
CContainedWindow m_wndOKBtn, m_wndExitBtn;
CButtonImpl m_wndAboutBtn;
CEditImpl m_wndEdit;
};
最后,在OnInitDialog()中调用DoDataExchange()函数,这个函数是继承自CWinDataExchange。DoDataExchange()第一次被调用时完成相关控件的子类化工作,所以在这个例子中,DoDataExchange()子类化ID为IDC_EDIT的控件,将其与m_wndEdit建立关联。
LRESULT CMainDlg::OnInitDialog(...)
{
// ...
// Attach CContainedWindows to OK and Exit buttons
m_wndOKBtn.SubclassWindow ( GetDlgItem(IDOK) );
m_wndExitBtn.SubclassWindow ( GetDlgItem(IDCANCEL) );
// CButtonImpl: subclass the About button
m_wndAboutBtn.SubclassWindow ( GetDlgItem(ID_APP_ABOUT) );
// First DDX call, hooks up variables to controls.
DoDataExchange(false);
return TRUE;
}
DDX的详细内容
DDX 宏
DDX可以使用6种宏,每一种宏都对应一个CWinDataExchange类的方法支持其工作,每一种宏都用相同的形式:DDX_FOO(控件ID, 变量),每一种宏都可以支持多种类型的变量,例如DDX_TEXT的重载就支持多种类型的数据。
DDX_TEXT
在字符串和edit box控件之间传输数据,变量类型可以是CString, BSTR, CComBSTR或者静态分配的字符串数组,但是不能使用new动态分配的数组。
DDX_INT
在edit box控件和数字变量之间传输int型数据。
DDX_UINT
在edit box控件和数字变量之间传输无符号int型数据。
DDX_FLOAT
在edit box控件和数字变量之间传输浮点型(float)数据或双精度型数据(double)。
DDX_CHECK
在check box控件和int型变量之间转换check box控件的状态。
DDX_RADIO
在radio buttons控件组和int型变量之间转换radio buttons控件组的状态。
有关 DoDataExchange()的详细内容
调用DoDataExchange()方法和在MFC中使用UpdateData()一样,DoDataExchange()的函数原型是:
BOOL DoDataExchange ( BOOL bSaveAndValidate = FALSE, UINT nCtlID = (UINT)-1 );
消息映射宏
要处理WM_COMMAND通知消息需要使用COMMAND_HANDLER_EX宏:
COMMAND_HANDLER_EX(id, code, func)
处理从某个控件发送得某个通知代码。
COMMAND_ID_HANDLER_EX(id, func)
处理从某个控件发送得所有通知代码。
COMMAND_CODE_HANDLER_EX(code, func)
处理某个通知代码得所有消息,不管是从那个控件发出的。
COMMAND_RANGE_HANDLER_EX(idFirst, idLast, func)
处理ID在idFirst和idLast之间得控件发送的所有通知代码。
COMMAND_RANGE_CODE_HANDLER_EX(idFirst, idLast, code, func)
处理ID在idFirst和idLast之间得控件发送的某个通知代码。
_ATL_MIN_CRT
ATL包含的优化设置让你创建一个不使用C运行库(CRT)的程序,使用这个优化需要在预处理设置中添加_ATL_MIN_CRT标号,向导生成的代码在Release配置中默认使用了这个优化。由于我写程序总是会用到CRT函数,所以我总是去掉这个标号,如果你在CString类或DDX中用到了浮点运算特性,你也要去掉这个标号。
特别的自画和外观定制类
COwnerDraw
控件的自画需要响应四个消息:WM_MEASUREITEM, WM_DRAWITEM, WM_COMPAREITEM, 和WM_DELETEITEM,在atlframe.h头文件中定义的COwnerDraw类可以简化这些工作,使用这个类就不需要处理这四个消息,你只需将消息链入COwnerDraw,它会调用你的类中的重载函数。
CCustomDraw
CCustomDraw类使用和COwnerDraw类相同的方法处理NM_CUSTOMDRAW消息
对话框中控件的UI Updating
首先需要记住的是对话框必须是无模式的,因为CUpdateUI需要在程序的消息循环控制下工作。如果对话框是模式的,系统处理消息循环,我们程序的空闲处理函数就不会被调用,由于CUpdateUI是在空闲时间工作的,所以没有空闲处理就没有UI updating。
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()
//...
};
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一样完成数据交换之外,还验证数字的最大最小值。
学习
https://www.codeproject.com/search.aspx?q=wtl&x=10&y=7&sbo=kw