S-JIS[2011-01-15/2011-10-16] 変更履歴

Scala Frame(Swing)

ScalaSwingのFrame(MainFrame)はフレーム(ウィンドウ)を表示するクラス。(JavaのJFrame相当)


Frame

Frameはフレーム(ウィンドウ)を表示するクラス。[2011-10-16]

ウィンドウの閉じるボタンを押したときに実行されるcloseOperaionメソッドは中身が空(無処理)なので、閉じるボタンを押すとウィンドウが消えたように見えるが、インスタンスは残り続ける。
閉じられたときにインスタンスも消したい場合は、disposeメソッドを呼び出すようにする。

class F extends scala.swing.Frame {
  title = "Frame test"

  override def closeOperation() = dispose()
}

MainFrame

MainFrameはFrameの子クラスであり、違いは、MainFrameのcloseOperaionメソッドの中身が「System.exit(0)」であること。
(単なるFrameのcloseOperation()には何も書かれていない。無処理)

つまりウィンドウを終了したらアプリケーション全体も終了する場合はMainFrameを使い、そうでない場合はFrameを使う。

SimpleSwingApplicationのtopメソッドでMainFrameインスタンスを返すようにするのが一番簡単な使い方。


MainFrameのサンプル

最も簡単な例(と解説)はSimpleSwingApplicationのサンプルで書いたので、もうちょっと複雑な例を。

画像と説明を出力するウィンドウを作ってみる。
こんなレイアウトを想定。



画像

 
「w=」+画像の幅
「h=」+画像の高さ
画像のファイル名

中央に画像を出し、下部に説明を出す。(BorderPanelのCenterに画像、Southに説明)
下部を左右に分けて、左側に属性、右側にファイル名を出す。(BoxPanelを左右に使う)
属性は何種類かあるので何行か出力する。(BoxPanelを上下に使う)

import scala.swing._
import BorderPanel.Position

object IconFrameSample extends SimpleSwingApplication {
  override def top = new MainFrame {
    title = "画像表示サンプル"
    contents = new BorderPanel {

      val filename = """C:\Documents and Settings\All Users\Documents\My Pictures\Sample Pictures\Blue hills.jpg"""
      val icon1 = Swing.Icon(filename)
      val nx = icon1.getIconWidth
      val ny = icon1.getIconHeight

      // 画像
      add(new Label {
        icon = icon1
      }, Position.Center)

      // 説明
      add(new BoxPanel(Orientation.Horizontal) { //横方向
        //BoxPanelの左側
        contents += new BoxPanel(Orientation.Vertical) { //縦方向
          contents += new Label("w=" + nx)
          contents += new Label("h=" + ny)
        }
        //BoxPanelの右側
        contents += new ScrollPane(
          new TextArea(filename) {
            editable = false
          }
        )
      }, Position.South)
    }
  }
}

MainFrameのcontents(内容)にはBorderPanelを指定している。
BorderPanelの“画像”部には画像(アイコン)のラベルを指定している。
BorderPanelの“説明”部にはBoxPanelを指定している。

MainFrameのcontentsは1種類しか指定できないのでcontentsに代入する形をとっているが、
BoxPanelは複数のコンポーネントを並べるものなので、「+=」演算子を使って追加するような形になっている。

テキスト領域(TextArea)をスクロールさせたい時はScrollPaneでラップするというのも、Swingではよくある方式。


GlassPane

SwingのFrame(javax.swing.JFrame)では、通常のコンポーネントを描くペイン(Scalaのcontents・JavaのContentPane)の上に、GlassPaneというものがある。[2011-04-08]
Glass(グラス・ガラス)の名の通り、GlassPaneは基本的に透明(非表示)なので、普段はContentPaneで描画されたものがそのままフレームとして表示されるが、
GlassPaneに描画することも出来るし、GlassPaneでイベントを受け取ることも出来る。
GlassPaneはフレーム(ウィンドウ)サイズと同じ大きさであることが保証されるので、ウィンドウ全体で最初にイベントを受け取りたい(横取りしたい)場合に使える。

import scala.swing._
object GlassPaneSample extends SimpleSwingApplication {
  override def top = new MainFrame {
    title = "サンプル"

    // 通常のContentPane
    contents = new FlowPanel {
      contents += new Label("ラベル1")
      contents += new Button("ボタン")
      contents += new Label("ラベル2")
    }

    // グラスペイン
    val glassPane = new Component {
      var point = new Point

      listenTo(this.mouse.clicks)
      reactions += {
        case event.MouseClicked(_, pt, modifiers, _, _) =>
          if (modifiers == 0) { //左クリックのみ
            point = pt
            repaint()
          } else {
            println(modifiers)
          }
      }
      override def paint(g: Graphics2D) {
        g.setColor(java.awt.Color.BLUE)
        g.drawRect(point.x, point.y, 16, 16)
      }
    }
    peer.setGlassPane(glassPane.peer)
    peer.getGlassPane.setVisible(true)
  }
}

ContentPaneにはScalaのSwingではcontentsというメンバーでアクセスできるが、GlassPaneに関しては特にメンバーが用意されていないようだ。
そこで、peerを使ってjavax.swingのコンポーネントに直接アクセスする。(peer.setGlassPane
重要なのが、GlassPaneに対するsetVisible(true)
デフォルトではこれがfalseなので、描画もされないしイベントを捕捉することも出来ない。

GlassPaneはContentPaneの様にPanelを使っても描画できると思うが、普通はコンポーネントを配置することは無いと思うので、Panelでなく単なるComponentでよい

今回の例では、マウスで左クリックされた座標に四角を描いている。
イベントの中で再描画を指示するのがrepaintメソッド。
(revalidateは呼ばなくても大丈夫なようだ)

実際に描画を行うpaintメソッドに関しては、独自コンポーネントを作る際の描画方法と同様。


なお、GlassPaneでイベントを横取りすると、ContentPaneのイベントは実行されないようだ。
つまりボタンをContentPaneに貼り付けておいても、実行されない…。


Scala Swingへ戻る / Scala目次へ戻る / 技術メモへ戻る
メールの送信先:ひしだま