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) } } }