事情的緣起是,耐不住寂寞,準備開始造GUI的輪子。
GUI框架,要做的事情我想大概是這么幾步:
- 實現回調函數的成員化。
- 實現方便程度可接受的消息映射。
- 確定上述核心部件的使用方式。
- 制造大量的控件。
前三步要走的比較小心,第四步是體力勞動。
第一步,Windows下可參考的是MFC方式、WTL方式,以及利用Window相關屬性中的某些空位。前不久剛初步看過WTL的機制,雖然當時沒寫GUI框架的打算,不過也有點技術準備的意思了。現學現用吧。這里一個可以預見的問題是64位兼容,現在沒有測試環境,先不管。
接下來看第二步了,所要做的事情就是把 WndProc 下的 一堆 case 有效地組織起來,或者換個寫法。之前還真不知道 MFC/WTL 的 BEGIN_MSG_MAP。以為很高深的,想不到就是拼裝成一個大的 WndProc。先抄了,做成一個可運行的版本。但是,這方面會直接決定以后的大部分使用方式,單單抄一下意義不大。后來去 @OwnWaterloo 曾推薦過的 @cexer 的博客上逛了幾圈,第一圈看了一些描述性文字,第二圈大概看了下技術,第三圈是挖墳,那個傳說中的 cppblog 第一高樓啊。。其中有一個使用方式很新穎,嗯……是那個不需要手動寫映射代碼,直接實現消息處理函數的方式。不過我后來覺得還是不要這種樣子了,憑我個人的直覺,如果我寫下這樣的處理函數,我大概會因為不知道何時注冊了這個函數而找不到調用來源而感到郁悶。在Windows回調機制的影響下,我可能會很抱有偏見地認為,只有直接來自WndProc的調用,才算是來源明確的,不需要繼續追蹤的——當然,這是建立在我不熟悉這個框架的基礎上的。框架必然需要隱藏調用來源,以及其他一些細節,但是在這一步,我覺得稍微有點早。
剛才說到的都是靜態綁定。現在我有點傾向于動態綁定。從使用方便程度上來看,動態綁定更具靈活性。從性能上,動態綁定下,消息到處理函數的查找過程可以更快,靜態綁定只能遍歷。當然,未必將“添加處理函數”這樣的接口提供給最終用戶,但是這個操作對于整個控件體系的形成應該蠻有幫助的吧。比如MFC下一個控件類使用Message Map做了一些事情,繼承類就無法直接繼承這個動作,于是可能需要做兩套處理函數調用機制,一套是給內部繼承用的,一套是給用戶的。如果在最開始的基類保存一個消息映射,每個消息對應一族處理函數,每個繼承類都可以添加處理函數,但不刪除父類已添加的函數,這樣就可以在一套Message Map機制下獲得父類的行為。以上,不知道考慮得對不對,歡迎討論。
其中,父類保存子類給出的可調用體并正確執行是個問題。折騰了一些時間,都沒有成功。我比較糾結,想知道除了用function之類的玩意兒外還有沒有其他簡單可行的辦法。后來去@zblc的群上問,@vczh也說需要一套function機制。看來是逃不開這個問題了。嗯……想起來大約兩個月前一個同事從codeproject找來了一個GUI框架看,看到幾行整整齊齊的 AddMsgHandler(WM_CREATE, XXX(this, &MyWindow::OnCreate));,嘆不已。我當時打趣說,這很簡單的,無非是搞了個 function 而已,哥哥兩天就能搞定。于是他們叫我兩天搞定。我鼓搗了10分鐘,搞不定,只好丟一句,真的很簡單的,類似boost::function,你去看一下就知道了,哥哥要干活了。
既然現在還是繞不開這個問題,那還是搞一下了,搞好以后就權且當做給他們交作業吧。我會另寫一篇文章說說function的事情,這里先略過。現在開始假設這個設施已經造好了。那么,窗口類中大概可以這么定義相關類型:
typedef Function<bool (WPARAM, LPARAM)> MsgHandler;
typedef List<MsgHandler> MsgHandlerList;
typedef Map<UINT, MsgHandlerList> MsgMap;
然后再定義一個變量:
MsgMap m_MsgMap;
它用于保存消息映射。最終的回調函數可以寫成:
LRESULT WndProc(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
bool bHandled = false;
MsgMap::Iterator itMsgMap = m_MsgMap.Find(uMsg);
if (itMsgMap != m_MsgMap.End())
{
for (MsgHandlerList::Iterator it = itMsgMap->Value.Begin();
!bHandled && it != itMsgMap->Value.End(); ++it)
{
bHandled = (*it)(wParam, lParam);
}
}
return bHandled ? TRUE : DefWindowProc(m_hWnd, uMsg, wParam, lParam);
}
最后給個添加消息映射的接口:
void AppendMsgHandler(UINT uMsg, MsgHandler pMsgHandler)
{
m_MsgMap[uMsg].PushBack(pMsgHandler);
}
到目前為止,我們的窗口類大致上可以寫成這樣:
#include <Windows.h>
#include <tchar.h>
#include "../GUIFramework/xlWindowBase.h"
class Window : public xl::WindowBase
{
public:
Window()
{
AppendMsgHandler(WM_ERASEBKGND, MsgHandler(this, &Window::OnEraseBackground));
AppendMsgHandler(WM_PAINT, MsgHandler(this, &Window::OnPaint));
AppendMsgHandler(WM_LBUTTONUP, MsgHandler(this, &Window::OnLButtonUp));
AppendMsgHandler(WM_RBUTTONUP, MsgHandler(this, &Window::OnRButtonUp));
AppendMsgHandler(WM_DESTROY, MsgHandler(this, &Window::OnDestroy));
}
protected:
bool OnEraseBackground(WPARAM wParam, LPARAM lParam)
{
return false;
}
bool OnPaint(WPARAM wParam, LPARAM lParam)
{
PAINTSTRUCT ps = {};
BeginPaint(m_hWnd, &ps);
RECT rect = { 200, 200, 400, 400 };
DrawText(ps.hdc, _T("Hello, world!"), -1, &rect, DT_CENTER | DT_VCENTER);
EndPaint(m_hWnd, &ps);
return false;
}
bool OnLButtonUp(WPARAM wParam, LPARAM lParam)
{
MessageBox(m_hWnd, _T("LButtonUp"), _T("Message"), MB_OK | MB_ICONINFORMATION);
return false;
}
bool OnRButtonUp(WPARAM wParam, LPARAM lParam)
{
MessageBox(m_hWnd, _T("RButtonUp"), _T("Message"), MB_OK | MB_ICONINFORMATION);
return false;
}
bool OnDestroy(WPARAM wParam, LPARAM lParam)
{
PostQuitMessage(0);
return false;
}
};
在最基礎的 WindowBase 里,搞成這樣大概差不是很多了。暫時先看第三步。到目前為止,我所聽說過的 GUI 框架都是真正的框架,似乎沒有“GUI 庫”。為什么一定要以繼承某個基類的方式來使用呢?如果像下面這樣使用呢?
class Window
{
private:
xl::WindowBase m_WindowBase;
public:
Window()
{
m_WindowBase.AppendMsgHandler(WM_ERASEBKGND, MsgHandler(this, &Window::OnEraseBackground));
m_WindowBase.AppendMsgHandler(WM_PAINT, MsgHandler(this, &Window::OnPaint));
m_WindowBase.AppendMsgHandler(WM_LBUTTONUP, MsgHandler(this, &Window::OnLButtonUp));
m_WindowBase.AppendMsgHandler(WM_RBUTTONUP, MsgHandler(this, &Window::OnRButtonUp));
m_WindowBase.AppendMsgHandler(WM_DESTROY, MsgHandler(this, &Window::OnDestroy));
}
};
這個問題,不知道各位有沒有什么思考?
還有一個問題是,接下去要不要將 WPARAM 和 LPARAM 的含義徹底解析掉,搞成一系列 PaintParam、EraseBackgroundParam、LButtonUpParam、RButtonUpParam,DestroyParam,讓使用的時候與原始消息參數徹底隔離呢?
最后一步,雖說是體力活,但這跟最終的應用場合密切相關,需要提供怎么樣的功能是一件需要考量的事。
目前走在第二步,所以下面的兩個問題思考得不多。求經驗,求意見。
posted on 2011-01-16 20:05
溪流 閱讀(4194)
評論(11) 編輯 收藏 引用 所屬分類:
C++ 、
Windows