随笔 - 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。可以通过查看Fl_Group的官方文档了解更多关于子控件的相关信息。另外要注意的一点是,只有从Fl_Group继承的控件才存在这样的父子关系,幸运的是Fl_Window就是从Fl_Group继承而来。这种做法有一个很大的好处,就是只需要销毁Fl_Window(父控件),子控件就可以自动销毁无需人为操作。以这个例子而言,当窗口'win'被销毁时,子控件'but'就会自动被销毁。这是FLTK的一个特点。在本例中win和but都是在堆中创建,无论如何都会自动销毁,所以还无从证明,但后面有控件创建于栈的例子,就很好的演示了这一点。这个特点非常的方便。

   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控件的一些方法。



   b->label("Good job");
本行改变了按钮的标签(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 阅读(20716) 评论(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 | 龙哥

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理