1. 程式人生 > >WIN32介面開發之三:DUI雛形開發(一)

WIN32介面開發之三:DUI雛形開發(一)

前言:這部分涉及工程比較大,所以我打算分開為兩篇來寫,第一篇完成基本框架的構建,第二篇新增上EVENT和NOTIFY機制。

完成目標:仿照DirectUI,完成一個基本雛形,開發一個佈局控制元件(Dialog),和一個按鈕控制元件(Button),通過XML來佈局窗體,最後按鈕響應點選、滑鼠移動等事件資訊,使用者還可以通過NOTIFY機制來定製,使用者具體行為時,介面所要做的動作。給大家看下最終介面吧,一個背景和四個按鈕。
正常狀態:

點選按鈕狀態(點選按鈕時,並拖動窗體)

正文

一、CDialogBuilder的構建

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對容進行分析,構建出結點樹,具體程式碼就不講了,只要知道所完成的功能就可以了,大家有興趣可以看看,

但有幾個介面,要注意一下:

CMarkup::Load(LPCTSTR pstrXML) //載入並分析XML,構建XML結點樹
CMarkup::GetRoot()         //獲取XML樹的根結點

CMarkupNode CMarkupNode::GetParent();//獲取父結點
CMarkupNode CMarkupNode::GetSibling();//獲取下一緊臨的兄弟結點
CMarkupNode CMarkupNode::GetChild(); //獲取孩子結點
CMarkupNode CMarkupNode::GetChild(LPCTSTR pstrName);//根據名字獲取孩子結點

bool CMarkupNode::HasAttributes();
bool CMarkupNode::HasAttribute(LPCTSTR pstrName);  //指定結點是否具有屬性
int CMarkupNode::GetAttributeCount();          //具有的屬性個數
LPCTSTR CMarkupNode::GetAttributeName(int iIndex);//根據索引獲取屬性名

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);//獲取對應屬性名的屬性的值的幾個函式
3、CDialogBuilder講解

完成功能:

1、利用上面的CMarkUp類分析XML,構建出XML結點樹
2、然後根據XML中的結點,NEW 出控制元件,並利用XML結點樹的屬性,構建出控制元件的屬性

先看定義:

class CDialogBuilder
{
public:
	CDialogBuilder();
	~CDialogBuilder();
public:
   CControlUI* Create(LPCTSTR pstrXML);

private:
   CControlUI* _Parse(CMarkupNode* parent, CControlUI* pParent = NULL);

   CMarkup m_xml;
};
可以看到,CDialogBuilder比較簡單,只有兩個函式,Create()和_Parse(),另一個變數m_xml是用來分析XML,並構建XML結點樹的。
看下Create函式
CControlUI* CDialogBuilder::Create(LPCTSTR pstrXML)
{
   if( !m_xml.Load(pstrXML) ) return NULL;//載入XML,並生成XML結點樹
   // 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結點樹的根結點
   return _Parse(&root);//分析結點樹
}
下面看看_Parse函式完成的功能:
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;
      }/////根據XML樹,生成對應的控制元件

      ASSERT(pControl);
      if( pControl == NULL ) return NULL;
      // Add children
      if( node.HasChildren() ) {
         _Parse(&node, pControl);//利用遞規,遍歷XML樹
      }
      // Attach to parent
      if( pParent != NULL ) {//如果它的父親不為空,說明它的父結點肯定具有CONTAINER屬性,所以把它加到它的父結點的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() ) {//分析屬性,然後設定控制元件屬性
         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));
         }
      }
      // 返回根結點,對於我們的XML,返回的是CDialogUI的例項指標
      if( pReturn == NULL ) pReturn = pControl;
   }
   return pReturn;
}

二、控制元件與容器的構建

一、總體關係

這裡我們先講下,控制元件類和佈局類的區別,在這裡我們在構建幾個類,先看下繼承圖(這篇文章先不管INotifyUI的事)

1、一個控制元件基類(CControlUI)和一個按鈕控制元件類(CButtonUI)
控制元件基類,包含有控制元件繪製的最基本的幾個函式,當然都是虛擬函式,比如:SetAttribute(設定控制元件屬性),GetInterface(獲取控制元件的this指標),DoPaint(繪圖)
2、一個容器基類,故名思義,容器是用來盛控制元件的,所以它必須具有的幾個函式:
Add(將控制元件新增到此容器的子佇列中),Remove(刪除某個子控制元件)、RemoveAll(清空子控制元件佇列)、GetItem(獲取某個子控制元件)、GetCount(獲取子控制元件的個數)
所以這幾個函式也是容器最基類IContainerUI的函式,

