S-JIS[2011-04-06] 変更履歴
ScalaのSwingでラベルやボタン等のコンポーネントの親クラスがscala.swing.Component。
内部でjavax.swing.JComponentを保持している。
abstract class Component extends UIElement {
override lazy val peer: javax.swing.JComponent = new javax.swing.JComponent with SuperMixin {}
〜
}
ちなみにpeerは親クラスのUIElementで宣言されており、「def peer: java.awt.Component」。
また、例えばComponentの子クラスであるLabelでは以下の様に上書き(オーバーライド)されている。
class Label(text0: String, icon0: Icon, align: Alignment.Value) extends Component {
override lazy val peer: JLabel = new JLabel(text0, toNullIcon(icon0), align.id) with SuperMixin
〜
}
一番親では「def」で宣言されているのに、子クラスでは「val」でオーバーライドしている。
これは、Scalaではvalで定義した(publicな)変数は、コンパイルされるとセッターメソッド・ゲッターメソッドとして定義される、すなわちメソッド定義(def)と同じ為。
しかも「lazy val」なので、一度しかインスタンス化されない。
また、Scalaでは(Javaと同じく)共変戻り値型が使用できるので、戻り型をjava.awt.Component → javax.swing.JComponent → javax.swing.JLabelとする事が出来る。
直線を描く独自コンポーネントを作ってみる。
独自コンポーネントではjavax.swingのコンポーネントをラップする訳では無いので、peerはデフォルトのままでいい。
import scala.swing._
object ComponentSample extends SimpleSwingApplication {
override def top = new MainFrame {
title = "サンプル"
contents = new FlowPanel {
contents += new Label("ラベル1")
import java.awt.Color
contents += new LineComponent(64, 48)
contents += new LineComponent(-64, 48, Color.BLUE)
contents += new LineComponent(64, -48, Color.GREEN)
contents += new LineComponent(-64, -48, Color.YELLOW)
contents += new Label("ラベル2")
}
}
}
class LineComponent(nx: Int, ny: Int, c: Color = java.awt.Color.BLACK) extends Component {
peer.setPreferredSize(new Dimension(nx.abs, ny.abs))
override protected def paintComponent(g: Graphics2D) {
g.setColor(c)
val sx = if (nx < 0) -nx else 0
val sy = if (ny < 0) -ny else 0
val ex = if (nx < 0) 0 else nx
val ey = if (ny < 0) 0 else ny
g.drawLine(sx, sy, ex, ey)
}
}

Componentにはpaint系のメソッドがあるので、オーバーライドして独自の描画を行う。
なお、javax.swing.JComponentではpaint系メソッドの引数はGraphicsであり、実体はGraphics2Dなのでキャストして使うのが普通。
だが、Scalaではその部分を影で実行してくれるので、引数がGraphics2Dのpaint系メソッドが使用できる。
| 呼ばれる順 | メソッド名 | 備考 |
|---|---|---|
| 1 | paint | 描画の大元。このメソッドをオーバーライドして、super.paintを呼び出さないと、下記の各メソッドは呼ばれない。 |
| 2 | paintComponent | コンポーネントの描画を行う。 |
| 3 | paintBorder | コンポーネントの枠の描画を行う。Borderクラスの指定による枠線とは別に呼ばれる模様。 |
| 4 | paintChildren | 子コンポーネントの描画を行う。 |
Graphics2Dの描画範囲は、左上の座標が(0,0)で、サイズはコンポーネントの推奨サイズになる模様。
したがって、コンストラクターの中でコンポーネントに対して推奨サイズをセットしている。
(scala.swing.Componentには推奨サイズをセットするメソッドが無いようなので、peerのメソッドを直接呼び出している)
レイアウトマネージャーに従って大きさが変わるコンポーネントにしたければ、(最小サイズや最大サイズをセットして、)
paint系メソッドの中でコンポーネントのsizeを取得してそれを元に描画すればいい。
(sizeはレイアウトマネージャーの中で(そのコンポーネントの大きさとして)セットされるから)
描画範囲より広い範囲に描きたい場合は、クリップ(描画領域)を大きくする。
override protected def paintBorder(g: Graphics2D) {
val r = g.getClipBounds
g.setClip(r.x - 1, r.y - 1, r.width + 2, r.height + 2)
val sz = size
g.drawRect(-1, -1, sz.width + 1, sz.height + 1)
}
クリックなどのイベントによって再描画したい場合はrepaintを呼び出す。
(revalidateとかは呼ばなくても描画されるようだ)
import scala.swing._
object ComponentClickSample extends SimpleSwingApplication {
override def top = new MainFrame {
title = "サンプル"
contents = new FlowPanel {
contents += new Label("ラベル1") with MyBorder
contents += new Label("ラベル2") with MyBorder
contents += new Button("ボタン") with MyBorder
}
}
}
trait MyBorder {
self: Component =>
var borderFlag = false
// イベント
listenTo(this.mouse.clicks)
reactions += {
case event.MouseClicked(_, _, _, _, _) =>
borderFlag = !borderFlag
repaint()
}
// 描画
override protected def paintBorder(g: Graphics2D) {
if (borderFlag) {
g.setColor(java.awt.Color.RED)
val r = new Rectangle(size)
g.drawRect(r.x, r.y, r.width - 1, r.height - 1)
}
}
}