|
[本教程翻譯自http://www3.telus.net/public/robark/ ]
新手入門 版本: 1.1 目標人群
本教程是為那些打算編寫GUI的C++程序員而準備的。FLTK的官方文檔編寫的很合理,從簡單的例子(Hello world)到復雜的例子(editor.cxx)一點點循序漸進。本教程是從FLTK Basics開始展開的。另外這里 是FLTK 2.0(Beta)的文檔,同樣是一個很好的資源。希望看完本教程能讓你盡快的進入FLTK編程。 Enjoy!
為何使用FLTK編寫GUI程序(Why use FLTK as opposed to other GUI toolkits?) - FLTK是開源的,基于GUN LGPL協(xié)議。 具體協(xié)議參看這里。 - 開發(fā)效率高,易懂 - 源代碼基于C++ - 編譯出來的程序尺寸小,執(zhí)行速度快 - 跨平臺(Linux/Unix, Windows, MacOSX),做到了一次編寫到處編譯。 - 支持OpenGL - 自帶界面生成器(FLUID) - 有趣,易學 獲取FLTK(Getting the Software:) Linux平臺:
- 從www.fltk.org下載FLTK源代碼壓縮文件(最新的版本是1.1.x(注:目前是1.3.x)) - 通過在控制臺輸入以下命令來進行安裝: tar -xvzf fltk-1.1.6-source.tar.gz cd fltk-1.1.6 ./configure make #make install 注意: 如果你下載的是后綴為.tar.bz2的源代碼壓縮文件,請將第一條指令中的 -xvzf 換成 -xvjf Linux下有很多優(yōu)秀的文本編輯器和編程工具,我個人比較喜歡 Anjuta 和 gedit. "#make install"會將編譯后的相關文件安裝到 /usr/local/ 和他的子目錄下。 雖然大多數的linux發(fā)行版都缺省內置了X開發(fā)包,但還是請確認操作系統(tǒng)中是否已經包含了X開發(fā)庫,如果沒有包含,./configure 會失敗。
=============================================
Windows平臺:方案1
- 從http://www.bloodshed.net獲取并安裝最新的 Dev-C++ with MinGW - 通過軟件的update功能安裝FLTK Devpak (最新版本1.1.x) - 或者從FLTK.net下載 DevPak FLTK1 - 如果從fltk.net下載的 DevPak 不能用可以試試下面的老版本。 - 老版本1.1.4 可以通過sourceforge下載 - FLTK2 Devpak是基于2.0版本的,雖然2.0也很不錯,但是本教程是基于1.1.x的,所以請使用上面提到的版本。
-創(chuàng)建一個新的FLTK工程,devcpp會自動生成一個包含了Hello world的代碼。
方案2
- 從www.fltk.org 下載FLTK源代碼 - 從sourceforge下載最新的MinGW, MSYS 和 msysDTK并安裝
1) MinGW-3.x.x-x.exe (32 bit Windows) 2) MSYS-1.x.x.exe (32 bit Windows) - 在MSYS之后輸入安裝指令要小心!(注:slash之后)[含義不明] Follow the MSYS post install script instructions carefully! (Note: forward slash / )3) msysDTK-1.x.x.exe (32 bit Windows)
- 按照上面的步驟安裝 - 執(zhí)行MSYS - 在MSYS控制臺里輸入以下指令:
tar -xvzf fltk-1.1.6-source.tar.gz cd fltk-1.1.6 ./configure make make install - 使用你習慣的編輯器來輸入代碼 - 通過在MSYS控制臺里輸入 "fltk-config --compile" 來編譯代碼 - 如果你沒有找到合適的編輯器,可以用Dev-C++ without MinGW代替 - 提示: MSYS自帶了為很多人所喜歡的 Vim編輯器。對于剛剛接觸這個編輯器的新手而言,不利的一面是Vim需要花一定的時間進行學習,好處是一旦學會了,你會對它愛不釋手。個人覺得這個編輯器值得嘗試。
方案1比較簡單,但是我個人還是推薦使用方案2,理由是,首先,你可以學到如何通過控制臺來編譯程序,這會激勵你去學習命令行系統(tǒng)(對于linux而言,控制臺是非常有用的),甚至會讓你學會如何編寫makefiles,很多l(xiāng)inux專家都善于使用命令行來完成工作。其次,你可以獲得完整的FLTK文件,特別是包括了所有示例程序源代碼的test目錄,在方案1中的Devpak包里是不包含test的。
注:實際上windows下使用vc更方便,在源代碼的ide目錄下有vc6,vc2008, vc2010的工程文件,可以直接使用。目前1.3.x版本的vc6需要修改幾處源代碼才能通過編譯,主要是有些代碼用到了新的c++標準,vc6不支持。 top 我不想從FLTK的官方文檔里復制大段的內容,所以請打開FLTK Basics并進行學習,之后再返回本教程并繼續(xù)閱讀下面的內容。 視頻教程(Flash Video !)
本節(jié)包括了一段13分鐘的視頻教程,可以用帶有shockwave flash插件的瀏覽器觀看。我已經用Mozilla,firefox,Konqueror和Opera測試過。個人建議使用Firefox,不建議用IE瀏覽器。視頻的作者是Greg Ercolano,我已獲得他的同意可以轉載視頻。Greg Ercolano是Fl_Table的貢獻者,同時也是fltk項目組的活躍人員。當我第一次看到這個視頻的時候相當喜歡,希望看完之后你也會喜歡。
下載視頻教程:fltk_video.zip(6.7 M),包含4個文件
Greg Ercolano還制作了一個關于FLUID(FLTK用戶界面設計器)的視頻 點擊這里在線觀看2部視頻
注意:第一個視頻是壓縮文件,這樣你不用每次都要在線播放,可以隨時在自己的硬盤上打開觀看。解壓后用瀏覽器打開tutorial-fltk-hello.html文件,四個視頻文件要和這個html文件在同一個目錄下。文件中絕無病毒和木馬,但是如果你是一個杯弓蛇影的windows用戶,不喜歡下載任何文件,那么你可以通過Greg的網站在線觀看。 觀看前請確認瀏覽器是否支持flash,并且記得打開音箱。對于linux用戶,如果聽不到聲音可以嘗試用aumix來調節(jié)聲音。
一個簡單的窗口程序(Simple Window Function) 好了,現在讓我們看看實際的代碼。我們要創(chuàng)建一個帶有按鈕的窗口,你可以把下面的代碼復制到編輯器里,保存,然后在linux控制臺或MSYS控制臺下輸入下面簡單的指令來編譯程序。
fltk-config --compile myprogram.cpp
myprogram.cpp 是被保存的文件名,fltk-config是安裝FLTK時生成的指令。
#include <FL/Fl.H> #include <FL/Fl_Window.H> #include <FL/Fl_Button.H> using namespace std;
//-------------------------------------------- void but_cb( Fl_Widget* o, void* ) { Fl_Button* b=(Fl_Button*)o; b->label("Good job"); //redraw not necessary b->resize(10,150,140,30); //redraw needed b->redraw(); }
//-------------------------------------------- int main() {
Fl_Window win( 300,200,"Testing" ); win.begin(); Fl_Button but( 10, 150, 70, 30, "Click me" ); win.end(); but.callback( but_cb ); win.show(); return Fl::run(); } 之前: 之后:

