深入布局
这节课我们学习如何把Fltk的各种Widgets排列在一起,对控件大小变化做出正确的反应。这里主要学习两个类:Fl_Widget和Fl_Group,他们是继承关系,前者是所有控件的父类,不能创建(只能继承),后者是所有容器的父类,可以作为通用容器创建。
上节课学的静态布局是布局的基础,但是不能对容器大小的改变做出正确的反应。使用Fl_Group把各种控件逻辑上组织起来,然后使用resizable函数,可以实现较为高级的布局。
1. 位置与大小
首先我们讲Fl_Widget有三个改变大小和位置的函数,分别如下:
size(width, height)
position(x, y)
resize(x, y, width, height)
这三个函数分别改变一个控件的大小、位置、同时大小和位置,比较简单,就不多说了。记着调用这些函数后可能需要调用redraw()重画控件。
2. 父与子
重点在于Fl_Group,这个类包含了一个子控件的列表,可以通过一系列方法来控制这个列表,例如:
Fl_Widget *array() //获得这个列表(数组)
int children() //获得列表的大小(有几个子控件)
Fl_Widget *child(int n) //获得第n个子控件
int find(Fl_Widget *) //查找某子控件在列表中的位置
clear() //清空整个列表,删除所有子控件
add(Fl_Widget *) // 把控件添加到列表尾
insert(Fl_Widget *, int index) or insert(Fl_Widget* , Fl_Widget * before) // 把控件添加到某个位置或控件之后
remove(Fl_Widget *) or remove(int index) // 删除某个控件
//注意添加操作会把控件从当前的父控件的列表删除,添加到另一个父控件的列表
// 删除操作并不会在内存删除控件,只是从列表中移除
相应的Fl_Widget有个方法叫parent()可以找到其父容器。因此,如果在一开始没有使用begin和end正确添加子控件,使用这些方法也可以比较方便地添加、删除、管理子控件。
3. 重头戏:resizable(Fl_Widget *)
这是Fl_Group的一个方法,在上一节课中我们也看到了,把参数设为容器自己使得自己可以改变大小,而且所有的子控件会在改变大小的时候随之缩放。但是更常用的是把某一个子控件设为resizable,这样不仅容器可以改变大小,也会使得相应的子控件有较好的行为。下面这个图摘自Fltk 1.3 Manual,显示了resizable的行为:
大家可以看到,在resizable的横向范围和纵向范围内的控件都改变了大小和位置,而在四个角位置的控件没有改变大小。在横向范围内的控件只改变高度,在纵向范围内的控件只改变宽度。这样,只要使用Fl_Group和不可见的Fl_Box,就可以完成各种复杂的缩放行为。例如下面的对话框:
记住Fltk的文本输入框前面的文字标签是和输入框绑定在一起的,不用另外使用标签控件。而输入框的左上角坐标却不包含标签,也就是说左上角是输入框的左上角,而不是加上标签后的左上角。这在指定坐标的时候要注意。
上面的程序,“姓名”一行包含在一个Group中,这个Group的输入框是它的resizable,从而“检查”按钮在输入框的横向范围内,只能改变高度,但是整个Group在Multiline_Input的纵向范围内,所以只能改变宽度,于是Group内的Input可以改变宽度,而“检查”按钮大小就不能变化了。“邮件”行的实现同上。最下面的“好的”按钮,实现和上面“姓名”和“邮件”一样,只不过把Input换成了一个不可见的Fl_Box。整个源程序如下(layout.cc):
#include "FL/Fl.H"
#include "FL/Fl_Double_Window.H"
#include "FL/Fl_Group.H"
#include "FL/Fl_Button.H"
#include "FL/Fl_Input.H"
#include "FL/Fl_Multiline_Input.H"
#include "FL/Fl_Box.H"
int main(int argc, char **argv)
{
Fl_Double_Window *w = new Fl_Double_Window(100, 200, 460, 320, "Fltk布局");
//size_range只可以用在顶层窗口,设置窗口resize的范围。它有7个参数,但只有前4个
//是在各个系统下都有效的。前两个是最小宽度和最小高度。后两个是最大宽度和
//最大高度。后两个设置成零,说明无限制。如果设置成和前面最小值一样,则不可变动
//大小。例如
//w->size_range(w->w(), w->h(), 0, w->h());
//设置窗口高度不变,宽度可变。
w->size_range(w->w(), w->h(), 0, 0);
Fl_Group * group1 = new Fl_Group(10, 10, w->w()-20, 30);
Fl_Input *input1 = new Fl_Input(50, 10, w->w()-175, 30, "姓名:");
Fl_Button *b1 = new Fl_Button(w->w()-110, 10, 100, 30, "检查!");
group1->end();
group1->resizable(input1);
Fl_Group * group2 = new Fl_Group(10, 50, w->w()-20, 30);
Fl_Input *input2 = new Fl_Input(50, 50, w->w()-175, 30, "邮件:");
Fl_Button *b2 = new Fl_Button(w->w()-110, 50, 100, 30, "检查!");
group2->end();
group2->resizable(input2);
Fl_Multiline_Input * comments = new Fl_Multiline_Input(50, 100, w->w()-60, w->h()-150, "评论:");
Fl_Group *group3 = new Fl_Group(10, w->h()-10-30, w->w()-20, 30);
Fl_Box *b = new Fl_Box(10, w->h()-10-30, group3->w() - 100, 30); //Fl_Box是默认不可见的
Fl_Button *b3 = new Fl_Button(w->w()-10-100, w->h()-10-30, 100, 30, "好的");
group3->end();
group3->resizable(b);
w->end();
w->resizable(comments);
w->show();
return Fl::run();
}
4. 使用Fl_Pack
Fl_Pack是Fl_Group的子类型,实现了额外的一些布局功能。Fl_Pack能自动把它的子控件按横向或纵向排列(通过方法type(Fl_Pack::HORIZONTAL或Fl_Pack::VERTICAL)来指定),这样子控件就不用再考虑位置(随便指定位置就行),但是还要指定正确的大小。这就是Fl_Pack和Fl_Group的不同之处。例如上述程序中使用Group的代码,可以用以下代码替换
Fl_Pack
*
pack1
=
new Fl_Pack(50, 10,
w->w()-20, 30);
pack1->type(Fl_Pack::HORIZONTAL);
pack1->spacing(10);
Fl_Input
*input1
=
new Fl_Input(0,0,w->w()-175, 30, "姓名:"); //
注意位置设为0, 0,没有影响
Fl_Button
*b1
=
new Fl_Button(0,0,100, 30, "检查!");
pack1->end();
pack1->resizable(input1);
可以看到,Fl_Pack有个spacing方法,指定了子控件之间的距离,而且子控件不用再考虑位置,因为所有的位置将由Fl_Pack来自动指定。Fl_Pack对手工布局有所帮助,但如果使用Fluid设计器,帮助不大。
5. 总结和下次预告
在fltk中实现正确的resize行为,需要使用Fl_Group的resizable方法。所有控件的坐标相对于整个顶层窗口(在今后的版本中可能改变)。Fl_Pack可以省去指定位置的麻烦,添加了一些方便性。有关布局的控件还有好几个,例如实现标签页的Fl_Tabs,实现拆分窗口的Fl_Tile,实现滚动显示的Fl_Scroll等。这些控件都很简单,将在今后的课程中做专门介绍。
下次我们将不再讨论布局的问题,主要讲一下怎样处理事件,这样我们的界面会对用户的输入做出反应,就能实现一些比较简单的有用的应用了。
参考
1. http://www.fltk.org/articles.php?L415 关于resizable怎样工作的好文章
2. Fltk 1.3 Manual