青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

隨筆 - 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媪酥羔槺旧?,而控件重繪Label的時(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ō)明?;旧蟰oid*就是一個(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ù)獲得子控件的原因??矗热籉LTK有了這個(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 閱讀(21182) 評(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   管理


青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <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>
            欧美二区在线| 久久精品一二三区| 欧美日韩一区二区三区四区在线观看| 亚洲国产精品va在线观看黑人| 另类国产ts人妖高潮视频| 久久久www成人免费精品| 精品粉嫩aⅴ一区二区三区四区| 另类国产ts人妖高潮视频| 老司机凹凸av亚洲导航| 亚洲免费观看高清完整版在线观看| 亚洲国产精品尤物yw在线观看 | 性久久久久久久久| 亚洲欧美日韩精品久久久久| 国产综合久久久久影院| 欧美国产精品中文字幕| 欧美日韩亚洲一区二区三区在线观看| 亚洲欧美日韩国产综合| 久久精品视频免费播放| 亚洲理伦在线| 欧美一级一区| 日韩午夜在线观看视频| 亚洲一区二区av电影| 激情久久五月天| 日韩一级黄色大片| 黄色av成人| av成人激情| 在线观看视频日韩| 亚洲图片欧美一区| 亚洲激情婷婷| 亚洲欧美视频| 亚洲美女尤物影院| 久久精品视频在线| 亚洲一区二区三区三| 久久久久久久综合日本| 亚洲伊人久久综合| 欧美二区乱c少妇| 欧美一区二区精品在线| 狠狠色狠狠色综合系列| 亚洲国产精品女人久久久| 国产精品推荐精品| 亚洲美女一区| 亚洲国产视频一区| 亚洲欧美久久| 亚洲一区二区三区四区五区黄 | 噜噜噜躁狠狠躁狠狠精品视频| 欧美日韩免费高清| 欧美高清在线| 亚洲二区视频| 久久精品一区蜜桃臀影院| 午夜精彩国产免费不卡不顿大片| 欧美韩国日本综合| 亚洲国产第一| 亚洲黄色一区二区三区| 久久久精品2019中文字幕神马| 午夜久久久久久| 欧美视频日韩视频在线观看| 亚洲精品123区| 亚洲国产精品va在线看黑人| 久久精品国产77777蜜臀| 欧美在线|欧美| 国产欧美一区二区三区国产幕精品 | 国产区日韩欧美| 亚洲一区视频在线| 欧美一级视频精品观看| 国产精品久久久久高潮| 一本久久综合| 亚洲欧美成人网| 国产精品久久久久久一区二区三区 | 亚洲在线观看免费视频| 欧美日韩在线视频观看| 亚洲精品一二| 在线中文字幕一区| 欧美性色视频在线| 亚洲性视频网站| 欧美在线视频不卡| 国产自产在线视频一区| 久久婷婷蜜乳一本欲蜜臀| 欧美sm重口味系列视频在线观看| 亚洲第一免费播放区| 欧美二区乱c少妇| 亚洲人在线视频| 亚洲在线观看视频网站| 国产日本欧美视频| 久久久噜噜噜久久| 亚洲人体一区| 欧美亚洲一区在线| 亚洲电影免费在线观看| 欧美精品久久久久a| 亚洲午夜视频在线观看| 久久久久久久久久看片| 亚洲国产一区在线| 国产精品a久久久久| 欧美一区二区视频97| 欧美成人激情视频免费观看| 夜夜嗨av色一区二区不卡| 国产精品最新自拍| 免费在线成人| 亚洲综合第一| 亚洲福利视频一区二区| 亚洲综合色在线| 亚洲黄色一区| 欧美77777| 日韩午夜激情| 国产综合视频| 欧美国产三区| 欧美在线日韩精品| 亚洲靠逼com| 久久久久久久综合| 亚洲视频观看| 亚洲国产成人av在线| 国产精品久久国产愉拍| 米奇777在线欧美播放| 亚洲系列中文字幕| 亚洲人成77777在线观看网| 久久成人综合视频| 亚洲一级免费视频| 亚洲乱亚洲高清| 国产主播精品在线| 国产精品久久久一区麻豆最新章节 | 亚洲欧美日韩精品一区二区| 亚洲人成精品久久久久| 国产在线高清精品| 国产精品免费观看视频| 欧美屁股在线| 美女诱惑一区| 久久久国产成人精品| 亚洲资源av| 亚洲深爱激情| 亚洲精品久久久久久久久久久| 蜜桃av噜噜一区| 久久九九99视频| 性娇小13――14欧美| 亚洲你懂的在线视频| 亚洲无限乱码一二三四麻| 亚洲精品色图| 日韩亚洲欧美在线观看| 亚洲激情欧美激情| 91久久精品一区二区别| 亚洲第一精品夜夜躁人人躁 | 欧美区高清在线| 欧美波霸影院| 欧美激情综合五月色丁香小说| 久久久欧美一区二区| 久久蜜臀精品av| 久久久噜噜噜久久中文字免| 久久精品一区中文字幕| 久久激情综合| 久久一综合视频| 久久综合亚洲社区| 欧美激情精品久久久| 欧美国产先锋| 欧美日韩国内自拍| 国产精品国产三级国产专区53| 国产精品va在线播放| 国产精品一区二区欧美| 国内精品视频久久| 亚洲国产精品久久久久秋霞不卡| 亚洲精品久久久一区二区三区| 日韩一区二区精品| 亚洲小说春色综合另类电影| 午夜激情综合网| 久久这里只有精品视频首页| 女人天堂亚洲aⅴ在线观看| 亚洲激情一区二区| 亚洲视频在线看| 久久成人精品一区二区三区| 久久综合伊人| 欧美日韩一区二区在线| 国产色产综合产在线视频| 影音先锋日韩资源| aa级大片欧美三级| 欧美在线观看一区二区| 欧美11—12娇小xxxx| 亚洲精品一区二区在线| 午夜精品一区二区三区四区 | 另类尿喷潮videofree| 玖玖玖国产精品| 最新国产成人av网站网址麻豆| 亚洲精品视频免费| 亚洲欧美文学| 免费视频一区二区三区在线观看| 欧美日韩中文字幕在线视频| 国产亚洲欧洲| 一本大道av伊人久久综合| 久久精品91久久香蕉加勒比| 亚洲激精日韩激精欧美精品| 亚洲欧美另类在线观看| 欧美电影在线观看完整版| 国产精品婷婷午夜在线观看| 最近中文字幕mv在线一区二区三区四区| 亚洲在线黄色| 亚洲国产精品123| 亚洲欧美日韩中文视频| 欧美人与性动交cc0o| 狠狠噜噜久久| 午夜在线a亚洲v天堂网2018| 欧美激情一区二区在线| 久久国产免费| 国产精品日产欧美久久久久| 日韩视频免费观看高清完整版|