sid-swing-a

scala.swing パッケージ

The scala.swing package
Ingo Maier
November 1, 2009


1 紹介 (Introduction)


scala.swing パッケージは Java Swing クラスの周りに、薄いラッパーのコレクションを提供します。 我々の目標は、Scala 中で自然に使えると同時に、既存の Swing プログラミング経験のある Java プログラマーも気にいる、Java Swing に十分近いライブラリを配布することです。

このドキュメントは Scala における Swing プログラミングの簡単な紹介であり、scala.swing 中の利用可能なクラスを概観し、そのデザイン原則を示します。最後に、ライブラリ拡張者が追加ラッパーを書くときに従うべきガイドラインを示します。 このドキュメント全体を通して、scala.swing ディストリビューションの一部である scala.swing.test パッケージ中の例を参照します。

読者には、少なくとも Scala についての基本的知識と、Java Swing あるいは似たような GUI ツールキットのある程度の経験が必要とされます。Scala の一般的な紹介については、http://www.scala-lang.org/node/1305 を参照してください。scala.swing の基本を含む、より包括的な紹介については、本『Programing in Scala』[1]を推奨します。Java Swing の良いチュートリアルは http://java.sun.com/docs/books/tutorial/uiswing にあります。


2 はじめ (Getting started)


次の図 1 で示す例は、コンパイルして簡単な "Hello World" scala.swing プログラムを生成します。

   import swing._
   
   object HelloWorld extends SimpleSwingApplication {
     def top = new MainFrame {
       title = "Hello, World!"
       contents = new Button {
         text = "Click Me!"
       }
     }
   }
#ref error :画像を取得できませんでした。しばらく時間を置いてから再度お試しください。
図 1 : 簡単な "Hello World!" アプリケーションのフレーム

たいていの簡単な scala.swing アプリケーションは、上記の HelloWorld のような、クラス SimpleSwingApplication(*1) を拡張するメインオブジェクトから成ります、メインオブジェクトは、引数をとらず、Frame あるいはこの場合 MainFrame を返す、メソッド top を実装する必要があります。フレームは、閉じるボタンやリサイズハンドルのような飾りをもつ、OS の通常の表示窓です。 MainFrame はフレームであり、閉じるときに自動的にアプリケーションを停止し、上記のようにただ 1 つのフレームだけを必要とする大部分のアプリケーションで使われています。

(*1) SimpleGUIApplication は Scala 2.8 では クラス SimpleSwingApplication に置き換わりました。

起動時は、クラス SimpleSwingApplication は Swing フレームワークの初期化を行い、メソッド top によって返されるフレームを開きます。上の実装は、メンバーの初期化に scala.swing の標準的な構文を使ってメインフレームを生成します。本当は、次の式をもつ無名クラスを生成しています。

   new MainFrame {
     title = "Hello, World!"
     contents = new Button {
       text = "Click Me!"
     }
   }

無名クラス構文により、弓カッコ内のクラス MainFrame のすべてのメンバーにアクセスできます。フレームのタイトルプロパティを文字列 "Hello、World!" に設定し、フレームのコンテンツを、"Click Me!" と書いてある、クリックされても何もしないシンプルなクリックボタンに設定しています。

多くのクラスでは、Java Swing が提供するものに似た少数の便利なコンストラクタを提供し、コンポーネント生成をより簡潔にしています。上記のボタンインスタンス化は、次のようにも書けます。

   contents = new Button("Click Me!")

しかし、より多数のプロパティ設定では、プロパティに明示的に名前をつけると scala.swing コードが分かり易くなることがよくあるので、一般的には無名クラス構文の使用が望ましいです。


2.1 クラス SwingApplication と SimpleSwingApplication


上記の クラス SimpleSwingApplication は、デフォルト main メソッドを実装する SwingApplication を拡張しています。それはさらに次の 3 つのメソッドを提供します。:

   def startup(args: Array[String])
   def quit() { ... }
   def shutdown() { ... }

メソッド startup は、デフォルト main メソッド実装中で呼び出され、クライアントによって実装される必要があります。これは Swing イベントディスパッチングのスレッド上で呼び出されます。メソッド quit は、アプリケーションを潔く停止するときに呼び出されるべきです。それは、アプリケーションを最終的に終了させる前に、最後のステップとしてメソッド shutdown を呼び出します。リソースをクリーンアップし、シャットダウンで特定のコードを実行する必要があるクライアントは、メソッド shutdownを上書きすべきです。

