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

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

私に教えられることなら

Pharo4/Moose5でGlamourを使ってGUIを構築するメモ

Squeak SmalltalkでToolBuilderを使ってGUIを構築するメモ - レガシーコード生産ガイドに続き、今回はMoose5(Pharo4)でGlamourを使い、Dictionaryの内容を表示するGUIを作ってみる。

Pharo4にGlamourを入れれば多分使えると思うけど、Moose5が全部入りで楽なのでおすすめ。

Deep into Pharoにはより詳細な説明と、Glamourを使ってちゃんと動くクラスブラウザを作るサンプルがある。

Glamourとは

Smalltalkには、オブジェクトを覗いて調査する便利なツールがいくつかある。それらを「ブラウザ」と呼ぶことにする。「クラスブラウザ」はまさにその名の通りだし、「インスペクタ」や「デバッガ」にもよくお世話になる。

それらはその目的の為にはもちろん便利なんだけど、あるオブジェクト専用のGUIが欲しい、必要な情報のみわかりやすく表示したいというときにはそぐわない。例えばインスペクタの表示は実装に寄りすぎている。

Glamourはそういった専用の「ブラウザ」を構築するためのツールキットらしい。ブラウザ作成に特化しているため、その目的には非常に使いやすい。

今回の目的

まずはこの画像のように、左に辞書のキーのリスト、右に選択したキーに対応する値をテキスト表示してみる。

f:id:phaendal:20160507205205p:plain

大まかな流れ

Glamourで簡単なブラウザを構築する大まかな流れは

  1. ブラウザのインスタンスを作る
  2. transmitでデータを伝えていく
  3. ブラウザのインスタンスに表示するモデルを渡して開く

となる。

1.ブラウザのインスタンスを作る

今回はGLMTabulatorを使う。次のコードのように、rowcolumnの組み合わせで表示領域を作れる。

| browser |
browser := GLMTabulator new.
browser
  title: 'Dictionary Viewer';
  column: #keys;
  column: #val.

これで左に#keys、右に#valと名付けられた表示領域を持つブラウザを作れる。

row:column:にはシンボル又はブロックを渡せる。シンボルの場合はその領域の名前で、ブロックを使えば次のように入れ子にできる。

| browser |
browser := GLMTabulator new
  title: 'Nested';
  column: #a;
  column: [:col|
    col
      row: #b;
      row: c].

これに内容を入れると、次のような表示になる。

f:id:phaendal:20160507210810p:plain

rowは上から、columnは左からの指定。まずcolumn2つで対象を左右に2分割している。ブロックの引数colはそのcolumnで、row:で更に上下に分割している。

row:にもブロックを渡せる。好きなだけ入れ子にしていけるみたいだ。

とりあえず今回は左右だけに分割する。(再掲)

| browser |
browser := GLMTabulator new.
browser
  title: 'Dictionary Viewer';
  column: #keys;
  column: #val.

2. transmitでデータを伝えていく

ブラウザのインスタンスtransmitメッセージを送ることでtransmitオブジェクトが得られる。transmitに、どこにどのデータをどう表示するか、どうデータを送信して行くか指定することができる。

今回はDictionaryの内容を、

  • 左側の#keysにDictionaryそのものを伝えて、キーを表示させる
  • 右側の#valに、#keysで選択したキーを持つAssociationオブジェクトを伝えて、値を表示させる

と伝えて表示していく。

Dictionaryのキーをリスト表示

左側の#keys

browser transmit to: #keys;
    andShow: [ :a |
        a list
            title: 'keys';
            display: [ :model | model associations ];
            format: [ :assoc | assoc key]].

まず1行目。transmitオブジェクトを取り出し、どの領域にデータを送信するかto: 名前で指定する。どこから送られるかはfrom:で指定できて、指定されてない場合は一番おおもとのモデルが送られる。今回はDictionary。

次に、andShow:にブロックを渡して、扱うデータや表示の仕方などを設定する。

このaを何と呼べばいいのかわからない(presentation?)んだけど、とりあえずa listでリスト表示を指定している。a list title:...DSLにするための命名みたいだ。

title:は省略可能。そのままタイトルで、タブのように表示される(というかタブにできる)。

display:で、伝わってきたmodelの何を扱うか指定する。省略するとモデルと表示方式(これはlist)で適当に扱ってくれる。

しかしDictionaryの場合、Dictionary keysの内容になってしまう。これだと選択したらそのkeyが次の領域に送信される。今回は選択したAssociationを送信したいので、model associationsをリスト表示する。

format:で、associationsのそれぞれのAssociationをどう表示するか指定できる。省略可能で、最終的にdisplayStringメッセージの結果が表示される。今回は左側にはキーを表示したいのでassoc keyとした。

ちなみにdisplay:format:などのブロックを指定できるところで、メッセージ一つしか送らない場合は、次のようにシンボルでメッセージを指定することで短く書ける。

browser transmit to: #keys;
    andShow: [ :a |
        a list
            title: 'keys';
            display: #associations;
            format: #key].

選択したAssociationのvalueを表示

右側の#val

browser transmit from: #keys; to: #val;
    andShow: [ :a |
        a text
            title: 'value';
            display: [ :assoc | assoc value ] ].

今回は左側の#keysから選択したAssociationを送ってほしいので、from:で指定した。

a textでテキスト表示と指定している。

displayに何が来てるかに注意してほしい。最初に#keysに渡されるのはモデル(Dictionary)で、a list指定でassociationsを表示した。その中の一つのAssociationを選ぶと、それが#valに送られる。

3. ブラウザのインスタンスに表示するモデルを渡して開く

browser openOn: モデルで渡すだけ。サンプルのDictionaryの生成も含めて、全体のコードは以下。

| browser dict |
dict := {
    #foo -> 'bar'.
    #baz -> 'yo!'.
    } asDictionary.

browser := GLMTabulator new.
browser
    title: 'Dictionary Viewer';
    column: #keys;
    column: #val.
    
browser transmit to: #keys;
    andShow: [ :a |
        a list
            title: 'keys';
            display: #associations;
            format: #key].
    
browser transmit from: #keys; to: #val;
    andShow: [ :a |
        a text
            title: 'value';
            display: #value ].
        
browser openOn: dict.

これ以降

Deep into Pharoの説明・サンプルがわかりやすいのでオススメ。ただ、Moose5.1だとMondrianのサンプルが動かなかった。

a lista text以外にもいろんな表示があるし、それぞれにdisplay:format:以外の細かい指定もある。例えばある条件に合うときだけ表示するwhen:、データの保存などができるact:など。

今回書いたコードだとDictionaryに変更を加えても反映されないけど、Announcementを使って楽に通知できる方法もあるのかな?そこらへんは詳しい人に期待。

広告を非表示にする