---- TOPIC TITLE WITH LOGO--->
FOX-Toolkit:介绍 [Remove Frame]
为了以图解的方式来讲解你可以用来构建FOX应用程序的工具,
我们会展示一些简单的FOX应用程序。 首先是一个简单的画板程序。
这个程序的运行画面如下图所示:
画板
图 1. 画板程序(图见http://www.fox-toolkit.org/introduct.html:-)
画板程序向我们展示了怎样使用FOX的布局管理器,怎样创建按钮,
以及怎样处理消息。 说得够多得了,我们开始吧!
最开始要做的就是包含FOX的头文件。
这很简单,因为只有一个文件需要包含:
#include "fx.h"
然后,我们需要一个从FXMainWindow类派生出来的顶层窗口类。
应该只有一个主窗口;如果你需要其它的顶层窗口,
你最好从FXDialogBox类或者FXTopWindow类派生。
在本程序中,我们生成一个叫做Scribble的类:
// Event Handler Object
class ScribbleWindow : public FXMainWindow {
// Macro for class hierarchy declarations
FXDECLARE(ScribbleWindow)
第一行是说ScribbleWindow是从FXMainWindow派生出来的;
FXMainWindow类就像大多数的FOX类一样,是从FXObject类派生出来的。
你将要写的FOX程序中的大多数类都要直接或者间接地从一个简单的父类FXObject上派生而来。
FXDECLARE(ScribbleWindow)宏定义了很多若从FXObject类派生而来就要有的成员函数;
我们用宏是因为它们总是不变的,而这样写程序的话也会更舒服些。
然后,我们加了一些成员变量来保存将要用到的窗口控件,
以及画笔的颜色;我们还定义了一个表示鼠标是否按下的标志,
以及一个表示画板是否变脏,也就是有没有被画过的标志:
private:
FXHorizontalFrame *contents; // Content frame
FXVerticalFrame *buttonFrame; // Button frame
FXVerticalFrame *canvasFrame; // Canvas frame
FXCanvas *canvas; // Canvas to draw into
int mdflag; // Mouse button down?
int dirty; // Canvas has been painted?
FXColor drawColor; // Color for the line
为了满足宏的需要,我们要提供一个缺省构造函数:
protected:
ScribbleWindow(){}
FOX通过一个将用户消息发送给对应对象的系统来处理消息;
在本程序中,消息的接收者为ScribbleWindow类;因此,我们需要添加适当的成员函数来
接受消息并做出相应的动作;在FOX中,所有的消息处理函数都有相同的自变量列表:
long onSomeCommand(FXObject* sender,FXSelector sel,void *ptr);
其中:
sender 是给我们发送消息的那个对象。
sel 是选择器,是消息类型与标示的组合,用赤表示所要进行的动作。
ptr 是一个指向事件相关数据的指针;通常,
它指向一个包含引发消息的事件的FXEvent结构体。
对于Scribble应用程序,我们需要处理鼠标消息以及来自两个按钮的消息:
public:
long onPaint(FXObject*,FXSelector,void*);
long onMouseDown(FXObject*,FXSelector,void*);
long onMouseUp(FXObject*,FXSelector,void*);
long onMouseMove(FXObject*,FXSelector,void*);
long onCmdClear(FXObject*,FXSelector,void*);
long onUpdClear(FXObject*,FXSelector,void*);
同时,ScribbleWindow需要定义一些新的消息ID;一个消息由一个
类型和一个id标示组成;类型
定义发生了什么;id则标明了消息的来源;
就算我们知道消息来自哪个对象,但在很多情况下,可能会有来自不同对象的同一个消息,
因此,用id来表示的话还是很有用的。于是:
public:
enum{
ID_CANVAS=FXMainWindow::ID_LAST,
ID_CLEAR,
ID_LAST
};
很典型的,我们用一个enum结构定义了一个消息标识列表;
由于ScribbleWindow类是从FXMainWindow派生而来,
它自然也能够接收FXMainWindow父类所能接收的所有消息。
我们的新消息应该与它们有不同的值;
与其自己计算,我们可以定义一个额外的ID_LAST标示,用来让编译器替我们做这件事;
一个子类的消息队列可以从其父类的ID_LAST开始算起;
就算父类中加入了新的消息,我们的消息也可以由编译器自动重新编号。
ScribbleApp类还需要定义的是一个构造函数和一个成员函数create():
public:
ScribbleWindow(FXApp* a);
virtual void create();
};
在我们的实现中,实际是由ScribbleWindow构造函数创建
GUI界面。函数create()是一个由系统来调用的虚函数。
大部分的FOX控件都有这个函数。
FOX控件的创建过程分为两步:首先,客户端控件由正常的C++构造函数创建;
然后,当整个控件树构造完全后,一个对application的函数create()
的简单调用将为这些控件创建整个窗口。虽然需要两步,但通常第二步只是简单地
将所构造出来的控件与已经显示出来的图形建立连接。
现在,我们已经准备好要实现这个新的类了。通常,前面的代码要放在一个单独的头文件中,
而实现代码自然要放在一个C++源码文件中。因为这个例子十分简单,我们也就干脆
把所有东西都放在一个文件里了。
第一件事是要定义一个消息列表。
消息列表只是一个将消息类型以及消息标识与类的成员函数关联起来的数组。
有了消息列表,我们就可以将消息发送给任何一个由FXObject派生而来的对象了。
这样:
FXDEFMAP(ScribbleWindow) ScribbleWindowMap[]={
//________Message_Type_____________________ID_______________Message_Handler_______
FXMAPFUNC(SEL_PAINT, ScribbleWindow::ID_CANVAS,ScribbleWindow::onPaint),
FXMAPFUNC(SEL_LEFTBUTTONPRESS, ScribbleWindow::ID_CANVAS,ScribbleWindow::onMouseDown),
FXMAPFUNC(SEL_LEFTBUTTONRELEASE,ScribbleWindow::ID_CANVAS,ScribbleWindow::onMouseUp),
FXMAPFUNC(SEL_MOTION, ScribbleWindow::ID_CANVAS,ScribbleWindow::onMouseMove),
FXMAPFUNC(SEL_COMMAND, ScribbleWindow::ID_CLEAR, ScribbleWindow::onCmdClear),
FXMAPFUNC(SEL_UPDATE, ScribbleWindow::ID_CLEAR, ScribbleWindow::onUpdClear),
};
请注意一下这个列表:首先,这里有几个消息拥有相同的id,但却有不同的类型。
消息类型表示发生了什么,例如,SEL_LEFTBUTTONPRESS表示鼠标左键刚刚被按下。
消息id则表示消息的来源。FOX定义了很多有特殊含义的消息类型。
然后,我们需要实现那些由FXDECLARE宏所定义的“样板”函数:
FXIMPLEMENT(ScribbleWindow,FXMainWindow,ScribbleWindowMap,ARRAYNUMBER(ScribbleWindowMap))
其中,宏的第一个参数应为我们的类名,即ScribbleWindow;
第二个参数应为我们类的父类名,这里为FXMainWindow;
后两个参数为一个指向消息列表的指针以及列表的元素个数。
FOX有一个很好用的宏ARRAYNUMBER()可以帮我们在编译期计算出数组的长度,
这使得我们在增删消息时更加方便。
如果你的类并没有定义其它的消息,则后两个参数可以简单地设为NULL和0。
ScribbleWindow剩下的实现就是标准的C++代码了。构造函数如下所示:
// Construct a ScribbleWindow
ScribbleWindow::ScribbleWindow(FXApp *a)
: FXMainWindow(a,"ScribbleApplication",NULL,NULL,DECOR_ALL,0,0,800,600){
contents=new FXHorizontalFrame(this,LAYOUT_SIDE_TOP|LAYOUT_FILL_X|LAYOUT_FILL_Y,0,0,0,0,0,0,0,0);
// LEFT pane to contain the canvas
canvasFrame=new FXVerticalFrame(contents,
FRAME_SUNKEN|LAYOUT_FILL_X|LAYOUT_FILL_Y|LAYOUT_TOP|LAYOUT_LEFT,0,0,0,0,10,10,10,10);
// Label above the canvas
new FXLabel(canvasFrame,"CanvasFrame",NULL,JUSTIFY_CENTER_X|LAYOUT_FILL_X);
// Horizontal divider line
new FXHorizontalSeparator(canvasFrame,SEPARATOR_GROOVE|LAYOUT_FILL_X);
// Drawing canvas
canvas=new FXCanvas(canvasFrame,this,ID_CANVAS,
FRAME_SUNKEN|FRAME_THICK|LAYOUT_FILL_X|LAYOUT_FILL_Y|LAYOUT_TOP|LAYOUT_LEFT);
// RIGHT pane for the buttons
buttonFrame=new FXVerticalFrame(contents,
FRAME_SUNKEN|LAYOUT_FILL_Y|LAYOUT_TOP|LAYOUT_LEFT,0,0,0,0,10,10,10,10);
// Label above the buttons
new FXLabel(buttonFrame,"ButtonFrame",NULL,JUSTIFY_CENTER_X|LAYOUT_FILL_X);
// Horizontal divider line
new FXHorizontalSeparator(buttonFrame,SEPARATOR_RIDGE|LAYOUT_FILL_X);
// Button to clear
new FXButton(buttonFrame,"&Clear",NULL,this,ID_CLEAR,
FRAME_THICK|FRAME_RAISED|LAYOUT_FILL_X|LAYOUT_TOP|LAYOUT_LEFT,0,0,0,0,10,10,5,5);
// Exit button
new FXButton(buttonFrame,"&Exit",NULL,getApp(),FXApp::ID_QUIT,
FRAME_THICK|FRAME_RAISED|LAYOUT_FILL_X|LAYOUT_TOP|LAYOUT_LEFT,0,0,0,0,10,10,5,5);
// Initialize private variables
drawColor=FXRGB(255,0,0);
mdflag=0;
dirty=0;
}
在大多数情况下,构建军一个FOX控件只需要一行C++代码,即构造函数。
而且,由于大部分FOX控件的构造函数都提供了非常多的默认参数,我们也就不需特别
指定它们了。
第一行创建了一个顶层窗口。FOX中的顶层窗口没有父类,所以要传递一个application
对象的指针给它(这里是this)。其余的参数为窗口标题,定制类型
(如大小的调整,边框等),以及初始大小和位置。初始大小和位置可能会被
窗口管理器忽略掉,它们只是将其做为参考。
下一行创建了一个FXHorizontalFrame控件。FXHorizontalFrame控件是一个
布局管理器,它的子控件会横向排列。
FXMainWindow控件本身同样也是一个布局管理器,而传递给FXHorizontalFrame
控件构造函数的option参数将决定它在FXMainWindow中的摆放方式。
然后,两个FXVerticalFrame控件被创建,其中一个用来摆放画板,
另一个则放按钮。接着,我们在canvasFrame中放了一个标签,一个水平分隔符,
和一个画板。Canvas的目标对象被设定为ScribbleApp(也就是this),
而其消息标识设为ID_CANVAS,这样,Canvas就会将它的消息发送给ScribbleWindow
对象,其ID为ID_CANVAS。
同样的,在右边的buttonFrame上我们放了一个标签,一个水平分隔符,
以及两个按钮。清屏按钮的标题为“&Clear”。
&符号会自动为按钮创建一个快捷键Alt-C。而标题中的字母C会被加上一条下划线,
即“Clear”。清屏按钮的目标对象同样也是ScribbleApp,其消息标识为
ID_CLEAR。同时,退出按钮的标识为ID_QUIT。
需要注意的是,我们其实并不需要定义ID_QUIT,因为FXApp中已经定义过了。
因此,我们只要简单地将我们的按钮与之关联即可。
剩下的参数决定了按钮的样式(FRAME_THICK|FRAME_RAISED),以及它将被怎样
放在VerticalFrame布局管理器上(LAYOUT_FILL_X|LAYOUT_TOP|LAYOUT_LEFT),
本例的参数将使按钮在一定的范围内拉长并具有相同的长度以显得整齐。
最后,ScribbleWindow的构造函数初始化了它的成员变量的值以设定画笔的颜色
和标志位。
下一步,我们来实现create()函数:
// Create and initialize
void ScribbleWindow::create(){
// Create the windows
FXMainWindow::create();
// Make the main window appear
show();
}
首先,我们调用父类的create方法;然后,通过调用show()函数,主窗口被显示出来。
接下来,我们要对消息进行处理:
// Mouse button was pressed somewhere
long ScribbleWindow::onMouseDown(FXObject*,FXSelector,void*){
// While the mouse is down, we'll draw lines
mdflag=1;
return 1;
}
// The mouse has moved, draw a line
long ScribbleWindow::onMouseMove(FXObject*, FXSelector,void* ptr){
FXEvent *ev=(FXEvent*)ptr;
if(mdflag){
// Get DC for the canvas
FXDCWindow dc(canvas);
// Set foreground color
dc.setForeground(drawColor);
// Draw line
dc.drawLine(ev->last_x, ev->last_y,ev->win_x, ev->win_y);
// We have drawn something, sonow the canvas is dirty
dirty=1;
}
return 1;
}
// The mouse button was released again
long ScribbleWindow::onMouseUp(FXObject*,FXSelector,void*ptr){
FXEvent *ev=(FXEvent*) ptr;
if(mdflag){
FXDCWindow dc(canvas);
dc.setForeground(drawColor);
dc.drawLine(ev->last_x, ev->last_y,ev->win_x, ev->win_y);
// We have drawn something, sonow the canvas is dirty
dirty=1;
// Mouse no longer down
mdflag=0;
}
return 1;
}
// Paint the canvas
long ScribbleWindow::onPaint(FXObject*,FXSelector,void*ptr){
FXEvent *ev=(FXEvent*)ptr;
FXDCWindow dc(canvas,ev);
dc.setForeground(canvas->getBackColor());
dc.fillRectangle(ev->rect.x,ev->rect.y,ev->rect.w,ev->rect.h);
return 1;
}
onMouseDown消息处理函数简单地设置了鼠标被按下的标志。onMouseMove消息
处理函数自上一位置向鼠标当前位置画一条直线,然后将dirty变量置1以标明画板已被画过。
onMouseUp消息处理函数结束画线并将标志复位。最后,onPaint消息处理函数以背景色
填充画板。这些都没什么特别的。
下面的一些消息处理函数更加有趣一些:
// Handle the clear message
long ScribbleWindow::onCmdClear(FXObject*,FXSelector,void*){
FXDCWindow dc(canvas);
dc.setForeground(canvas->getBackColor());
dc.fillRectangle(0,0,canvas->getWidth(),canvas->getHeight());
dirty=0;
return 1;
}
// Update the clear button
long ScribbleWindow::onUpdClear(FXObject* sender,FXSelector,void*){
if(dirty)
sender->handle(this,FXSEL(SEL_COMMAND,ID_ENABLE),NULL);
else
sender->handle(this,FXSEL(SEL_COMMAND,ID_DISABLE),NULL);
return 1;
}
onCmdClear消息处理函数清空画板,然后将dirty复位。onUpdClear消息处理函数
更新clear按钮。
在FOX中,每个控件都会在消息循环空闲时收到叫它更新的消息。例如,
在应用程序的状态发生改变时,按钮会被激活或者失效;在本例中,当画板被清空时,
我们就会令clear按钮失效;而当它被画后(这由dirty标志标识),我们则会重新激活它。
这种GUI的更新处理是非常有效的:- 如果我们的应用程序有N条命令,并且每个命令
将会更新M个控件,我们可能需要写NxM个更新例程;而要利用GUI的更新处理的话,
我们就只需要写N+M个例程就够了。此外,如果应用程序的数据被其它因素改变的话(
如时钟控件,外部数据输入,以及多线程运行等),GUI会自动更新它自身的状态,
而不需要另外的代码。
为了完成我们的Scribble程序,只剩一件事要做了:那就是在main()函数中启动它。
// Here we begin
int main(int argc,char *argv[]){
// Make application
FXApp* application=new FXApp("Scribble","Test");
// Start app
application->init(argc,argv);
// Scribble window
new ScribbleWindow(application);
// Create the application's windows
application->create();
// Run the application
application->run();
return 0;
}
首先,我们通过new FXApp("Scribble","Test")这条语句创建了一个FXApp对象。
第一个字符串“Scribble”为程序名,它一般被用来作为程序的关键字;同时,
第二个字符串“Test”作为卖主关键字。这两个关键字是用来决定程序的注册信息的。
下一个函数调用application->init(argc,argv)用来初始化应用程序。
argc和argv这两个参数被传入,以使FOX系统能够处理一些FOX所特有的命令行参数,
如-display等。
下一条语句new ScribbleWindow(application)为我们的应用程序创建了整个GUI。
它实际上由两部分组成:一个是客户端资源,在我们的控制之下;另一个就是服务器
资源,即X server(X11)或者GDI(Windows)。
当我们创建一个FOX控件时,其实只是客户端资源被创建。一个对函数
application->create()的递归调用则为所有控件创建服务器资源。
最后,对application->run()成员函数的调用令整个应用程序运行起来,直到我们退出。