|
[本教程翻譯自http://www3.telus.net/public/robark/ ]
新手入門 版本: 1.1 目標(biāo)人群
本教程是為那些打算編寫GUI的C++程序員而準(zhǔn)備的。FLTK的官方文檔編寫的很合理,從簡(jiǎn)單的例子(Hello world)到復(fù)雜的例子(editor.cxx)一點(diǎn)點(diǎn)循序漸進(jìn)。本教程是從FLTK Basics開始展開的。另外這里 是FLTK 2.0(Beta)的文檔,同樣是一個(gè)很好的資源。希望看完本教程能讓你盡快的進(jìn)入FLTK編程。 Enjoy!
知識(shí)預(yù)備(Prerequisite): 為何使用FLTK編寫GUI程序(Why use FLTK as opposed to other GUI toolkits?) - FLTK是開源的,基于GUN LGPL協(xié)議。 具體協(xié)議參看這里。 - 開發(fā)效率高,易懂 - 源代碼基于C++ - 編譯出來(lái)的程序尺寸小,執(zhí)行速度快 - 跨平臺(tái)(Linux/Unix, Windows, MacOSX),做到了一次編寫到處編譯。 - 支持OpenGL - 自帶界面生成器(FLUID) - 有趣,易學(xué) 獲取FLTK(Getting the Software:) Linux平臺(tái):
- 從www.fltk.org下載FLTK源代碼壓縮文件(最新的版本是1.1.x(注:目前是1.3.x)) - 通過(guò)在控制臺(tái)輸入以下命令來(lái)進(jìn)行安裝: tar -xvzf fltk-1.1.6-source.tar.gz cd fltk-1.1.6 ./configure make #make install 注意: 如果你下載的是后綴為.tar.bz2的源代碼壓縮文件,請(qǐng)將第一條指令中的 -xvzf 換成 -xvjf Linux下有很多優(yōu)秀的文本編輯器和編程工具,我個(gè)人比較喜歡 Anjuta 和 gedit. "#make install"會(huì)將編譯后的相關(guān)文件安裝到 /usr/local/ 和他的子目錄下。 雖然大多數(shù)的linux發(fā)行版都缺省內(nèi)置了X開發(fā)包,但還是請(qǐng)確認(rèn)操作系統(tǒng)中是否已經(jīng)包含了X開發(fā)庫(kù),如果沒有包含,./configure 會(huì)失敗。
=============================================
Windows平臺(tái):方案1
- 從http://www.bloodshed.net獲取并安裝最新的 Dev-C++ with MinGW - 通過(guò)軟件的update功能安裝FLTK Devpak (最新版本1.1.x) - 或者從FLTK.net下載 DevPak FLTK1 - 如果從fltk.net下載的 DevPak 不能用可以試試下面的老版本。 - 老版本1.1.4 可以通過(guò)sourceforge下載 - FLTK2 Devpak是基于2.0版本的,雖然2.0也很不錯(cuò),但是本教程是基于1.1.x的,所以請(qǐng)使用上面提到的版本。
-創(chuàng)建一個(gè)新的FLTK工程,devcpp會(huì)自動(dòng)生成一個(gè)包含了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控制臺(tái)里輸入以下指令:
tar -xvzf fltk-1.1.6-source.tar.gz cd fltk-1.1.6 ./configure make make install - 使用你習(xí)慣的編輯器來(lái)輸入代碼 - 通過(guò)在MSYS控制臺(tái)里輸入 "fltk-config --compile" 來(lái)編譯代碼 - 如果你沒有找到合適的編輯器,可以用Dev-C++ without MinGW代替 - 提示: MSYS自帶了為很多人所喜歡的 Vim編輯器。對(duì)于剛剛接觸這個(gè)編輯器的新手而言,不利的一面是Vim需要花一定的時(shí)間進(jìn)行學(xué)習(xí),好處是一旦學(xué)會(huì)了,你會(huì)對(duì)它愛不釋手。個(gè)人覺得這個(gè)編輯器值得嘗試。
方案1比較簡(jiǎn)單,但是我個(gè)人還是推薦使用方案2,理由是,首先,你可以學(xué)到如何通過(guò)控制臺(tái)來(lái)編譯程序,這會(huì)激勵(lì)你去學(xué)習(xí)命令行系統(tǒng)(對(duì)于linux而言,控制臺(tái)是非常有用的),甚至?xí)屇銓W(xué)會(huì)如何編寫makefiles,很多l(xiāng)inux專家都善于使用命令行來(lái)完成工作。其次,你可以獲得完整的FLTK文件,特別是包括了所有示例程序源代碼的test目錄,在方案1中的Devpak包里是不包含test的。
注:實(shí)際上windows下使用vc更方便,在源代碼的ide目錄下有vc6,vc2008, vc2010的工程文件,可以直接使用。目前1.3.x版本的vc6需要修改幾處源代碼才能通過(guò)編譯,主要是有些代碼用到了新的c++標(biāo)準(zhǔn),vc6不支持。 top 我不想從FLTK的官方文檔里復(fù)制大段的內(nèi)容,所以請(qǐng)打開FLTK Basics并進(jìn)行學(xué)習(xí),之后再返回本教程并繼續(xù)閱讀下面的內(nèi)容。 視頻教程(Flash Video !)
本節(jié)包括了一段13分鐘的視頻教程,可以用帶有shockwave flash插件的瀏覽器觀看。我已經(jīng)用Mozilla,firefox,Konqueror和Opera測(cè)試過(guò)。個(gè)人建議使用Firefox,不建議用IE瀏覽器。視頻的作者是Greg Ercolano,我已獲得他的同意可以轉(zhuǎn)載視頻。Greg Ercolano是Fl_Table的貢獻(xiàn)者,同時(shí)也是fltk項(xiàng)目組的活躍人員。當(dāng)我第一次看到這個(gè)視頻的時(shí)候相當(dāng)喜歡,希望看完之后你也會(huì)喜歡。
下載視頻教程:fltk_video.zip(6.7 M),包含4個(gè)文件
Greg Ercolano還制作了一個(gè)關(guān)于FLUID(FLTK用戶界面設(shè)計(jì)器)的視頻 點(diǎn)擊這里在線觀看2部視頻
注意:第一個(gè)視頻是壓縮文件,這樣你不用每次都要在線播放,可以隨時(shí)在自己的硬盤上打開觀看。解壓后用瀏覽器打開tutorial-fltk-hello.html文件,四個(gè)視頻文件要和這個(gè)html文件在同一個(gè)目錄下。文件中絕無(wú)病毒和木馬,但是如果你是一個(gè)杯弓蛇影的windows用戶,不喜歡下載任何文件,那么你可以通過(guò)Greg的網(wǎng)站在線觀看。 觀看前請(qǐng)確認(rèn)瀏覽器是否支持flash,并且記得打開音箱。對(duì)于linux用戶,如果聽不到聲音可以嘗試用aumix來(lái)調(diào)節(jié)聲音。
一個(gè)簡(jiǎn)單的窗口程序(Simple Window Function) 好了,現(xiàn)在讓我們看看實(shí)際的代碼。我們要?jiǎng)?chuàng)建一個(gè)帶有按鈕的窗口,你可以把下面的代碼復(fù)制到編輯器里,保存,然后在linux控制臺(tái)或MSYS控制臺(tái)下輸入下面簡(jiǎn)單的指令來(lái)編譯程序。
fltk-config --compile myprogram.cpp
myprogram.cpp 是被保存的文件名,fltk-config是安裝FLTK時(shí)生成的指令。
#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)建一個(gè)新的窗口對(duì)象,參數(shù)包括窗口的寬度,高度和標(biāo)題。注意:這個(gè)對(duì)象是在堆中創(chuàng)建的,所以在主程序退出時(shí)會(huì)自動(dòng)注銷。
win.begin(); 此行代碼不是必需的,但我還是建議加上它,因?yàn)樗尨a更具有可讀性。這行代碼和win.end()之間一般放置創(chuàng)建子控件(widgets)的代碼,這些控件都從屬于當(dāng)前的Fl_Window。
Fl_Button but( 10, 150, 70, 30, "Click me"); 本行代碼創(chuàng)建了一個(gè)按鈕,參數(shù)(x, y, width, height, label)中,x和y的基點(diǎn)(0,0)是窗口的左上角。注意:此按鈕是從屬于父窗口win的子控件,這也意味著此按鈕是窗口的第一個(gè)子控件,編號(hào)為0??梢酝ㄟ^(guò)查看 win.end(); 本行將前面創(chuàng)建的控件都設(shè)定為從屬于父窗口(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中,當(dāng)事件發(fā)生時(shí)就會(huì)觸發(fā)回調(diào),這是GUI程序的基本功能。像鼠標(biāo)點(diǎn)擊、按鍵等都屬于事件。更多關(guān)于事件的描述可以參看后面的"事件"章節(jié)。在本例中,按鈕點(diǎn)擊后觸發(fā)回調(diào)改變按鈕的屬性。
需要注意的是,只有繼承自Fl_Widget的控件才可以使用回調(diào)功能,比如本例中的按鈕'but'就是繼承自Fl_Widget。void Fl_Widget::callback(Fl_Callback*, void* = 0)
第二個(gè)參數(shù)(void*)是用戶數(shù)據(jù)(任何你想送給回調(diào)函數(shù)的數(shù)據(jù)都可以通過(guò)這個(gè)參數(shù)來(lái)傳遞),這個(gè)參數(shù)是可選的,后面還有更多關(guān)于這個(gè)參數(shù)的描述。
Fl_Button的基類(Fl_Widget)有一個(gè) .callback 方法,而but_cb'就是callback的參數(shù),是一個(gè)函數(shù)指針,有2個(gè)參數(shù):Fl_Widget* 和 void*,其中Fl_Widget*指向的是控件(Fl_Button)的實(shí)例,在這個(gè)例子里,就是but。注意,因?yàn)樾枰{(diào)用but的方法(label, resize 和 redraw),所以在回調(diào)函數(shù)(but_cb)里我將Fl_Widget* o強(qiáng)制轉(zhuǎn)換成了Fl_Button* b。只要需要調(diào)用的方法是控件實(shí)例擁有的而基類Fl_Widget沒有的就需要做這樣的強(qiáng)制轉(zhuǎn)換。
順便說(shuō)一句,在回調(diào)處理函數(shù)的后面加上'cb'是一個(gè)好習(xí)慣,可以讓代碼更好閱讀。 win.show(); 本行代碼讓窗口真正顯示出來(lái),換句話講,它讓窗口可視。
void but_cb( Fl_Widget* o , void* ) { 本行定義了回調(diào)的處理函數(shù),注意第二個(gè)參數(shù)是沒有定義參數(shù)名的void*,因?yàn)樵诤瘮?shù)里我們沒有使用這個(gè)參數(shù)。 return Fl::run(); 大多數(shù)GUI庫(kù)都有類似本行的實(shí)現(xiàn)。主要功能是讓程序進(jìn)入一個(gè)不斷等待事件發(fā)生的死循環(huán),當(dāng)所有的窗口都被關(guān)閉或隱藏時(shí),函數(shù)run()返回0并退出程序。另外一個(gè)退出程序的辦法是執(zhí)行exit(0),后面還會(huì)講到。
接下來(lái)講解but_cb這個(gè)回調(diào)處理函數(shù):
void but_cb( Fl_Widget* o, void* ) { 觸發(fā)事件的控件是按鈕but,回調(diào)函數(shù)的參數(shù)時(shí)間是Fl_Widget*和void*,其中void*在這里沒有用到,所以沒有設(shè)定參數(shù)名。由于Fl_Button繼承自Fl_Widget,所以參數(shù)o用基類Fl_Widget來(lái)代替Fl_Button。 Fl_Button* b=(Fl_Button*)o; 本行將參數(shù)Fl_Widget* o強(qiáng)制轉(zhuǎn)換成了Fl_Button* b。注意傳入的本來(lái)就是Fl_Button*,而Fl_Button是繼承自Fl_Widget,所以才能做這樣的轉(zhuǎn)換。做這種轉(zhuǎn)換是因?yàn)樾枰獔?zhí)行Fl_Button控件的一些方法。 本行改變了按鈕的標(biāo)簽(Label)。注意,label()和value()這2個(gè)方法執(zhí)行后會(huì)自動(dòng)引發(fā)控件重繪,其他方法若想執(zhí)行完之后重繪控件就要手動(dòng)執(zhí)行redraw(),就像接下來(lái)的兩行代碼。順便說(shuō)一句,控件不是重新復(fù)制了一份傳入的Label字符串,而是僅僅保存了傳入字符串的指針,更多解釋請(qǐng)參看下一章節(jié)。
b->resize(10,150,140,30); 本行代碼改變了按鈕的位置和尺寸,四個(gè)參數(shù)重新定義了按鈕的位置和尺寸,其中位置不變,寬度不變,長(zhǎng)度拉伸一倍。特別需要注意的一點(diǎn)是,這個(gè)方法不會(huì)自動(dòng)觸發(fā)控件重繪,所以如果你沒有手動(dòng)執(zhí)行redraw(),按鈕的尺寸是看不到變化的。所以就有了下一行代碼。 b->redraw(); 本行代碼重繪了控件,當(dāng)執(zhí)行完resize()方法之后需要執(zhí)行本方法。這種盡量不做多余處理的模式也是FLTK的效率和速度非??斓囊粋€(gè)原因。
** 有關(guān)控件Label的陷阱(Widget Label Pitfall) **
當(dāng)設(shè)定一個(gè)控件的Label時(shí),控件內(nèi)部只保存了傳入字符串的指針,并沒有復(fù)制傳入的字符串。這就意味著,如果傳入的是靜態(tài)字符串,比如"Good job",沒有問題,但是如果傳入的是一個(gè)臨時(shí)生成的字符串,那么就要保證在控件存在期間此字符串也要一直存在。因?yàn)榭丶槐4媪酥羔槺旧恚丶乩LLabel的時(shí)候是直接使用這個(gè)指針的,如果指針已經(jīng)銷毀,那么在重繪的時(shí)候就會(huì)出錯(cuò)。作為演示,讓我看看下面這個(gè)例子(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(); } 之前: 移動(dòng)窗口之后:

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

