CellRenderer table idiomatiques Scala
-
19-09-2019 - |
Question
J'avais utilisé l'approche traditionnelle Java TableCellRenderer
pour fournir les équarrisseurs dans un scala.swing.Table
où je déclare mes équarrisseurs sur TableColumnModel
de la table. Le code de cette ressemblait:
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])
}
}
Malheureusement, cela semble avoir une fuite de mémoire - sans doute parce que je suis en train de créer une nouvelle instance de composants pour toutes les cellules de la table (pour les lignes de ~ 30K). Certes, quand je remplace ma table scala avec un JTable
(en utilisant exactement les mêmes colonne et données modèles) ma fuite de mémoire disparaît.
Ma question est donc, quel genre de code de gens utilisent quand redéfinissant la méthode rendererComponent
en supposant qu'on a les propres CellRenderer?
La solution
La façon idiomatiques d'utiliser CellRenderer de table Scala est d'utiliser Table.AbstractRenderer
(si la mise en œuvre de votre propre) ou un de ses sous-classes:
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)
}
}
Dans ce cas prepare
est une méthode que vous définiriez votre propre classe renderer:
class MyRenderer extends Label {
def prepare(o: MyObj) {
text = o.toString //or whatever
}
}
Ensuite, il est utilisé en remplaçant la méthode rendererComponent
sur 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 est livré avec ses propres implémentations de AbstractRenderer
, à savoir LabelRenderer
qui prend une fonction comme un argument, la conversion d'une instance de MyObj à un Tuple2
constitué d'un String
et un Icon
, pour cette étiquette pour afficher :
val ltcr = new LabelRenderer[MyObj] ( (o: MyObj) => (null, o.toString) )
Autres conseils
Merci une tonne pour votre exemple oxbow_lakes!
à mon humble avis ce scala-chose est devenu aussi laid que la table-rendu peut éventuellement obtenir. Essayer de cacher autant que 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
}
Utilisation comme (au moins la "configure" est parti ...)
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
}
}