下面分析一下代碼
Fl_Window win(300,200, "Testing"); 創(chuàng)建一個新的窗口對象,參數包括窗口的寬度,高度和標題。注意:這個對象是在堆中創(chuàng)建的,所以在主程序退出時會自動注銷。
win.begin(); 此行代碼不是必需的,但我還是建議加上它,因為它讓代碼更具有可讀性。這行代碼和win.end()之間一般放置創(chuàng)建子控件(widgets)的代碼,這些控件都從屬于當前的Fl_Window。
Fl_Button but( 10, 150, 70, 30, "Click me"); 本行代碼創(chuàng)建了一個按鈕,參數(x, y, width, height, label)中,x和y的基點(0,0)是窗口的左上角。注意:此按鈕是從屬于父窗口win的子控件,這也意味著此按鈕是窗口的第一個子控件,編號為0。可以通過查看 win.end(); 本行將前面創(chuàng)建的控件都設定為從屬于父窗口(this line sets the current group to the parent of the window (which in this case is null since the window has no parent))
but.callback( but_cb ); 本行很重要。在FLTK中,當事件發(fā)生時就會觸發(fā)回調,這是GUI程序的基本功能。像鼠標點擊、按鍵等都屬于事件。更多關于事件的描述可以參看后面的"事件"章節(jié)。在本例中,按鈕點擊后觸發(fā)回調改變按鈕的屬性。
需要注意的是,只有繼承自Fl_Widget的控件才可以使用回調功能,比如本例中的按鈕'but'就是繼承自Fl_Widget。void Fl_Widget::callback(Fl_Callback*, void* = 0)
第二個參數(void*)是用戶數據(任何你想送給回調函數的數據都可以通過這個參數來傳遞),這個參數是可選的,后面還有更多關于這個參數的描述。
Fl_Button的基類(Fl_Widget)有一個 .callback 方法,而but_cb'就是callback的參數,是一個函數指針,有2個參數:Fl_Widget* 和 void*,其中Fl_Widget*指向的是控件(Fl_Button)的實例,在這個例子里,就是but。注意,因為需要調用but的方法(label, resize 和 redraw),所以在回調函數(but_cb)里我將Fl_Widget* o強制轉換成了Fl_Button* b。只要需要調用的方法是控件實例擁有的而基類Fl_Widget沒有的就需要做這樣的強制轉換。
順便說一句,在回調處理函數的后面加上'cb'是一個好習慣,可以讓代碼更好閱讀。 win.show(); 本行代碼讓窗口真正顯示出來,換句話講,它讓窗口可視。
void but_cb( Fl_Widget* o , void* ) { 本行定義了回調的處理函數,注意第二個參數是沒有定義參數名的void*,因為在函數里我們沒有使用這個參數。 return Fl::run(); 大多數GUI庫都有類似本行的實現。主要功能是讓程序進入一個不斷等待事件發(fā)生的死循環(huán),當所有的窗口都被關閉或隱藏時,函數run()返回0并退出程序。另外一個退出程序的辦法是執(zhí)行exit(0),后面還會講到。
接下來講解but_cb這個回調處理函數:
void but_cb( Fl_Widget* o, void* ) { 觸發(fā)事件的控件是按鈕but,回調函數的參數時間是Fl_Widget*和void*,其中void*在這里沒有用到,所以沒有設定參數名。由于Fl_Button繼承自Fl_Widget,所以參數o用基類Fl_Widget來代替Fl_Button。 Fl_Button* b=(Fl_Button*)o; 本行將參數Fl_Widget* o強制轉換成了Fl_Button* b。注意傳入的本來就是Fl_Button*,而Fl_Button是繼承自Fl_Widget,所以才能做這樣的轉換。做這種轉換是因為需要執(zhí)行Fl_Button控件的一些方法。 本行改變了按鈕的標簽(Label)。注意,label()和value()這2個方法執(zhí)行后會自動引發(fā)控件重繪,其他方法若想執(zhí)行完之后重繪控件就要手動執(zhí)行redraw(),就像接下來的兩行代碼。順便說一句,控件不是重新復制了一份傳入的Label字符串,而是僅僅保存了傳入字符串的指針,更多解釋請參看下一章節(jié)。
b->resize(10,150,140,30); 本行代碼改變了按鈕的位置和尺寸,四個參數重新定義了按鈕的位置和尺寸,其中位置不變,寬度不變,長度拉伸一倍。特別需要注意的一點是,這個方法不會自動觸發(fā)控件重繪,所以如果你沒有手動執(zhí)行redraw(),按鈕的尺寸是看不到變化的。所以就有了下一行代碼。 b->redraw(); 本行代碼重繪了控件,當執(zhí)行完resize()方法之后需要執(zhí)行本方法。這種盡量不做多余處理的模式也是FLTK的效率和速度非??斓囊粋€原因。
** 有關控件Label的陷阱(Widget Label Pitfall) **
當設定一個控件的Label時,控件內部只保存了傳入字符串的指針,并沒有復制傳入的字符串。這就意味著,如果傳入的是靜態(tài)字符串,比如"Good job",沒有問題,但是如果傳入的是一個臨時生成的字符串,那么就要保證在控件存在期間此字符串也要一直存在。因為控件只保存了指針本身,而控件重繪Label的時候是直接使用這個指針的,如果指針已經銷毀,那么在重繪的時候就會出錯。作為演示,讓我看看下面這個例子(labeltest.cc):
#include <FL/Fl.H> #include <FL/Fl_Window.H> #include <FL/Fl_Button.H> using namespace std;
void butcb(Fl_Widget* o, void*){ char newcap[]="changed"; o->label(newcap); }
int main(){ Fl_Window win (200,100, "window"); Fl_Button but (50,50,80,25,"caption"); but.callback (butcb); win.end(); win.show(); return Fl::run(); } 之前: 移動窗口之后:

保存后編譯,執(zhí)行下面的指令:
"fltk-config --compile labeltest.cc"
編譯成功后執(zhí)行:"./labeltest"
發(fā)現了么?有個很嚴重的問題:點擊按鈕,然后移動窗口或是最小化/還原窗口,此時會觸發(fā)按鈕的重繪,看看按鈕的Label變成了什么?亂碼!為什么?因為FLTK使用了一個已經不存在的指針。按鈕一開始設定的Label是一個靜態(tài)字符串"Caption",一旦點擊按鈕,就將原本的Label換成了一個局部字符數組newcap,當butcb函數退出后,newcap就已經被銷毀,但是按鈕but中仍然保留了newcap的指針,認為它指向了Label的內容。于是當按鈕自繪時,問題就出現了。怎么解決這個問題呢?之前的常用做法是編寫一個從原始控件繼承而來的新控件,在新控件內部保存一份Label的拷貝。但是現在不用了,從版本1.1.6開始,FLTK加入了一個copy_label接口。你可以將代碼做如下修改:
o->label(newcap);
改為
o->copy_label(newcap);
現在控件內部不是保存指針而是復制了一份字符串,問題解決!這個問題是不是有點龜毛?幸好在FLTK中只有處理Label時需要注意,其他地方都不存在這個問題。
控件間通訊的簡單示例(Simple Window with widgets that talk to each other) 編寫這個教程的一個主要原因是我想講解控件(buttons, input boxes, output boxes等等)之間如何互相通訊。FLTK官方文檔中展示的例子大都是創(chuàng)建一個窗口,然后簡單的在上面放置一些控件,當你需要在多個控件之間傳遞信息的時候,這種方式會讓事情變得有點麻煩??纯聪旅娴睦?這個例子同時也是上面所說的控件創(chuàng)建于棧的情況),親自編譯一下看看。注意,這個例子是可以工作的,但是里面使用的通訊方法并不推薦。
#include <FL/Fl.H> #include <FL/Fl_Window.H> #include <FL/Fl_Button.H> #include <FL/Fl_Input.H> #include <FL/Fl_Output.H> #include <cstdlib> //for exit(0) #include <string.h> using namespace std;
void copy_cb( Fl_Widget* , void* ); //function prototypes void close_cb( Fl_Widget* , void* ); void make_window();
int main() {
make_window(); return Fl::run(); }
void make_window() { Fl_Window* win= new Fl_Window(300,200, "Testing 2"); win->begin(); Fl_Button* copy = new Fl_Button( 10, 150, 70, 30, "C&opy"); //child 0 : 1st widget Fl_Button* close = new Fl_Button(100, 150, 70, 30, "&Quit"); //child 1 : 2nd widget Fl_Input* inp = new Fl_Input(50, 50, 140, 30, "In"); //child 2 : 3rd widget Fl_Output* out = new Fl_Output(50, 100, 140, 30, "Out"); //child 3 : 4th widget win->end(); copy->callback( copy_cb ); close->callback( close_cb ); win->show(); }
void copy_cb( Fl_Widget* o , void* ) {
Fl_Button* b=(Fl_Button*)o; Fl_Input* iw = (Fl_Input*) b -> parent() -> child(2); Fl_Output* ow = (Fl_Output*) b -> parent() -> child(3); ow->value( iw->value() ); }
void close_cb( Fl_Widget* o, void*) {
exit(0); }
----------------------------------------------------------------------------
現在來執(zhí)行程序
 注意:在Copy的o和Quit的Q下面各有一條線,這表示可以通過ALT-c和ALT-q觸發(fā)按鈕事件,要實現這個功能很簡單,就是在初始化按鈕Label的時候在快捷字符前加一個&即可,比如C&opy。
void copy_cb( Fl_Widget* o , void* ) { Fl_Button* b=(Fl_Button*)o;
注意,o的定義是Fl_Widget,但實際傳入的是Fl_Widget的子類Fl_Button,所以這里將o強制轉換成了Fl_Button* b。
接下來是控件間通訊的代碼,這種方式很丑陋。
Fl_Input* iw = (Fl_Input*) b ->parent()->child(2); Fl_Output* ow = (Fl_Output*) b ->parent()->child(3); ow->value( iw->value() );
第一行代碼(Fl_Input*) b->parent()返回一個Fl_Group*,child(2)返回這個Fl_Group的第三個Fl_Widget子控件,也就是Fl_Input* inp,接下來通過強制轉換,將這個Fl_Widget轉換成Fl_Input* iw。第二行代碼做了類似的事情,最終獲得了Fl_Output* ow。最后一行將iw的value傳給了ow。
我需要再次提醒你,這是一個很不好的控件間通訊方式,首先它很丑陋而且沒有可讀性,再者這種方式必須要時刻小心子控件的序號,最后在處理序號的時候沒有范圍檢查。
提示:不知道你有沒有注意到,在這個例子里沒有手工刪除動態(tài)創(chuàng)建的'win'對象。在教程的第一個例子里窗口對象會在程序退出時自動銷毀,因為對象是創(chuàng)建在堆中的,但是這個例子里的win卻是創(chuàng)建在棧中,而且并沒有調用delete來進行銷毀,甚至使用exit(0)直接退出程序,這種方式會不會出現內存泄漏?當然不會!原因后面的章節(jié)會講到,請接著往下看。
回到控件間通訊的問題,如果你想到可以用void* userdata來傳遞數據,那么就讓我們看看接下來的章節(jié)。
控件間通訊的改進方案(Two Widgets Talking)
書接上回,這次演示的是一個界面有所變化但核心功能不變的程序,依然是從一個控件復制數據到另一個控件,但這次我們利用了userdata來傳遞數據。
#include <FL/Fl.H> #include <FL/Fl_Window.H> #include <FL/Fl_Button.H> #include <FL/Fl_Input.H> #include <FL/Fl_Output.H> #include <cstdlib> //for exit(0) using namespace std;
void copy_cb( Fl_Widget* , void* ); //function prototypes void close_cb( Fl_Widget* , void* ); void make_window();
int main() {
make_window(); return Fl::run(); }
void make_window() { Fl_Window* win= new Fl_Window(300,200, "Testing 2"); win->begin(); Fl_Button* copy = new Fl_Button( 50, 100, 140, 30, "change me"); Fl_Button* close = new Fl_Button(100, 150, 70, 30, "&Quit"); Fl_Input* inp = new Fl_Input(50, 50, 140, 30, "In"); win->end(); copy->callback( copy_cb, inp ); //userdata is the inp pointer close->callback( close_cb ); win->show(); }
void copy_cb( Fl_Widget* o , void* v) {
Fl_Button* b=(Fl_Button*)o; Fl_Input* i=(Fl_Input*)v; b->copy_label(i->value()); }
void close_cb( Fl_Widget* o, void*) {
exit(0); }
之前: 之后:

下面這行代碼是整個例子的核心:
copy->callback( copy_cb, inp );
有沒有注意到?我們將Fl_Input* inp通過void* userdata傳遞給了回調處理函數,所以回調里收到2個參數,第一個是Fl_Button* copy,第二個是Fl_Input* inp。
Fl_Input* i = (Fl_Input*)v; b->copy_label (i->value());
上面2行代碼很好理解,將傳入的第一個參數轉換成Fl_Button,第二個參數轉換成Fl_Input,最后再將Fl_Input的value()設定為Fl_Button的Label。簡單,清楚。
但是,如果我們需要處理的控件超過2個怎么辦?回調可只有2個參數。因此,這個例子依然不是一個最好的控件間通訊的辦法,不過這個例子演示了如何通過void* userdata來傳遞各種數據。 接下來的章節(jié)我們會演示真正的控件間通訊辦法。
另外如果你是一名有經驗的C++程序員,你會注意到上面的例子里用exit(0)來直接結束程序卻沒有注銷任何對象,這是個問題吧?是的,但不要著急,下面這個章節(jié)將揭開所有的謎團。
控件間通訊的最終解決方案(Simple Inherited Window) 這個例子也是為了解決控件間通訊的,但是方式完全不同,而且也是個人最為推薦的一種方法。
#include <FL/Fl.H> #include <FL/Fl_Window.H> #include <FL/Fl_Button.H> #include <FL/Fl_Input.H> #include <FL/Fl_Output.H> using namespace std;
//--------------------------------------------------- class SimpleWindow : public Fl_Window{ public: SimpleWindow(int w, int h, const char* title ); ~SimpleWindow(); Fl_Button* copy; Fl_Button* quit; Fl_Input* inp; Fl_Output* out; private: static void cb_copy(Fl_Widget*, void*); inline void cb_copy_i(); static void cb_quit(Fl_Widget*, void*); inline void cb_quit_i(); };
//----------------------------------------------------
int main (){ SimpleWindow win(300,200,"SimpleWindow"); return Fl::run(); }
//----------------------------------------------------
SimpleWindow::SimpleWindow(int w, int h, const char* title):Fl_Window(w,h,title){ begin(); copy = new Fl_Button( 10, 150, 70, 30, "C&opy"); copy->callback( cb_copy, this ); quit = new Fl_Button(100, 150, 70, 30, "&Quit"); quit->callback(cb_quit, this); inp = new Fl_Input(50, 50, 140, 30, "Input:"); out = new Fl_Output(50, 100, 140, 30, "Output:"); end(); resizable(this); show(); }
//----------------------------------------------------
SimpleWindow::~SimpleWindow(){}
//----------------------------------------------------
void SimpleWindow::cb_copy(Fl_Widget* o, void* v) { //SimpleWindow* T=(SimpleWindow*)v; //T->cb_copy_i(); // or just the one line below ( (SimpleWindow*)v )->cb_copy_i(); }
void SimpleWindow::cb_copy_i() {
out->value(inp->value()); }
//----------------------------------------------------
void SimpleWindow::cb_quit(Fl_Widget* , void* v) {
( (SimpleWindow*)v )->cb_quit_i(); }
void SimpleWindow::cb_quit_i() {
hide(); }
//----------------------------------------------------

現在讓我們來分析一下這個改進后的版本。
首先我們創(chuàng)建了一個繼承自Fl_Window的控件SimpleWindow,然后在public部分加入了所有即將創(chuàng)建的子控件對象,這種做法的好處是在類的外部要使用這些子控件可以直接獲取。接下來讓我們看看回調是如何處理的:
private: static void cb_copy (Fl_Widget*, void*); inline void cb_copy_i (); //---------------------------------------------------------------- void SimpleWindow::cb_copy(Fl_Button* o, void* v) { ( (SimpleWindow*)v )->cb_copy_i();}
void SimpleWindow::cb_copy_i() {
out->value ( inp->value() ); // Clean and simple } //----------------------------------------------------------------
這兩個方法很重要,他們成組出現,共同解決了將回調應用于類的問題。首先,回調函數必須是靜態(tài)的(static),也就是說,回調函數本身是無法獲取類的實例指針(THIS)的。而解決之道就是引入了2個函數,由static函數來執(zhí)行另外一個inline函數,執(zhí)行的關鍵就是引入了this指針,也就是利用了前面所說的userdata。 其中的inline函數并非static函數,它是類的內部方法,這一點要注意。用2個函數的組合來實現回調在類中的應用還是很值得的。
注意第二個函數名的尾部加了一個'_i'后綴,這是為了說明這個函數是inline,同時這個函數實際上可以不用輸入任何參數,因為它所需要的數據都在類里,可以直接使用。另外要注意的一點是在類似begin(),end(),show()這樣的方法前面無需指定對象,因為SimpleWindow是從Fl_Window繼承而來的。 如果在你忘記了void*在C++中的含義,這里有一個簡短的復習:-------------------------------------------------------------- 關于void*的簡短說明: 如果你還是C++的新手,有必要看一下這里關于空指針(void*)的說明。基本上void*就是一個能指向任何類型的數據的指針。一般而言,指針總是有一個類型的,用來說明所指向的數據是何種結構,但void*是沒有類型的,所以在回調中我們可以將void*轉換成任何類型的數據。
從另外一個角度來講,指針通常知道它所指向的數據是什么,但是void*是不知道的,void*只知道它指向了一堆數據而已,因此我們可以將void*轉換為任意的數據類型。
-------------------------------------------------------------- 在SimpleWindow的構造函數中我們new了一個Fl_Button,然后設定了這個按鈕的回調:
copy->callback (cb_copy, this);
本行傳入了回調函數(cb_copy)和userdata(this)2個參數,userdata的類型是void*,可以傳入任意數據,所以可以將this傳入。因此在cb_copy中我們就得到了對象的指針,接下來就可以調用類的內部數據和方法了???,多么簡單有效!
Get/Set方法 FLTK中對于get/set功能的處理很有特色,即用相同名字的函數但用不同的參數和返回值來處理。get功能只有返回值沒有參數,所以inp->value()就是獲取控件的label指針,相反的,out->value(const char*)就是set功能了。這種處理方法和前面第一種處理控件間通訊模式里的方法相比,不再有混亂的序號和類型轉換問題,高下立判,同時這也是類的封裝性的一個體現。
resizable(this); 本行代碼將窗口設定為可拉伸。我可以簡單的用resizable(copy)來設定copy按鈕是相對window可拉伸的,但這樣做就只有這個按鈕是相對window拉伸的,在這里例子里,我希望所有的控件都是可拉伸的。記住一條規(guī)則:每個group中只能設定一個widget可拉伸。因此如果界面的拉伸模式比較復雜,你需要加入適當的水平和垂直Fl_Group控件。下面的內容來自2004年1月17號的FLTK general新聞組: Marc R.J. Brevoort 寫到:
這里是一些小提示,仔細閱讀后試試看效果如何。
- 要學會提前設計。group中的widget只能朝一個方向拉伸:要么水平要么 垂直(這一條也對解釋下一條有幫助)。
- 如果你需要放置一個水平方向和垂直方向都拉伸的group,可以先 放置一個朝一個方向拉伸的group,然后在這個group里放置一個 子group,并設定為朝另外一個方向拉伸。
- 一個group里只有一個widget能夠設定為可拉伸(resizable)。將 多個widget設定為可拉伸的結果是只有最后一個widget才會被設 定為可拉伸。
- 將一個widget設為可拉伸意味著這個widget可以在水平和垂直2個方向上拉伸, 并不意味著這個group里的其他widget就不能拉伸。
- 在一個group中,可拉伸的widget可以在水平和垂直2個方向上拉伸, 而其他widget則只能在group拉伸的垂直方向上拉伸。
- 當一個group只在某一個方向上可拉伸,那么只有可拉伸的widget 會跟著拉伸,其他widget不會改變位置。
希望對你有幫助
謝謝 MRJB 謝謝Marc。我將你的信息抄錄在這里,作為一個備份。
最后要分析的一行代碼是:
hide(); // which calls hide() on the SimpleWindow 你可以通過2種方法退出程序,第一種是調用exit(0)退出,但是所有之前申請的內存都要靠操作系統(tǒng)來幫你銷毀;第二種是在所有會引起Fl::run()返回的window中調用hide()。故而SimpleWindow win可以正確執(zhí)行析構函數并正常退出。有一條準則要牢牢記?。翰灰獎?chuàng)建全局對象,否則調用hide()就不會觸發(fā)對象的析構函數,即便這個對象不是在主函數里創(chuàng)建的。我個人從不創(chuàng)建全局對象,這是一個好習慣。注意:exit(0)不會返回到Fl::run(),而是直接退出,之前申請的所有內存都要靠操作系統(tǒng)來銷毀。
現在你應該意識到,我并沒有在析構函數里釋放任何之前所創(chuàng)建的對象,這是因為SimpleWindow是繼承自Fl_Group,而Fl_Group會根據虛擬基類析構函數(virtual base class destructor)獲取所有子控件的析構函數并自動銷毀它們,這也是為什么基類可以通過child(int n)和children()這樣的函數獲得子控件的原因。看,既然FLTK有了這個功能,誰還需要JAVA?
注意:下面的代碼來自FLTK general newsgroup的Jason Bryan:
void fl_exit() { while( Fl::first_window() ) Fl::first_window()->hide(); }
這個短小的函數的作用是確保所有的window都執(zhí)行了hide(),因此當主循環(huán)Fl::run()返回的時候所以的窗口都已經被正確銷毀了。感謝Jason提供的代碼。
事件是GUI程序的響應機制,在FLTK的官方文檔上對事件有詳細的解釋:點擊這里。我個人喜歡通過例子代碼來學習,所以我寫了下面這個例子,里面基本包括了所有種類的事件。 .
#include <FL/Fl.H> #include <FL/Fl_Window.H> #include <FL/Fl_Button.H> #include <iostream> using namespace std;
class MyButton : public Fl_Button { static int count; public: MyButton(int x,int y,int w,int h,const char*l=0) :Fl_Button(x,y,w,h,l) {} int handle(int e) { int ret = Fl_Button::handle(e); cout<<endl<<count++<<" ******** button "<<label()<<" receives "; switch(e) { case FL_PUSH: cout<<"push"<<" event and returns:"<<ret<<endl; break; case FL_RELEASE: cout<<"release"<<" event and returns:"<<ret<<endl; break; case FL_ENTER: color(FL_CYAN); cout<<"enter"<<" event and returns:"<<ret<<endl; redraw(); break; case FL_LEAVE: color(FL_BACKGROUND_COLOR); cout<<"leave"<<" event and returns:"<<ret<<endl; redraw(); break; case FL_DRAG: cout<<"drag"<<" event and returns:"<<ret<<endl; break; case FL_FOCUS: cout<<"focus"<<" event and returns:"<<ret<<endl; break; case FL_UNFOCUS: cout<<"unfocus"<<" event and returns:"<<ret<<endl; break; case FL_KEYDOWN: cout<<"keydown"<<" event and returns:"<<ret<<endl; break;
case FL_KEYUP: if ( Fl::event_key() == shortcut() ){ box(FL_UP_BOX); redraw(); ret=1; //return handled so keyup event stops } //being sent to ALL other buttons unecessarily cout<<"keyup"<<" event and returns:"<<ret<<endl; break; case FL_CLOSE: cout<<"close"<<" event and returns:"<<ret<<endl; break; case FL_MOVE: cout<<"move"<<" event and returns:"<<ret<<endl; break; case FL_SHORTCUT: if ( Fl::event_key() == shortcut() ){ box(FL_DOWN_BOX); redraw(); } cout<<"shortcut"<<" event and returns:"<<ret<<endl; break; case FL_DEACTIVATE: cout<<"deactivate"<<" event and returns:"<<ret<<endl; break; case FL_ACTIVATE: cout<<"activate"<<" event and returns:"<<ret<<endl; break; case FL_HIDE: cout<<"hide"<<" event and returns:"<<ret<<endl; break; case FL_SHOW: cout<<"show"<<" event and returns:"<<ret<<endl; break; case FL_PASTE: cout<<"paste"<<" event and returns:"<<ret<<endl; break; case FL_SELECTIONCLEAR: cout<<"selectionclear"<<" event and returns:"<<ret<<endl; break; case FL_MOUSEWHEEL: cout<<"mousewheel"<<" event and returns:"<<ret<<endl; break; case FL_NO_EVENT: cout<<"no event"<<" and returns:"<<ret<<endl; break; } return(ret); } };
int MyButton::count=0;
void but_a_cb(Fl_Widget* w, void* v){ cout <<endl<< "Button A callback!"<<endl; }
void but_b_cb(Fl_Widget* w, void* v){ cout <<endl<< "Button B callback!"<<endl; }
void but_c_cb(Fl_Widget* w, void* v){ cout <<endl<< "Button C callback!"<<endl; }
int main() { Fl_Window win(120,150); win.begin();
MyButton but_a(10,10,100,25,"A"); but_a.shortcut('a'); but_a.callback(but_a_cb);
MyButton but_b(10,50,100,25,"B"); but_b.shortcut('b'); but_b.callback(but_b_cb);
MyButton but_c(10,90,100,25,"C"); but_c.shortcut('c'); but_c.callback(but_c_cb);
win.end(); win.show(); return(Fl::run()); }
現在讓我們來編譯程序:
(Linux) g++ -I/usr/local/include -I/usr/X11R6/include -o events events.cc -L/usr/X11R6/lib -L/usr/local/lib /usr/local/lib/libfltk.a -lm -lXext -lX11 -lsupc++
或
(Linux 或 Windows) fltk-config --compile events.cc
下面是執(zhí)行程序后在控制臺獲得的反饋信息(注意,不要通過dev-c++來執(zhí)行,因為控制臺被隱藏了,會看不到這些調試信息)。如果在windows平臺,可以在MSYS/MinGW下執(zhí)行。

未完待續(xù)..............
我在加拿大卑詩省一所大學里教物理和計算機編程(C++)。開發(fā)是我的業(yè)余愛好。2003年開始接觸FLTK,對于類似FLTK這樣的開源/自由軟件我一直很喜歡。FLTK開創(chuàng)了編程的一方天地。很感謝FLTK新聞組的Bill, Mike, Matt, Greg, Jason, Marc, Alexey, Roman 和 Dejan,如果有遺漏的純屬個人記憶力不好。是你們讓FLTK保持了活力和能力,非常感謝!
你可以通過links/bazaar tutorial page來給本教程評論建議和打分,如果你想給我發(fā)郵件,請發(fā)到:
fltk_beginner_tutorial@yahoo.com
Robert Arkiletian
|