vieweditattachhistorytopchangessearchhelp

第74回


■SqueakではじめるSmalltalk入門   第74回  鷲見 正人


本連載では、名前は知っていてもなかなか触れる機会のないSmalltalkについて、最近話題のSqueakシステムを使って紹介しています。今回は、製作中の簡易GUIビルダにおいて、扱うことができるウィジェットの種類を増やす拡張を施す際、メソッド定義のコピーをせずともそれができる仕組みを考えます。

テキストフィールドをウインドウに追加するためのメソッドは次のようなものでした。

addFieldTo: window
  | field relFrame |
  field := PluggableTextMorph
     on: self text: nil accept: nil readSelection: nil menu: nil.
  relFrame := (Rectangle fromUser
     scaleFrom: window layoutBounds
     to: (0 asPoint extent: 1e3 asPoint)) scaleBy: 1.0e-3.
  window addMorph: field frame: relFrame


前回は、ボタンを追加するためのメソッド「#addButton:to:」を、この#addFieldTo:の必要な部分を書き換えることで定義したのでしたね。しかし、新しいウィジェットを扱えるようにするたびに、似たようなメソッドの複製が増えてゆくことは避けるべきです。

そこで、どんなウィジェットでも利用できる汎用のウィジェット追加用メソッドがどんなものであればよいのかを考えます。この新しいメソッドは、ウィジェットの種類を#fieldや#buttonなどといったシンボル(widgetSym)で与えることにして、「add: widgetSym to: window」というメッセージで起動できるメソッド「#add:to:」であると想定します。#addFieldTo:と#addButtonTo:の共通部分を抽出して、#add:to:の定義は(まだ完全ではありませんが…)次ように書くことが可能です。

add: widgetSym to: window
  | widget relFrame |
  widget ":= この部分をどう書くか? ".
  relFrame := (Rectangle fromUser
     scaleFrom: window layoutBounds
     to: (0 asPoint extent: 1e3 asPoint)) scaleBy: 1.0e-3.
  window addMorph: widget frame: relFrame


ちなみに、この擬似的なメソッド定義はもちろん動作はしませんし、コンパイル時に警告も出ますが、コンパイル(accespt (cmd + S))それ自体は通るので、コピー&ペーストで登録作業を済ませてしまってかまいません。こうした良い意味で“ルーズ”なところはSmalltalkもObjective-Cとよく似ていますね。

さて。問題は、この疑似コードの一行目の「この部分を…」でwidgetSymで指定したシンボル(#field、#button)からウィジェットそのものを作って返すような式を書くことです。

まず、ウィジェットを表わすシンボルと同名で、起動すると、それぞれの名前が示すウィジェットを作成して返してくるメソッドを定義しましょう。次の二つのメソッドを別々にシステムブラウザにコピー&ペースト後、accept (cmd+ S)してください。

field
  ^ PluggableTextMorph
     on: self
     text: nil
     accept: nil
     readSelection: nil
     menu: nil

button
  ^ (PluggableButtonMorph
     on: self
     getState: nil
     action: nil) label: 'button'; yourself


これらのメソッドは通常、「self field」とか「self button」というメッセージ式を記述して起動します。しかし、メッセージを動的に送信するためのメソッド「#perform:」を用いることで、次のような記述も可能です。

self perform: #field
self perform: #button


パラメータ部分を変数widgetSymにすれば、次のよう書き直せます。

self perform: widgetSym


冒頭の#add:to:の問題の箇所をこの式で“穴埋め”しましょう。

add: widgetSym to: window
  | widget relFrame |
  widget := self perform: widgetSym.
  relFrame := (Rectangle fromUser
     scaleFrom: window layoutBounds
     to: (0 asPoint extent: 1e3 asPoint)) scaleBy: 1.0e-3.
  window addMorph: widget frame: relFrame


実際に、システムブラウザの#add:to:も、このように書き換えてから、あらためてaccept (cmd + S)してください。

仕上げに#addModelItemsToWindowMenu:も、これまでの#addFieldTo:や#addButtonTo:の代わりに、新しく定義した#add:to:を使ったものに書き換えましょう。パラメータがこれまでのwindowから、widgetSym、windowの二つに増えたので#add:target:selector:argumet:の代わりに、#add:target:selector:argumentList:を用います。

addModelItemsToWindowMenu: aMenu
  | window |
  window := aMenu defaultTarget.
  aMenu addLine.
  aMenu
     add: 'add field'
     target: self
     selector: #add:to:
     argumentList: {#field. window}.
  aMenu
     add: 'add button'
     target: self
     selector: #add:to:
     argumentList: {#button. window}.
  aMenu addLine.
  aMenu add: 'delete' target: self selector: #removeWidgetFrom: argument: window


#addField:to:、#addButtonTo:はもはや無用ですので、削除してしまってかまいません。メソッドの削除は、右上のペインで削除したいメソッドを選択した状態で黄ボタンメニューから「remove method」を選択します。

[fig.A]不要となったメソッドの削除操作
Uploaded Image: 74a.png

この仕組みにより、ウィジェットの登録の手続きは#add:to:に集約できたので、懸案だったメソッドの複製をして同じようなコードを分散させる心配もなくなりました。

次回はさらに一歩踏み込んで、Smalltalkの強力な動的性とリフレクション機能を最大限に活用し、ウィジェットの種類を増やすときにウィジェット生成のメソッドを登録するだけで済ませることができる(つまり、新たなウィジェット用のメニュー項目の追加も自動的に行なわれる…)ようなカラクリに挑戦してみましょう。

このページを編集 (4833 bytes)


Congratulations! 以下の 1 ページから参照されています。

This page has been visited 238 times.