• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            隨筆 - 16, 文章 - 0, 評論 - 55, 引用 - 0
            數據加載中……

            FLTK新手入門[翻譯]


            [本教程翻譯自
            http://www3.telus.net/public/robark/
            ]


            新手入門
            版本: 1.1

            目錄
            更新歷史
            目標人群
            知識預備
            為何使用FLTK編寫GUI程序?
            獲取FLTK
            進入FLTK基礎課程
            視頻教程 
            一個簡單的窗口程序(Simple Window Function)
            有關控件Label的陷阱(Widget Label Pitfall)  (新)
            控件間通訊的簡單示例(Simple Window with widgets that talk to each other)
            控件間通訊的改進方案(Two widgets talking)  (新)
            控件間通訊的最終解決方案(Simple Inherited Window)
            事件(Events) (新)
            更多內容
            關于本人


            更新歷史
            Date: Jan 4/05
            This tutorial has been updated. Cleaned up code and explanations in all sections. More examples and screenshots. Updated to match FLTK roadmap. New Sections: Widget Label Pitfall, Two widgets Talking.
            Under construction: Events, Layouts, Browser, Makefiles

            Date: Jan 1/05
            Released Fl_RPNCalc version 1.1

            Date: Sept 7/04
            Check out my latest contribution. Fl_RPNCalc 1.0 is a simple RPN Calculator with keyboard numpad functionality.



            目標人群

            本教程是為那些打算編寫GUI的C++程序員而準備的。FLTK的官方文檔編寫的很合理,從簡單的例子(Hello world)到復雜的例子(editor.cxx)一點點循序漸進。本教程是從FLTK Basics開始展開的。另外這里 是FLTK 2.0(Beta)的文檔,同樣是一個很好的資源。希望看完本教程能讓你盡快的進入FLTK編程。 Enjoy!



            知識預備(Prerequisite):

            你需要有基本的C++編程能力,尤其重要的是對類、繼承、指針和動態內存收集的理解,否則在閱讀本教程時會遇到困難。

            下面是一些學習C++的網站:

            Thinking In C++ 2nd Edition by Bruce Eckel (Free Online Book)
            http://cplus.about.com/library/blcplustut.htm
            http://www.cplusplus.com/doc/tutorial/



            為何使用FLTK編寫GUI程序(Why use FLTK as opposed to other GUI toolkits?)

            - FLTK是開源的,基于GUN LGPL協議。
              具體協議參看這里
            - 開發效率高,易懂
            - 源代碼基于C++
            - 編譯出來的程序尺寸小,執行速度快
            - 跨平臺(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下有很多優秀的文本編輯器和編程工具,我個人比較喜歡 Anjutagedit.
            "#make install"會將編譯后的相關文件安裝到 /usr/local/ 和他的子目錄下。
            雖然大多數的linux發行版都缺省內置了X開發包,但還是請確認操作系統中是否已經包含了X開發庫,如果沒有包含,./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的,所以請使用上面提到的版本。

            -創建一個新的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)


            - 按照上面的步驟安裝
            - 執行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,理由是,首先,你可以學到如何通過控制臺來編譯程序,這會激勵你去學習命令行系統(對于linux而言,控制臺是非常有用的),甚至會讓你學會如何編寫makefiles,很多linux專家都善于使用命令行來完成工作。其次,你可以獲得完整的FLTK文件,特別是包括了所有示例程序源代碼的test目錄,在方案1中的Devpak包里是不包含test的。

            注:實際上windows下使用vc更方便,在源代碼的ide目錄下有vc6,vc2008, vc2010的工程文件,可以直接使用。目前1.3.x版本的vc6需要修改幾處源代碼才能通過編譯,主要是有些代碼用到了新的c++標準,vc6不支持。

            top


            我不想從FLTK的官方文檔里復制大段的內容,所以請打開FLTK Basics并進行學習,之后再返回本教程并繼續閱讀下面的內容。


            視頻教程(Flash Video !)

            本節包括了一段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來調節聲音。



            一個簡單的窗口程序(Simple Window Function)

            好了,現在讓我們看看實際的代碼。我們要創建一個帶有按鈕的窗口,你可以把下面的代碼復制到編輯器里,保存,然后在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();
            }


            之前:                                                之后:
            ex_1a.png      ex_1b.png


            下面分析一下代碼

               Fl_Window win(300,200, "Testing");
            創建一個新的窗口對象,參數包括窗口的寬度,高度和標題。注意:這個對象是在堆中創建的,所以在主程序退出時會自動注銷。

               win.begin()
            此行代碼不是必需的,但我還是建議加上它,因為它讓代碼更具有可讀性。這行代碼和win.end()之間一般放置創建子控件(widgets)的代碼,這些控件都從屬于當前的Fl_Window。

                Fl_Button but( 10, 150, 70, 30, "Click me");
            本行代碼創建了一個按鈕,參數(x, y, width, height, label)中,x和y的基點(0,0)是窗口的左上角。注意:此按鈕是從屬于父窗口win的子控件,這也意味著此按鈕是窗口的第一個子控件,編號為0。可以通過查看
              
            win.end();
            本行將前面創建的控件都設定為從屬于父窗口(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中,當事件發生時就會觸發回調,這是GUI程序的基本功能。像鼠標點擊、按鍵等都屬于事件。更多關于事件的描述可以參看后面的"事件"章節。在本例中,按鈕點擊后觸發回調改變按鈕的屬性。

            需要注意的是,只有繼承自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庫都有類似本行的實現。主要功能是讓程序進入一個不斷等待事件發生的死循環,當所有的窗口都被關閉或隱藏時,函數run()返回0并退出程序。另外一個退出程序的辦法是執行exit(0),后面還會講到。

            接下來講解but_cb這個回調處理函數:

               void but_cb( Fl_Widget* o, void* ) {
            觸發事件的控件是按鈕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,所以才能做這樣的轉換。做這種轉換是因為需要執行Fl_Button控件的一些方法。



              
            本行改變了按鈕的標簽(Label)。注意,label()和value()這2個方法執行后會自動引發控件重繪,其他方法若想執行完之后重繪控件就要手動執行redraw(),就像接下來的兩行代碼。順便說一句,控件不是重新復制了一份傳入的Label字符串,而是僅僅保存了傳入字符串的指針,更多解釋請參看下一章節。

                b->resize(10,150,140,30);
            本行代碼改變了按鈕的位置和尺寸,四個參數重新定義了按鈕的位置和尺寸,其中位置不變,寬度不變,長度拉伸一倍。特別需要注意的一點是,這個方法不會自動觸發控件重繪,所以如果你沒有手動執行redraw(),按鈕的尺寸是看不到變化的。所以就有了下一行代碼。
                 
                b->redraw();
            本行代碼重繪了控件,當執行完resize()方法之后需要執行本方法。這種盡量不做多余處理的模式也是FLTK的效率和速度非常快的一個原因。





            **  有關控件Label的陷阱(Widget Label Pitfall) **

            當設定一個控件的Label時,控件內部只保存了傳入字符串的指針,并沒有復制傳入的字符串。這就意味著,如果傳入的是靜態字符串,比如"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();
            }


            之前:                               移動窗口之后:
            ex_2a.png       ex_2b.png

            保存后編譯,執行下面的指令:

            "fltk-config --compile labeltest.cc"

            編譯成功后執行:"./labeltest"

            發現了么?有個很嚴重的問題:點擊按鈕,然后移動窗口或是最小化/還原窗口,此時會觸發按鈕的重繪,看看按鈕的Label變成了什么?亂碼!為什么?因為FLTK使用了一個已經不存在的指針。按鈕一開始設定的Label是一個靜態字符串"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官方文檔中展示的例子大都是創建一個窗口,然后簡單的在上面放置一些控件,當你需要在多個控件之間傳遞信息的時候,這種方式會讓事情變得有點麻煩。看看下面的例子(這個例子同時也是上面所說的控件創建于棧的情況),親自編譯一下看看。注意,這個例子是可以工作的,但是里面使用的通訊方法并不推薦。

            #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);
            }


            ----------------------------------------------------------------------------

            現在來執行程序

            simple win 2



            注意:在Copy的o和Quit的Q下面各有一條線,這表示可以通過ALT-c和ALT-q觸發按鈕事件,要實現這個功能很簡單,就是在初始化按鈕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。

            我需要再次提醒你,這是一個很不好的控件間通訊方式,首先它很丑陋而且沒有可讀性,再者這種方式必須要時刻小心子控件的序號,最后在處理序號的時候沒有范圍檢查。

            提示:不知道你有沒有注意到,在這個例子里沒有手工刪除動態創建的'win'對象。在教程的第一個例子里窗口對象會在程序退出時自動銷毀,因為對象是創建在堆中的,但是這個例子里的win卻是創建在棧中,而且并沒有調用delete來進行銷毀,甚至使用exit(0)直接退出程序,這種方式會不會出現內存泄漏?當然不會!原因后面的章節會講到,請接著往下看。

            回到控件間通訊的問題,如果你想到可以用void* userdata來傳遞數據,那么就讓我們看看接下來的章節。


            控件間通訊的改進方案(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);
            }



            之前:                                                   之后:
            ex_4a.png          ex_4b.png

            下面這行代碼是整個例子的核心:

            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來傳遞各種數據。 接下來的章節我們會演示真正的控件間通訊辦法。

            另外如果你是一名有經驗的C++程序員,你會注意到上面的例子里用exit(0)來直接結束程序卻沒有注銷任何對象,這是個問題吧?是的,但不要著急,下面這個章節將揭開所有的謎團。



            控件間通訊的最終解決方案(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();
            }

            //----------------------------------------------------



            ex_5.png

            現在讓我們來分析一下這個改進后的版本。

            首先我們創建了一個繼承自Fl_Window的控件SimpleWindow,然后在public部分加入了所有即將創建的子控件對象,這種做法的好處是在類的外部要使用這些子控件可以直接獲取。接下來讓我們看看回調是如何處理的:

            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
            }

            //----------------------------------------------------------------

            這兩個方法很重要,他們成組出現,共同解決了將回調應用于類的問題。首先,回調函數必須是靜態的(static),也就是說,回調函數本身是無法獲取類的實例指針(THIS)的。而解決之道就是引入了2個函數,由static函數來執行另外一個inline函數,執行的關鍵就是引入了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拉伸的,在這里例子里,我希望所有的控件都是可拉伸的。記住一條規則:每個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)退出,但是所有之前申請的內存都要靠操作系統來幫你銷毀;第二種是在所有會引起Fl::run()返回的window中調用hide()。故而SimpleWindow win可以正確執行析構函數并正常退出。有一條準則要牢牢記住:不要創建全局對象,否則調用hide()就不會觸發對象的析構函數,即便這個對象不是在主函數里創建的。我個人從不創建全局對象,這是一個好習慣。注意:exit(0)不會返回到Fl::run(),而是直接退出,之前申請的所有內存都要靠操作系統來銷毀。

            現在你應該意識到,我并沒有在析構函數里釋放任何之前所創建的對象,這是因為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都執行了hide(),因此當主循環Fl::run()返回的時候所以的窗口都已經被正確銷毀了。感謝Jason提供的代碼。



            事件(Events)

            事件是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

            下面是執行程序后在控制臺獲得的反饋信息(注意,不要通過dev-c++來執行,因為控制臺被隱藏了,會看不到這些調試信息)。如果在windows平臺,可以在MSYS/MinGW下執行。

              console1 


                 event1


            未完待續..............




             

            更多內容

            等我學到更多關于FLTK的內容我會更新到站點上。




            關于本人

            我在加拿大卑詩省一所大學里教物理和計算機編程(C++)。開發是我的業余愛好。2003年開始接觸FLTK,對于類似FLTK這樣的開源/自由軟件我一直很喜歡。FLTK開創了編程的一方天地。很感謝FLTK新聞組的Bill, Mike, Matt, Greg, Jason, Marc, Alexey, Roman 和 Dejan,如果有遺漏的純屬個人記憶力不好。是你們讓FLTK保持了活力和能力,非常感謝!

            你可以通過links/bazaar tutorial page來給本教程評論建議和打分,如果你想給我發郵件,請發到:

            fltk_beginner_tutorial@yahoo.com

            Robert Arkiletian

            posted on 2012-05-13 15:01 cyantree 閱讀(21088) 評論(8)  編輯 收藏 引用

            評論

            # re: FLTK新手入門[翻譯]  回復  更多評論   

            非常感謝,您的東西對我很有用。
            2012-06-07 15:31 | zyx

            # re: FLTK新手入門[翻譯]  回復  更多評論   

            十分感謝你的付出,這對我很有幫助
            2013-11-28 09:40 | xue

            # re: FLTK新手入門[翻譯]  回復  更多評論   

            十分感謝,您的東西入門真的好有幫助
            2014-05-09 09:37 | uutian

            # re: FLTK新手入門[翻譯]  回復  更多評論   

            非常感謝,對于入門學習非常有用。
            2014-12-31 10:04 | pony

            # re: FLTK新手入門[翻譯]  回復  更多評論   

            fltk真變*態,同樣的代碼,在windows下中文顯示就好好的(只要用UTF-8編碼就行),Linux下無論怎樣都不行,顯示成框框,連替換系統字體這陰招都用了,還是不行。
            哪位大蝦有解決方案?
            (我用的fltk1.3.3)
            2015-05-02 10:15 | dadajia

            # re: FLTK新手入門[翻譯]  回復  更多評論   

            to dadajia:
            linux下面顯示中文需要安裝xft,光安裝xfree是不夠的,xft=x freetype
            2015-05-10 11:21 | cyantree

            # re: FLTK新手入門[翻譯]  回復  更多評論   

            內容真的很有用,謝謝樓主。
            但是樓主使用的“棧”、“堆”兩個詞和平時說的正好相反
            2015-12-23 22:09 | westcoast

            # re: FLTK新手入門[翻譯]  回復  更多評論   

            感覺windows的界面編程風格遠遠復雜過linux風格的,windows寫個基本界面需要一堆的代碼,而mfc這些東西,則是超級反人類,說好聽點那是強大,說難聽點就是用復雜的邏輯來做簡單的東西。難怪人家說在linux可以學習到更多東西。
            2016-02-09 23:31 | 龍哥
            久久久久亚洲Av无码专| 色婷婷噜噜久久国产精品12p| 国产精品午夜久久| 狠狠色噜噜色狠狠狠综合久久| 国产精品成人久久久久三级午夜电影 | 国产精品久久久久久久久鸭| 中文字幕日本人妻久久久免费| 国产精品一区二区久久精品无码 | 精品久久久久久综合日本| 国产精品久久久久久久久免费| 狼狼综合久久久久综合网| 999久久久国产精品| 国内精品久久久久影院老司 | 久久精品不卡| 色狠狠久久综合网| 亚洲精品无码久久久久sm| 97久久综合精品久久久综合| 粉嫩小泬无遮挡久久久久久| 欧美精品丝袜久久久中文字幕| 国产一级持黄大片99久久| 久久久久亚洲AV片无码下载蜜桃| 国产精品久久永久免费| 2021国产成人精品久久| 色婷婷久久久SWAG精品| 久久亚洲AV无码精品色午夜麻豆 | 亚洲AV日韩精品久久久久久| 久久久久亚洲AV无码专区体验| 久久91精品国产91久久小草| 精品国产综合区久久久久久| 99久久免费国产特黄| 久久九色综合九色99伊人| 99久久国产综合精品女同图片| 91久久成人免费| 狠狠久久综合| 99久久精品日本一区二区免费| 伊人久久大香线蕉综合网站| 精品无码久久久久久尤物| 国产精品免费久久久久影院| 亚洲国产另类久久久精品小说| 热久久国产欧美一区二区精品| 久久久久亚洲AV片无码下载蜜桃|