轉自:http://blog.163.com/zwei3666@126/blog/static/993128052012101272027399/
先發一張。支援多級擴充,支援菜單表頭,支援文本置中/靠左對齊/靠右對齊三種方式,支援字型顏色設定,支援添加自訂控制項,支援基本屬性(背景顏色,圖片,表徵圖,寬高等)的自訂設定。
漂亮不? 咳~一般般吧~其實DuiLib裡已經有個MenuDemo 不過那個是載入xml布局檔案的。當然直接載入xml對誰來說都方便很多。可繼承來繼承去,還用到了模板,好難呐!!!!。。。。。。。。 剛寫好,粗糙了些,勉強用吧
菜單吧也就那麼回事。
主要功能類:
* CMenuUI菜單視窗類別。用於建立菜單視窗,顯示菜單選項。
* MenuElementUI選項類。建立菜單選項
* WndsVector視窗隊列。用於管理所有菜單視窗的顯示和隱藏
先看架構流程:
整體流程並不複雜,但是如何處理視窗選項的擴充視窗(即二級菜單)的顯示與隱藏的問題呢?仔細想一下,不難發現問題關鍵就在於這兩個訊息迴圈。
先羅列出涉及視窗建立與銷毀、顯示與隱藏的地方:
1. 建立菜單時,只顯示主菜單,所有擴充視窗全部隱藏
2.當滑鼠進入有擴充視窗的選項時,顯示擴充視窗(訊息:UIEVENT_MOUSEENTER)
3.當滑鼠離開選項時,隱藏擴充視窗(UIEVENT_MOUSELEAVE)
4.當滑鼠點擊選項時,如果選項沒有擴充視窗,則銷毀整個菜單。並傳出該選項對應的ID。(UIEVENT_BUTTONDOWN)
5.當使用者在菜單視窗之外的任何地方點擊滑鼠時,銷毀整個菜單。(視窗訊息:WM_KILLFOCUS)
以上五點是一種很正常的思路,但是後來在處理滑鼠離開選項的時候我發現滑鼠離開的時候它可能是離開菜單視窗也可能只是進入下一選項,如何判斷著實讓我狠狠地糾結了一番。。後來仔細看了demo才幡然醒悟滑鼠離開菜單視窗的時候我是不需要做處理的。。。。。。而對於另一種,滑鼠離開這個選項必然進入下一選項,只處理一個就已經非常合適了。。於是2和3合二為一了~~~~即變成“當滑鼠進入選項時,關閉擴充視窗,再判斷它是否有擴充,有的話再重新顯示”,這樣講,好懂不?
為了方便處理視窗一切活動,我將建立的所有視窗的指標壓入隊列Vector:WndVector中便於後續處理。~~~當然,是建立一個壓入一個,不會漏了~~~也不會重了~~~因為這個隊列與使用者無關(不是使用者建立也不是使用者清理)的,它是專門管理視窗類別的,它是惟一的,所以這裡將這個類設成了靜態類~所有方法均為靜態~這樣無需建立執行個體就可以直接使用~
這個為了降低不同類之間的耦合度,也為了方便處理視窗的顯示與消失。。。咳。。總之我建立了一個訊號資料結構體:
struct ContextMenuParam
{
HWND hWnd;
WPARAM wParam; // 取值為1時,表示銷毀整個菜單, 取值為2時,表示只將hwnd及其所有父視窗顯示
};
在需要的地方給隊列發這麼一個訊號然後自己該幹嘛還幹嘛是不是挺乾脆利落的?反正我是這麼覺得的。。嘿嘿~~
貼一下這個管理類的代碼`~~
class WndsVector
{
private:
typedef std::vector<CMenuUI*> ReceiversVector;
static ReceiversVector receiverWnd;
public:
static HWND rootHwnd;//專門記錄建立菜單的主視窗,為了方便在滑鼠點擊選項時直接傳給主視窗ID號,免得計算。。。。
/*****
* 功 能:將視窗指標壓入視窗隊列
* 參 數:ptr視窗指標
* 返 回 值:
******/
static inline void AddWnd(CMenuUI* ptr)
{
receiverWnd.push_back(ptr);
}
/*****
* 功 能:將視窗訊號發送給隊列中的每一個視窗
* 參 數:param視窗訊號
* 返 回 值:
******/
static void BroadCast(ContextMenuParam ¶m)
{
if (receiverWnd.empty())
return;
ReceiversVector::reverse_iterator it = receiverWnd.rbegin();
for (; it != receiverWnd.rend(); it ++)
{
if ( *it != NULL )
(*it)->ReceiveCloseMsg(param);
}
}
/*****
* 功 能:尋找視窗隊列中包含該控制代碼的視窗類別指標
* 參 數:hwnd需要尋找的控制代碼
* 返 回 值:找到返回對應視窗類別指標,否則返回null
******/
static CMenuUI* FindWndClass(HWND hwnd)
{
if (receiverWnd.empty())
return NULL;
ReceiversVector::iterator it = receiverWnd.begin();
for (; it != receiverWnd.end(); it ++)
{
if (*it != NULL && (*it)->GetHWND() == hwnd)
return *it;
}
return NULL;
}
/*****
* 功 能:清空隊列
******/
static inline void RemoveAll()
{
receiverWnd.clear();
}
};
好了,說一下對於這四個問題的處理思路:
1. 建立菜單時,只顯示主菜單,所有擴充視窗全部隱藏
唔~這個只要讓主視窗在建立完成的時候不設定所有擴充視窗的位置和大小,那麼它們的大小就會預設為0~~位置也是在0,0
2.當滑鼠進入選項時,關閉擴充視窗,再判斷它是否有擴充,有的話再重新顯示
進入選項時先判斷一下當前選擇是否跟上次的選項相同~~避免滑鼠進入選項然後離開視窗轉一下再直接進入這個選項~因為這種情況完全不用做任何處理嘛~~~如果不相同,給管理隊列發送訊號{pManager.GetPaintWindow(), 2},調用WndsVector.BroadCast(訊號),將管理類負責把訊號傳給所有視窗,讓它們自己判斷是不是pManager.GetPaintWindow()的爸爸或爺爺~不是就隱藏,否則不處理~~~接著判斷它有沒有擴充視窗,有就顯示就可以了
3.當滑鼠點擊選項時,如果選項沒有擴充視窗,則銷毀整個菜單。並傳出該選項對應的ID。(UIEVENT_BUTTONDOWN)
發送訊號{NULL, 1}。這裡都要全部銷毀了,給個自殺訊號就可以了,沒必要同去傳控制代碼啦
傳ID。PostMessage(WndsVector::rootHwnd, uID, event.wParam, event.lParam);之所以不用sendmessage是因為這裡只管發送這個ID,發送完自己它就要自我銷毀,postMessage不用返回,將訊息加入進程的訊息佇列等行即可
4.當使用者在菜單視窗之外的任何地方點擊滑鼠時,銷毀整個菜單。(視窗訊息:WM_KILLFOCUS)
這個做個檢測就可以了
先發一張。支援多級擴充,支援菜單表頭,支援文本置中/靠左對齊/靠右對齊三種方式,支援字型顏色設定,支援添加自訂控制項,支援基本屬性(背景顏色,圖片,表徵圖,寬高等)的自訂設定。
漂亮不? 咳~一般般吧~其實DuiLib裡已經有個MenuDemo 不過那個是載入xml布局檔案的。當然直接載入xml對誰來說都方便很多。可繼承來繼承去,還用到了模板,好難呐!!!!。。。。。。。。 剛寫好,粗糙了些,勉強用吧
菜單吧也就那麼回事。
主要功能類:
* CMenuUI菜單視窗類別。用於建立菜單視窗,顯示菜單選項。
* MenuElementUI選項類。建立菜單選項
* WndsVector視窗隊列。用於管理所有菜單視窗的顯示和隱藏
先看架構流程:
整體流程並不複雜,但是如何處理視窗選項的擴充視窗(即二級菜單)的顯示與隱藏的問題呢?仔細想一下,不難發現問題關鍵就在於這兩個訊息迴圈。
先羅列出涉及視窗建立與銷毀、顯示與隱藏的地方:
1. 建立菜單時,只顯示主菜單,所有擴充視窗全部隱藏
2.當滑鼠進入有擴充視窗的選項時,顯示擴充視窗(訊息:UIEVENT_MOUSEENTER)
3.當滑鼠離開選項時,隱藏擴充視窗(UIEVENT_MOUSELEAVE)
4.當滑鼠點擊選項時,如果選項沒有擴充視窗,則銷毀整個菜單。並傳出該選項對應的ID。(UIEVENT_BUTTONDOWN)
5.當使用者在菜單視窗之外的任何地方點擊滑鼠時,銷毀整個菜單。(視窗訊息:WM_KILLFOCUS)
以上五點是一種很正常的思路,但是後來在處理滑鼠離開選項的時候我發現滑鼠離開的時候它可能是離開菜單視窗也可能只是進入下一選項,如何判斷著實讓我狠狠地糾結了一番。。後來仔細看了demo才幡然醒悟滑鼠離開菜單視窗的時候我是不需要做處理的。。。。。。而對於另一種,滑鼠離開這個選項必然進入下一選項,只處理一個就已經非常合適了。。於是2和3合二為一了~~~~即變成“當滑鼠進入選項時,關閉擴充視窗,再判斷它是否有擴充,有的話再重新顯示”,這樣講,好懂不?
為了方便處理視窗一切活動,我將建立的所有視窗的指標壓入隊列Vector:WndVector中便於後續處理。~~~當然,是建立一個壓入一個,不會漏了~~~也不會重了~~~因為這個隊列與使用者無關(不是使用者建立也不是使用者清理)的,它是專門管理視窗類別的,它是惟一的,所以這裡將這個類設成了靜態類~所有方法均為靜態~這樣無需建立執行個體就可以直接使用~
這個為了降低不同類之間的耦合度,也為了方便處理視窗的顯示與消失。。。咳。。總之我建立了一個訊號資料結構體:
struct ContextMenuParam
{
HWND hWnd;
WPARAM wParam; // 取值為1時,表示銷毀整個菜單, 取值為2時,表示只將hwnd及其所有父視窗顯示
};
在需要的地方給隊列發這麼一個訊號然後自己該幹嘛還幹嘛是不是挺乾脆利落的?反正我是這麼覺得的。。嘿嘿~~
貼一下這個管理類的代碼`~~
class WndsVector
{
private:
typedef std::vector<CMenuUI*> ReceiversVector;
static ReceiversVector receiverWnd;
public:
static HWND rootHwnd;//專門記錄建立菜單的主視窗,為了方便在滑鼠點擊選項時直接傳給主視窗ID號,免得計算。。。。
/*****
* 功 能:將視窗指標壓入視窗隊列
* 參 數:ptr視窗指標
* 返 回 值:
******/
static inline void AddWnd(CMenuUI* ptr)
{
receiverWnd.push_back(ptr);
}
/*****
* 功 能:將視窗訊號發送給隊列中的每一個視窗
* 參 數:param視窗訊號
* 返 回 值:
******/
static void BroadCast(ContextMenuParam ¶m)
{
if (receiverWnd.empty())
return;
ReceiversVector::reverse_iterator it = receiverWnd.rbegin();
for (; it != receiverWnd.rend(); it ++)
{
if ( *it != NULL )
(*it)->ReceiveCloseMsg(param);
}
}
/*****
* 功 能:尋找視窗隊列中包含該控制代碼的視窗類別指標
* 參 數:hwnd需要尋找的控制代碼
* 返 回 值:找到返回對應視窗類別指標,否則返回null
******/
static CMenuUI* FindWndClass(HWND hwnd)
{
if (receiverWnd.empty())
return NULL;
ReceiversVector::iterator it = receiverWnd.begin();
for (; it != receiverWnd.end(); it ++)
{
if (*it != NULL && (*it)->GetHWND() == hwnd)
return *it;
}
return NULL;
}
/*****
* 功 能:清空隊列
******/
static inline void RemoveAll()
{
receiverWnd.clear();
}
};
好了,說一下對於這四個問題的處理思路:
1. 建立菜單時,只顯示主菜單,所有擴充視窗全部隱藏
唔~這個只要讓主視窗在建立完成的時候不設定所有擴充視窗的位置和大小,那麼它們的大小就會預設為0~~位置也是在0,0
2.當滑鼠進入選項時,關閉擴充視窗,再判斷它是否有擴充,有的話再重新顯示
進入選項時先判斷一下當前選擇是否跟上次的選項相同~~避免滑鼠進入選項然後離開視窗轉一下再直接進入這個選項~因為這種情況完全不用做任何處理嘛~~~如果不相同,給管理隊列發送訊號{pManager.GetPaintWindow(), 2},調用WndsVector.BroadCast(訊號),將管理類負責把訊號傳給所有視窗,讓它們自己判斷是不是pManager.GetPaintWindow()的爸爸或爺爺~不是就隱藏,否則不處理~~~接著判斷它有沒有擴充視窗,有就顯示就可以了
3.當滑鼠點擊選項時,如果選項沒有擴充視窗,則銷毀整個菜單。並傳出該選項對應的ID。(UIEVENT_BUTTONDOWN)
發送訊號{NULL, 1}。這裡都要全部銷毀了,給個自殺訊號就可以了,沒必要同去傳控制代碼啦
傳ID。PostMessage(WndsVector::rootHwnd, uID, event.wParam, event.lParam);之所以不用sendmessage是因為這裡只管發送這個ID,發送完自己它就要自我銷毀,postMessage不用返回,將訊息加入進程的訊息佇列等行即可
4.當使用者在菜單視窗之外的任何地方點擊滑鼠時,銷毀整個菜單。(視窗訊息:WM_KILLFOCUS)
這個做個檢測就可以了