読者です 読者をやめる 読者になる 読者になる

レガシーコード生産ガイド

私に教えられることなら

Squeak SmalltalkでToolBuilderを使ってGUIを構築するメモ

いい加減FooMorph newからMorphを作っていくのが面倒になってきた。

じゃあ簡単なフレームワークを作ろうかとも思ったけど、せっかくなので既にあるものをSqueak内部で探してみる。

調べたところ、Squeak5時点でのクラスブラウザ等のツールは、ToolBuilder(MorphicToolBuilder)を使って構築されているみたいだ。じゃあ中身を見てみよう。

……というところでずっと止まっていた。フレームワークの類は流石に使用例以外にドキュメントが無いと辛い。しかし探しても見つからなかったので触りだけ調べてメモしておく。

基本

specオブジェクトでMorphを設計し、builderに渡して構築させるみたいだ。specはToolBuilder-Kernelなどにいろんな種類がある。

今回は

  • ウインドウを表示
  • 押すとブロックを実行するボタンを配置
  • コレクションの変更を反映するリストビューを配置

を目標に、3つのspecを作ってみる。

ウインドウを表示

PluggableWindowSpecからspecを作って配置する。

  1. PluggableWindowSpecインスタンスを作り、label:メッセージでタイトルを設定
  2. ToolBuilder class>>#open:にそのインスタンスを渡す
"1"
windowSpec := PluggableWindowSpec new.
windowSpec label: 'hello!'.

"2"
ToolBuilder open: windowSpec.

Workspaceでdo itするとウインドウが表示される。

f:id:phaendal:20160504175720p:plain

ボタンを追加

PluggableButtonSpecからspecを作って配置する。

  1. 配置するために、windowSpecのchildrenをOrderedCollectionで初期化する
  2. PluggableButtonSpecインスタンスを作る
  3. label:でボタンのラベルを、action:で押したときに実行されるブロックを設定
  4. frame:で配置エリアを設定 (自由自在Squeakプログラミングの8章で解説されている)
  5. windowSpecのchildrenに追加

specは設計図なので、ToolBuilderに渡した後に変更しても、それで生成されたMorphには反映されないことに注意。

windowSpec := PluggableWindowSpec new.
windowSpec
    label: 'hello!';
"1"
    children: OrderedCollection new.

"2" 
buttonSpec := PluggableButtonSpec new.

"3"
buttonSpec
  label: 'yo';
  action: [Transcript cr; show: 'yo!'];
"4"
  frame: (0@0 corner: 0.5@0.1).

"5"
windowSpec children add: buttonSpec.

ToolBuilder open: windowSpec.

実行結果。ボタンを押すとyo!とTranscriptに追加されていく。

f:id:phaendal:20160504180659p:plain

クラスを追加

リストビューはPluggableListSpecで作れるんだけど、その更新をDependencyメカニズムを使って通知する必要がある。(詳しくは使わないと損をするModel-View-Controllerなどを参照)

Workspaceだけでできなくもないんだろうけど、クラスを作ってしまった方が手っ取り早い。クラス名FooToolとしてクラスを作り、ついでにクラスブラウザなどのspecの作り方を真似してみる。

ToolBuilder build: fooとオブジェクトを渡すと、ToolBuilderのインスタンス(builderとする)がそのオブジェクトのbuildWith:メソッドに渡される。

とりえあず、PluggableWindowSpec newなどSpecクラスから直に生成するのではなく、builder pluggableWindowSpec newという風にbuilderからクラスを取得して生成した方がいいみたいだ。

FooToolクラスのインスタンスメソッドbuildWith:に上記のボタン追加までを書いてみる

buildWith: builder
    |windowSpec buttonSpec|
    windowSpec := builder pluggableWindowSpec new.
    windowSpec
        label: 'hello!';
        children: OrderedCollection new.

    buttonSpec := builder pluggableButtonSpec new.
    buttonSpec
      label: 'yo';
      action: [Transcript cr; show: 'yo!'];
     frame: (0@0 corner: 0.5@0.1).
    windowSpec children add: buttonSpec.

    ^builder open: windowSpec

生成がbuilder SpecClass newに変わっただけ。そしてToolBuilder build: FooTool new.do itすると、前と同じように表示される。

リストビューを追加

  1. リストの内容を全て取得するメソッド(#listとする)
  2. リストに項目を追加し、self changed: #listで更新を通知するメソッド

を追加する必要がある。

また、クラスブラウザなどではbuildFooWith: builderのように各配置アイテムのspec生成を分けているので、真似してリストビューspec生成メソッド#buildListWith:を作ってみる。

前提として、#listメソッドで対象のOrderedCollectionを取得できることにする。(自由に作ってください)

buildListWith: builder
    | spec |
    spec := builder pluggableListSpec new.
    spec 
        model: self;
        list: #list; 
        frame: (0@0.1 corner: 1@1).
    ^spec

それから、アイテムを追加して、かつ更新を通知する#addメソッドを作っておく。

add: anObject
    self list add: anObject.
    self changed: #list.

最後に、#buildWith:メソッドにspec生成/配置を追加する。また、ボタンをクリック時に適当な文字列をリストに追加してみる。

buildWith: builder
    |windowSpec buttonSpec listSpec|
    windowSpec := builder pluggableWindowSpec new.
    windowSpec
        label: 'hello!';
        children: OrderedCollection new.
        
    listSpec := self buildListWith: builder.
    windowSpec children add: listSpec.  

    buttonSpec := builder pluggableButtonSpec new.
    buttonSpec
      label: 'yo';
      action: [self add: 'yo!', self list size];
     frame: (0@0 corner: 0.5@0.1).
    windowSpec children add: buttonSpec.

    ^builder open: windowSpec

ToolBuilder build: FooTool newで表示してみた結果

f:id:phaendal:20160504184723p:plain

リストビューに、リストへの追加が反映されている。

これ以降

例えばBrowserクラスの#buildWith:では、フレーム -> [spec生成]というassocの配列を使って生成し、配置位置を1つのメソッドで一覧できるようにしている。

そういった細々した使い方の違いはあるけど、とりあえずここに書いた知識から辿っていけるのではないでしょうか。あとはもっと詳しい人が詳しい使い方を書いてくれるのを期待します。じゃ、頼みましたぞ。

広告を非表示にする