前言:這部分涉及工程比較大,,所以我打算分開為兩篇來寫,第一篇完成基本框架的構(gòu)建,,第二篇添加上EVENT和NOTIFY機制。
完成目標:仿照DirectUI,,完成一個基本雛形,開發(fā)一個布局控件(Dialog),,和一個按鈕控件(Button),,通過XML來布局窗體,,最后按鈕響應(yīng)點擊、鼠標移動等事件信息,,用戶還可以通過NOTIFY機制來定制,用戶具體行為時,,界面所要做的動作。給大家看下最終界面吧,,一個背景和四個按鈕。 正常狀態(tài):
點擊按鈕狀態(tài)(點擊按鈕時,,并拖動窗體)
正文
一,、CDialogBuilder的構(gòu)建
1,、先看下我們布局的XML
- "<XML>"
- "<Dialog bk=\"C:\\bg.png\" pos=\"0 0 390 212\">"
- "<Canvas pos=\"0 0 100 212\">"
- "<Button pos=\"0 0 60 60\" />"
- "<Button pos=\"70 70 100 100\" />"
- "</Canvas>"
- "<Canvas pos=\"100 0 300 212\">"
- "<Button pos=\"120 20 160 60\" />"
- "<Button pos=\"170 170 200 200\" />"
- "</Canvas>"
- "</Dialog>"
- "</XML>"
2,、兩個文件:UIMarkup.h和UIMarkup.cpp 這兩個文件里主要完成的功能就是加載XML,并且對XML對容進行分析,,構(gòu)建出結(jié)點樹,,具體代碼就不講了,只要知道所完成的功能就可以了,,大家有興趣可以看看,,
但有幾個接口,,要注意一下:
- CMarkup::Load(LPCTSTR pstrXML) //加載并分析XML,構(gòu)建XML結(jié)點樹
- CMarkup::GetRoot() //獲取XML樹的根結(jié)點
-
- CMarkupNode CMarkupNode::GetParent();//獲取父結(jié)點
- CMarkupNode CMarkupNode::GetSibling();//獲取下一緊臨的兄弟結(jié)點
- CMarkupNode CMarkupNode::GetChild(); //獲取孩子結(jié)點
- CMarkupNode CMarkupNode::GetChild(LPCTSTR pstrName);//根據(jù)名字獲取孩子結(jié)點
-
- bool CMarkupNode::HasAttributes();
- bool CMarkupNode::HasAttribute(LPCTSTR pstrName); //指定結(jié)點是否具有屬性
- int CMarkupNode::GetAttributeCount(); //具有的屬性個數(shù)
- LPCTSTR CMarkupNode::GetAttributeName(int iIndex);//根據(jù)索引獲取屬性名
-
- LPCTSTR CMarkupNode::GetAttributeValue(int iIndex);
- LPCTSTR CMarkupNode::GetAttributeValue(LPCTSTR pstrName);
- bool CMarkupNode::GetAttributeValue(int iIndex, LPTSTR pstrValue, SIZE_T cchMax);
- bool CMarkupNode::GetAttributeValue(LPCTSTR pstrName, LPTSTR pstrValue, SIZE_T cchMax);//獲取對應(yīng)屬性名的屬性的值的幾個函數(shù)
3,、CDialogBuilder講解
完成功能:
1,、利用上面的CMarkUp類分析XML,,構(gòu)建出XML結(jié)點樹 2,、然后根據(jù)XML中的結(jié)點,NEW 出控件,,并利用XML結(jié)點樹的屬性,構(gòu)建出控件的屬性
先看定義:
- class CDialogBuilder
- {
- public:
- CDialogBuilder();
- ~CDialogBuilder();
- public:
- CControlUI* Create(LPCTSTR pstrXML);
-
- private:
- CControlUI* _Parse(CMarkupNode* parent, CControlUI* pParent = NULL);
-
- CMarkup m_xml;
- };
可以看到,,CDialogBuilder比較簡單,,只有兩個函數(shù),Create()和_Parse(),,另一個變量m_xml是用來分析XML,并構(gòu)建XML結(jié)點樹的,。 看下Create函數(shù)
- CControlUI* CDialogBuilder::Create(LPCTSTR pstrXML)
- {
- if( !m_xml.Load(pstrXML) ) return NULL;//加載XML,,并生成XML結(jié)點樹
- // NOTE: The root element is actually discarded since the _Parse() methods is
- // parsing children and attaching to the current node.
- CMarkupNode root = m_xml.GetRoot();//獲取XML結(jié)點樹的根結(jié)點
- return _Parse(&root);//分析結(jié)點樹
- }
下面看看_Parse函數(shù)完成的功能:
- CControlUI* CDialogBuilder::_Parse(CMarkupNode* pRoot, CControlUI* pParent)
- {
- IContainerUI* pContainer = NULL;
- CControlUI* pReturn = NULL;
- for( CMarkupNode node = pRoot->GetChild() ; node.IsValid(); node = node.GetSibling() ) {
- LPCTSTR pstrClass = node.GetName();
- SIZE_T cchLen = _tcslen(pstrClass);
- CControlUI* pControl = NULL;
- switch( cchLen ) {
- case 6:
- if( _tcscmp(pstrClass, _T("Canvas")) == 0 ) pControl = new CContainerUI;
- else if( _tcscmp(pstrClass, _T("Button")) == 0 ) pControl = new CButtonUI;
- else if ( _tcscmp(pstrClass, _T("Dialog")) == 0) pControl=new CDialogUI;
- break;
- }/////根據(jù)XML樹,,生成對應(yīng)的控件
-
- ASSERT(pControl);
- if( pControl == NULL ) return NULL;
- // Add children
- if( node.HasChildren() ) {
- _Parse(&node, pControl);//利用遞規(guī),,遍歷XML樹
- }
- // Attach to parent
- if( pParent != NULL ) {//如果它的父親不為空,,說明它的父結(jié)點肯定具有CONTAINER屬性,所以把它加到它的父結(jié)點的CONTAINER中
- if( pContainer == NULL ) pContainer = static_cast<IContainerUI*>(pParent->GetInterface(_T("Container")));
- ASSERT(pContainer);
- if( pContainer == NULL ) return NULL;
- pContainer->Add(pControl);
- }
- // Process attributes
- if( node.HasAttributes() ) {//分析屬性,然后設(shè)置控件屬性
- TCHAR szValue[500] = { 0 };
- SIZE_T cchLen = lengthof(szValue) - 1;
- // Set ordinary attributes
- int nAttributes = node.GetAttributeCount();
- for( int i = 0; i < nAttributes; i++ ) {
- pControl->SetAttribute(node.GetAttributeName(i), node.GetAttributeValue(i));
- }
- }
- // 返回根結(jié)點,對于我們的XML,返回的是CDialogUI的實例指針
- if( pReturn == NULL ) pReturn = pControl;
- }
- return pReturn;
- }
二,、控件與容器的構(gòu)建
一、總體關(guān)系
這里我們先講下,,控件類和布局類的區(qū)別,,在這里我們在構(gòu)建幾個類,,先看下繼承圖(這篇文章先不管INotifyUI的事)
1、一個控件基類(CControlUI)和一個按鈕控件類(CButtonUI) 控件基類,,包含有控件繪制的最基本的幾個函數(shù),,當然都是虛函數(shù),,比如:SetAttribute(設(shè)置控件屬性),,GetInterface(獲取控件的this指針),,DoPaint(繪圖) 2,、一個容器基類,,故名思義,,容器是用來盛控件的,,所以它必須具有的幾個函數(shù): Add(將控件添加到此容器的子隊列中),,Remove(刪除某個子控件),、RemoveAll(清空子控件隊列)、GetItem(獲取某個子控件),、GetCount(獲取子控件的個數(shù)) 所以這幾個函數(shù)也是容器最基類IContainerUI的函數(shù), 3,、CContainerUI,這個才是稍微有意義點的布局類,,它用于在窗體上劃分出不同的區(qū)域,,然后在區(qū)域中布局控件.這里最值得注意的地方是派生出它的基類,它是兩個基類的派生類(IContainerUI和CControlUI),,所以容器也是控件,,這一點在后面繪圖代碼中特點重要,在繪圖時,,我們將所有的容器和控件全部都定義為CControlUI,,所以如果當前的如果是CDialogUI的指針的話,它就會根據(jù)繼承關(guān)系,,先到CDialogUI中執(zhí)行相關(guān)函數(shù),,這點尤其注意!?。,。?/SPAN>(這里暫時搞不明白也沒關(guān)系,,后面遇到的時候,,我會重新講) 4、CDialogUI,,這是窗體的構(gòu)建類,,里面包含所在構(gòu)建窗體的大小、背景圖案,,現(xiàn)在只加了這兩個屬性,,其它都沒加
二、控件類的實現(xiàn) 1,、CContolUI的實現(xiàn)
- class CControlUI
- {
- public:
- CControlUI();
- virtual ~CControlUI();
- public:
- virtual void SetAttribute(LPCTSTR pstrName, LPCTSTR pstrValue);//設(shè)置屬性
- virtual LPVOID GetInterface(LPCTSTR pstrName);//獲取當前control的this指針
- virtual void DoPaint(HWND hwnd,HDC hDC)=0;//控件的繪制函數(shù),,注意這里把它聲明為純虛函數(shù),強制子類中必須對其進行實現(xiàn)
-
- //設(shè)置控件屬性的幾個函數(shù)
- void SetPos(RECT rc);///設(shè)置控件位置
- RECT GetPos();
- virtual CControlUI* GetParent();//設(shè)置當前控件的父結(jié)點
- virtual void SetParent(CControlUI* parent);
-
- virtual void SetHwnd(HWND hwnd);//設(shè)置窗體句柄
-
- public:
- void Invalidate();
- protected:
- RECT m_RectItem;///控件位置
- CControlUI *m_pParent;//父結(jié)點指針
-
- HWND m_hwnd;//保存?zhèn)鬟^來的窗體的句柄
- };
這里大家可能會有個疑問,,控件要窗體句柄干嘛,,這里呢,因為我們只有一個窗體,,也就是CreateWindowEx函數(shù)創(chuàng)建后,,返回的那個HWND,我們這里保存的就是這個HWND,,那我們要它有什么用呢,,這是因為我們這里的控件全部都是模擬的控件的行為而已,,是并沒有句柄的,所以也不可能像MFC那樣根據(jù)控件的句柄單獨刷新了,,所以我們要刷新控件的話,,就得刷新整個窗體(當然,我們會利用緩存技術(shù)局部繪制來提高效率),,我們要刷新窗體就得利用SendMessage(hwnd,msg,wparam,lparam),注意這里的第一個變量就是窗體的句柄,,當然這里要保存了,就這樣的道理,。
看各個函數(shù)的具體實現(xiàn)
- void CControlUI::SetPos(RECT rc)
- {
- m_RectItem = rc;
- }
- RECT CControlUI::GetPos()
- {
- return m_RectItem;
- }
這倆函數(shù)很簡單,就是設(shè)置控件位置和獲取控件位置的,。
- void CControlUI::SetAttribute(LPCTSTR pstrName, LPCTSTR pstrValue)
- {
- if( _tcscmp(pstrName, _T("pos")) == 0 ) {
- RECT rcPos = { 0 };
- LPTSTR pstr = NULL;
- rcPos.left = _tcstol(pstrValue, &pstr, 10); ASSERT(pstr);
- rcPos.top = _tcstol(pstr + 1, &pstr, 10); ASSERT(pstr);
- rcPos.right = _tcstol(pstr + 1, &pstr, 10); ASSERT(pstr);
- rcPos.bottom = _tcstol(pstr + 1, &pstr, 10); ASSERT(pstr);
- SetPos(rcPos);
- }
- }
這個函數(shù)是設(shè)置控件屬性,,哪里用到這個函數(shù)了呢,嘿嘿,,還記得不,,CDialogBuilder的_Parse函數(shù)里
- void CControlUI::SetParent(CControlUI* parent)
- {
- m_pParent=parent;
- }
- CControlUI* CControlUI::GetParent()
- {
- return m_pParent;
- }
設(shè)置父結(jié)點,也是在CDialogBuilder的_Parse函數(shù)里用到
- LPVOID CControlUI::GetInterface(LPCTSTR pstrName)
- {
- if( _tcscmp(pstrName, _T("Control")) == 0 ) return this;
- return NULL;
- }
根據(jù)要獲取的指針名,,返回當前實例的this指針,,哪里用到了呢,也是CDialogBuilder的_Parse函數(shù)里,,下面是_Parse里的,,我摘過來
- if( pParent != NULL ) {
- if( pContainer == NULL ) pContainer = static_cast<IContainerUI*>(pParent->GetInterface(_T("Container")));
- ASSERT(pContainer);
- if( pContainer == NULL ) return NULL;
- pContainer->Add(pControl);
- }
看到了吧,獲取的是父結(jié)點的Container的指針,,然后將控件加到父結(jié)點中,,下面繼續(xù)
- void CControlUI::SetHwnd(HWND hwnd)
- {
- m_hwnd=hwnd;
- }
-
- void CControlUI::Invalidate()
- {
- SendMessage(m_hwnd,WM_PAINT,NULL,NULL);
- }
SetHwnd用來保存窗體的HWND,然后是Invalidate函數(shù)的實現(xiàn),,也就是向窗體發(fā)送WM_PAINT消息 2,、CButtionUI的實現(xiàn) 定義:
- class CButtonUI:public CControlUI
- {
- public:
- CButtonUI();
- ~CButtonUI();
- public:
- virtual void SetAttribute(LPCTSTR pstrName, LPCTSTR pstrValue);
- virtual LPVOID GetInterface(LPCTSTR pstrName);
- virtual void DoPaint(HWND hwnd,HDC hDC);
- };
可以看到,很簡單,,設(shè)置屬性,、獲取THIS指針,然后就是繪圖類,,看具體實現(xiàn)
- void CButtonUI::SetAttribute(LPCTSTR pstrName, LPCTSTR pstrValue)
- {//因為實現(xiàn)的是最簡易版,,Button里也就不設(shè)置什么獨有的屬性了,直接返回基類的SetAttribute(),設(shè)置Pos屬性
- return CControlUI::SetAttribute(pstrName,pstrValue);
- }
- LPVOID CButtonUI::GetInterface(LPCTSTR pstrName)
- {
- if( _tcscmp(pstrName, _T("Button")) == 0 ) return this;
- return CControlUI::GetInterface(pstrName);
-
- }
- void CButtonUI::DoPaint(HWND hwnd,HDC hDC)
- {
- assert(hDC);
- Graphics graph(hDC);
- graph.FillRectangle(&SolidBrush(Color::Green),m_RectItem.left,m_RectItem.top,m_RectItem.right-m_RectItem.left,m_RectItem.bottom-m_RectItem.top);
- graph.ReleaseHDC(hDC);
-
- }
這里要說明的一點就是,,DoPaint()函數(shù),,因為我們這篇文章里面還沒有添加EVENT事件通知功能,所以我們就先初始化的時候,,把按鈕統(tǒng)一畫成綠色,。 三,、容器類的實現(xiàn) 1、容器最基類IContainerUI的實現(xiàn)
- class IContainerUI
- {
- public:
- virtual CControlUI* GetItem(int iIndex) const = 0;
- virtual int GetCount() const = 0;
- virtual bool Add(CControlUI* pControl) = 0;
- virtual bool Remove(CControlUI* pControl) = 0;
- virtual void RemoveAll() = 0;
- };
其實這個類沒有任何的實際意義,,就是個接口類,,它的這幾個函數(shù)全部都聲明為純虛函數(shù) 2、CContainerUI的實現(xiàn) 定義:
- class CContainerUI : public CControlUI, public IContainerUI
- {
- public:
- CContainerUI();
- virtual ~CContainerUI();
- public:
- ///先實現(xiàn)繼承的純虛函數(shù)
- virtual CControlUI* GetItem(int iIndex) const;
- virtual int GetCount() const;
- virtual bool Add(CControlUI* pControl);
- virtual bool Remove(CControlUI* pControl);
- virtual void RemoveAll();
- public:
- void SetAttribute(LPCTSTR pstrName, LPCTSTR pstrValue);///設(shè)置屬性
- virtual LPVOID GetInterface(LPCTSTR pstrName);//獲取THIS指針
- virtual void DoPaint(HWND hwnd,HDC hDC);//繪圖函數(shù)
-
- void LoadBackground(LPCTSTR Parth);//根據(jù)路徑加載圖像,,并保存在m_pImage中
- protected:
- CStdPtrArray m_items; //當前容器中的子變量隊列
-
- Gdiplus::Image* m_pImage;//容器的背景
- };
大家看到了吧,,這里除了實現(xiàn)IContainerUI幾個純虛函數(shù)以外,其它幾個函數(shù)SetAttribute,、GetInterface,、DoPaint也都是控件所具有的??聪戮唧w實現(xiàn):
- CControlUI* CContainerUI::GetItem(int iIndex) const
- {
- if( iIndex < 0 || iIndex >= m_items.GetSize() ) return NULL;
- return static_cast<CControlUI*>(m_items[iIndex]);
- }
-
- int CContainerUI::GetCount() const
- {
- return m_items.GetSize();
- }
-
- bool CContainerUI::Add(CControlUI* pControl)
- {
- return m_items.Add(pControl);
- }
-
- bool CContainerUI::Remove(CControlUI* pControl)
- {
- for( int it = 0;it < m_items.GetSize(); it++ ) {
- if( static_cast<CControlUI*>(m_items[it]) == pControl ) {
- delete pControl;
- return m_items.Remove(it);
- }
- }
- return false;
- }
-
- void CContainerUI::RemoveAll()
- {
- for( int it = 0;it < m_items.GetSize(); it++ ) delete static_cast<CControlUI*>(m_items[it]);
- m_items.Empty();
- }
這幾個就不講了,,就是向隊列里添加、刪除變量的操作,。
- LPVOID CContainerUI::GetInterface(LPCTSTR pstrName)
- {
- if( _tcscmp(pstrName, _T("Container")) == 0 ) return static_cast<IContainerUI*>(this);
- return CControlUI::GetInterface(pstrName);
- }
獲取this指針
- void CContainerUI::LoadBackground(LPCTSTR Parth)
- {
- m_pImage = Gdiplus::Image::FromFile(Parth);
- }
-
- void CContainerUI::SetAttribute(LPCTSTR pstrName, LPCTSTR pstrValue)
- {
- if( _tcscmp(pstrName, _T("bk")) == 0 ) LoadBackground(pstrValue);
- else CControlUI::SetAttribute(pstrName, pstrValue);
- }
設(shè)置屬性,,這里只在CControlUI的基礎(chǔ)上增加了一個屬性,bk-----背景
- void CContainerUI::DoPaint(HWND hwnd,HDC hDC)
- {
- for( int it = 0; it < m_items.GetSize(); it++ ) {
- CControlUI* pControl = static_cast<CControlUI*>(m_items[it]);
- pControl->DoPaint(hwnd,hDC);
- }
- }
最重要的,,繪制函數(shù),,我這里沒有對它本身進行繪制,只是遍歷當前容器中所有的控件,,然后再它里面的控件進行逐個繪制,,這里我要留下一個疑問,如果容器里嵌套有容器是怎么完成繪制的呢,?,??,??。。,?! 3、CDialogUI實現(xiàn) 定義:
- class CDialogUI:public CContainerUI
- {
- public:
- CDialogUI();
- ~CDialogUI();
- public:
- void DoPaintBackground(HDC hdc);//畫背景
- virtual LPVOID GetInterface(LPCTSTR pstrName);//獲取this指針
- };
大家可以看到這個CDialogUI的實現(xiàn)是非常簡單的,,只有一個DoPaintBackground(),,這個函數(shù)只是用來畫背景的
實現(xiàn):
- void CDialogUI::DoPaintBackground(HDC hdc)
- {
- Gdiplus::Graphics graph(hdc);
- graph.SetSmoothingMode(Gdiplus::SmoothingModeNone);
- graph.DrawImage(m_pImage, 0, 0, m_RectItem.right-m_RectItem.left, m_RectItem.bottom-m_RectItem.top);
- graph.ReleaseHDC(hdc);
- }
- LPVOID CDialogUI::GetInterface(LPCTSTR pstrName)
- {
- if( _tcscmp(pstrName, _T("Dialog")) == 0 ) return static_cast<IContainerUI*>(this);
- return CContainerUI::GetInterface(pstrName);
- }
好了,到這幾個控件的實現(xiàn)就全部實現(xiàn)了,,下面看窗體的創(chuàng)建,。
三、窗體創(chuàng)建
基本思想,,我們?yōu)榱俗層脩舨槐刈鎏嗟墓ぷ?,我們建一個基類,,這個基類完成窗口類注冊、創(chuàng)建窗口,、消息響應(yīng)等功能,,而用戶只需要從我們的基類派生,并且簡單調(diào)用Create()函數(shù)就可以了,。 一,、創(chuàng)建窗體基類(CWindowWnd)
定義
- class CWindowWnd{
- public:
- CWindowWnd();
- ~CWindowWnd();
-
- HWND GetHWND() const{return m_hWnd;}
-
- bool RegisterWindowClass(WNDPROC fWndProc,HINSTANCE hInstance,LPCTSTR szClassName);//注冊窗口類
-
- HWND Create();//創(chuàng)建窗體
-
- void ShowWindow(bool bShow = true, bool bTakeFocus = true);
- void SetInstance(HINSTANCE instance){m_instance=instance;}
-
- protected:
- virtual LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam);
-
- static LRESULT CALLBACK __WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
- protected:
- HWND m_hWnd;// 保存所創(chuàng)建窗體的句柄
-
- HINSTANCE m_instance;//保存WinMain入口,參數(shù)里的hInstance,,因為注冊窗口類要用到
- };
注意一點:這里必須把窗口消息處理函數(shù)(_WndProc)定義為靜態(tài)函數(shù),,否則就定義為全局函數(shù) 我們看下具體實現(xiàn),由簡到難講:
- bool CWindowWnd::RegisterWindowClass(WNDPROC fWndProc,HINSTANCE hInstance,LPCTSTR szClassName)
- {
- WNDCLASSEX wce={0};
- wce.cbSize=sizeof(wce);
- wce.style=CS_HREDRAW|CS_VREDRAW;
- wce.lpfnWndProc=fWndProc;
- wce.cbClsExtra=0;
- wce.cbWndExtra=0;
- wce.hInstance=hInstance;
- wce.hIcon=NULL;
- wce.hCursor=LoadCursor(NULL,IDC_ARROW);
- wce.hbrBackground=(HBRUSH)(6);//(HBRUSH)(COLOR_WINDOW+1);
- wce.lpszMenuName=NULL;
- wce.lpszClassName=szClassName;
- wce.hIconSm=NULL;
- ATOM nAtom=RegisterClassEx(&wce);
- if(nAtom==0) return false;
- return true;
-
- }
注冊窗口類,,沒什么好講的了,,標準流程。不過這里要注意一下,,fWndProc是消息處理函數(shù),,這個函數(shù)必須是靜態(tài)的或是全局的?。,。。,?!
- void CWindowWnd::ShowWindow(bool bShow /*= true*/, bool bTakeFocus /*= false*/)
- {
- ASSERT(::IsWindow(m_hWnd));
- if( !::IsWindow(m_hWnd) ) return;
- ::ShowWindow(m_hWnd, bShow ? (bTakeFocus ? SW_SHOWNORMAL : SW_SHOWNOACTIVATE) : SW_HIDE);
- }
顯示窗口,調(diào)用系統(tǒng)API ----ShowWindow()
- HWND CWindowWnd::Create()
- {
- if (!RegisterWindowClass(__WndProc,m_instance,L"transparent"))
- {//注冊窗口類,,看到了吧,,窗口處理函數(shù)的函數(shù)名為:_WndProc,后面講
- assert(L"注冊窗口失敗");
- }
-
- assert(this);
- m_hWnd = ::CreateWindowEx(WS_EX_LAYERED, L"transparent", _T(""),WS_POPUP|WS_MAXIMIZE|WS_MINIMIZE|WS_SYSMENU,
- CW_USEDEFAULT, CW_USEDEFAULT,
- CW_USEDEFAULT, CW_USEDEFAULT,
- NULL, NULL, (HINSTANCE)::GetModuleHandle(NULL),(LPVOID)this);
-
- if(m_hWnd == NULL || !::IsWindow(m_hWnd))
- return NULL;
-
- return m_hWnd;
- }
創(chuàng)建窗口,,流程是,,先注冊窗口類,如果注冊成功,,就調(diào)用CreateWindowEx創(chuàng)建窗口,。這里最注意的一個地方,單獨把CreateWindowEx調(diào)出來
- m_hWnd = ::CreateWindowEx(WS_EX_LAYERED, L"transparent", _T(""),WS_POPUP|WS_MAXIMIZE|WS_MINIMIZE|WS_SYSMENU,CW_USEDEFAULT, CW_USEDEFAULT,CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, (HINSTANCE)::GetModuleHandle(NULL),(LPVOID)this);
注意最后一個參數(shù):傳進去的當前CWindowWnd的this指針?。,。?!這個參數(shù)會做為lparam傳進CREATESTRUCT結(jié)構(gòu)體里的lpCreateParams參數(shù)里,,在WM_NCCREATE消息里可以獲取到。那傳進去他有什么用呢,?往下看,,_WndProc消息處理函數(shù)
- LRESULT CWindowWnd::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
- {
- return ::CallWindowProc(::DefWindowProc, m_hWnd, uMsg, wParam, lParam);
- }
- LRESULT CALLBACK CWindowWnd::__WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
- {
- CWindowWnd* pThis = NULL;
- if( uMsg == WM_NCCREATE ) {
- LPCREATESTRUCT lpcs = reinterpret_cast<LPCREATESTRUCT>(lParam);
- pThis = static_cast<CWindowWnd*>(lpcs->lpCreateParams);
- pThis->m_hWnd = hWnd;
- ::SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast<LPARAM>(pThis));
- }
- else {
- pThis = reinterpret_cast<CWindowWnd*>(::GetWindowLongPtr(hWnd, GWLP_USERDATA));
- if( uMsg == WM_NCDESTROY && pThis != NULL ) {
- ::SetWindowLongPtr(pThis->m_hWnd, GWLP_USERDATA, 0L);
-
- pThis->m_hWnd = NULL;
- return true;
- }
- }
- if( pThis != NULL ) {
- return pThis->HandleMessage(uMsg, wParam, lParam);
- }
- else {
- return ::DefWindowProc(hWnd, uMsg, wParam, lParam);
- }
- }
注意在遇到WM_NCCREATE消息時,, 首先將lParam強轉(zhuǎn)成LPCREATESTRUCT 變量,我們上面說過我們傳過來的CWindowWnd的this指針,,保存在CREATESTRUCT結(jié)構(gòu)體的lpCreateParams參數(shù)里
- pThis = static_cast<CWindowWnd*>(lpcs->lpCreateParams);
這句,,就是取出this指針!,,有同學可能會問,,為什么非要這樣得到this指針,_WndProc不是CWindowWnd的成員函數(shù)么,,不是直接有this么,?注意,_WndProc是靜態(tài)函數(shù),,會在CWindowWnd創(chuàng)建之前編譯,,所以,由于在編譯_WndProc時,,沒有CWindowWnd還沒有被實例化,,當然要報錯了。所以我們要通過傳參的方式得到this指針,,那接下來的問題就是,,那下次我怎么再在這個函數(shù)里得到this指針呢,這里我們用SetWindowLongPtr把他保存在GWLP_USERDATA域,,然后調(diào)用GetWindowLongPtr就可以得到了,。
- ::SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast<LPARAM>(pThis));
這句的意思就是把THIS指針保存在GWLP_USERDATA域。
在其它消息到來的是,,我們先通過GetWindowLongPtr得到THIS指針,,然后調(diào)用虛函數(shù)pThis->HandleMessage(uMsg, wParam, lParam);把消息轉(zhuǎn)到HandleMessage函數(shù)中處理,而用戶的函數(shù)是派生自CWindowWnd的,,所以只需要在HandleMessage函數(shù)中處理消息就可以了,。
二、用戶窗口類
- class CStartPage: public CWindowWnd
- {
- public:
- CStartPage();
- ~CStartPage();
- public:
- virtual LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam);
-
- LPCTSTR GetDialogResource();//傳進去XML字符串
-
- void Paint(HWND m_hWnd);//響應(yīng)WM_PAINT消息的函數(shù)
- private:
- WCHAR m_resource[556];//XML字符串
-
- CDialogBuilder m_dialogBuilder;
- CControlUI *m_root;
-
- HDC hdcBKMemory;//內(nèi)存DC,,參見《之二----GDI+中的局部刷新技術(shù)》
- HBITMAP hBKBitmap;
- HGDIOBJ hBKBitmapOld;
- };
看下具體實現(xiàn):
- LPCTSTR CStartPage::GetDialogResource()
- {
- char temp[]=
- "<XML>"
- "<Dialog bk=\"C:\\bg.png\" pos=\"0 0 390 212\">"
- "<Canvas pos=\"0 0 100 212\">"
- "<Button pos=\"0 0 60 60\" />"
- "<Button pos=\"70 70 100 100\" />"
- "</Canvas>"
- "<Canvas pos=\"100 0 300 212\">" //一定要給canvas加上POS,,因為我們根據(jù)鼠標點來查找時,首先看是否在CANVAS的區(qū)域內(nèi),,如果不在,,就直接返回NULL了
- "<Button pos=\"120 20 160 60\" />"
- "<Button pos=\"170 170 200 200\" />"
- "</Canvas>"
- "</Dialog>"
- "</XML>";
- int iStrlen=sizeof(temp);
- MultiByteToWideChar(CP_ACP,0,(LPCSTR)(temp),iStrlen,m_resource,iStrlen);
- return m_resource;
- }
這個函數(shù)很簡單,就是加載XML,,并保存在m_resource字符串中
- LRESULT CStartPage::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
- {
- switch(uMsg){
- case WM_DESTROY:
- {
- PostQuitMessage(100);
-
- ::SelectObject( hdcBKMemory, hBKBitmapOld); //不要把默認的位圖選回來,,如果選回來的話,我們新建的位圖就被替換掉了,,當然我們上面畫的東東也就沒有了
- ::DeleteObject(hBKBitmapOld);//這三個在清除的時候,,一塊清除
- ::DeleteObject(hBKBitmap); //先不要刪除,,先保存起來,后面再跟hmdmDC一起刪除
- ::DeleteDC(hdcBKMemory);
- }
- break;
- case WM_CREATE:
- {
- m_root=m_dialogBuilder.Create(GetDialogResource());///傳入XML文檔,,讓其分析
-
- SendMessage(GetHWND(),WM_PAINT,NULL,NULL);
- }
- break;
- case WM_PAINT:
- {
- Paint(GetHWND());
- }
- break;
- }
- return CWindowWnd::HandleMessage(uMsg, wParam, lParam);
注意兩個地方,,在CREATE的時候,傳入XML,,然后發(fā)送WM_PAIT消息,。然后在WM_PAIT中,Paint函數(shù)繪圖
- void CStartPage::Paint(HWND m_hWnd)
- {
- RECT rcWindow;
- GetWindowRect(m_hWnd,&rcWindow);
- SIZE sizeWindow;
- /////因為根結(jié)點,,必是DLG結(jié)點,,肯定是個容器,所以將它強制轉(zhuǎn)換成CContainerUI*,,定位到
- CDialogUI *pdlg=(CDialogUI*)m_root;
-
- RECT Pos=pdlg->GetPos();
- sizeWindow.cx=Pos.right-Pos.left;
- sizeWindow.cy=Pos.bottom-Pos.top;
-
-
- HDC hDC = ::GetDC(m_hWnd);
- if (hdcBKMemory==NULL)
- {
- hdcBKMemory = CreateCompatibleDC(hDC);
- //創(chuàng)建背景畫布
- BITMAPINFOHEADER stBmpInfoHeader = { 0 };
- int nBytesPerLine = ((sizeWindow.cx * 32 + 31) & (~31)) >> 3;
- stBmpInfoHeader.biSize = sizeof(BITMAPINFOHEADER);
- stBmpInfoHeader.biWidth = sizeWindow.cx;
- stBmpInfoHeader.biHeight = sizeWindow.cy;
- stBmpInfoHeader.biPlanes = 1;
- stBmpInfoHeader.biBitCount = 32;
- stBmpInfoHeader.biCompression = BI_RGB;
- stBmpInfoHeader.biClrUsed = 0;
- stBmpInfoHeader.biSizeImage = nBytesPerLine * sizeWindow.cy;
- PVOID pvBits = NULL;
- hBKBitmap = ::CreateDIBSection(NULL, (PBITMAPINFO)&stBmpInfoHeader, DIB_RGB_COLORS, &pvBits, NULL, 0);
- assert(hBKBitmap != NULL);
- hBKBitmapOld = ::SelectObject( hdcBKMemory, hBKBitmap);
-
- pdlg->DoPaintBackground(hdcBKMemory);//繪制窗體背景
-
- }
-
-
- HDC hdcEnd = CreateCompatibleDC(hDC);//新建兼容DC
- BITMAPINFOHEADER stBmpInfoHeader2 = { 0 };
- int nBytesPerLine2 = ((sizeWindow.cx * 32 + 31) & (~31)) >> 3;
- stBmpInfoHeader2.biSize = sizeof(BITMAPINFOHEADER);
- stBmpInfoHeader2.biWidth = sizeWindow.cx;
- stBmpInfoHeader2.biHeight = sizeWindow.cy;
- stBmpInfoHeader2.biPlanes = 1;
- stBmpInfoHeader2.biBitCount = 32;
- stBmpInfoHeader2.biCompression = BI_RGB;
- stBmpInfoHeader2.biClrUsed = 0;
- stBmpInfoHeader2.biSizeImage = nBytesPerLine2 * sizeWindow.cy;
- PVOID pvBits2 = NULL;
- HBITMAP hbmpMem2 = ::CreateDIBSection(NULL, (PBITMAPINFO)&stBmpInfoHeader2, DIB_RGB_COLORS, &pvBits2, NULL, 0);//新建畫布
-
- HGDIOBJ hEndBitmapOld=SelectObject(hdcEnd,hbmpMem2);
- POINT ptSrc = { 0, 0};
- BLENDFUNCTION blendFunc;
- blendFunc.BlendOp = 0;
- blendFunc.BlendFlags = 0;
- blendFunc.AlphaFormat = 1;
- blendFunc.SourceConstantAlpha = 255;//AC_SRC_ALPHA
- ::AlphaBlend(hdcEnd,0,0,sizeWindow.cx,sizeWindow.cy,hdcBKMemory,0,0,sizeWindow.cx,sizeWindow.cy,blendFunc);//將背景復(fù)制到新畫布上
-
- m_root->DoPaint(m_hWnd,hdcEnd);/////////////繪制畫布和控件
-
- POINT ptWinPos = { rcWindow.left, rcWindow.top };
-
- //UpdateLayeredWindow
- HMODULE hFuncInst = LoadLibrary(_T("User32.DLL"));
- typedef BOOL (WINAPI *MYFUNC)(HWND, HDC, POINT*, SIZE*, HDC, POINT*, COLORREF, BLENDFUNCTION*, DWORD);
- MYFUNC UpdateLayeredWindow;
- UpdateLayeredWindow = (MYFUNC)::GetProcAddress(hFuncInst, "UpdateLayeredWindow");
- if(!UpdateLayeredWindow(m_hWnd, hDC, &ptWinPos, &sizeWindow, hdcEnd, &ptSrc, 0, &blendFunc, ULW_ALPHA))
- {
- assert(L"UpdateLayeredWindow 調(diào)用失敗");
- TCHAR tmp[255] = {_T('\0')};
- }//使用UpdateLayeredWindow更新到當前窗體上
- //釋放資源
-
- SelectObject(hdcEnd,hEndBitmapOld);
-
- ::DeleteObject(hFuncInst);
- ::DeleteObject(hEndBitmapOld);
- ::DeleteObject(hbmpMem2);
- ::DeleteDC(hdcEnd);
-
- ::DeleteDC(hDC);
-
- }
Paint()函數(shù)中用了雙緩沖,,具體講解,參看《WIN32界面開發(fā)之二:GDI+中的局部刷新技術(shù)》 這里我只講兩個地方: 1,、窗體背景的繪制
- CDialogUI *pdlg=(CDialogUI*)m_root;
因為我們在定義m_root時,,定義的是CControlUI,但它實際是CDialogUI的實例,,所以將它強轉(zhuǎn)成CDialogUI指針,,然后內(nèi)存內(nèi)存DC里調(diào)用
- pdlg->DoPaintBackground(hdcBKMemory);//繪制窗體背景
來繪制窗體背景。 2,、容器及控件的繪制
- m_root->DoPaint(m_hWnd,hdcEnd);/////////////繪制畫布和控件
主要是靠這個函數(shù)來完成的,! 這個得著重講一下,,看它是怎么完成繪制的,。我們先看一下根據(jù)XML生成的控件樹
從上面這個圖就能看得出來,CDialogUI包含兩個子控件(Canvas),這也就是我說的容器里包含容器,,而每個Canvas又包含兩個Button控件,。下面我們看DoPaint的調(diào)用順序,首先m_root雖然被定義為CControlUI的變量,,但實際是CDialogUI的實例指針,,所以會沿著繼承圖去找CDialogUI的DoPaint,而CDialogUI是沒有DoPaint的,,所以會去找它的子類CContainerUI的DoPaint,,并且執(zhí)行,我們重新看下這個DoPaint方法
- void CContainerUI::DoPaint(HWND hwnd,HDC hDC)
- {
- for( int it = 0; it < m_items.GetSize(); it++ ) {
- CControlUI* pControl = static_cast<CControlUI*>(m_items[it]);
- pControl->DoPaint(hwnd,hDC);
- }
- }
從這里就可以看到CDialogUI的DoPaint執(zhí)行順序了: 1,、找到CDialogUI的第一個孩子結(jié)點(一個CContainerUI實例,假設(shè)為ContainerA),,然后調(diào)用這個實例的DoPaint 2、執(zhí)行ContainerA->DoPaint(),,依然是這個函數(shù),,也就是說,,它會挨個執(zhí)行那兩個Button的DoPaint,完成之后返回,。 3,、找到CDialogUI的第二個孩子結(jié)點(同樣,一個CContainerUI實例,假設(shè)為ContainerB),,然后調(diào)用這個實例的DoPaint 4,、執(zhí)行ContainerB>DoPaint(),同樣,,它也是同樣執(zhí)行這個函數(shù),,然后逐個執(zhí)行它的那個Button的DoPaint,之后返回,。 三,、WinMain函數(shù)
- void Message(){
- MSG msg={0};
- while(GetMessage(&msg,NULL,0,0)){
- TranslateMessage(&msg);
- DispatchMessage(&msg);
- }
- }
-
- ULONG_PTR gdiplusToken = 0;
- int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nShowCmd){
- Gdiplus::GdiplusStartupInput gdiplusStartupInput;
- Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
-
- CStartPage *startPage=new CStartPage();
-
- startPage->SetInstance(hInstance);
- startPage->Create();
- startPage->ShowWindow(true);
-
- Message();
-
- delete startPage;
-
- Gdiplus::GdiplusShutdown(gdiplusToken);
- return 0;
- }
最終的程序界面:(現(xiàn)在還沒有添加EVENT和NOTIFY,還不具有事件響應(yīng)功能,,下篇實現(xiàn))
本文由HARVIC完成,,轉(zhuǎn)載請標明出處(http://blog.csdn.net/harvic880925/article/details/9491387),謝謝
源碼地址:http://download.csdn.net/detail/harvic880925/5820507
聲明:本文只僅交流,,轉(zhuǎn)載請標明出處,,感謝金山影音漂亮的界面圖片。
|