第75回
■
■SqueakではじめるSmalltalk入門 第75回 鷲見 正人
■
本連載では、名前は知っていてもなかなか触れる機会のないSmalltalkについて、最近話題のSqueakシステムを使って紹介しています。
前回は、#addFieldTo:、#addButtonTo:といった、各ウィジェットをGUIビルダのウインドウへ追加するための専用メソッドの共通部分を新しい#add:To:メソッドとしてまとめ、ウィジェット生成のほうは、ウィジェットタイプ(#fieldや#buttonといった…)を名前に持つメソッドに分担させる改変を行ないました。
これにより、当初のテキストフィールドやプッシュボタン以外のウィジェットを扱えるようにする拡張手続きはとても簡単になりました。が、まだ相変わらず、追加したウィジェット用にメニュー項目を新設する作業は必要です。具体的には、 #addModelItemsToWindowMenu:というメソッドに、メニュー項目の追加、および、そのメニュー項目に適切なウィジェットタイプのシンボルをパラメータとして#add:to:メソッドを呼び出すよう機能の割り振りをする行を挿入しないといけません。
そこで今回は、このメニュー構築の手続き自体も動的にしてしまい、新しく扱うウィジェットを生成するためのメソッドを定義するだけで済ませることが可能な仕組みを考えることにします。
▼与えられたウィジェットタイプ群からメニューを自動的に生成する
たとえば、#fieldと#buttonという二種類のウィジェットタイプを扱えるとして、それを表わす情報が配列#(#field #button)として与えられたと想定しましょう。ここから、「add field」「add button」といったメニュー項目用の文字を生成することは#collect:を使えば簡単です。
#(#field #button) collect: [:each | 'add ', each] " => #('add field' 'add button') "
なお、配列リテラル式において、要素となるシンボルの#は(対象となるシンボルがスペースなどの記号を含まないなら…)省略可能なので、こう書いても同じです。
#(field button) collect: [:each | 'add ', each]
#collect:は結果を集めていますが、ただブロック内の処理を繰り返すだけなら#do:を用います。このことをふまえて、#addModelItemsToWindowMenu:を次のように書き換えてみましょう。書き換えたらaccept (cmd + S)によるコンパイルもお忘れなく。
addModelItemsToWindowMenu: aMenu
| window |
window := aMenu defaultTarget.
aMenu addLine.
#(field button) do: [:widgetSym |
aMenu
add: 'add ', widgetSym
target: self
selector: #add:to:
argumentList: {widgetSym. window}].
aMenu addLine.
aMenu add: 'delete' target: self selector: #removeWidgetFrom: argument: window
ウインドウメニューをプルダウンしても、これまでとの違いはまったくありませんし、項目を選択したときの動作も同じです。しかし、#(field button)のところにウィジェットタイプを書き足すだけでメニュー項目を増やすことができるようになっています。ためしに、#(field button list)と書き換えてacceptしてみてください。メニュー項目には「add list」が追加されているはずです。
[fig.A]メニュー項目に自動的に追加された「add list」
このメニュー項目はまだ機能しませんが(選択するとエラー)、項目リストを扱うためのウィジェットであるa PluggableListMorphを生成する次のメソッド「#list」を追加することで動作するようになります。
list
^ PluggableListMorph
on: self
list: nil
selected: nil
changeSelected: nil
menu: nil
▼扱うことができるウィジェットの種類を動的に得る
残る問題は、#(field button list)といった配列をどうやって得るか?です。これにはSmalltalkの強力なリフレクション機能を活用することにいたします。具体的には、まず、新しい「widget types」というカテゴリを設けて、そこに#field、#button、#listというウィジェット生成用のメソッドを分類しておき、改めて「widget types」カテゴリにあるメソッド名をクラスに尋ねることで、目的の情報を得ようというもくろみです。
システムブラウザにて、メソッドカテゴリリスト(上段右から二番目の枠)で「-- all --」が選択されていることを確認してから、メソッド名リスト(右隣、上段右端の枠)から「field」を選択。そのペインのシフト黄ボタンメニュー(shiftキーを押しながら黄ボタン、あるいは、黄ボタンメニューをポップアップさせてmore...を選択)から「change category...」→「new...」を選択し、新しいカテゴリを追加するための入力欄で「widgettypes」とタイプして入力し「了解(s)」(英語モードならAccept)します。
[fig.B]widget typesカテゴリの追加
すると、メソッドカテゴリリスト(左隣の枠)に「widget types」というカテゴリが現れます。見た目では分かりませんが、一連の作業により#fieldメソッドは、このカテゴリに再分類されています。次に、メソッド名リストから「button」を選択し、同じようにシフト黄ボタンメニューから「changecategory...」を選択してください。今度はメニューに「widget types」が含まれているので、これを選んでおしまいです。#listについても同様に行ないます。
メソッドカテゴリリストで「widget types」をクリックして選択し、三つのメソッドがきちんと収まっているか確認しておきましょう。もし再分類から漏れたメソッドがあるときは「-- all --」を選び直して、操作を繰り返します。
[fig.C]widget typesカテゴリに分類されたウィジェット生成用メソッド群
以上で準備は整ったので最後の仕上げです。クラスに対して、指定したカテゴリに属するメソッド名(セレクタ)を列挙してくれるよう頼むのには、次の式を用います。
GuiBuilder allMethodsInCategory: 'widget types' " => #(#button #field #list) "
この式を先の#addModelItemsToWindowMenu:でハードコードした配列に置き換えれば、当初の「メニュー生成までの自動化」という目的は達成できます。
addModelItemsToWindowMenu: aMenu
| window |
window := aMenu defaultTarget.
aMenu addLine.
(self class allMethodsInCategory: 'widget types') do: [:widgetSym |
aMenu
add: 'add ', widgetSym
target: self
selector: #add:to:
argumentList: {widgetSym. window}].
aMenu addLine.
aMenu add: 'delete' target: self selector: #removeWidgetFrom: argument: window
ためしに、'widget types'のメソッドを別のカテゴリに再分類したり元に戻したりして、メニュー項目がきちんとその変更に追従できているか確認してみてください。
このページを編集 (5690 bytes)
|
以下の 1 ページから参照されています。 |
This page has been visited 249 times.