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





