S-JIS[2011-01-15/2011-04-03] 変更履歴

Scala Panel(Swing)

JavaのSwingでは、JPanelにレイアウトマネージャーを組み入れることで様々な種類のレイアウトを実現していた。
ScalaSwingではレイアウト種類毎のPanelが用意されている。


Panelの種類

Scala Java相当 内容
BorderPanel BorderLayout 上・下・左・中央・右のそれぞれに1つずつコンポーネントを配置する。
BoxPanel BoxLayout 横一列あるいは縦一列に(折り返し無しで)コンポーネントを並べる。
  CardLayout 複数のコンポーネントを保持するが、そのうち1つだけを表示する。
でもこれを使うならタブペインの方がいいのでは…?
FlowPanel FlowLayout 横一列にコンポーネントを並べる。
入り切らなかったら折り返して次の行に並べられる。
ただ単にボタンを並べたりしたい場合に便利。
GridPanel GridLayout m×nの升目に区切ってその中に配置する。
GridBagPanel GridBagLayout m×nの升目に区切ってその中に配置する。
升目同士を繋いで大きなマスにすることが出来る。
  GroupLayout コンポーネントを縦横にグルーピングする。
同じグループ内では同じような位置に(整列して)配置される。
ダイアログを作るのに便利。
  OverlayLayout コンポーネントを互いに重ね合わせられる。
  SpringLayout コンポーネント同士の間隔を指定する。
ウィンドウサイズを変更すると、間隔指定に応じて位置が変化する。

BorderPanel

JavaのBorderLayout相当。

以下のように5つのエリアに分かれており、それぞれの位置に1つずつ(1つのみ)コンポーネントを配置できる。

BorderPanelの位置を表す定数
Position.North
Position.West Position.Center Position.East
Position.South
 
import scala.swing.BorderPanel
import BorderPanel.Position
new BorderPanel {


  add(上部コンポーネント, Position.North)
  add(中央コンポーネント, Position.Center)
  add(下部コンポーネント, Position.South)
}
import scala.swing.BorderPanel
new BorderPanel {
  import BorderPanel.Position._

  add(上部コンポーネント, North)
  add(中央コンポーネント, Center)
  add(下部コンポーネント, South)
}

BoxPanel

JavaのBoxLayout相当。

横一列あるいは縦一列に折り返し無しでコンポーネントを並べる。
各コンポーネントは基本的に推奨サイズ、というかウィンドウ内に入り切る範囲で最大サイズになる。

import scala.swing.BoxPanel
import scala.swing.Orientation
//横方向(右に追加されていく)
new BoxPanel(Orientation.Horizontal) {
  contents += コンポーネント
  contents += コンポーネント
  contents += コンポーネント
  …
}
//縦方向(下に追加されていく)
new BoxPanel(Orientation.Vertical) {
  contents += コンポーネント
  contents += コンポーネント
  contents += コンポーネント
  …
}

独自Panel(独自LayoutManager)の作り方

独自のパネル(レイアウトマネージャー)の作り方のメモ。[2011-04-03]

独自パネルはscala.swing.Panelを継承し、LayoutContainerあるいはSequentialContainer.Wrapperをミックスインして作成する。
SequentialContainer.Wrapperを使うと「contents+=」でコンポーネントを追加できるようになる。(ただしConstraintsは指定できない)
制約(Constraints)を使いたい場合はLayoutContainerを使う。

独自パネルの中でPanel#peerをオーバーライドし、javax.swing.JPanelと独自レイアウトマネージャーを指定する。

独自レイアウトマネージャーは、JavaのSwingと全く同様にjava.awt.LayoutManagerあるいはLayoutManager2を実装する。

以下、例として、(BoxPanelと似た感じで)下方向にコンポーネントを並べて表示するパネルを作ってみる。


SequentialContainerの例

SequentialContainer.Wrapperは、制約(Constraints)を指定せず、順番にコンポーネントを追加すればいいだけの場合に使用する。[2011-04-03]

import scala.swing._
class MyPanel0 extends Panel with SequentialContainer.Wrapper {
  override lazy val peer = new javax.swing.JPanel(new MyLayout1) with SuperMixin
}

//ウィンドウ(フレーム)表示
object LayoutSample0 extends SimpleSwingApplication {
  override def top = new MainFrame {
    title = "サンプル"
    contents = new MyPanel0 {
      contents += new Label("独自レイアウトサンプル0")
      contents += new Label("ラベル2")
      contents += new Button("ボタン")
    }
  }
}

LayoutContainerの例

制約(Constraints)を指定したい場合はLayoutContainerをミックスインする。[2011-04-03]

import scala.swing._
class MyPanel1 extends Panel with LayoutContainer {
  override type Constraints = String
  override lazy val peer = new javax.swing.JPanel(new MyLayout1) with SuperMixin

  protected def add(comp: Component, c: Constraints) {
    peer.add(comp.peer, c)
  }
  protected def areValid(c: Constraints): (Boolean, String) = (true, "")
  protected def constraintsFor(comp: Component): Constraints = ""
}

LayoutContainerを使う場合、いくつかのメソッドを実装する必要がある。

メソッド名 備考
add パネルにコンポーネントを追加する為に呼び出される。
peer(JavaのJPanel)に対してコンポーネントを追加する。
areValid 指定された制約が正しいかどうかを判定する。
戻り値はタプルで、1番目の要素が判定結果。trueの場合は正しい制約。
エラーのある制約の場合はfalseとし、2番目の要素にエラーメッセージを指定する。
constraintsFor 指定されたコンポーネントに対する制約を返す。