下面這行代碼是整個(gè)例子的核心:
copy->callback( copy_cb, inp );
有沒有注意到?我們將Fl_Input* inp通過(guò)void* userdata傳遞給了回調(diào)處理函數(shù),所以回調(diào)里收到2個(gè)參數(shù),第一個(gè)是Fl_Button* copy,第二個(gè)是Fl_Input* inp。
Fl_Input* i = (Fl_Input*)v; b->copy_label (i->value());
上面2行代碼很好理解,將傳入的第一個(gè)參數(shù)轉(zhuǎn)換成Fl_Button,第二個(gè)參數(shù)轉(zhuǎn)換成Fl_Input,最后再將Fl_Input的value()設(shè)定為Fl_Button的Label。簡(jiǎn)單,清楚。
但是,如果我們需要處理的控件超過(guò)2個(gè)怎么辦?回調(diào)可只有2個(gè)參數(shù)。因此,這個(gè)例子依然不是一個(gè)最好的控件間通訊的辦法,不過(guò)這個(gè)例子演示了如何通過(guò)void* userdata來(lái)傳遞各種數(shù)據(jù)。 接下來(lái)的章節(jié)我們會(huì)演示真正的控件間通訊辦法。
另外如果你是一名有經(jīng)驗(yàn)的C++程序員,你會(huì)注意到上面的例子里用exit(0)來(lái)直接結(jié)束程序卻沒有注銷任何對(duì)象,這是個(gè)問題吧?是的,但不要著急,下面這個(gè)章節(jié)將揭開所有的謎團(tuán)。
控件間通訊的最終解決方案(Simple Inherited Window) 這個(gè)例子也是為了解決控件間通訊的,但是方式完全不同,而且也是個(gè)人最為推薦的一種方法。
#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(); }
//----------------------------------------------------

