LL脳がscalaの勉強を始めたよ その113


Scalaコップ本の32章に入っていきますよー。32章はJavaGUIクラスフレームワークSwingを使ったGUIプログラミングをやっていきますデス。

最初に作るSwingアプリケーション

Swingを使った一番シンプルなサンプルとして、1個のボタンを含むウィンドウを作ってみます。

とりあえずサンプルはこんな感じですね

// ScalaのSwing APIを読み込みマス
import scala.swing._
// scala.swing.SimpleGUIApplicationを継承してGUIアプリを作りますね
object FirstSwingApp extends SimpleGUIApplication {
  // Swingフレームワークを対象とするセットアップメソッドを含んだ
  // mainメソッドから呼び出されるtopメソッドを定義します
  // topメソッドではメインフレームを定義してやりますね
  def top = new MainFrame {
    // フレーム属性としてフレームのタイトルを定義します
    title = "First Swing App"
    // コンテンツとしてボタンを定義デス
    contents = new Button {
      // ボタンのタイトルを設定してやります
      text = "Click me"
    }
  }
}

上記で定義したtitle, contentsの各プロパティはセッター(title_=やcontsnts_=)とゲッター(titleやcontents)を持っているので、こちらを使って設定してやりますです。

アプリケーションの実行はコンパイルして実行するだけです。実行するとGUIウィンドウが立ち上がりますデス。Σ( ゚Д゚) スッ、スゲー!!簡単だ

$ scalac FirstWingApp.scala
$ scala FirstSwingApp 

パネルとレイアウト

次にシンプルなレイアウトの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"
    }
    // コンテンツとして上記ラベルとボタンのレイアウトを定義します
    // ここではBoxPanelの中で単純に縦に並べるレイアウトを採用します
    contents = new BoxPanel(Orientation.Vertical){
      // ボタン→ラベルの順に縦に並べます
      contents += button
      contents += label
      // 境界線を追加します。特に色とかは無しデスネ(´・ω・`)
      border = Swing.EmptyBorder(30, 30, 10, 30)
    }
  }
}

こちらもコンパイルして実行するとGUIウィンドウが立ち上がりますね。(´ε`;)ウーン…ほんと簡単に作れるんですな。アプリケーションの定義自体もGUIコンポーネント木構造で定義できるし、ちょっと楽しいです。

イベント処理

ここまでつくってきたGUIアプリケーションはレイアウトを定義しただけで、ボタンをクリックしても何も動作しないという寂しい状態です(´・ω・`)なので、ボタンに対するイベントを定義してそのイベントとボタンをヒモづけてやる必要があるわけですね

Scalaでのイベント定義

Scalaでは基本的にjavaと同様の「パブリッシュ/サブスクライブ」アプローチのイベント処理を行うみたいです。このアプローチでは各コンポーネントが発行者(パブリッシャー)または購読者(サブスクライバー)のどちらかの役割に付き、発行者はイベントを発行して、購読者はそのイベントを受け取るような動作になるみたいです。

また、購読者がイベントを受け取るためには発行者に対してイベント購読の登録を行う必要があるみたいです。なお、発行者はイベントソース、購読者はイベントリスナーとも呼ばれるみたいデス。

ちなみにボタンをクリックするというButtonClickedイベントについて考えると、イベントソースはButtonになり、コレに対してイベントリスナーを登録していくことになるわけですね。

イベントリスナーの登録

イベントリスナーの登録はlistenTo(source)で登録しますデス。また購読解除はdeafTo(sourse)で行えるのですね(`・ω・´)

発行されたイベントの受け取り

ScalaによるSwing利用においては予め定義されたcaseクラスのインスタンスをイベントとしてやり取りすることになるみたいです。例えばボタンクリックに関するイベントを定義するケースクラスは次のようになりますね。

case class ButtonClicked(source:Button)

イベントを表現する各caseクラスはscala.swing.eventパッケージに含まれるみたいデス。また、送られてきたイベントはreactionsというプロパティーにハンドラーを追加するような形になるみたいです。

ハンドラーは次のようにマッチ式風味で記述するみたいですね

    var nClicks = 0
    reactions += {
      // ボタンクリックイベントにマッチした場合の処理を記述します
      case ButtonClicked(b) =>
        nClicks += 1
        label.text = "Number of button clicks: " + nClicks
    }

各イベントがハンドラーに適用される順序としてはreactionsに新しく追加された順になるみたいです。ちなみにreactionsプロパティーは-=演算子で取り除くことも出来るみたいです(´・ω・`)

サンプルコードの実装

それではlistenToとreactionsを使ってイベント付きコードを書いてやりますよ

import scala.swing._
import scala.swing.event._
object ReactiveSwingApp extends SimpleGUIApplication {
  def top = new MainFrame {
    title = "Reactive 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)
    }
    // buttonへのイベントの通知を定義します
    listenTo(button)
    // イベントごとの処理を定義しますデス
    var nClicks = 0
    reactions += {
      case ButtonClicked(b) =>
        nClicks += 1
        label.text = "Number of button clicks: " + nClicks
    }
  }
}

こちらもコンパイルして実行することでGUIアプリケーションが立ち上がりますね

サンプル:摂氏・華氏換算プログラム

実際的なアプリケーションサンプルとして摂氏と華氏の間で温度換算を行うGUIプログラムを書いてみますです。

動作的にはユーザが編集できる摂氏・華氏の両方のフィールドを持っていて、ユーザがどちらかを編集すると、もう一方のフィールドの温度も自動的に更新されるようなものです。

それではコードを書いていきますよ(`・ω・´)

// このインポートは下記と同義デス
// import scala.swing._
// import scala.swing.event._
import 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 }
   // 各オブジェクトを配置します
   // 左から順に並べる感じですね
    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 += {
      // `で括ることでイベントソースが
      // fahrenheitのときのみに限定します
      case EditDone(`fahrenheit`) =>
        val f = fahrenheit.text .toInt
        val c = (f - 32) * 5 / 9
        celsius.text = c.toString
      // `で括ることでイベントソースが
      // celsiusのときのみに限定します
      case EditDone(`celsius`) =>
        val c = celsius.text .toInt
        val f = c * 9 / 5 + 32
        fahrenheit.text = f.toString  
    }
  }
}

スクリーンショットはあっさりと省略しますが、こいつもコンパイルして実行するだけでGUIアプリケーションが動作しますね(`・ω・´)

Scalaだと結構あっさりとSwingベースのGUIアプリが作れるっぽいので素晴らしいデス(`・ω・´)詳しいコンポーネントなんかの情報についてはswingパッケージのScaladocにあるみたいなので、時間を見つけて読んでみようと思いマス。

以上です

32章は以上です。GUIの書き方が個人的にとても親しみやすいので、積極的に書いていこうと思いますデス。

次回は最後の33章SCellsスプレッドシートをやりますです。なんとか年内には終わりたい所存…頑張りまっす(`・ω・´)