サブクラス SimpleSwingApplication は、メソッド startup のデフォルト実装を提供していて、それは、クライアントが実装したメソッド top によって返さるフレームを表示します。

我々の手法が、http:://java.sun.com/developer/technicalArticles/javase/swingappfr/ において導入された Java Swing アプリケーションフレームワークに似てはいるがよりシンプルであることに言及しておきます。


3 レイアウトコンポーネント (Laying out components)


前に、フレームにボタンを加える方法を見ました。より複雑なレイアウトに対しては、コンテナ内にコンポーネントをネストし、特定のルールに従って、それら子コンポーネントを配置します。Java Swing と同じく scala.swing にも、コンテナコンポーネントに 2 つの型、ペインとパネルがあります。ペインは、可能なら各ペインに特有の装飾と一緒に、決まった数の子コンポーネントを表示します。1 つの例は分割ペインです。それは 2 つのコンポーネントを水平あるいは垂直に並べて表示し、そのあいだにノブをいれて、ユーザーが 2 つのコンポーネントの広さを調整できるようにします。もう 1 つの例はスクロールペインです。それはスクロールバーのついたキャンバスの中にただ一つのコンポーネントを置きます。

他方、パネルは、クライアントがある程度カスタマイズ可能なレイアウトルールに従って、任意数の子コンポーネントを配置するコンテナです。


3.1 強く型付けされた、簡潔なコンテナインターフェース

(A strongly typed, concise container interface)

Java Swing では、コンテナは特定のレイアウトマネージャからは切り離されています。Java Swing コンテナを生成するために、次のように、クライアントは適切なレイアウトマネージャと一緒に JPanel コンポーネントを使います。

   val panel = new JPanel()
   panel.setLayout(new BorderLayout())

java.awt.Component 基底クラスの add メソッドの 1 つを使って、コンポーネントを加えることができます。:

   def add(comp: Component, constraints: Object): Component
   def add(comp: Component, index: Int): Component
   def add(comp: Component,constraints: Object, index: Int): Component

次は、上のパネルに、Borderlayout マネージャ ルックスのボタンを加えます。:

   panel.add(new JButton("click me"), BorderLayout.CENTER)

JPanel.add メソッドは、与えられたコンポーネントとそのレイアウト制約を利用できるようにする共通のインターフェースを介して、レイアウトマネージャを呼び出します。Java Swing と異なり、scala.swing 中のコンテナとレイアウトマネージャは繋がっています。これにより、インターフェースがより簡潔になり、コンパイル時により多くの型エラーを検出できます。Java Swing では、上記 add メソッのインターフェースで示されるように、レイアウト制約は、型 Object あるいは Int でなければなりません。型引数としてレイアウトマネージャと制約型をとるジェネリックなパネルクラスは、もっと良く型付けできますが、より複雑なインターフェースとなります。

   class JGenericPanel[L <: LayoutManager[C],
                       C <: LayoutConstraint] extends JComponent {
     protected def layout: L
     def add(comp: Component, constraints: C) = {
       ...
       layout.addLayoutComponent(comp, constraints)
       ...
     }
   }


さらにまた、レイアウトマネージャとコンテナにオブジェクト合成を使っても、何も柔軟性を得られません。なぜなら、レイアウトマネージャを変えることは必然的に、すべての子コンポーネントに対して相容れない制約を再設定することになるからです。これは、新しいコンテナを生成しすべての子コンポーネントを読み出すのと同じ量の仕事になります。

上記 2 つの議論から、次の統一されたレイアウトコンテナのインターフェースが導かれます。:

   trait LayoutContainer extends Container.Wrapper {
     type Constraints <: AnyRef
   
       val layout: Map[Component, Constraints] = ...
   
       protected def constraintsFor(c: Component): Constraints
       protected def areValid(c: Constraints): (Boolean, String)
       protected def add(comp: Component, c: Constraints)
   }

コンポーネントをレイアウトマップに加えることで、それらを、型 Constraints の制約に関連づけできます。


3.2 LayoutContainer の拡張

(Extending LayoutContainer)

LayoutContainer の具象サブクラスは、抽象 Contraints 型を修正し、ラッパーを基盤のピアへ接続する次の 3 つのメソッドを追加実装する必要があります。:

constraintsFor(c: Component): Constraints
与えられたコンポーネントの現在の制約を入手します。

areValid(c: Constraints): (Boolean, String)
与えられた制約が有効かどうか決定するための実行時チェックを行い、もし有効でなければ、Option型でエラーメッセージを提供します。

