ATL WTL 实现分析(五)
CDialogImpl
對(duì)話框本質(zhì)上是布局和行為受約束的窗口。最原始的模態(tài)對(duì)話框API是:
1: WINUSERAPI2: INT_PTR3: WINAPI4: DialogBoxParamW(5: __in_opt HINSTANCE hInstance, //applicaton instance6: __in LPCWSTR lpTemplateName, //IDD : dialog template resource identifies7: __in_opt HWND hWndParent, //hWndParent8: __in_opt DLGPROC lpDialogFunc, //WndProc ::StartDialogProc9: __in LPARAM dwInitParam); //initialization value非模態(tài)對(duì)話框的唯一區(qū)別在于返回值:
1: WINUSERAPI2: HWND3: WINAPI4: CreateDialogIndirectParamW 。。。對(duì)話框的Win32API編程步驟,首先用資源編輯器布局窗口,然后寫一個(gè)DlgProc,組后創(chuàng)建(模態(tài)或非模態(tài))。ATL對(duì)對(duì)話框的封裝和Window類似,類圖參考分析一,在實(shí)現(xiàn)DlgProc時(shí),同樣用到了兩級(jí)跳轉(zhuǎn)以及thunk,從StartDiallgProc到實(shí)際的ProcessWindowMessage跳轉(zhuǎn)。而窗口的布局則使用資源描述符IDD所預(yù)先布置好的位置圖來(lái)得到。
CSimpleDlg是一個(gè)簡(jiǎn)化版本的對(duì)話框,用以簡(jiǎn)單的彈出式對(duì)話框。
?
DataExchange和驗(yàn)證
對(duì)話框使用時(shí)還是比較復(fù)雜的,主要原因有將數(shù)據(jù)寫給對(duì)話框所包含的子控件,對(duì)話框從子控件讀取數(shù)據(jù)。
模態(tài)數(shù)據(jù)交互流程如下:
1、創(chuàng)建繼承自CDialogImpl的類實(shí)例;
2、將數(shù)據(jù)拷貝進(jìn)對(duì)話框類的數(shù)據(jù)成員;
3、調(diào)用DoModal。
4、在WM_INITDIALOG時(shí)將數(shù)據(jù)傳遞給子控件;
5、當(dāng)對(duì)話框處理OK鍵消息時(shí),對(duì)子控件獲得的數(shù)據(jù)進(jìn)行驗(yàn)證;
6、當(dāng)數(shù)據(jù)有效,把數(shù)據(jù)拷貝到對(duì)話框的數(shù)據(jù)成員,結(jié)束對(duì)話框;
7、應(yīng)用從對(duì)話框DoModal返回獲得IDOK,并從中把數(shù)據(jù)返回。
非模態(tài)對(duì)話框的交互流程類似,不同在于第6、7步:
6、當(dāng)數(shù)據(jù)有效,并把數(shù)據(jù)拷貝到對(duì)話框的數(shù)據(jù)成員,應(yīng)用接受到通知并從對(duì)話框數(shù)據(jù)成員獲取這些數(shù)據(jù);
7、應(yīng)用在收到通知后接收數(shù)據(jù),拷貝到應(yīng)用自己的數(shù)據(jù)成員中。
不同在于,模態(tài)時(shí)對(duì)數(shù)據(jù)的驗(yàn)證工作室對(duì)話框自己做,非模態(tài)則將接收數(shù)據(jù)的消息傳給了應(yīng)用,由應(yīng)用進(jìn)行處理。消息處理的位置不同。
ATL沒(méi)有提供類似MFC中DDX、DDV的功能,不過(guò)很容易模仿實(shí)現(xiàn);而在WTL中隨機(jī)對(duì)這一功能進(jìn)行了補(bǔ)充。
?
Windows Control Wrappers
Child Window Management
操作一個(gè)子控件時(shí),例如設(shè)定edit control的文本顯示或禁用OK按鈕等,我們使用到的函數(shù)都是來(lái)自CWindow,在CWindow中提供了一系列的helper function來(lái)操作子控件:
1: class CWindow 2: { 3: public: 4: ... 5: // Dialog-Box Item Functions 6: 7: BOOL CheckDlgButton(int nIDButton, UINT nCheck) throw() 8: { 9: ATLASSERT(::IsWindow(m_hWnd));10: return ::CheckDlgButton(m_hWnd, nIDButton, nCheck);11: }12: ?13: BOOL CheckRadioButton(int nIDFirstButton, int nIDLastButton, int nIDCheckButton) throw()14: {15: ATLASSERT(::IsWindow(m_hWnd));16: return ::CheckRadioButton(m_hWnd, nIDFirstButton, nIDLastButton, nIDCheckButton);17: }18: ?19: int DlgDirList(_Inout_z_ LPTSTR lpPathSpec, _In_ int nIDListBox, _In_ int nIDStaticPath, _In_ UINT nFileType) throw()20: {21: ATLASSERT(::IsWindow(m_hWnd));22: return ::DlgDirList(m_hWnd, lpPathSpec, nIDListBox, nIDStaticPath, nFileType);23: }24: ...25: BOOL SetDlgItemText(int nID, LPCTSTR lpszString) throw()26: {27: ATLASSERT(::IsWindow(m_hWnd));28: return ::SetDlgItemText(m_hWnd, nID, lpszString);29: }30: };這種實(shí)現(xiàn)效率上不高,CWindow是一個(gè)龐大的類,包含了很多的help functions。如,每次傳入一個(gè)子控件ID,windows將會(huì)查找以得到HWND,然后再調(diào)用實(shí)際的處理函數(shù),SetDlgItemText的【API】實(shí)現(xiàn)“應(yīng)該”如下:
1: BOOL SetDlgItemText(HWND hwndParent, int nID, LPCTSTR lpszString) 2: {3: HWND hwndChild = ::GetDlgItem(hwndParent, nID);4: if (!hwndChild) return FALSE;5: return ::SetWindowText(HWND, lpszString);6: }
也即,首先根據(jù)子控件ID找到HWND, 然后調(diào)用實(shí)際的窗口方法。所以,在CWindow中實(shí)現(xiàn)的這個(gè)help function并不高效,每次調(diào)用都有一個(gè)查找的步驟。更好的方法是在使用時(shí)首先緩存每個(gè)控件的HWND,然后調(diào)用時(shí)就省去這個(gè)查找的步驟。使用時(shí)如下:
1: LRESULT CStringDlg :: OnInitDialog(UINT, WPARAM, LPARAM, BOOL&) 2: { 3: ::CenterWindow(); 4: ? 5: //cache the HWNDs 6: m_edit.Attach(GetDlgItem(IDC_STRING)); 7: m_ok.Attach(GetDlgItem(IDOK)); 8: ? 9: //then we can use it like this10: m_edit.SetWindowText(m_sz);11: ...12: return 1;13: }14: ?15: LRESULT CStringDlg :: OnOK(WORD, UINT, HWND, BOOL&)16: {17: m_edit.GetWindowText(m_sz, lengthof(m_sz));18: ?19: ::EndDialog(IDOK);20: return 0;21: }在OnInitDialog中綁定HWNDs,然后在其他方法中就可以直接調(diào)用了。【當(dāng)然,以上方法需要在類中具體實(shí)現(xiàn),實(shí)現(xiàn)很簡(jiǎn)單,就是直接調(diào)用win32API了】
?
A Better Class of Wrappers
帶edit control的對(duì)話框無(wú)論怎么寫,實(shí)現(xiàn)起來(lái)都很簡(jiǎn)單。而當(dāng)使用listbox等控件時(shí),就不只是簡(jiǎn)單調(diào)用::SetWindowText了。操作listbox就要用到Windows messages了。例如填充一個(gè)listbox并選中的代碼如下:
1: LRESULT CStringListDlg :: OnInitDialog(UINT, WPARAM, LPARAM, BOOL&) 2: { 3: ::CenterWindow(); 4: ? 5: //cache list box HWND 6: m_lb.Attach(GetDlgItem(IDC_LIST)); 7: ? 8: //fullfill the listbox 9: m_lb.SendMessage(LB_ADDSTRING, 0, (LPARAM)__T("Hello, ATL"));10: m_lb.SendMessage(LB_ADDSTRING, 0, (LPARAM)__T("Ain't ATL cool?"));11: m_lb.SendMessage(LB_ADDSTRING, 0, (LPARAM)__T("ATL foooooo"));12: ?13: //Set initial selection14: int n = m_lb.SendMessage(LB_FINDSTRING, 0, (LPARAM)m_sz);15: if (n == LB_ERR) n = 0;16: m_lb.SendMessage(LB_SETCURSEL, n);17: ?18: return 1;19: }ATL中CWindows確實(shí)提供了很多wrapper函數(shù),但是對(duì)于windows內(nèi)置的控件卻并沒(méi)有提供,如listbox等。然而,ATL非官方的提供了一些這樣的類,在atlcontrols.h中。例如CListBox,其實(shí)現(xiàn)如下:
1: //取自WTL8.0實(shí)現(xiàn) 2: template <class TBase> 3: class CListBoxT : public TBase 4: { 5: public: 6: // Constructors 7: CListBoxT(HWND hWnd = NULL) : TBase(hWnd) 8: { } 9: ?10: CListBoxT< TBase >& operator =(HWND hWnd)11: {12: m_hWnd = hWnd;13: return *this;14: }15: ?16: HWND Create(HWND hWndParent, ATL::_U_RECT rect = NULL, LPCTSTR szWindowName = NULL,17: DWORD dwStyle = 0, DWORD dwExStyle = 0,18: ATL::_U_MENUorID MenuOrID = 0U, LPVOID lpCreateParam = NULL)19: {20: return TBase::Create(GetWndClassName(), hWndParent, rect.m_lpRect, szWindowName, dwStyle, dwExStyle, MenuOrID.m_hMenu, lpCreateParam);21: }22: ?23: // Attributes24: static LPCTSTR GetWndClassName()25: {26: return _T("LISTBOX");27: }28: ?29: // for entire listbox30: int GetCount() const31: {32: ATLASSERT(::IsWindow(m_hWnd));33: return (int)::SendMessage(m_hWnd, LB_GETCOUNT, 0, 0L);34: }35: ?36: #ifndef _WIN32_WCE37: int SetCount(int cItems)38: {39: ATLASSERT(::IsWindow(m_hWnd));40: ATLASSERT(((GetStyle() & LBS_NODATA) != 0) && ((GetStyle() & LBS_HASSTRINGS) == 0));41: return (int)::SendMessage(m_hWnd, LB_SETCOUNT, cItems, 0L);42: }這樣前面的listbox填充并選中的代碼可以改寫如下:
1: LRESULT CStringListDlg :: OnInitDialog(UINT, WPARAM, LPARAM, BOOL&) 2: { 3: ::CenterWindow(); 4: ? 5: //cache list box HWND 6: m_lb.Attach(GetDlgItem(IDC_LIST)); 7: ? 8: //fullfill the listbox 9: m_lb.AddString(__T("Hello,ATL"));10: //m_lb.SendMessage(LB_ADDSTRING, 0, (LPARAM)__T("Hello, ATL"));11: ...12: //Set initial selection13: int n = m_lb.FindString(0, m_sz);14: if(n ==LB_ERR) n = 0;15: m_lb.SetCurSel(n);16: ?17: return 1;18: }而WTL則對(duì)這部分進(jìn)行了完善,實(shí)現(xiàn)了所有的控件類。
?
CContainedWindow
CContainedWindow可以使父窗口處理其子窗口傳遞過(guò)來(lái)的消息,從而可以將消息處理函數(shù)集中放到父窗口中。父窗口即可以主動(dòng)創(chuàng)建這樣的子窗口,也可以使用已創(chuàng)建好的然后將其子類化(subclassing)實(shí)現(xiàn)父窗口處理子窗口消息的方法是使用Altmessage maps。每個(gè)CContainedWindow都包含了一個(gè)message map ID,由此實(shí)現(xiàn)將消息轉(zhuǎn)移到父窗口的消息映射中。
1: template <class TBase /* = CWindow */, class TWinTraits /* = CControlWinTraits */> 2: class CContainedWindowT : public TBase 3: { 4: public: 5: CWndProcThunk m_thunk; 6: LPCTSTR m_lpszClassName; 7: WNDPROC m_pfnSuperWindowProc; 8: CMessageMap* m_pObject; 9: DWORD m_dwMsgMapID;10: const _ATL_MSG* m_pCurrentMsg;11: ?12: // If you use this constructor you must supply13: // the Window Class Name, Object* and Message Map ID14: // Later to the Create call15: CContainedWindowT() : m_pCurrentMsg(NULL)16: { }17: ?18: CContainedWindowT(LPTSTR lpszClassName, CMessageMap* pObject, DWORD dwMsgMapID = 0)19: : m_lpszClassName(lpszClassName),20: m_pfnSuperWindowProc(::DefWindowProc),21: m_pObject(pObject), m_dwMsgMapID(dwMsgMapID),22: m_pCurrentMsg(NULL)23: { }24: ...CContainedWindow即非繼承自CWindowImpl也沒(méi)有繼承CMessageMap,所以CContainedWindow對(duì)象沒(méi)有消息映射,而是通過(guò)WindowProc靜態(tài)成員函數(shù)傳遞給了父窗口,message map ID是由構(gòu)造函數(shù)活著Create函數(shù)提供。CContainedWindow提供了很多構(gòu)造函數(shù)和Create方法。舉個(gè)應(yīng)用例子,我們是一個(gè)edit control只接收字母,實(shí)現(xiàn)如下:
1: class CMainWindow : 2: public CWindowImpl<CMainWindow, CWindow, CMainWindowTraits> 3: { 4: public: 5: ... 6: BEGIN_MESSAGE_MAP(CMainWindow) 7: ... 8: //Handle the child edit controls' messages 9: ALT_MSG_MAP(2013) //message map ID10: MESSAGE_HANDLER(WM_CHAR, OnEditChar)11: END_MSG_MAP()12: ?13: LRESULT OnCreate(UINT, WPARAM, LPARAM, BOOL&)14: {15: //Create the contained window, routing its message to us16: if(m_edit.Create("edit", this, 2013, m_hWnd, &CWindow::rcDefault)) {17: return 0;18: }19: ? /* 第一個(gè)參數(shù)是控件名,第二個(gè)參數(shù)是CMessageMap的指針,這樣子窗口的的消息才能找到消息處理映射,第三個(gè)是消息映射ID, 第四個(gè)是HWND。*/20: return -1;21: }22: ?23: //child edit message handler24: LRESULT OnEditChar(UINT, WPARAM, LPARAM, BOOL &bHandled)25: {26: if (isalpha((TCHAR)wparam)) bHandled = FALSE;27: else return 0;28: }29: private:30: CContainedWindow m_edit;31: };
注意在OnEditChar中,將是字母的輸入設(shè)為消息未處理,從而可以讓CContainedWindow對(duì)應(yīng)控件的默認(rèn)消息處理procedure來(lái)進(jìn)行響應(yīng);而對(duì)于非字母輸入,則bHandled為TRUE,消息不再傳遞下去,表現(xiàn)出來(lái)就是該字符被忽略。
Subclassing Contained Windows
如果是包含一個(gè)已經(jīng)創(chuàng)建的子控件,就需要用到子類化功能。之前已經(jīng)講到了超類化實(shí)現(xiàn)了窗口類的繼承,而子類化功能則更加溫和并且更加常用。子類化不需要完全創(chuàng)建一個(gè)新類,而只是對(duì)原有窗口的一些消息進(jìn)行hack,功能表現(xiàn)更像一個(gè)filter。子類化的實(shí)現(xiàn)是通過(guò)創(chuàng)建一個(gè)特定的窗口對(duì)象,然后替換掉其窗口過(guò)程SetWindowLong(GWL_WNDPROC)。替換的窗口過(guò)程首先接收所有的消息,然后再?zèng)Q定是否讓原有的窗口過(guò)程是否也處理。可以認(rèn)為,超類化是一個(gè)類的特化,而子類化是一個(gè)對(duì)象實(shí)例的特化。子類化常用在窗口子控件上,如:
1: class CLetterDlg : public CDialogImpl<CLetterDlg> 2: { 3: public: 4: //Set the CMessageMap* and the message map ID 5: CLetterDlg(): m_edit(this, 2013) {} 6: ? 7: BEGIN_MESSAGE_MAP(CLetterDlg) 8: ... 9: ALT_MSG_MAP(2013)10: MESSAGE_HANDLER(WM_CHAR, OnEditChar)11: END_MSG_MAP()12: ?13: enum {IDD = IDD_LETTER_ONLY};14: ?15: LRESULT OnInitDialog(UINT, WPARAM, LPARAM, BOOL&)16: {17: //Subclass the exiting child edit control18: m_edit.SubclassWindow(GetDlgItem(IDC_EDIT));19: ?20: return 1;21: }22: ...23: ?24: private:25: CContainedWindow m_edit;26: ?27: };因?yàn)闆](méi)有窗口創(chuàng)建的步驟,所以只能在構(gòu)造函數(shù)中將CMessageMap指針和message map ID傳遞給CContainedWindow對(duì)象(CContainedWindow就是一個(gè)輔助類),這樣在WM_INITDIALOG中只需要獲得窗口句柄即可了。那么子類化過(guò)程是如何實(shí)現(xiàn)的?
1: BOOL SubclassWindow(HWND hWnd) 2: { 3: BOOL result; 4: ATLASSUME(m_hWnd == NULL); 5: ATLASSERT(::IsWindow(hWnd)); 6: ? 7: result = m_thunk.Init(WindowProc, this); 8: if (result == FALSE) 9: {10: return result;11: }12: ?13: WNDPROC pProc = m_thunk.GetWNDPROC();14: WNDPROC pfnWndProc = (WNDPROC)::SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)pProc);15: if(pfnWndProc == NULL)16: return FALSE;17: m_pfnSuperWindowProc = pfnWndProc;18: m_hWnd = hWdnd;19: return TRUE;20: }首先使用thunk技術(shù)獲得當(dāng)前父窗口的WndProc保存其中,然后使用SetWindowLongPtr把父窗口的WndProc植入,這樣子窗口的消息首先進(jìn)入父窗口的消息映射中。同時(shí)還cache了子窗口的WndProc,可以用來(lái)處理父窗口未處理的消息。
Containing the Windows Control Wrappers
在CContainedWindow中默認(rèn)把CWindow當(dāng)作了基類,當(dāng)然不必非這樣做。同樣可以對(duì)ATL Windows control wrapper classes作為基類來(lái)創(chuàng)建contained window。如:
1: CContainedWindowT<ATLControls::CEdit> m_edit;這對(duì)于使用Create來(lái)創(chuàng)建子類窗口時(shí)比較方便,若不這樣,就需要在Create中傳入window class的字符串名。而是用模板類,則可以自動(dòng)獲取,通過(guò)基類(如CEdit)所提供的GetWndClassName成員函數(shù)來(lái)獲得,其實(shí)現(xiàn)如下:
1: static LPCTSTR GetWndClassName()2: {3: return _T("LISTBOX");4: }這樣,創(chuàng)建子類化控件時(shí),無(wú)需那么多參數(shù)了:
1: LRESULT OnCreate(UINT, WPARAM, LPARAM, BOOL&)2: {3: //Create the contained window, routing its message to us4: if(m_edit.Create( this, 2013, m_hWnd, &CWindow::rcDefault)) {5: return 0;6: }7: ?8: return -1;9: }?
后記:基本是《ATL技術(shù)內(nèi)幕》第九章的讀書筆記(翻譯)。可以看到ATL所提供的窗口框架基本已經(jīng)成型,WTL只是后續(xù)做了一些擴(kuò)展和完善。通過(guò)閱讀這一部分,對(duì)中間的消息流算是比較清楚了,還有各種類為何如此設(shè)計(jì),整個(gè)過(guò)程有了比較清晰的理解。由于本人對(duì)Windows窗口編程本身的知識(shí)掌握的都不甚完善,所以只是盡最大努力寫出自己的理解和見解。后續(xù)若有空,再進(jìn)行WTL中代碼的分析,以及自定義控件等進(jìn)行學(xué)習(xí)。
轉(zhuǎn)載于:https://www.cnblogs.com/macrolee/archive/2013/04/22/3036609.html
總結(jié)
以上是生活随笔為你收集整理的ATL WTL 实现分析(五)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Erlang TCP Socket的接收
- 下一篇: 谷歌浏览器Chrome离线安装包下载地址