S-JIS[2011-01-15/2011-04-03] 変更履歴
JavaのSwingでは、JPanelにレイアウトマネージャーを組み入れることで様々な種類のレイアウトを実現していた。
ScalaのSwingではレイアウト種類毎のPanelが用意されている。
| 
 | 
| Scala | Java相当 | 内容 | 
|---|---|---|
| BorderPanel | BorderLayout | 上・下・左・中央・右のそれぞれに1つずつコンポーネントを配置する。 | 
| BoxPanel | BoxLayout | 横一列あるいは縦一列に(折り返し無しで)コンポーネントを並べる。 | 
| CardLayout | 複数のコンポーネントを保持するが、そのうち1つだけを表示する。 でもこれを使うならタブペインの方がいいのでは…? | |
| FlowPanel | FlowLayout | 横一列にコンポーネントを並べる。 入り切らなかったら折り返して次の行に並べられる。 ただ単にボタンを並べたりしたい場合に便利。 | 
| GridPanel | GridLayout | m×nの升目に区切ってその中に配置する。 | 
| GridBagPanel | GridBagLayout | m×nの升目に区切ってその中に配置する。 升目同士を繋いで大きなマスにすることが出来る。 | 
| GroupLayout | コンポーネントを縦横にグルーピングする。 同じグループ内では同じような位置に(整列して)配置される。 ダイアログを作るのに便利。 | |
| OverlayLayout | コンポーネントを互いに重ね合わせられる。 | |
| SpringLayout | コンポーネント同士の間隔を指定する。 ウィンドウサイズを変更すると、間隔指定に応じて位置が変化する。 | 
JavaのBorderLayout相当。
以下のように5つのエリアに分かれており、それぞれの位置に1つずつ(1つのみ)コンポーネントを配置できる。
| 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)
} | 
JavaのBoxLayout相当。
横一列あるいは縦一列に折り返し無しでコンポーネントを並べる。
各コンポーネントは基本的に推奨サイズ、というかウィンドウ内に入り切る範囲で最大サイズになる。
| import scala.swing.BoxPanel import scala.swing.Orientation | |
| //横方向(右に追加されていく)
new BoxPanel(Orientation.Horizontal) {
  contents += コンポーネント
  contents += コンポーネント
  contents += コンポーネント
  …
} | //縦方向(下に追加されていく)
new BoxPanel(Orientation.Vertical) {
  contents += コンポーネント
  contents += コンポーネント
  contents += コンポーネント
  …
} | 
独自のパネル(レイアウトマネージャー)の作り方のメモ。[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.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("ボタン")
    }
  }
}
制約(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メソッドを呼び出し、コンポーネントを追加する。
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で、各コンポーネントの位置・サイズを設定する。
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にメソッドを用意し、そちらに委譲する
  }
}