3、CContainerUI,這個才是稍微有意義點的佈局類,它用於在窗體上劃分出不同的區域,然後在區域中佈局控制元件.
這裡最值得注意的地方是派生出它的基類,它是兩個基類的派生類(IContainerUI和CControlUI),所以容器也是控制元件,這一點在後面繪圖程式碼中特點重要,在繪圖時,我們將所有的容器和控制元件全部都定義為CControlUI,所以如果當前的如果是CDialogUI的指標的話,它就會根據繼承關係,先到CDialogUI中執行相關函式,這點尤其注意!!!!(這裡暫時搞不明白也沒關係,後面遇到的時候,我會重新講)
4、CDialogUI,這是窗體的構建類,裡面包含所在構建窗體的大小、背景圖案,現在只加了這兩個屬性,其它都沒加

二、控制元件類的實現
1、CContolUI的實現

class CControlUI
{
public:
	CControlUI();
	virtual ~CControlUI();
public:
	virtual void SetAttribute(LPCTSTR pstrName, LPCTSTR pstrValue);//設定屬性
	virtual LPVOID GetInterface(LPCTSTR pstrName);//獲取當前control的this指標
	virtual void DoPaint(HWND hwnd,HDC hDC)=0;//控制元件的繪製函式,注意這裡把它宣告為純虛擬函式,強制子類中必須對其進行實現
	
//設定控制元件屬性的幾個函式
	void SetPos(RECT rc);///設定控制元件位置
	RECT GetPos();
	virtual CControlUI* GetParent();//設定當前控制元件的父結點
	virtual void SetParent(CControlUI* parent);
	
	virtual void SetHwnd(HWND hwnd);//設定窗體控制代碼

public:
	void Invalidate();
protected:
	RECT m_RectItem;///控制元件位置
	CControlUI *m_pParent;//父結點指標

	HWND m_hwnd;//儲存傳過來的窗體的控制代碼
};
這裡大家可能會有個疑問,控制元件要窗體控制代碼幹嘛,這裡呢,因為我們只有一個窗體,也就是CreateWindowEx函式建立後,返回的那個HWND,我們這裡儲存的就是這個HWND,那我們要它有什麼用呢,這是因為我們這裡的控制元件全部都是模擬的控制元件的行為而已,是並沒有控制代碼的,所以也不可能像MFC那樣根據控制元件的控制代碼單獨重新整理了,所以我們要重新整理控制元件的話,就得重新整理整個窗體(當然,我們會利用快取技術區域性繪製來提高效率),我們要重新整理窗體就得利用SendMessage(hwnd,msg,wparam,lparam),注意這裡的第一個變數就是窗體的控制代碼,當然這裡要儲存了,就這樣的道理。大笑

看各個函式的具體實現

void CControlUI::SetPos(RECT rc)
{
	m_RectItem = rc;
}
RECT CControlUI::GetPos()
{
	return m_RectItem;
}
這倆函式很簡單,就是設定控制元件位置和獲取控制元件位置的。
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);
	}
}
這個函式是設定控制元件屬性,哪裡用到這個函數了呢,嘿嘿,還記得不,CDialogBuilder的_Parse函式裡
void CControlUI::SetParent(CControlUI* parent)
{
	m_pParent=parent;
}
CControlUI* CControlUI::GetParent()
{
	return m_pParent;
}
設定父結點,也是在CDialogBuilder的_Parse函式裡用到
LPVOID CControlUI::GetInterface(LPCTSTR pstrName)
{
	if( _tcscmp(pstrName, _T("Control")) == 0 ) return this;
	return NULL;
}
根據要獲取的指標名,返回當前例項的this指標,哪裡用到了呢,也是CDialogBuilder的_Parse函式裡,下面是_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);
      }
看到了吧,獲取的是父結點的Container的指標,然後將控制元件加到父結點中,下面繼續
void CControlUI::SetHwnd(HWND hwnd)
{
	m_hwnd=hwnd;
}

