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にメソッドを用意し、そちらに委譲する
}
}