本文基于Windows操作系統下的Fltk 2.0版本,使用VS2008。該版本和之前的版本改動(dòng)了不少地方,其招牌的FL_前綴去掉了,增加了貼圖支持和更好的非英文字符支持。不過(guò)代碼還是一如既往的“亂”。
在深入源碼之前,先學(xué)習一下Fltk的基本用法。一個(gè)簡(jiǎn)單的Sample如下:
int main(int argc, char **argv) { Window *window = new Window(300, 180); window->begin(); Widget *box = new Widget(20, 40, 260, 100, "Hello, World!"); box->box(UP_BOX); box->labelfont(HELVETICA_BOLD_ITALIC); box->labelsize(36); box->labeltype(SHADOW_LABEL); window->end(); window->show(argc, argv); return run();}
這個(gè)Sample簡(jiǎn)單的展現了Fltk程序的大致流程:
- 創(chuàng )建Window(窗口)
- 向該窗口塞控件
- 顯示窗口
- 進(jìn)行Messsage Loop
窗口
Fltk窗口使用Window類(lèi)表示,該類(lèi)派生自Widget。不過(guò)和普通的Widget不同的是,Window加入了窗口操作函數,比如最大化窗口等。同時(shí)Window作為一個(gè)控件樹(shù)(本身也是一個(gè)控件)的根節點(diǎn),它包含一個(gè)定位控件的邏輯,即在收到消息后定位具體的控件,并將消息派發(fā)給它。
控件樹(shù)
Fltk不用顯式地往控件中插入子控件,這個(gè)操作通過(guò) Group類(lèi)(Window類(lèi)派生自它)的begin和end成員函數來(lái)完成。
它的實(shí)現原來(lái)相當之簡(jiǎn)單,在Group類(lèi)中定義了一個(gè)靜態(tài)成員 static Group *current_,當調用begin函數時(shí),就將該靜態(tài)變量賦值為自己的指針(this),當調用end函數時(shí),將它賦值為父控件的指針。當一個(gè)控件創(chuàng )建時(shí),會(huì )查詢(xún)上述靜態(tài)變量是否為空,如否則將自己插入上述容器控件,具體見(jiàn)Widget類(lèi)的構造函數。
在Fltk中,有普通控件Widget和容器控件Group 之分,區別是Group可以包含子控件。Widget包含一個(gè)Group指針來(lái)指向父控件,Group則還包含一個(gè)Widget**指針來(lái)包含子控件。Fltk就通過(guò)上述結構來(lái)構造一棵控件樹(shù)。
窗口創(chuàng )建
Window類(lèi)的構造僅僅只是初始化一些數據,而真正的窗口并未創(chuàng )建。這個(gè)過(guò)程在Window類(lèi)的函數void show(int argc, char **argv)實(shí)現。除了窗口創(chuàng )建(CreateWindow),窗口顯示(ShowWindow)也在這一并實(shí)現。
為了實(shí)現跨平臺,Ftlk不可能直接實(shí)現操作系統API來(lái)做上述事情,所以在Window類(lèi)下面又整了一個(gè)CreatedWindow類(lèi)(fltk\win32.h),在這個(gè)類(lèi)的(靜態(tài))成員函數封裝操作系統API。實(shí)際的窗口創(chuàng )建操作在靜態(tài)成員函數CreatedWindow::create(Window* window)中實(shí)現,具體的Api函數在函數指針對象__CreateWindowExW中(Windows使用API CreateWindowEx)。
多窗口管理
Fltk支持多窗口,為了做到這一點(diǎn),CreatedWindow類(lèi)包含一個(gè)靜態(tài)成員 first。這是一個(gè)CreatedWindow指針。每一次創(chuàng )建新窗口(會(huì )生成一個(gè)CreatedWindow實(shí)例)后,就將當前的CreatedWindow指針?lè )旁谶@個(gè)鏈表的最前(窗口Destroy時(shí)從鏈表中清除)。整個(gè)鏈表就存儲著(zhù)所有已經(jīng)創(chuàng )建的窗口。
當收到窗口消息時(shí),Fltk就通過(guò)窗口ID查詢(xún)上述鏈表獲得具體的窗口類(lèi)實(shí)例,然后將消息派發(fā)給它。在Windows操作系統下,這個(gè)ID是HWND。而在X11平臺下,這個(gè)ID是XWindow(src\run.cxx)。
消息循環(huán)
Fltk通過(guò)運行全局函數run來(lái)進(jìn)入消息循環(huán)。首先檢測是否還有窗口實(shí)例,如有則繼續wait,然后處理一些idle和timer等雜七雜八的消息,最后在操作系統層面的消息循環(huán)中等待。
消息
Fltk在分發(fā)消息時(shí),僅僅傳遞了一個(gè)消息ID,而沒(méi)有一些附加的消息,例如鼠標消息的坐標,按鍵消息的KeyValue等等。Fltk定義了一堆全局變量(src\run.cxx),將這些附加信息放在那,每次收到新的窗口消息后,首先會(huì )更新需要更新的變量。在后面的控件處理中,可以去這些變量去查詢(xún)和獲取需要的值(這種方式可能比較“省”,可是做法鄙人不敢茍同)。
對于鼠標消息,Fltk通過(guò)鼠標位置和控件的位置比較來(lái)定位。后添加的控件Z軸在上,所以如果兩控件重疊,后添加的控件將獲得鼠標消息。
Fltk消息種類(lèi)如下圖(2.0版本沒(méi)有前綴FL_):
來(lái)源(http://www.ibm.com/developerworks/cn/linux/l-fltk/index.html )
具體的定義在(fltk\events.h)
Fltk所有的消息通過(guò)函數bool fltk::handle(int event, Window* window)來(lái)分發(fā)。Fltk和控件通過(guò)int Widget::send(int event)函數來(lái)傳遞消息,在該函數中會(huì )調用虛函數 Widget::handle。
回調
為了處理業(yè)務(wù)邏輯,需要向控件注冊回調。和MFC/WTL或者WxWidget等UI框架相比,FLTK的回調綁定機制相當簡(jiǎn)單。這個(gè)綁定通過(guò)Widget類(lèi)的成員函數callback實(shí)現。Fltk定義了幾個(gè)回調函數類(lèi)型:
typedef void (Callback )(Widget*, void*);typedef Callback* Callback_p; // needed for BORLANDtypedef void (Callback0)(Widget*);typedef void (Callback1)(Widget*, long);
在widget類(lèi)中包含一個(gè)成員Callback*callback_,callback函數所做的就是給它賦值。不過(guò)這種實(shí)現也是有些不足,一是無(wú)法同時(shí)綁定多個(gè)回調,而是對類(lèi)成員函數(非靜態(tài))和仿函數支持不足。當然既然人家都叫Fltk,那也就無(wú)可厚非了。
Fltk針對回調定義了一些Flag:
enum { // Widget::when() values WHEN_NEVER = 0, WHEN_CHANGED = 1, WHEN_RELEASE = 4, WHEN_RELEASE_ALWAYS = 6, WHEN_ENTER_KEY = 8, WHEN_ENTER_KEY_ALWAYS =10, WHEN_ENTER_KEY_CHANGED=11, WHEN_NOT_CHANGED = 2 // modifier bit to disable changed() test};
這些標志通過(guò)widget類(lèi)的成員函數when來(lái)設置,這些標志可以影響回調的調用機制。例如Button默認在鍵盤(pán)Release時(shí)觸發(fā)回調,而設置WHEN_CHANGED標志后Press和Release都會(huì )觸發(fā)回調。
Fltk這套回調機制雖然使用簡(jiǎn)單,不過(guò)個(gè)人認為大可不必在widget這一層支持回調。試想Static控件一般情況下是不需要回調的,所以完全沒(méi)必要給每一個(gè)控件強制分配一個(gè)回調指針。第二個(gè),控件的回調需要什么格式很難用幾個(gè)簡(jiǎn)單的定義覆蓋,而且這些標志位也很難覆蓋所有的需求,試想我需要在鼠標移入按鈕時(shí)觸發(fā)回調用標志位怎么處理(雖然這個(gè)需求比較猥瑣)?所以最好是將回調綁定交給控件自己去實(shí)現,底層僅僅將具體的消息分發(fā)給它。


