Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Idiomatic table cell renderers in Scala

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?

like image 849
oxbow_lakes Avatar asked Jul 28 '09 15:07

oxbow_lakes


2 Answers

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)  )
like image 84
oxbow_lakes Avatar answered Nov 16 '22 06:11

oxbow_lakes


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
  }
}
like image 20
hotzen Avatar answered Nov 16 '22 07:11

hotzen