制約の型「Constraints」は、LayoutContainer内でAnyRefとして宣言(typeで定義)されている。
使いたい制約クラスになるようtypeでオーバーライドすると便利。


//ウィンドウ(フレーム)表示
object LayoutSample1 extends SimpleSwingApplication {
  override def top = new MainFrame {
    title = "サンプル"
    contents = new MyPanel1 {
      add(new Label("独自レイアウトサンプル1"), "")
      add(new Label("ラベル2"), "")
      add(new Button("ボタン"), "")
    }
  }
}

独自パネルで定義(実装)したaddメソッドを呼び出し、コンポーネントを追加する。


LayoutManagerの例

java.awt.LayoutManagerの実装例。[2011-04-03]
下方向にコンポーネントを並べて表示するレイアウトマネージャーを作ってみる。

class MyLayout1 extends java.awt.LayoutManager {
  import java.awt._

  def addLayoutComponent(name: String, comp: Component): Unit = {}
  def removeLayoutComponent(comp: Component): Unit = {}

  def preferredLayoutSize(target: Container): Dimension =
    target.getComponents.map(_.getPreferredSize).foldLeft(new Dimension) { (d, sz) =>
      d.width = math.max(d.width, sz.width)
      d.height += sz.height
      d
    }
  def minimumLayoutSize(target: Container): Dimension =
    target.getComponents.map(_.getMinimumSize).foldLeft(new Dimension) { (d, sz) =>
      d.width = math.max(d.width, sz.width)
      d.height += sz.height
      d
    }

  def layoutContainer(target: Container): Unit = {
    var y = 0
    target.getComponents.foreach { c =>
      val sz = c.getPreferredSize
      c.setBounds(0, y, sz.width, sz.height)
      y += sz.height
    }
  }
}

このクラス内では、基本的にjava.awtのクラスしか扱わない。
ここで出てくるComponentは、scala.swing.Componentではなく、java.awt.Componentである。

addLayoutComponentは、JPanelのaddメソッド内から呼ばれる。
LayoutContainerを使った独自パネルのpeer.add()の呼び出し)
ただし、呼ばれるのは制約のクラスがStringの場合だけ。引数nameがその制約そのもの。

preferredLayoutSizeで全体の推奨サイズ、minimumLayoutSizeで最小サイズを返すようにする。
引数のtargetは、今回の例で言えばJPanel。getComponentsでコンポーネント一覧(配列)を取得できる。

layoutContainerで、各コンポーネントの位置・サイズを設定する。


LayoutManager2の例

java.awt.LayoutManager2の実装例。[2011-04-03]
下方向にコンポーネントを並べて表示するレイアウトマネージャーという点では先の例と同じ。

class MyLayout2 extends java.awt.LayoutManager2 {
  import java.awt._

  def addLayoutComponent(name: String, comp: Component): Unit = {} //LayoutManager2では、こちらは呼ばれない
  def addLayoutComponent(comp: Component, constraint: AnyRef): Unit = {}
  def removeLayoutComponent(comp: Component): Unit = {}

  def preferredLayoutSize(target: Container): Dimension = calcSize(target, _.getPreferredSize)
  def minimumLayoutSize(target: Container): Dimension = calcSize(target, _.getMinimumSize)
  def maximumLayoutSize(target: Container): Dimension = calcSize(target, _.getMaximumSize)
  protected def calcSize(target: Container, getSize: (Component) => Dimension): Dimension =
    target.getComponents.map(getSize).foldLeft(new Dimension) { (d, sz) =>
      d.width = math.max(d.width, sz.width)
      d.height += sz.height
      d
    }

  def getLayoutAlignmentX(target: Container): Float = 0
  def getLayoutAlignmentY(target: Container): Float = 0

  def invalidateLayout(target: Container): Unit = {}
  def layoutContainer(target: Container): Unit = {
    var y = 0
    target.getComponents.foreach { c =>
      val sz = c.getPreferredSize
      c.setBounds(0, y, sz.width, sz.height)
      y += sz.height
    }
  }
}

LayoutManager2はLayoutManagerを継承しているので、実装すべきメソッドが増えている形になる。

addLayoutComponentは2種類あるが、LayoutManager2の場合は引数に制約が入っている方が呼ばれる。
ここでは制約の型はAnyRefとしているので(JavaではObjectなので)、本来のクラスにキャストする必要がある。
必要に応じてMap等を用意し、制約を保持する。

サイズを返すメソッドでは、maximumLayoutSizeが増えている。
今回の例では同じような計算をするので、共通の計算用メソッドを定義してみた。違いはコンポーネントからサイズを取得するメソッドのみ。異なる部分を渡す為にJavaならインターフェースを定義したりする必要がある(C言語なら共通計算用のマクロでも作るところだ)が、Scalaではすっきり書ける(笑)


import scala.swing._
class MyPanel2 extends Panel with LayoutContainer {
//override type Constraints = 独自制約クラス
  override lazy val peer = new javax.swing.JPanel(new MyLayout2) with SuperMixin
  def layoutManager = peer.getLayout.asInstanceOf[MyLayout2]

  protected def add(comp: Component, c: Constraints) {
    peer.add(comp.peer, c)
  }
  protected def areValid(c: Constraints): (Boolean, String) = (true, "")
  protected def constraintsFor(comp: Component): Constraints = {
    null.asInstanceOf[Constraints]
//  layoutManager.getConstraints(comp.peer)	//TODO: MyLayout2にメソッドを用意し、そちらに委譲する
  }
}

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