TrustNoOne TrustNoOne - 13 days ago 6
Scala Question

Erroneous data in some cells of tableview

New to scala and scalafx and having an issue with a tableview in a simple stock quote app [note: no prior javafx experience except in ways it is similar to things Android]

Problem: (see image) erroneous data displayed in change column where there should be none.

To create: Multiple scenarios, shown here is entry of new ticker symbol. It seems unpredictable how many cells are in error. Changing window size (say shorter then taller) generally creates more bad cells. Never happens if no change to windowsize and/or symbol list are made.

Code for the 'ticker' and 'change' columns below, though I suspect it is something in how I implemented the change column to do green/red text coloring.

(Note: data is updated in a single batch periodically which is why the new symbol does not immediately display quote data)

val colTicker = new TableColumn[Quote, String] {
editable = true
text = "Ticker"
prefWidth = 80
alignmentInParent = scalafx.geometry.Pos.Center
cellValueFactory = {
_.value.ticker
}
cellFactory = _ => new TextFieldTableCell[Quote, String](new DefaultStringConverter())
onEditCommit = (evt: CellEditEvent[Quote, String]) => {
val quote: Quote = evt.rowValue
val newTickerVal: String = evt.newValue.toUpperCase()
val oldTickerVal: String = evt.oldValue
// is it a valid ticker and not a dupe or is it blank (erase old ticker)?
if ((isValidTicker(newTickerVal) || newTickerVal.length == 0) && !symbolList.contains(newTickerVal)) {
// lock in the new value on the screen
quote.ticker.set(newTickerVal)
// if the new value is not empty add it to symbol list
if (newTickerVal.length > 0) {
symbolList.append(newTickerVal)
}
// now delete the old value
symbolList -= oldTickerVal
// sort and add another blank line
characters.sortWith(_.ticker.getValueSafe < _.ticker.getValueSafe)
if (oldTickerVal.length < 1) characters += Quote()
// now need to update the data file
putListToFile(dataFile, symbolList.sorted)
} else {
// bad ticker so keep the old one and don't update file
quote.ticker.set(oldTickerVal)
evt.getTableView.getColumns.get(0).setVisible(false)
evt.getTableView.getColumns.get(0).setVisible(true)
println("bad ticker, exiting symbol list: " + symbolList)
}
}

}
val colLast = new TableColumn[Quote, String] {
editable = false

text = "Last"
cellValueFactory = {
_.value.last
}

prefWidth = 80
alignmentInParent = scalafx.geometry.Pos.Center
}
val colChange = new TableColumn[Quote, String] {
editable = false
text = "Change"
cellFactory = {
_ =>
new TableCell[Quote, String] {
item.onChange { (_, _, newChange) =>
if (newChange != null) {
if (newChange.toString.contains("+")) textFill = Color.Green
else textFill = Color.Red
text = newChange
}
}
}
}
cellValueFactory = {
_.value.change
}
prefWidth = 80
alignmentInParent = scalafx.geometry.Pos.Center
}


enter image description here

Answer

JavaFX is reusing cells when rendering. This is especially noticeable when dynamically updating TableView content. Your cellFactory has to clear cell content when receiving and empty or null item: text and graphic need to be set to null. It may be sufficient to simply check for newChange == null

cellFactory = { _ =>
  new TableCell[Quote, String] {
    item.onChange { (_, _, newChange) =>
      if (newChange == null) {
        text = null
        graphic = null
      else {
        if (newChange.toString.contains("+")) textFill = Color.Green
        else textFill = Color.Red
        text = newChange
      }
    }
  }
}

It that is nor reliable you will have to implement the cellFactory the JavaFX way by implementing javafx.scene.control.TableCell and overwriting method updateItem that is passing in the empty flag:

cellFactory = {_ =>
  new javafx.scene.control.TableCell[Quote, String] {
    override def updateItem(item: String, empty: Boolean): Unit = {
      super.updateItem(item, empty) 
      if (item == null || empty) {
         text = null
         graphic = null
      }
      else {
        ...
      }
    }
  }
}