FOX笔记(三)

FOX笔记(三)

为什么是一个target/Message系统

有许多方法可以将图形用户界面元素连接到应用程序代码; 今天最常用的方法是回调函数。 然而,在 C++ 中,回调函数并不是一个显而易见的选择,因为该技术不容易指定某个对象。
326 / 5,000
Translation results
C++ 中使用的另一种方法是信号槽技术。 在其典型实现中,会创建将信号连接到插槽的连接器对象。 但是,为了在调用者和被调用者之间提供必要的隔离,涉及到模板实例化; 这限制了它对编译时连接的使用。
FOX 采用的方法是目标/消息系统。 每个 Widget 将其消息发送到称为target的特定对象。 由于可能有多个 Widget 向一个特定目标发送消息,因此使用message id 将它们区分开来。
此外,单个 Widget 可能能够发送多种消息; 这个问题是通过按message type键入消息来解决的。 使用消息类型和消息 ID,可以唯一标识 GUI 事件或操作的来源和类型。
消息可以发送到从 FXObject 派生(直接或间接)的任何对象。 当然,所有的 FOX Widget 都是从 FXObject 派生的,FXApp 应用程序对象也是如此。 因此,FOX 中的几乎每个对象都能够接收消息。
显式对象是消息的目标(与隐式消息路由方案相反)这一事实的一个优点是消息 id 在应用程序中不必是全局唯一的。 所需要的只是它对于某个类及其基类是唯一的。
当考虑制作面向组件的软件时,这是一个特别重要的考虑因素,其中组件可能由不同的人甚至不同的组织编写。
使用 FOX,它们不必相互协调消息 id 以使组件正确交互。
目标/消息系统的另一个重要好处是 Widget 发送的消息以及它发送到的目标可以在运行时更改。
这对于构建诸如 GUI Builders 和其他面向组件的软件之类的程序来说是一个显着的好处。 最后,由于所有 FOX Widget 都派生自 FXObject,它们能够接收和发送消息。
这允许 FOX Widgets 实现一些在 GUI 系统中常见的典型命令; 例如,考虑以下代码片段:

new FXHorizontalFrame(main,LAYOUT_SIDE_TOP|LAYOUT_FILL_X);
....
....
....
new FXMenuCommand(windowmenu,"&Toolbar",NULL,toolbar,FXWindow::ID_TOGGLESHOWN);

在上面的示例中,工具栏 Widget 是 MenuCommand Widget 的直接目标。 每次调用工具栏命令时,都会打开或关闭工具栏小部件。 此外,当GUI更新过程发生在空闲时间时,MenuCommand也会向工具栏Widget发送更新消息; 作为对该更新的响应,工具栏检查其当前状态,并通过向其发送回 ID_CHECK 或 ID_UNCHECK 消息来选中或取消选中 MenuCommand。
注意工具栏不能假设更新消息的发送者是一个MenuCommand; 但它确实知道它是一个 FXObject! 所以它需要向这个对象发送一个ID_CHECK(ID_UNCHECK)消息,而不是试图直接调用MenuCommand的check()或uncheck()成员函数。
上面的代码片段展示了目标/消息系统的灵活性,尤其是在结合 GUI Update 空闲处理能力时。 该机制在 FOX 内部也被广泛使用。

消息映射

对象接收到的消息通过消息映射映射到对象的特定成员函数。 消息映射只不过是一个静态的、编译时定义的表,它将一个或多个消息与某个成员函数相关联。 复杂的小部件可能有几十个以这种方式映射的消息。 不幸的是,消息映射在 C++ 中是必需的,因为消息与成员函数的精确绑定是在运行时执行的; C++ 本身并不能很好地支持这种动态绑定。幸运的是,FOX 通过提供许多宏来设置它们,从而使定义这些消息映射变得相当容易。 以下代码片段说明了该过程:

FXDEFMAP(FXGLViewer) FXGLViewerMap[]={
  FXMAPFUNC(SEL_PAINT,0,FXGLViewer::onPaint),
  ....
  FXMAPFUNCS(SEL_UPDATE,MINKEY,MAXKEY,FXGLViewer::onUpdAll),
  };

FXIMPLEMENT(FXGLViewer,FXGLCanvas,FXGLViewerMap,ARRAYNUMBER(FXGLViewerMap))