add(comp: Component, c: Constraints)
このコンテナに、与えられた制約をもつ与えられたコンポーネントを加えます。

Subclass BorderPanel, for instance, assigns the Contraints type to a custom Position enumeration and obtains the constraints from the underlying layout_manager while performing some casting operations.
たとえば、サブクラス BorderPanel は、カスタムの Position 列挙に Contraints 型を割り当て、そしていくつかのキャスト操作を実行している間に、基盤のレイアウトマネージャから制約を入手します。有効な Position 列挙値だけなので、メソッド areValid はどのような実行時チェックもする必要が無く、常に (true、" ") を返します。BorderPanel にコンポーネントを加えるのは、たいていの場合、簡単で、単に適切な引数で JPanel.add メソッドを呼び出すことです。:

   class BorderPanel extends Panel with LayoutContainer {
     import BorderPanel._
     def layoutManager = peer.getLayout.asInstanceOf[BorderLayout]
     type Constraints = Position.Value
     
     protected def constraintsFor(comp: Component) =
       wrapPosition(layoutManager.getConstraints(comp.peer).asInstanceOf[String])        
     protected def areValid(c: Constraints): (Boolean, String) = (true, "")
     
     protected def add(c: Component, l: Constraints) {
       peer.add(c.peer, l.toString)
     }
   }


4 イベントへの反応 (Reacting to events)


scala.swing 中のイベントの監視と反応は、発行者とリアクタ(反応者)のコンセプトをベースにしています。あらゆる scala.swing コンポーネントは、自動的に、イベントを発行する発行者およびイベントに反応するリアクタの両方です。図 2 で描かれたシンプルな Celsius から Fahrenheit への変換器をどのように実装できるか見ていきましょう。

最初に、2 つのそっくりなテキストフィールドをもつフレームを生成し、ユーザーが Celsius と Fahrenheit で温度を入力できるようにします。

#ref error :画像を取得できませんでした。しばらく時間を置いてから再度お試しください。
図 2 : シンプルな Celsius から Fahrenheit への変換器

   object Converter extends SimpleSwingApplication {
     def newField = new TextField {
       text = "0"
       columns = 5
     }
     val celsius = newField
     val fahrenheit = newField
     
     def top = new MainFrame {
       title = "Convert Celsius / Fahrenheit"
       contents = new FlowPanel(celsius, new Label(" Celsius = "),
                                  fahrenheit, new Label(" Fahrenheit"))
     }
   }

テキストフィールドは、型 EditDone のイベント発行者であり、このイベントは、ユーザーがフィールドをちょうど編集し終えたことを示します。それらのイベントに反応するために、テキストフィールドを監視するリアクタにリアクションを加える必要があります。SimpleSwingApplication はアプリケーションのデフォルトのグローバルなリアクタであり、それを拡張するメインオブジェクト Converter に、次のコードを入れることができます。

   listenTo(fahrenheit, celsius)
   reactions += {
     case EditDone(`fahrenheit`) =>
       val f = Integer.parseInt(fahrenheit.text)
       val c = (f - 32) * 5 / 9
       celsius.text = c.toString
     case EditDone(`celsius`) =>
       val c = Integer.parseInt(celsius.text)
       val f = c * 9 / 5 + 32
       fahrenheit.text = f.toString
   }

最初に、クラス Reactor のメソッド listenTo を呼び出すことで、メインオブジェクトが各テキストフィールドからのイベントを待つことを指示します。Reactor は SimpleSwingApplication の基底クラスです。次に、テキストフィールド celsius とテキストフィールド fahrenheit からの EditDone イベントに 2 つのリアクションを加えます。このリアクションはそれぞれ、編集されなかった他方のコンテンツを更新します。イベントは通常ケースクラス、あるいは抽出子(extractor)をもつクラスであり、上記の例のように、クライアントはそれらを容易にパターンマッチできます。

この例の完全なソースコードは、scala.swing テストパッケージにあります。


4.1 Java Swing Listeners 対 scala.swing Reactions


上述で見たように、scala.swing 中のイベントはパターンマッチング中でディスパッチされます。各イベントは普通の Scala オブジェクトなので、パターンマッチ方法を決定するただ一つの型を持ちます。これを Java Swing と対比してみてください。Java Swing では、それらの型プラス追加のリスナーメソッドに応じて 2 つのフェーズでディスパッチします。次の例は、Java Swing 中でのマウスクリックイベントの監視方法を示します。

   new JComponent {
     addMouseListener(new MouseAdapter {
       @Override
       def mouseClicked(e: MouseEvent) {
         System.out.println("Mouse clicked at " + e.getPoint)
       }
     })
   }

