S-JIS[2011-04-12/2011-11-10] 変更履歴
Tableの例。
import scala.swing._
object TableSample extends SimpleSwingApplication { override def top = new MainFrame { title = "テーブルサンプル" contents = new ScrollPane(new Table { override lazy val model = super.model.asInstanceOf[javax.swing.table.DefaultTableModel] autoResizeMode = Table.AutoResizeMode.Off //列の幅を自動変更しない // 列(カラム)の初期化 model.addColumn("列1") model.addColumn("列2") model.addColumn("列3") // 行(データ)の追加 model.addRow(Array[AnyRef]("foo", "bar", "zzz")) model.addRow(Array[AnyRef]("ほげ", "ふが", "はげ")) }) size = new Dimension(256, 212) } }
Tableのコンストラクターは何種類かあるが、行と列の初期値を与えるコンストラクターはmodelがDefaultTableModelではないので、可変のテーブルを作るには不便そう。
行追加時にAnyRef(Object)の配列を渡しているが、内部でさらにjava.util.Vectorに変換されるので、
何らかのクラスから行データを作るなら、そのクラスから(配列でなく)直接java.util.Vectorに変換する方がいいだろう。
セル毎にツールチップの内容を変える例。
ateraiさん(てんぷらメモ)のJTableのTooltipsを行ごとに変更によると、ツールチップを変更するにはJTableのメソッドをオーバーライドする必要がある。
しかしScalaのTableはJTableをpeerとして保持しているものの、独自拡張した無名クラスになっている。したがってそれを継承したクラスを作ることが出来ない。(ソースをコピペすることは出来るが、今後のScalaのバージョンアップで内容が変わりうる事を考えれば、やりたくない)
そんな中で、レンダラーを変える方法だとTableの中でレンダラー取得用メソッドが用意されているので、オーバーライドすることが出来る。
contents = new ScrollPane(new Table { 〜 override protected def rendererComponent(isSelected: Boolean, focused: Boolean, row: Int, column: Int): Component = { val r = super.rendererComponent(isSelected, focused, row, column) val d = model.getValueAt(row, column) r.tooltip = d.toString r } })
表示されるセルの行位置・列位置が引数で渡されるので、それを元にデータを取得すれば、データ内容をツールチップとして表示することが出来る。
ここで列位置を無視して値を取得するようにすれば、行毎にツールチップを出せる状態になる。
デフォルトでは、セルをクリックすると行全体が選択される。[2011-11-10]
JavaのJTableではsetSelectionMode()で選択方法を変更したが、ScalaのTableではTable.selectionのIntervalModeで変更する。
IntervalMode(Scala) | ListSelectionModel(Java) | 内容 |
---|---|---|
Single | SINGLE_SELECTION | 1行だけ選択可能。 |
SingleInterval | SINGLE_INTERVAL_SELECTION | 間をとばさずに範囲選択可能。(Shiftキー) |
MultiInterval | MULTIPLE_INTERVAL_SELECTION | 複数行を選択可能。(Shiftキー+Ctrlキー) |
個別のセルを選択できるようにする為にはElementModeを指定する。
ElementMode(Scala) | 内容 |
---|---|
Row | 行を選択する。 |
Column | 列を選択する。 |
Cell | セルを選択する。 |
None | 選択しない。 |
class MyTable extends Table { selection.intervalMode = Table.IntervalMode.MultiInterval selection.elementMode = Table.ElementMode.Cell }
デフォルトでは、セルをダブルクリックしたりF2キーを押したり、あるいは文字を入力すると、どのセルでも編集できる。[2011-04-13]
JavaのJTableではgetCellEditor()メソッドがエディターを返すようになっており、ここでnullを返すと編集できない。
ScalaのTableではgetCellEditorからeditorメソッドが呼ばれるようになっているので、これをオーバーライドする。
contents = new ScrollPane(new Table {
〜
// 3列目(列番号が2)のときだけ編集できるようにする例
override protected def editor(row: Int, column: Int) = {
if (viewToModelColumn(column) == 2) super.editor(row, column) else null
}
})
※viewToModelColumnを使っておかないと、ドラッグ&ドロップで列を入れ替えたときに別の列が変更可能になってしまう。
セルを編集して更新された値を取得する方法。[2011-04-14]
セルが更新されると、TableModelに値がセットされ、更新イベントが発生する。なのでそのイベントを捕捉してやればいい。
ただ、TableのデフォルトコンストラクターでTableModelを初期化した場合は、イベントを捕捉できる状態になっていない。自分でリスナーを登録してやる必要がある。(リスナー自体は用意されている)
contents = new ScrollPane(new Table {
〜
model.addTableModelListener(modelListener)
listenTo(this)
reactions += {
case event.TableUpdated(source, range, column) =>
range.foreach { row =>
println(model.getValueAt(row, column))
}
}
})
TableModelを自分でセットしている場合は、modelのセッターメソッド内でリスナーが登録される。
contents = new ScrollPane(new Table {
super.model = new javax.swing.table.DefaultTableModel
〜
listenTo(this)
reactions += {
case event.TableUpdated(source, range, column) =>
range.foreach { row =>
println(model.getValueAt(row, column))
}
}
})
しかしmodelを自分でセットするなら、素直に新しいクラスを作ってsetValueAtをオーバーライドすればいいような気もする^^;
contents = new ScrollPane(new Table { super.model = new javax.swing.table.DefaultTableModel { override def setValueAt(value: AnyRef, row: Int, column: Int) { println("old=" + getValueAt(row, column)) super.setValueAt(value, row, column) println("new=" + value) } } 〜 })
列挙型(Enumeration)をコンボボックスで選択できるようにしてみよう。[2011-04-15]
(参考: JTableの日付エディター)
import javax.swing._ class EnumCellEditor extends DefaultCellEditor(new JComboBox) { import java.awt._ protected var evalue: Enumeration#Value = _ override def getComponent() = super.getComponent.asInstanceOf[JComboBox] override def getTableCellEditorComponent(table: JTable, value: AnyRef, isSelected: Boolean, row: Int, column: Int): Component = { evalue = null val combo = getComponent combo.removeAllItems() // 列挙型クラスの取得 val e = value.getClass.getMethod("scala$Enumeration$$outerEnum").invoke(value).asInstanceOf[Enumeration] e.values.foreach { combo.addItem(_) } combo.setSelectedItem(value) super.getTableCellEditorComponent(table, value, isSelected, row, column) } override def stopCellEditing(): Boolean = { evalue = super.getCellEditorValue.asInstanceOf[Enumeration#Value] super.stopCellEditing() } override def getCellEditorValue() = evalue }
contents = new ScrollPane(new Table { 〜 peer.setDefaultEditor(classOf[Enumeration#Value], new EnumCellEditor()); 〜 })
列挙型クラスは、リフレクションを使って値(列挙子)から取得してみた。
コンストラクター辺りで列挙型クラス(オブジェクト)を渡すようにすれば、もっと確実だろうけど…。
ヘッダーの列と列の間にマウスカーソルがある(列幅変更の矢印マークになっている)ときにダブルクリックして、データの最大幅に自動的に合わせる方法。[2011-04-14]
以前、JavaのJTableHeaderを拡張してダブルクリック時に列幅を自動調節できるテーブルヘッダーを作ったので、それをそのまま使うか、Scalaにコンバートすればいいんだけど。
contents = new ScrollPane(new Table { peer.setTableHeader(new SampleHeader(peer.getTableHeader().getColumnModel)) 〜 })
しかしせっかくScalaを使っているのだから、トレイトにしてみる。
import scala.swing._ trait ColumnWidthToFitDataTable { selfTable: Table => import java.awt._ import java.awt.event._ import javax.swing.SwingUtilities import javax.swing.table.JTableHeader protected val mouseListener = new MouseAdapter { override def mouseClicked(e: MouseEvent) { if (SwingUtilities.isLeftMouseButton(e) && e.getClickCount % 2 == 0) { //左ダブルクリック val header = selfTable.peer.getTableHeader if (header.getCursor.getType == Cursor.E_RESIZE_CURSOR) { // 矢印カーソル val vc = header.columnAtPoint(new Point(e.getX - 3, e.getY)) if (vc >= 0) { columnWidthToFitData(vc) e.consume() } } } } } // 列幅をデータの最大幅に変更する def columnWidthToFitData(vc: Int) { val table = selfTable.peer val vrows = table.getRowCount // 表示行数 if (vrows > 0) { val w = (0 until vrows).map { i => val r = table.getCellRenderer(i, vc) // レンダラー val value = table.getValueAt(i, vc) // データ r.getTableCellRendererComponent(table, value, false, false, i, vc).getPreferredSize.width // データ毎の幅 }.max table.getColumnModel.getColumn(vc).setPreferredWidth(w + 1); } } // 初期状態としてマウスリスナーをセット selfTable.peer.getTableHeader.addMouseListener(mouseListener) // テーブルヘッダーが差し替えられた時にマウスリスナーを移し替える selfTable.peer.addPropertyChangeListener(new java.beans.PropertyChangeListener { def propertyChange(e: java.beans.PropertyChangeEvent) { if ((e.getSource eq selfTable.peer) && e.getPropertyName == "tableHeader") { Option(e.getOldValue.asInstanceOf[JTableHeader]).foreach { _.removeMouseListener(mouseListener) } Option(e.getNewValue.asInstanceOf[JTableHeader]).foreach { _.addMouseListener(mouseListener) } } } }) }
contents = new ScrollPane(new Table with ColumnWidthToFitDataTable { 〜 })
JavaのSwingではsetAutoCreateRowSorter(true)によって行ソート(一番上の行ヘッダーの項目をクリックするとその項目でソートされる)が出来る。[2011-10-17]
Scalaにはこのメソッドに対するラッパーメソッドが無いので、peerに対して呼び出す。(peer.setAutoCreateRowSorter(true))
また、ソートする際には各項目のクラスが必要になるので、クラスも返すようにする。(TableModel#getColumnClass())
が、ScalaのSwingにはバグがあって、これだけではソートが実行されないので対処する必要がある。(apply()をオーバーライドし、viewToModelRow()を呼ぶようにする)
Scala(2.9.1)のSwingのTableのソートのバグとは、Tableクラスは値を取得する際にapplyメソッドを呼ぶようになっているが、このapplyメソッドは行インデックスに対する考慮がされていない事。
(参考: Bug in scala.swing.Table?)class Table extends Component with Scrollable.Wrapper { override lazy val peer: JTable = new JTable with Table.JTableMixin with SuperMixin { 〜 override def getValueAt(r: Int, c: Int) = Table.this.apply(r,c).asInstanceOf[AnyRef] } 〜 def apply(row: Int, column: Int): Any = model.getValueAt(row, viewToModelColumn(column)) def viewToModelColumn(idx: Int) = peer.convertColumnIndexToModel(idx) def modelToViewColumn(idx: Int) = peer.convertColumnIndexToView(idx) 〜 }
class RowSortTable extends Table { import javax.swing.table._ class RowSortTableModel extends DefaultTableModel { private var columnClass = Array[Class[_]]() override def addColumn(name: Object) { addColumn(name, classOf[Object]) } def addColumn(name: Object, c: Class[_]) { columnClass :+= c super.addColumn(name) } override def getColumnClass(index: Int) = columnClass(index) } override def apply(row: Int, column: Int): Any = model.getValueAt(viewToModelRow(row), viewToModelColumn(column)) def viewToModelRow(idx: Int) = peer.convertRowIndexToModel(idx) def modelToViewRow(idx: Int) = peer.convertRowIndexToView(idx) super.model = new RowSortTableModel override def model = super.model.asInstanceOf[RowSortTableModel] peer.setAutoCreateRowSorter(true) }
class MyTable extends RowSortTable { model.addColumn("name", classOf[String]) model.addColumn("size", classOf[java.lang.Integer]) }