32. GUI Programming
ScalaのGUIの開発にJavaのSwingベースのframeworkを使う。
複雑さを隠ぺいしているので簡単に使う事が出来る。
Scalaで単純化した後でも沢山の機能がある。
Eclipseの様なIDEを利用して作るのが良い。
良く知らないライブラリを最初に使うのにはIDEは向いている。
どの様なmethodやclassやobjectが使えるかなどを補完できる。
** ScalaのSwingのサポートは終わって、今後はJavaFXベースのscalafxに移行する??
32.1 A first Swing application
簡単なScalaのGUI
// Swingを使う場合のimport
import scala.swing._
// SimpleGUIApplicationをextendsする。
// コマンドラインアプリケーションではscala.Applicationをextendsしていた。
// main methodやswingを使うための事前設定が行われる。
object FirstSwingApp extends SimpleGUIApplication {
// top leveのGUI部品を定義する。
// 通常は任意のデータを格納できるFrame
// mainから呼び出されて処理される。
// MainFrameが閉じられるとアプリケーションも終了する。
def top = new MainFrame {
// title等はpropertyとしてモデル化されている。getter、setterがある。
// def title: String
// def title_=(s: String)
title = "First Swing App"
// Containerで他の(複数の)部品を格納できる。
// Seq[Component]をsetterのcontents_=で設定する。
// frameもContainerの一種だが一つの部品だけを格納できる。
contents = new Button {
text = "Click me"
}
}
}
32.2 Panels and layouts
複数の部品を持つGUI
import scala.swing._
object SecondSwingApp extends SimpleGUIApplication {
def top = new MainFrame {
title = "Second Swing App"
val button = new Button {
text = "Click me"
}
val label = new Label {
text = "No button clicks registered"
}
contents = new BoxPanel(Orientation.Vertical) {
contents += button
contents += label
border = Swing.EmptyBorder(30, 30, 10, 30)
}
}
}
Frameは一つしか部品を持てないので、複数部品を持てるPanelを使う。
Panelの部品のレイアウトによって色々なsubclassが用意されている。
適切なレイアウトを選択するのはGUIでは最も難しい事の一つ。
どんな大きさでも上手く使える様にするのが難しいから。
BoxPanelは単に部品を並べるだけのシンプルなレイアウト。
contentsはbufferになっていて+=演算子で部品を追加できる。
部品に枠をつけるborderもSwing.EmptyBorderで四辺の太さを指定する。
枠線もobjectで指定する。
Labelは編集できないテキスト表示様の部品。
GUIの基本構成
部品のclassから部品を生成して他の部品のcontentsに設定する。
top frameをrootにする木構造になる。
32.3 Handling events
ScalaではJavaと同様に"publish/subscribe"アプローチを使っている。
publisherがeventを発生させてsubscriberが受け取る。
publisherのインスタンスはevent source
subscriberのインスタンスはevent listener
listenTo(source)を使ってeventをsubscribeする。
deafTo(source)を使ってeventをunsubscribeする。
eventを受けてからの処理の仕方はScalaはJavaより大分簡単。
Javaではlistenerのnotifyメソッドが呼び出されるのみ。
notifyメソッドは間違いやすくて手間のかかる処理。
このせいでコードが書きにくくなっている。
Scalaではeventはcase classとしてlistenerに渡される。
case class ButtonClicked(source: Button)
全てのeventはscala.swing.eventで定義されている。
reactionsプロパティにPartialFunctionを追加する事でeventをハンドル出来る。
var nClicks = 0
reactions += {
case ButtonClicked(b) =>
nClicks += 1
label.text = "Number of button clicks: "+ nClicks
}
reactionsはstack的に登録の逆順でhandlerを適用する。
-= 演算子でhandlerを削除する事も出来る。
?? あるhandlerにmatchしても次のhandlerにも渡ってしまう ??
?? 複数のobjectで共通のsourceをsubscribeした時も登録の逆順 ??
32.4 Example: Celsius/Fahrenheit converter
Celsius/Fahrenheitの温度変換GUIアプリケーション
// scala._パッケージはimport済なので、scala.は省略できる。
import swing._
// scala.swing._パッケージをimportしたので、scala.swingは省略できる。
import event._
object TempConverter extends SimpleGUIApplication {
def top = new MainFrame {
title = "Celsius/Fahrenheit Converter"
// 一行の編集可能なTextField
object celsius extends TextField { columns = 5 }
object fahrenheit extends TextField { columns = 5 }
// ウインドウの幅に応じて左上から右下に部品を自動配置するPanel
contents = new FlowPanel {
contents += celsius
contents += new Label(" Celsius = ")
contents += fahrenheit
contents += new Label(" Fahrenheit")
border = Swing.EmptyBorder(15, 10, 10, 10)
}
listenTo(celsius, fahrenheit)
reactions += {
// back tick``は必要。
// fahrenheitだと任意のオブジェクトとmatchして変数fahrenheitにbind。
// `fahrenheit`だとobject fahrenheitにのみmatch。
// object Fahrenheitとして定義すれば変数にはならないので個別にmatch。
case EditDone(`fahrenheit`) =>
val f = fahrenheit.text.toInt
val c = (f - 32) * 5 / 9
celsius.text = c.toString
case EditDone(`celsius`) =>
val c = celsius.text.toInt
val f = c * 9 / 5 + 32
fahrenheit.text = f.toString
}
}
}
32.5 Conclusion
Scalaの機能でJavaのSwingをシンプルで統一的に出来ている。
propertyのエミュレーションと演算子のoverload
propertyの定義が簡単になる。
+= でpropertyを割り当てる事が出来る。
「全てはオブジェクト」の思想
GUIアプリケーションのmainメソッドも継承出来る。
つまらないセットアップ処理などを隠ぺいできる。
first-class functions and pattern matching
イベントハンドリングをreaction component propertyとして簡単に扱える。
0 件のコメント:
コメントを投稿