官网文档链接: http://www.fox-toolkit.org/
官网给出了第一个简单的小例子Scribble
这个程序演示了如何使用FOX布局管理其,如何创建按钮,以及如何处理信息。
首先是需要引入头文件。
#include <fx.h>
其次,我们需要一个顶层的windows对象,这是一个从FXMainWindow衍生出来的一个类。大多数应用程序只会拥有一个MainWindow,但是拥有多个MainWindow也是可以的。
对于Scribble我们创建一个继承于FXMainWindow类、叫ScribbleWindow的类
// Main Window
class ScribbleWindow : public FXMainWindow {
// Macro for class hierarchy declarations
FXDECLARE(ScribbleWindow)
FXMainWindow像其他大多数的FOX类一样是从FXObject衍生而来的。每一个从FXObject衍生出来的对象都应该包含这个宏。
接下来,定义一些成员变量来追踪各种小部件和绘画颜色。我们也要设置一个flag位来标识鼠标是否被按下,一个flag位来标识画布是否已经被画上了东西等
private:
FXHorizontalFrame *contents; // Content frame
FXVerticalFrame *canvasFrame; // Canvas frame
FXVerticalFrame *buttonFrame; // Button 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中的所有消息处理函数都有相同的参数签名。
这里比较拗口,大致意思是用一个对象来处理不同的消息,
这里用的就是这个ScribbleWindow类,所有的消息处理函数都有相同的参数构造如下:
long onSomeCommand(FXObject* sender,FXSelector sel,void *ptr);
sender:是发送消息给我们的发送者对象
sel:是选择器,是信息类型和信息ID的组合,用于识别正在执行的行动。
ptr:是一个指向一些事件相关数据的指针;通常指向FXEent结构,该结构包含了导致该消息的事件。
对于Scribble应用程序来说,我们想要处理鼠标事件,以及两个按钮的消息:
public:
// Message handlers
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。一个消息由一个type和一个消息ID组成。type定义了所发生的事件。ID确定了消息的来源。尽管我们知道向我们发送消息的对象,但在许多情况下,我们可以从不同的来源发送相同的消息,而id则更方便。
public:
// Messages for our class
enum{
ID_CANVAS=FXMainWindow::ID_LAST,
ID_CLEAR,
ID_LAST
};
我们通常把一些目标理解的消息列表定义为一个枚举类型。 由于ScribbleWindow类是从FXMainWindow派生出来的,它也理解所有已经被基本的FXMainWindow理解的消息。 我们的新消息应该有与这些消息不同的数字。 子类可以简单地使用它的基类的ID_LAST来开始计算它的消息ID;如果有任何新的消息ID被添加到基类中,我们自己的消息会被编译器自动重新编号,而不是手工计算。
我们通过定义一个构造函数和一个名为 create() 的成员函数来结束 ScribbleApp 类声明的其余部分:
public:
// ScribbleWindow's constructor
ScribbleWindow(FXApp* a);
// Initialize
virtual void create();
virtual ~ScribbleWindow();
在我们的实现中,构造函数ScribbleWindow将实际建立GUI。create()函数是一个虚拟函数,由系统调用。大多数FOX Widgets都有这个创建函数。FOX Widgets有一个两阶段的创建过程;首先,客户端Widgets被构建,使用普通的C++构造函数。然后,一旦整个Widget树完成,对应用程序的create()函数的一次调用将为这些Widget创建所有窗口。这个两步过程是必要的,因为第二步只能在与显示器的连接建立起来后执行。
现在,我们准备实现这个新的类;在大多数情况下,以前的代码会放在头文件中,而实现当然是在C++源文件中。在ScribbleWindow的例子中,它是如此简单,以至于我们把所有东西都放在一个文件中。
要做的第一件事是定义消息映射。消息映射是一个简单的表格,它将消息类型和消息ID与一个类的成员函数联系起来。有了消息映射,我们就可以向任何[FXObject-derived]对象发送任何消息。
// Message Map for the Scribble Window class
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宏所声明的 "boilerplate "的东西。
// Macro for the ScribbleApp class hierarchy implementation
FXIMPLEMENT(ScribbleWindow,FXMainWindow,ScribbleWindowMap,ARRAYNUMBER(ScribbleWindowMap))
这个宏的第一个参数应该是类的名称,在本例中是ScribbleWindow;第二个参数应该是派生类的名称;在本例中,是FXMainWindow。最后一个参数是一个指向消息映射的指针,以及该映射中的消息数量。FOX有一个方便的宏ARRAYNUMBER(),它可以扩展为编译时定义的数组中的元素数量;这使得添加或删除消息更加容易。
如果你定义的类没有实现额外的消息,那么FXIMPLEMENT的最后两个参数应该是NULL和0。
ScribbleWindow的其余部分的实现几乎是普通的C++代码。下面是构造函数。
// Construct a ScribbleWindow
ScribbleWindow::ScribbleWindow(FXApp *a):FXMainWindow(a,"Scribble Application",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,"Canvas Frame",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_FILL_ROW|LAYOUT_FILL_COLUMN);
// 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,"Button Frame",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;
}
在几乎所有的情况下,只需要一行C++代码就可以创建一个FOX Widget。一般来说,这是一个构造函数的调用。由于大多数FOX小部件为构造器提供了方便的默认参数,你可能不需要指定它们中的大多数。
构造函数正文的第一行创建了一个顶层窗口;FOX中的顶层窗口没有父级,所以传入一个指向应用程序对象的指针(本例中是这样)。其余的参数是窗口标题、窗口装饰(如调整大小的手柄、边框等),以及初始尺寸和位置。初始尺寸和位置可能被你的特定窗口管理器忽略,它们只是提示。
下一行创建了一个FXHorizontalFrame Widget。FXHorizontalFrame Widget是一个Layout Manager,它可以水平地放置它的孩子。
FXMainWindow小部件本身也是一个布局管理器,传递给FXHorizontalFrame小部件构造函数的选项决定了它如何被放置在FXMainWindow中。
接下来,创建了两个 FXVerticalFrame 小部件,一个用于绘图 Canvas,一个用于按钮。 然后在 canvasFrame 中放置一个标签、一个带凹槽的分隔符和用于绘图的 Canvas。 Canvas 的目标对象是 ScribbleWindow(即 this),其消息设置为 ID_CANVAS。 这会导致 Canvas 将其所有消息发送到 ScribbleApp 对象,并将 ID 设置为 ID_CANVAS。
同样,在右边的 buttonFrame 中,我们放置了一个 Label、一个带凹槽的 Separator 和两个 Button。 清除按钮有一个标题“&Clear”。 后者前面的 & 将导致 Button 自动安装热键 Alt-C。 标题用 C 下划线绘制,如“清除”。 清除按钮的目标还是 ScribbleApp 对象,它的消息 ID 是 ID_CLEAR。 同样,退出按钮发送 ID_QUIT。
请注意,我们不必定义 ID_QUIT,因为这是每个 FXApp 对象都已经理解的消息。 因此,我们可以简单地将按钮连接到它们的目标。
Buttons 的其余参数决定了它的框架样式 (FRAME_THICK|FRAME_RAISED),以及它如何放置在 VerticalFrame 布局管理器中 (LAYOUT_FILL_X|LAYOUT_TOP|LAYOUT_LEFT) 告诉布局管理器拉伸 Buttons 以填充可用空间,使它们很好 相同的大小。
最后,ScribbleWindow 的构造函数为绘图颜色和标志初始化其成员变量。
接下来,我们实现 create() 例程:
// Create and initialize
void ScribbleWindow::create(){
// Create the windows
FXMainWindow::create();
// Make the main window appear
show(PLACEMENT_SCREEN);
}
首先,我们调用基类的create; 然后,通过调用其 show() 成员函数将主窗口显示在屏幕上。
现在,我们准备处理一些消息:
// Mouse button was pressed somewhere
long ScribbleWindow::onMouseDown(FXObject*,FXSelector,void*){
canvas->grab();
// 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;
// Draw
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, so now 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;
canvas->ungrab();
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, so now 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 处理程序从最后一个鼠标位置到当前鼠标位置绘制一条线; 然后它将脏标志设置为 1 以记住 Canvas 已被绘制到上面。 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,FXWindow::ID_ENABLE),NULL);
else
sender->handle(this,FXSEL(SEL_COMMAND,FXWindow::ID_DISABLE),NULL);
return 1;
}
onCmdClear 消息处理程序清除画布,然后重置脏标志。 onUpdClear 消息处理程序更新清除按钮。
FOX 中的每个 Widget 在空闲处理期间都会收到一条消息,要求对其进行更新。 例如,当应用程序的状态发生变化时,可以对按钮进行敏感或脱敏。 在这种情况下,我们在 Canvas 已经被清除时使发送者(清除按钮)脱敏,并在它被绘制时使其敏感(如脏标志所示)。
这个 GUI 更新过程非常强大:- 如果应用程序有 N 个命令,并且每个命令要更新 M 个小部件,则可能必须编写 NxM 个更新例程; 使用 GUI 更新过程,只需要编写 N+M 个例程。 此外,如果应用程序数据因其他方式(例如定时器、外部数据输入、多个计算线程等)发生变化,GUI 将自动保持最新状态,无需任何额外编码。
要完成 Scribble 应用程序,只剩下一件事:- 从 main() 例程开始:
// Here we begin
int main(int argc,char *argv[]){
// Make application
FXApp application("Scribble","FoxTest");
// Start app
application.init(argc,argv);
// Scribble window
new ScribbleWindow(&application);
// Create the application's windows
application.create();
// Run the application
return application.run();
}
首先,我们通过调用FXApp application(“Scribble”,“FoxTest”)来构造一个FXApp对象。 第一个字符串是应用程序的名称“Scribble”通常称为应用程序密钥,而第二个字符串“FoxTest”称为供应商密钥。 这两个字符串一起用于确定应用程序的注册表或首选项设置。
调用 application.init(argc,argv) 初始化应用程序; 传入命令行的 argc 和 argv 以便 FOX 系统可以过滤掉一些 FOX 特定的命令行参数,例如 -display 参数。
调用 new ScribbleWindow(application) 为我们的应用程序构建了整个 GUI; GUI 基本上由两部分组成:- 存在于我们自己的进程中的客户端资源,以及存在于 X 服务器 (X11) 或 GDI (Windows) 中的服务器端资源。
当我们构造 FOX 小部件时,只确定客户端资源。 随后对 application.create() 的调用递归地为之前构建的每个小部件创建所有服务器端资源。
最后,调用 application.run() 成员函数来运行应用程序。 这个函数永远不会返回。