I had been using the traditional Java TableCellRenderer
approach for providing the renderers in a scala.swing.Table
where I declare my renderers on the table's TableColumnModel
. The code for this looked like:
val myTable = new Table {
lazy val tcm = initColumnModel
peer.setColumnModel(tcm)
override
protected def rendererComponent(sel: Boolean, foc: Boolean, row: Int, col: Int) = {
//GET THE VALUE FROM THE TableModel
val value = model.getValueAt(
peer.convertRowIndexToModel(row),
peer.convertColumnIndexToModel(col))
//GET THE RENDERER FROM THE ColumnModel
val renderer = tcm.getColumn(col).getCellRenderer
//WRAP IN A COMPONENT
Component.wrap(renderer.getTableCellRendererComponent(
peer,
value,
sel,
foc,
row,
col).asInstanceOf[JComponent])
}
}
Unfortunately this appears to have a memory leak - presumably because I am creating a new Component instance for every cell in the table (for ~30k rows). Certainly when I replace my scala table with a JTable
(using exactly the same column and data models) my memory leak goes away.
My question is therefore, what sort of code do people use when overriding the rendererComponent
method assuming one has ones own cell renderers?
The idiomatic way of using Scala table cell renderers is to use Table.AbstractRenderer
(if implementing your own) or one of its subclasses:
val tcr = new Table.AbstractRenderer[MyObj, MyRenderer](new MyRenderer) {
def configure(t: Table, sel: Boolean, foc: Boolean, o: MyObj, row: Int, col: Int) = {
//component variable is bound to your renderer
component.prepare(o)
}
}
In this case prepare
is a method you would define on your own renderer class:
class MyRenderer extends Label {
def prepare(o: MyObj) {
text = o.toString //or whatever
}
}
Then this is used by overriding the rendererComponent
method on Table
:
val t = new Table {
override def rendererComponent(sel: Boolean, foc: Boolean, row: Int, col: Int) = {
//FIND VALUE
val v = model.getValueAt(
peer.convertRowIndexToModel(row),
peer.convertColumnIndexToModel(row))
col match {
case 0 => tcr.componentFor(this, sel, foc, v, row, col)
}
}
}
Scala comes with its own implementations of AbstractRenderer
, namely LabelRenderer
which takes a function as an argument, converting an instance of MyObj to a Tuple2
consisting of a String
and an Icon
, for that label to display:
val ltcr = new LabelRenderer[MyObj] ( (o: MyObj) => (null, o.toString) )
Thanks a ton for your example oxbow_lakes!
IMHO this scala-thing has become as ugly as table-rendering can possibly get. Trying to hide it as much as possible...
class TableRenderer[A](comp: TableRendererComp[A]) extends Table.AbstractRenderer[A,TableRendererComp[A]](comp) {
def configure(t: Table, sel: Boolean, foc: Boolean, a: A, row: Int, col: Int): Unit =
component.render(a, sel, foc)
}
trait TableRendererComp[A] extends Component {
def render(a: A, sel: Boolean, foc: Boolean): Unit
}
Using like (at least the "configure" is gone...)
val tcr = new TableRenderer[MyObj](new MyRenderer)
class MyRenderer extends Label with TableRendererComp[MyObj] {
def render(o: MyObj, sel: Boolean, foc: Boolean) {
text = o.toString //or whatever
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With