現(xiàn)在讓我們來(lái)分析一下這個(gè)改進(jìn)后的版本。
首先我們創(chuàng)建了一個(gè)繼承自Fl_Window的控件SimpleWindow,然后在public部分加入了所有即將創(chuàng)建的子控件對(duì)象,這種做法的好處是在類的外部要使用這些子控件可以直接獲取。接下來(lái)讓我們看看回調(diào)是如何處理的:
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 } //----------------------------------------------------------------
這兩個(gè)方法很重要,他們成組出現(xiàn),共同解決了將回調(diào)應(yīng)用于類的問題。首先,回調(diào)函數(shù)必須是靜態(tài)的(static),也就是說(shuō),回調(diào)函數(shù)本身是無(wú)法獲取類的實(shí)例指針(THIS)的。而解決之道就是引入了2個(gè)函數(shù),由static函數(shù)來(lái)執(zhí)行另外一個(gè)inline函數(shù),執(zhí)行的關(guān)鍵就是引入了this指針,也就是利用了前面所說(shuō)的userdata。 其中的inline函數(shù)并非static函數(shù),它是類的內(nèi)部方法,這一點(diǎn)要注意。用2個(gè)函數(shù)的組合來(lái)實(shí)現(xiàn)回調(diào)在類中的應(yīng)用還是很值得的。
注意第二個(gè)函數(shù)名的尾部加了一個(gè)'_i'后綴,這是為了說(shuō)明這個(gè)函數(shù)是inline,同時(shí)這個(gè)函數(shù)實(shí)際上可以不用輸入任何參數(shù),因?yàn)樗枰臄?shù)據(jù)都在類里,可以直接使用。另外要注意的一點(diǎn)是在類似begin(),end(),show()這樣的方法前面無(wú)需指定對(duì)象,因?yàn)镾impleWindow是從Fl_Window繼承而來(lái)的。 如果在你忘記了void*在C++中的含義,這里有一個(gè)簡(jiǎn)短的復(fù)習(xí):-------------------------------------------------------------- 關(guān)于void*的簡(jiǎn)短說(shuō)明: 如果你還是C++的新手,有必要看一下這里關(guān)于空指針(void*)的說(shuō)明?;旧蟰oid*就是一個(gè)能指向任何類型的數(shù)據(jù)的指針。一般而言,指針總是有一個(gè)類型的,用來(lái)說(shuō)明所指向的數(shù)據(jù)是何種結(jié)構(gòu),但void*是沒有類型的,所以在回調(diào)中我們可以將void*轉(zhuǎn)換成任何類型的數(shù)據(jù)。
從另外一個(gè)角度來(lái)講,指針通常知道它所指向的數(shù)據(jù)是什么,但是void*是不知道的,void*只知道它指向了一堆數(shù)據(jù)而已,因此我們可以將void*轉(zhuǎn)換為任意的數(shù)據(jù)類型。
-------------------------------------------------------------- 在SimpleWindow的構(gòu)造函數(shù)中我們new了一個(gè)Fl_Button,然后設(shè)定了這個(gè)按鈕的回調(diào):
copy->callback (cb_copy, this);
本行傳入了回調(diào)函數(shù)(cb_copy)和userdata(this)2個(gè)參數(shù),userdata的類型是void*,可以傳入任意數(shù)據(jù),所以可以將this傳入。因此在cb_copy中我們就得到了對(duì)象的指針,接下來(lái)就可以調(diào)用類的內(nèi)部數(shù)據(jù)和方法了??矗嗝春?jiǎn)單有效!
Get/Set方法 FLTK中對(duì)于get/set功能的處理很有特色,即用相同名字的函數(shù)但用不同的參數(shù)和返回值來(lái)處理。get功能只有返回值沒有參數(shù),所以inp->value()就是獲取控件的label指針,相反的,out->value(const char*)就是set功能了。這種處理方法和前面第一種處理控件間通訊模式里的方法相比,不再有混亂的序號(hào)和類型轉(zhuǎn)換問題,高下立判,同時(shí)這也是類的封裝性的一個(gè)體現(xiàn)。
resizable(this); 本行代碼將窗口設(shè)定為可拉伸。我可以簡(jiǎn)單的用resizable(copy)來(lái)設(shè)定copy按鈕是相對(duì)window可拉伸的,但這樣做就只有這個(gè)按鈕是相對(duì)window拉伸的,在這里例子里,我希望所有的控件都是可拉伸的。記住一條規(guī)則:每個(gè)group中只能設(shè)定一個(gè)widget可拉伸。因此如果界面的拉伸模式比較復(fù)雜,你需要加入適當(dāng)?shù)乃胶痛怪盕l_Group控件。下面的內(nèi)容來(lái)自2004年1月17號(hào)的FLTK general新聞組: Marc R.J. Brevoort 寫到:
這里是一些小提示,仔細(xì)閱讀后試試看效果如何。
- 要學(xué)會(huì)提前設(shè)計(jì)。group中的widget只能朝一個(gè)方向拉伸:要么水平要么 垂直(這一條也對(duì)解釋下一條有幫助)。
- 如果你需要放置一個(gè)水平方向和垂直方向都拉伸的group,可以先 放置一個(gè)朝一個(gè)方向拉伸的group,然后在這個(gè)group里放置一個(gè) 子group,并設(shè)定為朝另外一個(gè)方向拉伸。
- 一個(gè)group里只有一個(gè)widget能夠設(shè)定為可拉伸(resizable)。將 多個(gè)widget設(shè)定為可拉伸的結(jié)果是只有最后一個(gè)widget才會(huì)被設(shè) 定為可拉伸。
- 將一個(gè)widget設(shè)為可拉伸意味著這個(gè)widget可以在水平和垂直2個(gè)方向上拉伸, 并不意味著這個(gè)group里的其他widget就不能拉伸。
- 在一個(gè)group中,可拉伸的widget可以在水平和垂直2個(gè)方向上拉伸, 而其他widget則只能在group拉伸的垂直方向上拉伸。
- 當(dāng)一個(gè)group只在某一個(gè)方向上可拉伸,那么只有可拉伸的widget 會(huì)跟著拉伸,其他widget不會(huì)改變位置。
希望對(duì)你有幫助
謝謝 MRJB 謝謝Marc。我將你的信息抄錄在這里,作為一個(gè)備份。
最后要分析的一行代碼是:
hide(); // which calls hide() on the SimpleWindow 你可以通過(guò)2種方法退出程序,第一種是調(diào)用exit(0)退出,但是所有之前申請(qǐng)的內(nèi)存都要靠操作系統(tǒng)來(lái)幫你銷毀;第二種是在所有會(huì)引起Fl::run()返回的window中調(diào)用hide()。故而SimpleWindow win可以正確執(zhí)行析構(gòu)函數(shù)并正常退出。有一條準(zhǔn)則要牢牢記住:不要?jiǎng)?chuàng)建全局對(duì)象,否則調(diào)用hide()就不會(huì)觸發(fā)對(duì)象的析構(gòu)函數(shù),即便這個(gè)對(duì)象不是在主函數(shù)里創(chuàng)建的。我個(gè)人從不創(chuàng)建全局對(duì)象,這是一個(gè)好習(xí)慣。注意:exit(0)不會(huì)返回到Fl::run(),而是直接退出,之前申請(qǐng)的所有內(nèi)存都要靠操作系統(tǒng)來(lái)銷毀。
現(xiàn)在你應(yīng)該意識(shí)到,我并沒有在析構(gòu)函數(shù)里釋放任何之前所創(chuàng)建的對(duì)象,這是因?yàn)镾impleWindow是繼承自Fl_Group,而Fl_Group會(huì)根據(jù)虛擬基類析構(gòu)函數(shù)(virtual base class destructor)獲取所有子控件的析構(gòu)函數(shù)并自動(dòng)銷毀它們,這也是為什么基類可以通過(guò)child(int n)和children()這樣的函數(shù)獲得子控件的原因???,既然FLTK有了這個(gè)功能,誰(shuí)還需要JAVA?
注意:下面的代碼來(lái)自FLTK general newsgroup的Jason Bryan:
void fl_exit() { while( Fl::first_window() ) Fl::first_window()->hide(); }
這個(gè)短小的函數(shù)的作用是確保所有的window都執(zhí)行了hide(),因此當(dāng)主循環(huán)Fl::run()返回的時(shí)候所以的窗口都已經(jīng)被正確銷毀了。感謝Jason提供的代碼。
事件是GUI程序的響應(yīng)機(jī)制,在FLTK的官方文檔上對(duì)事件有詳細(xì)的解釋:點(diǎn)擊這里。我個(gè)人喜歡通過(guò)例子代碼來(lái)學(xué)習(xí),所以我寫了下面這個(gè)例子,里面基本包括了所有種類的事件。 .
#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()); }
現(xiàn)在讓我們來(lái)編譯程序:
(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í)行程序后在控制臺(tái)獲得的反饋信息(注意,不要通過(guò)dev-c++來(lái)執(zhí)行,因?yàn)榭刂婆_(tái)被隱藏了,會(huì)看不到這些調(diào)試信息)。如果在windows平臺(tái),可以在MSYS/MinGW下執(zhí)行。

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