• <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, 評(píng)論 - 55, 引用 - 0
            數(shù)據(jù)加載中……

            FLTK新手入門[翻譯]


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


            新手入門
            版本: 1.1

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


            更新歷史
            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.



            目標(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):

            你需要有基本的C++編程能力,尤其重要的是對(duì)類、繼承、指針和動(dòng)態(tài)內(nèi)存收集的理解,否則在閱讀本教程時(shí)會(huì)遇到困難。

            下面是一些學(xué)習(xí)C++的網(wǎng)站:

            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協(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è)人比較喜歡 Anjutagedit.
            "#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ù),如果沒(méi)有包含,./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)編譯代碼
            - 如果你沒(méi)有找到合適的編輯器,可以用Dev-C++ without MinGW代替
            - 提示: MSYS自帶了為很多人所喜歡的 Vim編輯器。對(duì)于剛剛接觸這個(gè)編輯器的新手而言,不利的一面是Vim需要花一定的時(shí)間進(jìn)行學(xué)習(xí),好處是一旦學(xué)會(huì)了,你會(huì)對(duì)它愛(ài)不釋手。個(gè)人覺(jué)得這個(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();
            }


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


            下面分析一下代碼

               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沒(méi)有的就需要做這樣的強(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ù)是沒(méi)有定義參數(shù)名的void*,因?yàn)樵诤瘮?shù)里我們沒(méi)有使用這個(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*在這里沒(méi)有用到,所以沒(méi)有設(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ā)控件重繪,所以如果你沒(méi)有手動(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)部只保存了傳入字符串的指針,并沒(méi)有復(fù)制傳入的字符串。這就意味著,如果傳入的是靜態(tài)字符串,比如"Good job",沒(méi)有問(wèn)題,但是如果傳入的是一個(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)窗口之后:
            ex_2a.png       ex_2b.png

            保存后編譯,執(zhí)行下面的指令:

            "fltk-config --compile labeltest.cc"

            編譯成功后執(zhí)行:"./labeltest"

            發(fā)現(xiàn)了么?有個(gè)很嚴(yán)重的問(wè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í),問(wèn)題就出現(xiàn)了。怎么解決這個(gè)問(wèn)題呢?之前的常用做法是編寫一個(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ù)制了一份字符串,問(wèn)題解決!這個(gè)問(wèn)題是不是有點(diǎn)龜毛?幸好在FLTK中只有處理Label時(shí)需要注意,其他地方都不存在這個(gè)問(wèn)題。



            控件間通訊的簡(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í)行程序

            simple win 2



            注意:在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è)很不好的控件間通訊方式,首先它很丑陋而且沒(méi)有可讀性,再者這種方式必須要時(shí)刻小心子控件的序號(hào),最后在處理序號(hào)的時(shí)候沒(méi)有范圍檢查。

            提示:不知道你有沒(méi)有注意到,在這個(gè)例子里沒(méi)有手工刪除動(dòng)態(tài)創(chuàng)建的'win'對(duì)象。在教程的第一個(gè)例子里窗口對(duì)象會(huì)在程序退出時(shí)自動(dòng)銷毀,因?yàn)閷?duì)象是創(chuàng)建在堆中的,但是這個(gè)例子里的win卻是創(chuàng)建在棧中,而且并沒(méi)有調(diào)用delete來(lái)進(jìn)行銷毀,甚至使用exit(0)直接退出程序,這種方式會(huì)不會(huì)出現(xiàn)內(nèi)存泄漏?當(dāng)然不會(huì)!原因后面的章節(jié)會(huì)講到,請(qǐng)接著往下看。

            回到控件間通訊的問(wèn)題,如果你想到可以用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);
            }



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

            下面這行代碼是整個(gè)例子的核心:

            copy->callback( copy_cb, inp );

            有沒(méi)有注意到?我們將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é)束程序卻沒(méi)有注銷任何對(duì)象,這是個(gè)問(wèn)題吧?是的,但不要著急,下面這個(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();
            }

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



            ex_5.png

            現(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)用于類的問(wèn)題。首先,回調(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ō)明。基本上void*就是一個(gè)能指向任何類型的數(shù)據(jù)的指針。一般而言,指針總是有一個(gè)類型的,用來(lái)說(shuō)明所指向的數(shù)據(jù)是何種結(jié)構(gòu),但void*是沒(méi)有類型的,所以在回調(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功能只有返回值沒(méi)有參數(shù),所以inp->value()就是獲取控件的label指針,相反的,out->value(const char*)就是set功能了。這種處理方法和前面第一種處理控件間通訊模式里的方法相比,不再有混亂的序號(hào)和類型轉(zhuǎn)換問(wè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í)到,我并沒(méi)有在析構(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提供的代碼。



            事件(Events)

            事件是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í)行。

              console1 


                 event1


            未完待續(xù)..............




             

            更多內(nèi)容

            等我學(xué)到更多關(guān)于FLTK的內(nèi)容我會(huì)更新到站點(diǎn)上。




            關(guān)于本人

            我在加拿大卑詩(shī)省一所大學(xué)里教物理和計(jì)算機(jī)編程(C++)。開發(fā)是我的業(yè)余愛(ài)好。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

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

            評(píng)論

            # re: FLTK新手入門[翻譯]  回復(fù)  更多評(píng)論   

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

            # re: FLTK新手入門[翻譯]  回復(fù)  更多評(píng)論   

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

            # re: FLTK新手入門[翻譯]  回復(fù)  更多評(píng)論   

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

            # re: FLTK新手入門[翻譯]  回復(fù)  更多評(píng)論   

            非常感謝,對(duì)于入門學(xué)習(xí)非常有用。
            2014-12-31 10:04 | pony

            # re: FLTK新手入門[翻譯]  回復(fù)  更多評(píng)論   

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

            # re: FLTK新手入門[翻譯]  回復(fù)  更多評(píng)論   

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

            # re: FLTK新手入門[翻譯]  回復(fù)  更多評(píng)論   

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

            # re: FLTK新手入門[翻譯]  回復(fù)  更多評(píng)論   

            感覺(jué)windows的界面編程風(fēng)格遠(yuǎn)遠(yuǎn)復(fù)雜過(guò)linux風(fēng)格的,windows寫個(gè)基本界面需要一堆的代碼,而mfc這些東西,則是超級(jí)反人類,說(shuō)好聽點(diǎn)那是強(qiáng)大,說(shuō)難聽點(diǎn)就是用復(fù)雜的邏輯來(lái)做簡(jiǎn)單的東西。難怪人家說(shuō)在linux可以學(xué)習(xí)到更多東西。
            2016-02-09 23:31 | 龍哥

            只有注冊(cè)用戶登錄后才能發(fā)表評(píng)論。
            網(wǎng)站導(dǎo)航: 博客園   IT新聞   BlogJava   博問(wèn)   Chat2DB   管理


            人妻少妇久久中文字幕| 99久久99久久精品免费看蜜桃 | 国产精品美女久久久久网| 午夜精品久久久久9999高清| 久久夜色精品国产亚洲| 国产精品一区二区久久| 久久精品夜夜夜夜夜久久| 狼狼综合久久久久综合网| 免费无码国产欧美久久18| 日韩欧美亚洲国产精品字幕久久久| 国产高清国内精品福利99久久| 精品久久一区二区三区| 1000部精品久久久久久久久| 97热久久免费频精品99| 69SEX久久精品国产麻豆| 久久国产一区二区| 2021少妇久久久久久久久久| 狠狠色丁香婷综合久久| 久久综合综合久久97色| 国产精品丝袜久久久久久不卡| 国产99久久久久久免费看| 国产精品欧美久久久久无广告| 久久精品国产色蜜蜜麻豆| 久久亚洲中文字幕精品一区| 日韩十八禁一区二区久久| 久久精品国产欧美日韩99热| 亚洲欧美日韩中文久久| 国产成人久久精品激情| 久久免费高清视频| 亚洲精品国产第一综合99久久| 久久久久久曰本AV免费免费| 久久久久99精品成人片欧美| 久久综合狠狠综合久久激情 | 久久国产视屏| 久久精品国产男包| www.久久热.com| 久久影院久久香蕉国产线看观看| 一本色综合网久久| 99国内精品久久久久久久| 2021国产精品午夜久久| 国产精品久久网|