void CControlUI::Invalidate()
{
	SendMessage(m_hwnd,WM_PAINT,NULL,NULL);
}
SetHwnd用來儲存窗體的HWND,然後是Invalidate函式的實現,也就是向窗體傳送WM_PAINT訊息
2、CButtionUI的實現
定義:
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);
};
可以看到,很簡單,設定屬性、獲取THIS指標,然後就是繪圖類,看具體實現
void CButtonUI::SetAttribute(LPCTSTR pstrName, LPCTSTR pstrValue)
{//因為實現的是最簡易版,Button裡也就不設定什麼獨有的屬性了,直接返回基類的SetAttribute(),設定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()函式,因為我們這篇文章裡面還沒有新增EVENT事件通知功能,所以我們就先初始化的時候,把按鈕統一畫成綠色。
三、容器類的實現
1、容器最基類IContainerUI的實現
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;
};
其實這個類沒有任何的實際意義,就是個介面類,它的這幾個函式全部都宣告為純虛擬函式
2、CContainerUI的實現
定義:
class CContainerUI : public CControlUI, public IContainerUI
{
public:
	CContainerUI();
	virtual ~CContainerUI();
public:
	///先實現繼承的純虛擬函式
	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);///設定屬性
	virtual LPVOID GetInterface(LPCTSTR pstrName);//獲取THIS指標
	virtual  void DoPaint(HWND hwnd,HDC hDC);//繪圖函式

	void LoadBackground(LPCTSTR Parth);//根據路徑載入影象,並儲存在m_pImage中
protected:
	CStdPtrArray m_items;   //當前容器中的子變數佇列

	Gdiplus::Image* m_pImage;//容器的背景
};
大家看到了吧,這裡除了實現IContainerUI幾個純虛擬函式以外,其它幾個函式SetAttribute、GetInterface、DoPaint也都是控制元件所具有的。看下具體實現:
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);
}
設定屬性,這裡只在CControlUI的基礎上增加了一個屬性,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);
	}
}
最重要的,繪製函式,我這裡沒有對它本身進行繪製,只是遍歷當前容器中所有的控制元件,然後再它裡面的控制元件進行逐個繪製,這裡我要留下一個疑問,如果容器裡巢狀有容器是怎麼完成繪製的呢????!!!!
3、CDialogUI實現
定義:
class CDialogUI:public CContainerUI
{
public:
	CDialogUI();
	~CDialogUI();
public:
	void DoPaintBackground(HDC hdc);//畫背景
	virtual LPVOID GetInterface(LPCTSTR pstrName);//獲取this指標
};

大家可以看到這個CDialogUI的實現是非常簡單的,只有一個DoPaintBackground(),這個函式只是用來畫背景的

實現:

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);
}
好了,到這幾個控制元件的實現就全部實現了,下面看窗體的建立。

三、窗體建立

基本思想,我們為了讓使用者不必做太多的工作,我們建一個基類,這個基類完成視窗類註冊、建立視窗、訊息響應等功能,而使用者只需要從我們的基類派生,並且簡單呼叫Create()函式就可以了。
一、建立窗體基類(CWindowWnd)

定義

class CWindowWnd{
public:
	CWindowWnd();
	~CWindowWnd();

	HWND GetHWND() const{return m_hWnd;}

	bool RegisterWindowClass(WNDPROC fWndProc,HINSTANCE hInstance,LPCTSTR szClassName);//註冊視窗類

	HWND Create();//建立窗體

	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;// 儲存所建立窗體的控制代碼

	HINSTANCE m_instance;//儲存WinMain入口,引數裡的hInstance,因為註冊視窗類要用到
};
注意一點:這裡必須把視窗訊息處理函式(_WndProc)定義為靜態函式,否則就定義為全域性函式
我們看下具體實現,由簡到難講:
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是訊息處理函式,這個函式必須是靜態的或是全域性的!!!!!
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);
}
顯示視窗,呼叫系統API ----ShowWindow()
HWND CWindowWnd::Create()
{
	if (!RegisterWindowClass(__WndProc,m_instance,L"transparent"))
	{//註冊視窗類,看到了吧,視窗處理函式的函式名為:_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;	
}
建立視窗,流程是,先註冊視窗類,如果註冊成功,就呼叫CreateWindowEx建立視窗。這裡最注意的一個地方,單獨把CreateWindowEx調出來
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); 
注意最後一個引數:傳進去的當前CWindowWnd的this指標!!!!這個引數會做為lparam傳進CREATESTRUCT結構體裡的lpCreateParams引數裡,在WM_NCCREATE訊息裡可以獲取到。那傳進去他有什麼用呢?往下看,_WndProc訊息處理函式
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強轉成LPCREATESTRUCT 變數,我們上面說過我們傳過來的CWindowWnd的this指標,儲存在CREATESTRUCT結構體的lpCreateParams引數裡
pThis = static_cast<CWindowWnd*>(lpcs->lpCreateParams);
這句,就是取出this指標!,有同學可能會問,為什麼非要這樣得到this指標,_WndProc不是CWindowWnd的成員函式麼,不是直接有this麼?注意,_WndProc是靜態函式,會在CWindowWnd建立之前編譯,所以,由於在編譯_WndProc時,沒有CWindowWnd還沒有被例項化,當然要報錯了。所以我們要通過傳參的方式得到this指標,那接下來的問題就是,那下次我怎麼再在這個函式裡得到this指標呢,這裡我們用SetWindowLongPtr把他儲存在GWLP_USERDATA域,然後呼叫GetWindowLongPtr就可以得到了。
::SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast<LPARAM>(pThis));
這句的意思就是把THIS指標儲存在GWLP_USERDATA域。

