查看源代码 wx:wxWidgets 的 Erlang 绑定

wx 应用程序是 wxWidgets 的 Erlang 绑定。本文档描述了 Erlang 到 wxWidgets 的映射及其实现。它不是 wxWidgets 的完整用户指南。如果您需要该指南,您必须阅读 wxWidgets 文档。wx 尝试与原始 API 保持一对一的映射,以便尽可能方便地使用原始文档和示例。

可以在 Erlang src 版本中找到 Wx 示例和测试套件。它们还可以为如何使用 API 提供一些帮助。

这目前只是对 wx 的一个非常简短的介绍。该应用程序仍在开发中,这意味着接口可能会更改,并且测试套件目前的覆盖率很低。

目录

简介

原始的 wxWidgets 是一个面向对象 (C++) API,这反映在 Erlang 映射中。在大多数情况下,wxWidgets 中的每个类都表示为 Erlang 中的一个模块。这使得 wx 应用程序具有一个巨大的接口,分布在多个模块中,并且一切都从 wx 模块开始。wx 模块包含创建和销毁 GUI 的函数,即 wx:new/0wx:destroy/0 和其他一些有用的函数。

wx 中的对象或对象引用应被视为 Erlang 进程,而不是 Erlang 项。当您对它们进行操作时,它们可能会更改状态,例如,它们不是像 Erlang 项那样的函数式对象。每个对象都有一个类型或更确切地说是一个类,该类由相应的模块或该对象的子类进行操作。类型检查的目的是确保一个模块只操作其对象或继承的类。

对象使用 new 创建,使用 destroy 销毁。类中的大多数函数都与 C++ 对应的函数同名,但为了方便起见,在 Erlang 中它们以小写字母开头,并且第一个参数是对象引用。可选参数位于最后,并以任何顺序表示为带标签的元组。

例如,wxWindow C++ 类在 wxWindow Erlang 模块中实现,成员 wxWindow::CenterOnParent 因此是 wxWindow:centerOnParent。以下 C++ 代码

  wxWindow MyWin = new wxWindow();
  MyWin.CenterOnParent(wxVERTICAL);
  ...
  delete MyWin;

在 Erlang 中看起来像这样

  MyWin = wxWindow:new(),
  wxWindow:centerOnParent(MyWin, [{dir,?wxVERTICAL}]),
  ...
  wxWindow:destroy(MyWin),

当您阅读 wxWidgets 文档或示例时,您会注意到 wx 中缺少一些最基本的类,它们直接映射到相应的 Erlang 项

  • wxPoint 由 {X坐标, Y坐标} 表示

  • wxSize 由 {宽度, 高度} 表示

  • wxRect 由 {X坐标, Y坐标, 宽度, 高度} 表示

  • wxColour 由 {红色, 绿色, 蓝色[, Alpha]} 表示

  • wxStringunicode:charlist() 表示

  • wxGBPosition 由 {行, 列} 表示

  • wxGBSpan 由 {行跨度, 列跨度} 表示

  • wxGridCellCoords 由 {行, 列} 表示

在 Erlang API 与原始 API 不同的地方,Erlang 文档应该清楚地说明使用了哪种表示形式。例如,C++ 数组和/或列表有时表示为 Erlang 列表,有时表示为元组。

颜色用 {红色, 绿色, 蓝色[, Alpha]} 表示,当用作函数的参数时,Alpha 值是可选的,但它将始终从 wx 函数返回。

定义、枚举和全局变量在 wx.hrl 中作为定义存在。大多数这些定义是常量,但并非全部。有些是平台相关的,因此必须在运行时实例化全局变量。这些将通过调用从驱动程序获取,因此并非所有定义都可以在匹配语句中使用。类局部枚举将以类名和下划线作为前缀,如 ClassName_Enum

此外,一些全局函数(即非类函数)存在于 wx_misc 模块中。

Wx 作为(线程化的)驱动程序和 C++ API 的相当直接的接口实现,其缺点是如果 Erlang 程序员出错,可能会导致仿真器崩溃。

由于驱动程序是线程化的,因此它需要一个启用 smp 的仿真器,该仿真器提供到驱动程序的线程安全接口。

多进程和内存处理