FXDEFMAP 宏将类的名称作为参数。 它用于定义消息映射表中的条目。 FXMAPFUNC 宏接受三个参数:- 第一个是消息的类型,第二个是消息的 id,最后是这个消息被映射到的成员函数。 一个称为 FXMAPFUNCS 的类似宏用于定义一系列消息 ID,而不仅仅是一个。 您可以使用此宏将许多消息映射到同一个成员函数。
例如,在一个计算器程序中,您可能只有一个按钮用于“0”、“1”等直到“9”。 您可以只定义其中一个,而不是定义十个非常相似的成员函数。 成员函数可以使用宏FXSELID(sel)获取调用它的消息的id,使用FXSELTYPE(sel)查找消息的消息类型。
最后一个宏 FXIMPLEMENT 有四个参数:类的名称、直接基类的名称、指向消息映射的指针和消息映射中的条目数。 如果一个对象没有实现任何消息处理程序,您可以为最后两个参数传递 NULL 和 0。 头文件中对应的宏称为 FXDECLARE。
每个 FOX 对象都应始终在其头文件或类声明中使用 FXDECLARE,并在其实现文件中使用 FXIMPLEMENT!
除了 FXMAPFUNC 和 FXMAPFUNCS 之外,还有两个(很少使用的)宏只针对消息类型; FXMAPTYPE 只接受两个参数,消息类型和成员函数,而 FXMAPTYPES 接受三个参数,第一条和最后一条消息 id 以及成员函数。 FXMAPTYPE 和 FXMAPTYPES 将完全忽略消息 id,并将适当类型的任何消息映射到指定的成员函数。
所有消息 ID 应在 MINKEY 到 MAXKEY 范围内,所有消息类型应在 MINTYPE 到 MAXTYPE 范围内。 此外,零 (0) 的特殊消息 id 是为系统发起的消息保留的。
消息从派生类向上解析到基类的消息处理函数。 这允许开发人员在其派生类中捕获消息,然后在基类中进行处理。 因此,您可以轻松地重新定义 FOX 内置小部件的行为。
由于消息关联是在运行时执行的,因此通常的做法是将最常出现的消息放在映射的最前面; 这样,找到它们的搜索量最少; 因此,SEL_PAINT 消息通常放在首位。

跟踪消息编号

FOX 不要求所有消息 id 都是全局唯一的。 但是,它确实要求它们对于特定目标是唯一的。 目标理解的消息是目标类及其所有基类理解的消息的联合。
保持编号正确的一种简单方法是使用枚举。 FOX 本身使用如下所示的技术:

class FXWindow : public FXDrawable {
  ...
public:
  enum {
    ID_SHOW=1,
    ID_HIDE,
    ...

    ID_LAST

  };
public:
...
};

class MyWindow : public FXWindow {
...
public:
  enum {
    ID_MYMESSAGE=FXWindow::ID_LAST,
    ID_MYOTHERMESSAGE,
    ...
    ID_LAST
    };
public:
...
};

这样,编译器会自动安排以确保编号正确。 在 ID_LAST 之前添加更多消息也很容易,重新编译会自动调整消息 ID。 当然,如果需要,欢迎您使用任何其他方案; 只需确保您的消息不会与您的对象的基类发生冲突。

消息目标应该比消息源更长寿

很明显,当一个 Widget 向某个对象发送消息时,接收对象当然应该仍然存在。 如果这不是真的,一个潜在的陷阱就会抬起丑陋的脑袋。 幸运的是,在大多数情况下,控件小部件会向其包含的对话框、应用程序对象或其他长期存在的对象发送消息。 在极少数情况下,您可能希望确保在删除 Widget 或 Object 时,也会清除对它的所有引用。FOX 提供了两个成员函数:

 FXWindow::setTarget(FXObject* tgt)
  FXWindow::setSelector(FXSelector sel)

允许您更改目标,以及 Widget 将发送的消息。 将 Widget 的目标设置为 NULL 将阻止它向任何人发送任何未来的消息。
为了捕捉消息将被发送到已被破坏的对象的可能性,FOX 将彻底破坏析构函数中的每个对象。 因此,如果应用程序中存在这样的错误,它很可能会迅速浮出水面,从而产生更可靠的程序。

