FOX学习(二)scribble

官网文档链接: http://www.fox-toolkit.org/

官网给出了第一个简单的小例子Scribble
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() 成员函数来运行应用程序。 这个函数永远不会返回。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值