目的是每个 Erlang 应用程序调用 wx:new() 一次以设置其 GUI,这将创建一个环境和内存映射。为了能够从应用程序中的多个进程使用 wx,您必须共享环境。您可以使用 wx:get_env/0 获取活动环境,并使用 wx:set_env/1 在新进程中设置它。两个都调用了 wx:new() 的进程或应用程序将无法使用彼此的对象。

  wx:new(),
  MyWin = wxFrame:new(wx:null(), 42, "Example", []),
  Env = wx:get_env(),
  spawn(fun() ->
           wx:set_env(Env),
           %% Here you can do wx calls from your helper process.
           ...
        end),
  ...

当调用 wx:destroy/0 或当应用程序中的所有进程都死亡时,内存将被删除,并且该应用程序创建的所有窗口都将被关闭。

只要用户应用程序处于活动状态,wx 应用程序就永远不会清理或垃圾回收内存。大多数对象在窗口关闭时被删除,或者至少所有具有非空父参数的对象都被删除。通过尽可能使用 wxCLASS:destroy/1,您可以避免内存使用量不断增加。当 wxWidgets 假设或建议您(或者更确切地说,C++ 程序员)已在堆栈上分配对象时,这一点尤为重要,因为在 Erlang 绑定中永远不会这样做。例如 wxDC 类或其子类或 wxSizerFlags

目前,模态函数显示的对话框会冻结 wxWidgets,直到对话框关闭。这是有意的,但在 Erlang 中,您可以同时运行多个 GUI 应用程序,这会引起麻烦。希望在未来的 wxWidgets 版本中能够修复此问题。

事件处理

wx 中的事件处理与原始 API 的差异最大。您必须在 wxWidgets 中指定要处理的每个事件,这在 Erlang 绑定中也是如此,但您可以选择将事件作为消息接收或使用回调 funs 处理它们。

否则,事件订阅将作为 wxWidgets 动态事件处理程序连接处理。您可以从具有 IDID 范围内的对象订阅特定类型的事件。回调 fun 是可选的,如果没有提供,则该事件将被发送到调用 connect/2 的进程。因此,处理程序是一个回调 fun 或一个将接收事件消息的进程。

事件按照从下到上的顺序,在小部件层次结构中,由最后订阅的处理程序优先处理。根据是否调用 wxEvent:skip(),该事件将随后由其他处理程序处理。大多数事件都安装了默认的事件处理程序。

消息事件看起来像 #wx{id=integer(), obj=wx:wxObject(), userData=term(), event=Rec }。id 是接收事件的对象的标识符。obj 字段包含您使用 connect 的对象。userData 字段包含用户提供的项,这是 connect 的一个选项。event 字段包含一个具有事件类型相关信息的记录。事件记录中的第一个元素始终是您订阅的类型。例如,如果您订阅了 key_up 事件,您将收到 #wx{event=Event},其中 Event 将是一个 wxKey 事件记录,其中 Event#wxKey.type = key_up

wxWidgets 中,如果开发人员希望事件被其他处理程序处理,则必须调用 wxEvent:skip()。如果使用回调,则可以在 wx 中执行相同操作。如果您希望将事件作为消息,则只需不提供回调,并且可以在 connect 调用中将 skip 选项设置为 true 或 false,默认值为 false。True 表示您收到消息,但也允许后续处理程序处理该事件。如果您想动态更改此行为,则必须使用回调并调用 wxEvent:skip()

回调事件处理是通过在附加处理程序时使用可选的回调 fun/2 来完成的。fun(#wx{},wxObject() 必须接受两个参数,其中第一个参数与上面描述的消息事件相同,第二个参数是对实际事件对象的对象引用。使用事件对象,您可以调用 wxEvent:skip() 并访问所有数据。使用回调时,如果要将任何事件转发到后续处理程序,则必须自己调用 wxEvent:skip()。在 fun 返回后,将删除实际的事件对象。

回调始终由另一个进程调用,并在调用时独占使用 GUI。这意味着回调 fun 不能使用进程字典,也不应调用其他进程。如果另一个进程正在等待其对 GUI 的调用的完成,则在回调 fun 内调用另一个进程可能会导致死锁。

鸣谢

Mats-Ola Persson 编写了最初的 wxWidgets 绑定,作为其硕士论文的一部分。当前版本是完全重写的,但许多想法已被重用。重写的主要原因是我们在他身上提出的要求有限。

还要感谢开发和支持它的 wxWidgets 团队,以便我们有东西可以使用。