2014年9月16日火曜日

Programming in Scala, First Edition: Chapter 32

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 件のコメント:

コメントを投稿