ここで、インターフェース MouseListener のすべてのメソッドを実装する便利なクラス MouseAdapter を使って、MouseListener の他メソッドの実装を避けていることに注意してください。

This interface is one example were we first dispatch on the event's Java type (MouseEvent) and then refine the match by implementing one of the listener's methods which all take the same parameter (using listener MouseListener and method mouseClicked).
このインターフェースは、最初にイベントの Java 型 (MouseEvent)に応じてディスパッチし、次に、すべて同じパラメータをとるリスナーのメソッドの 1 つを実装することで(リスナー MouseListener とメソッド mouseClicked を使います)、マッチイングを改善する 1 つの例です。scala.swing において、イベントの型に応じてマッチする等価なものは、次のようです。

   new Component {
     listenTo(mouse)
     reactions += {
       case e: MouseClicked =>
         println("Mouse clicked at " + e.point)
     }
   }

効率の点から、マウスイベントはコンポーネント自身ではなく、そのメンバー mouse によって発行されます。そのため、最初にマウス発行者を listenTo しなければなりません。


4.2 発行者とリアクタ (Publishers and Reactors)


発行/リアクト システム全体は、クラス Publisher、Reactor、Reactions 中で実装されます。API ユーザーにとって、Publisher 中で唯一興味を引くメソッドは次です。

   def publish(e: Event)

これは、登録された全てのリアクタに通知を行います。トレイト Event の唯一の目的は今のところ、あるクラスがイベントを定義することを示すことです。オブジェクトを発行者にするには、クラス Publisher を拡張して、イベントを発行させる上記のメソッドを呼び出すだけです。クラス Reactor 中の次のメソッドを使って、1 つ以上の発行者にリアクタの登録と登録解除を行えます。:

   def listenTo(ps: Publisher*)
   def deafTo(ps: Publisher*)

クラス Reactor の 3 つめに興味を引くメンバーは次です

   val reactions: Reactions

これは、リアクションのコレクションを表します。リアクションは、型 Reactions.Reaction のオブジェクトであり、PartialFunction[Event,Unit] のエイリアスです。上記で、リアクションをメソッド += を使って加える方法を見てきました。クラス Reaction の他の興味を引くメソッド -= は、前に登録されたリアクションを撤去します。両方のメソッドとも、受け取ったリアクションオブジェクトを返します。:

   def +=(r: Reactions.Reaction): this.type
   def -=(r: Reactions.Reaction): this.type

このことによりクライアントは、一つのリアクションの撤去や付加を構文上連結して書けます。


4.3 アクション(Actions)


Java Swing は、イベントへのリアクションとして実行されるコードをカプセル化する、インターフェース javax.swing.Action を提供します。リスナーと異なりますが、しかしアクションもまた、ユーザーインターフェースコンポーネントのいくつかの型に関して役に立つ情報を保存します。しばしば使われるプロパティには、アクションの名称があります。それは通常、ボタンタイトル、あるいはメニュー項目のタイトルと、(各種のボタンとメニュー項目に対するキーボード・ショートカットに使用される)アクションのニーモニックキーとして表示されます。アクションのプロパティの完全なリストについては、http://java.sun.com/docs/books/tutorial/uiswing/misc/action.html を参照してください。

scala.swing では、コンポーネントに似た Java Swing ピア周りのラッパーである Action トレイトによって、アクションを表現します。Action コンパニオンオブジェクトは、いくつかの便利なメンバーを定義しています。次のメソッドにより、クライアントは大変手軽にアクションを生成できます。

   def apply(title: String)(block: =>Unit) = new Action(title) {
     def apply() { block }
   }

次のように書いて、アクションを生成できます。:

   Action("Run me") {
     println("Someone executed this action.")
   }

コンポーネントをアクションに関連づけるには、トレイト Action.Triger を拡張します。次は、クリックボタンにアクションを関連づける 1 つの方法です。

   val button = new Button {
     action = Action("Click me") {
       println("Someone executed clicked button " + this)
     }
   }

ボタンがアクションを持つのは非常に一般的なので、コンパニオンオブジェクト Button 中に便利なファクトリメソッドがあります。次は、前の例と等価です。

   val button = Button("Click me") {
     println("Someone executed clicked button " + this)
   }

