查看源代码 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/0
、wx: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]} 表示
wxString 由 unicode: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 动态事件处理程序连接处理。您可以从具有 ID 或 ID 范围内的对象订阅特定类型的事件。回调 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 团队,以便我们有东西可以使用。