如何使用 lablgtk2 编写实现 MVC 的新小部件?

How to write new widgets implementing MVC with lablgtk2?

我正在为 lablgtk2(Gtk+ 的 OCaml 绑定)编写一系列新的小部件。其中一些小部件可以编辑或呈现相当复杂的信息,因此我对使用模型-视图-控制器或主题-观察器很感兴趣,类似于 GTree 模块中的内容。

这个模块定义了一个GTree.model和一个GTree.viewclass,每个都有可以连接的信号,一个GTree.model可以连接一个或多个GTree.view的。

在纯 OCaml 中模仿这个组织并不是那么简单,因为库中可用的代码是 C 库的绑定。我需要完成以下步骤:

  1. 定义新的小部件
  2. 定义新信号
  3. 触发这些新信号
  4. 定义新模型

我可以完成 1 和 2,但我不确定如何完成 3 和 4。如何正确完成这些?

定义新的小部件

新widgets的定义本身没有问题。新的小部件通常是 Gnome canvas 的专用版本或复合版本。在前一种情况下,我们的新小部件可以作为 GObj.widget 从 Gnome canvas 继承,在后一种情况下,我们可以使用容器提供的 GObj.widget 来保存组合。这通常看起来像

class view () =
  let vbox = GPack.vbox () in
  …
  object(self)
    inherit GObj.widget vbox#as_widget
    …
  end

定义新信号

绑定为定义新信号的代码提供了大量示例,因此我们可以为我们的小部件定义新信号,如下面的代码片段所示,考虑没有参数的信号的简单情况:

open GtkSignal

module Event =
struct
  let plop : ([>`widget], unit -> unit) t = {
    name = "plop_event";
    classe = `widget;
    marshaller = marshal_unit;
  }
  let fizz : ([>`widget], unit -> unit) t = {
    name = "fizz_event";
    classe = `widget;
    marshaller = marshal_unit;
  }
end

class pill_signals obj =
object (self)
  inherit ['a] GObj.gobject_signals (obj :> Gtk.widget Gobject.obj)
  method plop = self#connect Event.plop
  method fizz = self#connect Event.fizz
end

使用这些定义,我们的 view 小部件可以通过定义适当的 connect 方法来公开这些信号:

  method connect =
    new pill_signals obj

触发新信号

似乎函数 GtkSignal.emit 的目的是向对象发出信号,触发已注册的回调。此函数用作以下签名:

val emit :
  'a Gobject.obj ->
  sgn:('a, 'b) GtkSignal.t ->
  emitter:(cont:('c Gobject.data_set array -> 'd) -> 'b) ->
  conv:(Gobject.g_value -> 'd) -> 'b

前两个参数不言自明,但不太清楚,剩下的两个参数是什么。不幸的是,lablgtk 源代码中没有使用示例,因为信号是从代码的 C 端发出的。这两个参数似乎与信号参数的准备有关,具体化为 'c Gobject.data_set array 以及使用标记为 ~conv 的参数检索产生的值。尽管如此,~cont参数在发射器中的作用仍需明确。

定义新模型

模型定义中棘手的部分是它应该继承自 GObj.object 以便能够发送接收信号。不幸的是,没有允许直接定义最小 Gtk+ 对象的函数。我在这个方向走得最远的是

module Model =
struct
  let create () =
    GtkObject.make ~classe:"GObject" []
end

let model () =
  new model (Model.create ())

调用函数model实例化相应的对象会产生消息:

Gtk-CRITICAL **: IA__gtk_object_sink: assertion 'GTK_IS_OBJECT (object)' failed

显然,这里有些可疑,很可能是参数列表(上面代码段中的空列表)太小了。

LablGTK 为 Gtk 信号机制提供了一个很好的接口,这使我们可以在不修改 GtkSignal 和编组函数的情况下使用它。此接口由 GUtil 提供,并有详细的文档记录。


如何使用 GUtil,如模块文档中所述

将 ML 信号添加到 LablGTK 对象:

{[
   class mywidget_signals obj ~mysignal1 ~mysignal2 = object
     inherit somewidget_signals obj
     inherit add_ml_signals obj [mysignal1#disconnect; mysignal2#disconnect]
     method mysignal1 = mysignal1#connect ~after
     method mysignal2 = mysignal2#connect ~after
   end

   class mywidget obj = object (self)
     inherit somewidget obj
     val mysignal1 = new signal obj
     val mysignal2 = new signal obj
     method connect = new mywidget_signals obj ~mysignal1 ~mysignal2
     method call1 = mysignal1#call
     method call2 = mysignal2#call
   end
]}

您还可以将ML信号添加到任意对象;只是继承 ml_signals 代替 widget_signalsadd_ml_signals.

{[ 
  class mysignals ~mysignal1 ~mysignal2 = object
     inherit ml_signals [mysignal1#disconnect; mysignal2#disconnect]
     method mysignal1 = mysignal1#connect ~after
     method mysignal2 = mysignal2#connect ~after
   end
]}

现在很容易解决上面的第 1、2、3 和 4 点:

  1. 这很好
  2. 使用 GUtil 来定义新信号而不是 GtkSignal
  3. 触发新信号是通过 ['a] GUtil.signalcall 方法完成的。
  4. 既然不用GtkSignal了,其实也没什么问题。