日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

ATL WTL 实现分析(五)

發布時間:2023/12/10 编程问答 38 豆豆
生活随笔 收集整理的這篇文章主要介紹了 ATL WTL 实现分析(五) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

CDialogImpl

對話框本質上是布局和行為受約束的窗口。最原始的模態對話框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

非模態對話框的唯一區別在于返回值:

1: WINUSERAPI2: HWND3: WINAPI4: CreateDialogIndirectParamW 。。。

對話框的Win32API編程步驟,首先用資源編輯器布局窗口,然后寫一個DlgProc,組后創建(模態或非模態)。ATL對對話框的封裝和Window類似,類圖參考分析一,在實現DlgProc時,同樣用到了兩級跳轉以及thunk,從StartDiallgProc到實際的ProcessWindowMessage跳轉。而窗口的布局則使用資源描述符IDD所預先布置好的位置圖來得到。

CSimpleDlg是一個簡化版本的對話框,用以簡單的彈出式對話框。

?

DataExchange和驗證

對話框使用時還是比較復雜的,主要原因有將數據寫給對話框所包含的子控件,對話框從子控件讀取數據。

模態數據交互流程如下:

1、創建繼承自CDialogImpl的類實例;

2、將數據拷貝進對話框類的數據成員;

3、調用DoModal。

4、在WM_INITDIALOG時將數據傳遞給子控件;

5、當對話框處理OK鍵消息時,對子控件獲得的數據進行驗證;

6、當數據有效,把數據拷貝到對話框的數據成員,結束對話框;

7、應用從對話框DoModal返回獲得IDOK,并從中把數據返回。

非模態對話框的交互流程類似,不同在于第6、7步:

6、當數據有效,并把數據拷貝到對話框的數據成員,應用接受到通知并從對話框數據成員獲取這些數據;

7、應用在收到通知后接收數據,拷貝到應用自己的數據成員中。

不同在于,模態時對數據的驗證工作室對話框自己做,非模態則將接收數據的消息傳給了應用,由應用進行處理。消息處理的位置不同。

ATL沒有提供類似MFC中DDX、DDV的功能,不過很容易模仿實現;而在WTL中隨機對這一功能進行了補充。

?

Windows Control Wrappers

Child Window Management

操作一個子控件時,例如設定edit control的文本顯示或禁用OK按鈕等,我們使用到的函數都是來自CWindow,在CWindow中提供了一系列的helper function來操作子控件:

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: };

這種實現效率上不高,CWindow是一個龐大的類,包含了很多的help functions。如,每次傳入一個子控件ID,windows將會查找以得到HWND,然后再調用實際的處理函數,SetDlgItemText的【API】實現“應該”如下:

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: }

也即,首先根據子控件ID找到HWND, 然后調用實際的窗口方法。所以,在CWindow中實現的這個help function并不高效,每次調用都有一個查找的步驟。更好的方法是在使用時首先緩存每個控件的HWND,然后調用時就省去這個查找的步驟。使用時如下:

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,然后在其他方法中就可以直接調用了。【當然,以上方法需要在類中具體實現,實現很簡單,就是直接調用win32API了】

?

A Better Class of Wrappers

帶edit control的對話框無論怎么寫,實現起來都很簡單。而當使用listbox等控件時,就不只是簡單調用::SetWindowText了。操作listbox就要用到Windows messages了。例如填充一個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確實提供了很多wrapper函數,但是對于windows內置的控件卻并沒有提供,如listbox等。然而,ATL非官方的提供了一些這樣的類,在atlcontrols.h中。例如CListBox,其實現如下:

1: //取自WTL8.0實現 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則對這部分進行了完善,實現了所有的控件類。

?

CContainedWindow

CContainedWindow可以使父窗口處理其子窗口傳遞過來的消息,從而可以將消息處理函數集中放到父窗口中。父窗口即可以主動創建這樣的子窗口,也可以使用已創建好的然后將其子類化(subclassing)實現父窗口處理子窗口消息的方法是使用Altmessage maps。每個CContainedWindow都包含了一個message map ID,由此實現將消息轉移到父窗口的消息映射中。

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也沒有繼承CMessageMap,所以CContainedWindow對象沒有消息映射,而是通過WindowProc靜態成員函數傳遞給了父窗口,message map ID是由構造函數活著Create函數提供。CContainedWindow提供了很多構造函數和Create方法。舉個應用例子,我們是一個edit control只接收字母,實現如下:

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: ? /* 第一個參數是控件名,第二個參數是CMessageMap的指針,這樣子窗口的的消息才能找到消息處理映射,第三個是消息映射ID, 第四個是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中,將是字母的輸入設為消息未處理,從而可以讓CContainedWindow對應控件的默認消息處理procedure來進行響應;而對于非字母輸入,則bHandled為TRUE,消息不再傳遞下去,表現出來就是該字符被忽略。

Subclassing Contained Windows

如果是包含一個已經創建的子控件,就需要用到子類化功能。之前已經講到了超類化實現了窗口類的繼承,而子類化功能則更加溫和并且更加常用。子類化不需要完全創建一個新類,而只是對原有窗口的一些消息進行hack,功能表現更像一個filter。子類化的實現是通過創建一個特定的窗口對象,然后替換掉其窗口過程SetWindowLong(GWL_WNDPROC)。替換的窗口過程首先接收所有的消息,然后再決定是否讓原有的窗口過程是否也處理。可以認為,超類化是一個類的特化,而子類化是一個對象實例的特化。子類化常用在窗口子控件上,如:

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: };

因為沒有窗口創建的步驟,所以只能在構造函數中將CMessageMap指針和message map ID傳遞給CContainedWindow對象(CContainedWindow就是一個輔助類),這樣在WM_INITDIALOG中只需要獲得窗口句柄即可了。那么子類化過程是如何實現的?

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技術獲得當前父窗口的WndProc保存其中,然后使用SetWindowLongPtr把父窗口的WndProc植入,這樣子窗口的消息首先進入父窗口的消息映射中。同時還cache了子窗口的WndProc,可以用來處理父窗口未處理的消息。

Containing the Windows Control Wrappers

在CContainedWindow中默認把CWindow當作了基類,當然不必非這樣做。同樣可以對ATL Windows control wrapper classes作為基類來創建contained window。如:

1: CContainedWindowT<ATLControls::CEdit> m_edit;

這對于使用Create來創建子類窗口時比較方便,若不這樣,就需要在Create中傳入window class的字符串名。而是用模板類,則可以自動獲取,通過基類(如CEdit)所提供的GetWndClassName成員函數來獲得,其實現如下:

1: static LPCTSTR GetWndClassName()2: {3: return _T("LISTBOX");4: }

這樣,創建子類化控件時,無需那么多參數了:

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技術內幕》第九章的讀書筆記(翻譯)。可以看到ATL所提供的窗口框架基本已經成型,WTL只是后續做了一些擴展和完善。通過閱讀這一部分,對中間的消息流算是比較清楚了,還有各種類為何如此設計,整個過程有了比較清晰的理解。由于本人對Windows窗口編程本身的知識掌握的都不甚完善,所以只是盡最大努力寫出自己的理解和見解。后續若有空,再進行WTL中代碼的分析,以及自定義控件等進行學習。

轉載于:https://www.cnblogs.com/macrolee/archive/2013/04/22/3036609.html

總結

以上是生活随笔為你收集整理的ATL WTL 实现分析(五)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。