アクションをもつコンポーネントは、アクションの情報を必要なだけ使います。ボタンとメニュー項目は、通常それらのアクションの本体、名前、アクセラレータ・キー、アイコンその他を使いますが、テキストフィールドはアクションの本体のみ使うことがあります。コンポーネントが関連するアクションを持っていないと言うためには、コンポーネントのアクションのデフォルト値である Action.NoAction オブジェクトを使わなくてはなりません。

   button.action = Action.NoAction

NoAction をもつコンポーネントは、アクションのプロパティの代わりにそれ自身のプロパティを使います。Java Swing で、NoAction と等価なものは null です。


5 List Views と Tables


リストビューとテーブルは、任意数のアイテムを均一な配置で表示するコンポーネントです。リストビューは、各アイテムを単純に水平あるいは垂直に配置し、図 5 のように、各アイテムはしばしば単純な文字列として描画されます。テーブルは要素を格子中に配置し、図 6 のように、オプションで行と列ヘッダーを表示します。


5.1 クラス ListView


クラス ListView は、パラメータにアイテムのシーケンスをとる便利なコンストラクタを持っています。リストビューは、次のようにインスタンス化できます。:

   val items = List("Lausanne", "Paris", "New York", "Berlin", "Tokio")
   val view = new ListView(items)


#ref error :画像を取得できませんでした。しばらく時間を置いてから再度お試しください。
図 3 : ListView コンポーネント


#ref error :画像を取得できませんでした。しばらく時間を置いてから再度お試しください。
図 4 : Table コンポーネント

Java Swing と対照して、ListView とそのコンストラクタは、アイテム型について多相的です。:

   class ListView[A] extends Component {
     def this(items: Seq[A]) = ...
      ...
   }

ListView はメンバー選択を定義していて、それによりクライアントは、現在の選択状況を問いあわせできます。たとえば、現在選択されているアイテムのシーケンスです。 クラス ListView がジェネリックであることがどれだけ有益であるか見るために、上の例を少し修正してみましょう。:

 case class City(name: String, country: String, population: Int, capital: Boolean )  
 val items = List(City("Lausanne", "Switzerland", 129273, false),
                           City("Paris", "France", 2203817, true),
                           City("New York", "USA", 8363710 , false),
                       City("Berlin", "Germany", 3416300, true),
                       City("Tokio", "Japan", 12787981, true))
 val view = new ListView(items)

今度は、現在選択されているアイテムを問いあわせて、すぐに操作できる City オブジェクトのシーケンスを受け取ることができます。:

   val cityNames = view.selection.items.map(_.name)

Java Swing では、キャストを実行するか、選択されたインデックスのリストを経なければならず、そのどちらもエラーを起こしがちで簡潔でないコードになります。


5.2 レンダラ (Renderers)


もし実際に 上記から city オブジェクトを表示するリストビューコードを取り除いて、それをフレームの中に置くと、結果は図 7 のようになるでしょう。

#ref error :画像を取得できませんでした。しばらく時間を置いてから再度お試しください。
図 5 : City オブジェクトのリストを表示する ListView コンポーネント

こうなるのは、デフォルトでは、リストビューはそのアイテム上で toString を呼び出し、文字列を含むラベルとしてそれぞれを表示するからです。City のようなケースクラスに対して、メソッド toString はクラス名とそのコンストラクタ引数からなる文字列を返すので、図 7 に描かれたような結果になります。

形の上では、クラス ListView は、クラス ListView.Renderer のインスタンスを使って各アイテムをレンダー(描画)します。コンパニオンオブジェクト ListView は、クライアントの役に立つ、多数のレンダラ関連のメンバーを持っています。オブジェクト GenericRenderer は、Java Swing によって与えられたデフォルトレンダラのラッパーであり、上記のように、各アイテムに対して toString を呼び出します。

コンパニオンオブジェクト Renderer は、次の合成メソッドを定義しています。:

   def apply[A,B](f: A => B)(implicit renderer: Renderer[B]): Renderer[A]

これは、アイテム型 B に対して暗黙のうちに供給されたレンダラを使う、アイテム型 A のレンダラを返します。結果として生じるレンダラは、関数 f を各アイテムに適用することで、型 B のアイテムを入手します。この関数と暗黙の GenericRenderer オブジェクトを使って、もっと良い city ビューを生成できます。:

   import ListView._
   val view = new ListView(items) {
     renderer = Renderer(_.name)
   }

オブジェクト ListView の最後の役に立つクラスはクラス AbstractRenderer であり、より精巧なアイテムビューを生成できます。そのコンストラクタは、コンポーネント --- その paintComponent メソッドがアイテムを描画する --- を引数にとります。

   abstract class AbstractRenderer[-A, C<:Component](protected val component: C)
                  extends Renderer[A]

