|
[本教程翻译自http://www3.telus.net/public/robark/ ] 新手入门 版本: 1.1 目标人群
本教程是为那些打算编写GUI的C++程序员而准备的。FLTK的官方文档编写的很合理,从简单的例子(Hello world)到复杂的例子(editor.cxx)一点点循序渐进。本教程是从FLTK Basics开始展开的。另外这里 是FLTK 2.0(Beta)的文档,同样是一个很好的资源。希望看完本教程能让你尽快的进入FLTK编程。 Enjoy!
为何使用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下有很多优秀的文本编辑器和编程工具,我个人比较喜欢 Anjuta 和 gedit. "#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(); } 之前: 之后:
下面分析一下代码
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(); } 之前: 移动窗口之后:
保存后编译,执行下面的指令:
"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); }
----------------------------------------------------------------------------
现在来执行程序
注意:在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); }
之前: 之后:
下面这行代码是整个例子的核心:
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(); }
//----------------------------------------------------
现在让我们来分析一下这个改进后的版本。
首先我们创建了一个继承自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提供的代码。
事件是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下执行。
未完待续..............
我在加拿大卑诗省一所大学里教物理和计算机编程(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
|