Idiomatic table cell renderers in Scala
Asked Answered
Z

2

9

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?

Zetland answered 28/7, 2009 at 15:47 Comment(0)
Z
8

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)  )
Zetland answered 28/8, 2009 at 15:13 Comment(2)
I think you want peer.convertColumnIndexToModel(col) instead of peer.convertColumnIndexToModel(row)Nagel
You can also use scala.swing.Table.viewToModelColumn(Int): Int. Note sure why there isn't an equivalent wrapper method for rows..Nagel
A
1

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
  }
}
Achromatous answered 12/1, 2012 at 22:53 Comment(1)
I read your comment but at first didn't get it. Only after I created several sub classes of Table.AbstractRenderer did I realize why I needed your code. The improvement I would add is that you may as well pass the remaining configure() parameters to render(a, sel, foc, row, col). I eventually needed all of themFictile

© 2022 - 2024 — McMap. All rights reserved.