クライアントは、各アイテムにそのコンポーネントを設定するために、次のメソッドを実装する必要があります。:

   def configure(list: ListView[_], isSelected: Boolean, focused: Boolean,
                 a: A, index: Int)

クラス Item のアイテムを表示するビュー用のメソッド configure は、次のように実装できます。:

   def configure(list: ListView[_], isSelected: Boolean, focused: Boolean,
            icon: Icon, index: Int) {
     component.icon = icon
     component.xAlignment = Alignment.Center
     if(isSelected) {
       component.border = Swing.LineBorder(list.selectionBackground, 3)
     } else {
       component.border = Swing.EmptyBorder(3)
     }
   }

似たような例が、サンプルパッケージのコンボボックスのデモ中にあります。


5.3 クラス Table


クラス Table とそのレンダラの設計は、ListView とそのレンダラの設計に似ています。しかし クラス Table は、特定の用途向けに作られておらず、アイテムのリスト表示や、たとえば表計算ソフト中の 2 次元配列のようなより一般的な格子データ構造にも使えます。ですから、Table のアイテムは、1 つの列、1 つのカラム、あるいはただ 1 つのセルを表現できます。ですから、クラス Table はジェネリックではありません。

In the future, we might add a generic ListTable class tailored to lists of items similar to cass ListView but in a layout with multiple columns .
将来、我々は、クラス ListView に似てはいるが、複数カラムをもつレイアウトでアイテムのリストに仕立てるジェネリックな ListTable クラスを加えるかもしれません。


6 カスタム描画 (Custom Painting)


scala.swing 中のカスタムのコンポーネント描画は、Java Swing に非常によく似ています。コンポーネント中でカスタム描画を実行したいほとんどのユーザーは、Component あるいはそのサブクラスの 1 つをインスタンス化し、メソッド paintComponent をオーバライドしたいでしょう。次は、scala.swing サンプルパッケージの LinePainting デモでのやり方です。

   new Component {
     ...
     override def paintComponent(g: Graphics2D) = {
       super.paintComponent(g)
       g.setColor(new Color(100,100,100))
       g.drawString("Press left mouse button and drag to paint.",
                    10, size.height-10)
       g.setColor(Color.black)
       g.draw(path)
     }
   }

これは super.paintComponent() を呼び出し、コンポーネントがその背景と飾りを適切に描画することを確実にしています。次に、カスタムの文字列と、ユーザーがマウスをドラッグしている間デモによって維持されるパスを描画します。

次は、クラス Component で提供される利用可能な描画メソッドです。

   protected    def   paint(g: Graphics2D)
   protected    def   paintBorder(g: Graphics2D)
   protected    def   paintChildren(g: Graphics2D)
   protected    def   paintComponent(g: Graphics2D)

メソッド paint は、Swing 描画メカニズムによって呼び出され、コンポーネントを描画し、その標準的な実装では他の 3 つのメソッドを起動して、各コンポーネントに特有の飾り、境界線とその子等を描画します。javax.swing.Component 中の対応するメソッドが Graphics オブジェクトをとるのに対して、これら 3 つのメソッドが Graphics2D オブジェクトをとることに注意してください。内部的には、Swing は、あらゆるイベント中の Graphics2D オブジェクトを処理します。scala.swing シグニチャのおかげで、クラス Graphics2D のより新しいインターフェースにアクセスするために、よく使われるキャスト g.asInstanceOf[Graphics2D] をする必要はありません。

カスタム描画の入門については、http:://java.sun.com/docs/books/tutorial/ uiswing/painting/index.html を訪れてください。:Graphics2D と補助クラスのような、Swing 図形処理 API の包括的な紹介は、http:://java.sun.com/docs/ books/tutorial/2d/index.html にあります。:AWT と Swing 描画メカニズムについての詳細は、http:://java.sun.com/products/jfc/tsc/articles/painting/ で論じられています:


6.1 メソッドオーバーライドによるカスタマイズ

(Customizing by overriding methods)

カスタム描画は、メソッドオーバーライドによるより一般的なクラスカスタマイズ方法の 1 例に過ぎません。この方法をラッパーライブラリ中で機能させるために、ピアをインスタンス化するとき、メソッド委譲とミックスイン合成を実行します。