在其它訊息到來的是,我們先通過GetWindowLongPtr得到THIS指標,然後呼叫虛擬函式pThis->HandleMessage(uMsg, wParam, lParam);把訊息轉到HandleMessage函式中處理,而使用者的函式是派生自CWindowWnd的,所以只需要在HandleMessage函式中處理訊息就可以了。

二、使用者視窗類

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);//響應WM_PAINT訊息的函式
private:
	WCHAR m_resource[556];//XML字串

	CDialogBuilder m_dialogBuilder;
	CControlUI *m_root;

	HDC hdcBKMemory;//記憶體DC,參見《之二----GDI+中的區域性刷新技術》
	HBITMAP hBKBitmap;
	HGDIOBJ hBKBitmapOld;
};
看下具體實現:
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,因為我們根據滑鼠點來查詢時,首先看是否在CANVAS的區域內,如果不在,就直接返回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;
}
這個函式很簡單,就是載入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,然後傳送WM_PAIT訊息。然後在WM_PAIT中,Paint函式繪圖
void CStartPage::Paint(HWND m_hWnd)
{
	RECT rcWindow;
	GetWindowRect(m_hWnd,&rcWindow);
	SIZE sizeWindow;
	/////因為根結點,必是DLG結點,肯定是個容器,所以將它強制轉換成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);
		//建立背景畫布
		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);//將背景複製到新畫布上

	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 呼叫失敗");
		TCHAR tmp[255] = {_T('\0')};
	}//使用UpdateLayeredWindow更新到當前窗體上
	//釋放資源

	SelectObject(hdcEnd,hEndBitmapOld);

	::DeleteObject(hFuncInst);
	::DeleteObject(hEndBitmapOld);
	::DeleteObject(hbmpMem2);
	::DeleteDC(hdcEnd);

	::DeleteDC(hDC);

}
Paint()函式中用了雙緩衝,具體講解,參看《WIN32介面開發之二:GDI+中的區域性刷新技術
這裡我只講兩個地方:
1、窗體背景的繪製
CDialogUI *pdlg=(CDialogUI*)m_root;
因為我們在定義m_root時,定義的是CControlUI,但它實際是CDialogUI的例項,所以將它強轉成CDialogUI指標,然後記憶體記憶體DC裡呼叫
pdlg->DoPaintBackground(hdcBKMemory);//繪製窗體背景
來繪製窗體背景。
2、容器及控制元件的繪製
m_root->DoPaint(m_hWnd,hdcEnd);/////////////繪製畫布和控制元件
主要是靠這個函式來完成的!
這個得著重講一下,看它是怎麼完成繪製的。我們先看一下根據XML生成的控制元件樹

從上面這個圖就能看得出來,CDialogUI包含兩個子控制元件(Canvas),這也就是我說的容器裡包含容器,而每個Canvas又包含兩個Button控制元件。下面我們看DoPaint的呼叫順序,首先m_root雖然被定義為CControlUI的變數,但實際是CDialogUI的例項指標,所以會沿著繼承圖去找CDialogUI的DoPaint,而CDialogUI是沒有DoPaint的,所以會去找它的子類CContainerUI的DoPaint,並且執行,我們重新看下這個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執行順序了:
1、找到CDialogUI的第一個孩子結點(一個CContainerUI例項,假設為ContainerA),然後呼叫這個例項的DoPaint
2、執行ContainerA->DoPaint(),依然是這個函式,也就是說,它會挨個執行那兩個Button的DoPaint,完成之後返回。
3、找到CDialogUI的第二個孩子結點(同樣,一個CContainerUI例項,假設為ContainerB),然後呼叫這個例項的DoPaint
4、執行ContainerB>DoPaint(),同樣,它也是同樣執行這個函式,然後逐個執行它的那個Button的DoPaint,之後返回。

三、WinMain函式
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;
}
最終的程式介面:(現在還沒有新增EVENT和NOTIFY,還不具有事件響應功能,下篇實現)


宣告:本文只僅交流,轉載請標明出處,感謝金山影音漂亮的介面圖片。