发送您自己的消息

在许多情况下,您会希望自己向小部件发送消息。 例如,在 GUI 更新处理程序中,您可能希望向更新消息的发送者发送消息:

....
FXMAPFUNC(SEL_COMMAND,FXWindow::ID_TOGGLESHOWN,FXWindow::onCmdToggleShown),
// Command
FXMAPFUNC(SEL_UPDATE,FXWindow::ID_TOGGLESHOWN,FXWindow::onUpdToggleShown),
// Update
....
// Hide or show window<
long FXWindow::onCmdToggleShown(FXObject*,FXSelector,void*){
  ....
  return 1;
  }

// Update hide or show window
long FXWindow::onUpdToggleShown(FXObject* sender,FXSelector,void*){

  sender->handle(this,shown()?FXSEL(SEL_COMMAND,ID_CHECK)
                              :FXSEL(SEL_COMMAND,ID_UNCHECK),NULL);
  return 1;
  }

这里会发生什么? 在 GUI 更新期间,连接到工具栏的菜单命令发送一个 SEL_UPDATE 消息[而不是它在用户调用命令时发送的 SEL_COMMAND]。
上面的 onUpdToggleShown 函数确定 Toolbar 当前是否显示,然后将 ID_CHECK 或 ID_UNCHECK 发送回发送者。
获得 ID_CHECK 或 ID_UNCHECK 后,Menu Command 对象随后会在其标签前放置或移除一个小复选标记。
如果 SEL_UPDATE 消息的发送者是其他一些 Widget,例如 一个检查按钮,它仍然可以正常工作,尽管检查按钮的 ID_CHECK 和 ID_UNCHECK 处理程序的实现当然是完全不同的。
如果 SEL_UPDATE 消息的发送者是一些完全不同的 Widget,它会简单地忽略返回消息。
通过发送消息而不是直接调用成员函数,上面的函数不需要知道发送SEL_UPDATE消息的Widget是什么类型; 它只是发回一条消息; 如果消息的发送者不理解消息,则不会发生任何事情。 请注意,保证消息的发送者始终是从 FXObject 派生的对象。

消息处理程序返回值

您可能已经注意到,有些消息处理程序返回 1,有些返回 0。一般约定是,如果消息可以被视为已处理,即正常处理,则处理程序应返回 1。否则,应返回 0。
正确返回正确的返回值将允许通过您的应用程序进行智能消息路由。 对于直接由用户输入事件产生的消息,例如按钮按下等,FOX 将使用消息处理程序的返回值来确定是否需要刷新 GUI。
例如,如果系统向您的 Widget 发送了 SEL_LEFTBUTTONPRESS,并且您的 Widget 的处理程序返回 1,则认为已处理; 下次系统进入空闲处理时,应用程序中的所有 GUI Widget 将再次更新,因为假设通过处理按钮消息,某些内容可能已经改变。 如果您的处理程序返回 0,则该消息将被视为未处理,并且不会发生任何进一步的事情。

消息路由和委派

消息可以从一个对象转发到另一个对象。 例如,在收到消息后,目标可能首先尝试自己处理消息; 然后,如果没有找到匹配项,它可能会通过将消息转发给其他对象来试试运气。 以下是您将如何编码:

// Delegate message
long MyWidget::onDefault(FXObject* sender,FXSelector key,void* data){
  return delegateObject && delegateObject->handle(sender,sel,data);
  }

我们在这里使用我们可以重载所谓的默认消息处理程序 onDefault() 的事实。 当没有为消息找到消息绑定时,将调用默认消息处理程序。

在上面的代码片段中,delegateObject 被假定为从 FXObject 派生的某种类型的对象。 您可以非常有创意地使用这些委托技术。

请注意,您可能希望从委托者 MyWidget 停止的地方开始 delegateObject 的消息 id,即确保没有重叠,除非它是预期的。
在少数情况下,一些将由 MyWidget 的基类处理的消息需要转发给 delegateObject。 您只需将该消息映射到 MyWidget 消息映射中的 onDefault() 即可轻松完成此操作:

  FXMAPFUNC(SEL_COMMAND,BaseWidget::ID_DOSOMETHING,MyWidget::onDefault),
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值