クラス Component 中の描画メソッドの実装は、次のようになります。クラス Component は、次のトレイトを定義します。:

   protected trait SuperMixin extends JComponent {
     override def paint(g: Graphics) {
       Component.this.paint(g.asInstanceOf[Graphics2D])
     }
     def __super__paint(g: Graphics) {
       super.paint(g)
     }

このトレイトは、ピアの paint メソッドの呼び出しをラッパーの paint メソッドへリダイレクトし、__super__paint を介してオリジナルの paint メソッドが見えるようします。そうするために、クラス Component は、そのピアへ SuperMixin トレイトをミックスします。:

   override lazy val peer: javax.swing.JComponent =
     new javax.swing.JComponent with SuperMixin

クラス Component 中のメソッド paint のデフォルト実装は、そのピアのオリジナルの paint メソッドを呼び出します。

   def paint(g: Graphics2D) {
     peer match {
       case peer: SuperMixin => peer.__super__paint(g)
       case _ => peer.paint(g)
     }
   }

循環を避けるために、ミックスインがあるときは(たとえば、ピアが scala.swing によって生成されているとき) __super__paint メソッドを呼び出します。そうでなければ、単にピアの paint メソッドを呼び出します。

paintComponent のような private および protected のメソッドに対しては、ピアのメソッドを呼び出せません。上の Component.paint 実装中の 2 番目のケースを削らなければなりません。その結果、既存の Java Swing コンポーネント用に生成されたラッパーに対しては、そのようなメソッドは何もしません。


7 ラッパーを書くためのガイドライン

(Guidelines for Writing Wrappers)

前章では、 scala.swing の基本的なデザイン原則を概観しました。この章では、新しいラッパーを書くときに考慮すべき、多くの点を列挙、要約します。詳細については、示された章を参照してください。

Java Swing から離れない
まず最も重要な原則は、あまりに巧妙にしないことです。オリジナルのデザインから離れないことは、Java Swing に精通した開発者に時間的余裕をもたらします。ピア毎のラッパークラスから始めてください。プロパティは、できる限り同じ名前を維持すべきです。Java のゲッターとセッター( get、set, あるいは booleansに対して is で始まるメソッド)は、Scala ゲッターとセッターに対応するペアに変換し、便利な代入構文と統一アクセス原則を利用できるようにすべきです。他のメソッドは、ピアとラッパーの必要なラッピングとアンラッピングをする、ただの委譲コードであるべきです。この大前提から外れるのは、そうする良い理由がある場合に限ります(例としては、以下を参照)。

共通インターフェースの抽出
Java インターフェースあるいは基底クラスによって表わせない、共通の API を共有する Swing クラスがいくつかあります。1 つの例として、共通のメソッドを共有するが、しかし互いに拡張せず、AWT クラスとは異なる、ウィンドウクラスの部分階層があります(詳細は??参照)。共通インターフェースだけでなく共通ラッパーコード(??)のくくり出しにも、トレイトあるいは(抽象)基底クラスを使ってください。

必要とされるインターフェース(??)にピアを適合させるために、ピアのミックスインを使ってください。もし常にピア生成を管理したければ、トレイトを使ってください。;もしそうしないなら、scala.swing.Oriented 中にあるような構造型を使ってください。

オーバライド可能なメソッドにスーパーのミックスインパターンを使う
If a class can be customized by overriding certain methods such_as in custom component painting (6), create a peer mixin that overrides the desired methods each delegating to the respective wrapper_method. Supply a method that lets the wrapper access the original peer method for the default wrapper implementation (6.1) .
もしクラスを、カスタムコンポーネント描画(6)中のように、ある特定のメソッドのオーバーライドによってカスタマイズできるなら、それぞれ、ラッパーメソッドへ委譲するのに必要なメソッドをオーバライドする、ピアミックスインを生成してください。
ラッパーがデフォルトのラッパー実装(6.1)に代わってオリジナルのピアメソッドにアクセスできるような、メソッドを提供してください。

抽象と実装トレイトの対を使う
Traits that custom widgets can implement in Scala should stay abstract. For a default implementation that delegates to its Java Swing peer, create a trait called Wrapper in the companion_object.
Scala 中でカスタムウィジェットが実装できるトレイトは、抽象のままであるべきです。その Java Swing ピアへ委譲するデフォルト実装に対して、コンパニオンオブジェクト中で Wrapper と呼ばれるトレイトを生成してください。例としては、Container と Scrollable を見てください。

Scala コレクション API との統合
Java Swing と Java 標準コレクションとの統合は不完全です。我々はよりうまくやることを目指します。適切な所ではどこでも、Scala コレクションを使ってください。これはプログラマに 2 つの利益をもたらします。第一に、プログラマはすでに知っている強力な API を使うことができます。第二に、Scala 標準コレクションを要求する他の API を用いたコンポーネントあるいはコンポーネントメンバーを使えます。たとえば、scala.swing 中のコンテナクラスを見てください。

あなたの要求を完全に満たす利用可能なコレクションがないなら、上で述べたと同じ理由から、独自のカスタムインターフェースを生成する代わりに、既存のコレクションをサブクラス化してください。

ジェネリック型とメンバー型の使用
ジェネリック型と抽象メンバー型は、より型安全で、適切に使えば、より便利なそして一貫した API を導きます。リストやコンボボックスのような同じ形のアイテムコンテナは、ジェネリック型の理想的な例なのに対して、テーブルは議論の余地がある例です。我々は、標準的な Table クラスの型安全性よりも柔軟性と簡潔性を選びました。それは、目的にふさわしいがより制限された型安全なテーブルの可能性を排除するものではありません。

Member types are sometimes preferable over type_parameters because they can lead to more concise type signatures, e.g., if the member_type needs not to be refined at use site (*2) .
メンバー型は、型パラメータよりも望ましいことがしばしばあります。なぜなら、たとえばもしメンバー型を使用場所で修正する必要がないなら、メンバー型はより簡潔な型シグニチャで書けるからです(*2)。レイアウトコンテナにはメンバー型があっています。正確な制約とその意味が、LayoutContainer(3.1)のただ 1 つの具象サブクラスに限定されるからです。言い替えると、もしただ一つの適合している基底クラス、たとえば BorderPanel だけがあるなら、一般的に「BorderPanel.Position 制約をもつレイアウトコンテナ」について話をすることは意味がありません。

(*2) もしそれが一般的なら、ジェネリック型の MyClass[Arg] の代わりに、メンバー型の MyClass { type Param <: Arg } といつも書くことになります。

型パラメータはコンボボックスとリストには望ましいものです。なぜなら、コンパイラがコンストラクト時にアイテムの型を推論して欲しいからです。コンストラクタ引数中のメンバー型は参照できないので、それはメンバー型ではできません。複雑な型、あるいは、パス依存型のような Scala のより洗練された型システムフィーチャを使う前に、注意深く理性を働かせてください。我々は、そのようなフィーチャが、scala.swing のような伝統的なスタイルの GUI ツールキットの文脈中で役に立つ場面に出会ったことがありません。

便利なコンストラクタあるいはファクトリの提供
少数のプロパティ設定でいつも生成するコンポーネントに対して、便利なコンストラクタを提供してください。もし引数の意味がそれらの場所と型から明らかなら、名前付き引数は避けてください。1 つの文字列と 1 つの整数をとる単純なコンストラクタは問題ありませんが、3 つの文字列をとるコンストラクタは、そうではないでしょう。

scala.swing.Action のように、クロージャを最後の引数として渡すことに意味があるなら、ファクトリメソッドを提供してください。しかしながら、コンストラクタは、無名クラス構文を用いて便利なコンストラクタをミックスできるので、一般的には望ましいです。

フラグを列挙に変える
Java Swing が整数フラグを使う場所では、列挙を使ってください。Orientation あるいは Alignment のような scala.swing の既存の列挙を再利用してください。

ラッパーキャッシュの使用
ラッパーを自分で記憶しないでください。これは簡単にメモリリークを引き起こしがちです。組み込みキャッシュを使ってください。あなたのラッパーのコンパニオンオブジェクト(??)中にキャッシュメソッドを提供してください。

独自クラス用に指定された"null"オブジェクトの生成を検討
公開インターフェースで null は決して使わないでください。代わりに Option を考える前に、それらの "null 値" を表現するオブジェクトについて検討してください。例としては、クラス scala.swing.Action とそれに対応する scala.swing.Action.NoAction オブジェクト(4.4)を見てください。この手法は null 参照の共通の欠点を避け、ユーザーとフレームワークコードの両方に関して Options ほど冗長ではありません。

参考文献 (References)


[1] Martin_Odersky, Lex Spoon, and Bill Venners. Programming in Scala :A Comprehensive Stepby-step Guide.Artima Incorporation, USA, 2008 .

タグ:

+ タグ編集
  • タグ:

このサイトはreCAPTCHAによって保護されており、Googleの プライバシーポリシー利用規約 が適用されます。

最終更新:2011年04月18日 09:21
ツールボックス

下から選んでください:

新しいページを作成する
ヘルプ / FAQ もご覧ください。