ScalaFX: dynamic update of context menu of table rows

Context menus are useful to exhibit additional functionality. For my TLCockpit program I am listing the packages, updates, and available backups in a TreeTableView. The context for each row should be different depending on the status of the content displayed.

My first try, taken from searches on the web, was to add the context menu via the rowFactory of the TreeTableView:

table.rowFactory = { p =>
  val row = new TreeTableRow[SomeObject] {}
  val infoMI = new MenuItem("Info") { onAction = /* use row.item.value */ }
  val installMI = new MenuItem("Install") { onAction = /* use row.item.value */ }
  val removeMI = new MenuItem("Remove") { onAction = /* use row.item.value */ }
  val ctm = new ContextMenu(infoMI, installMI, removeMI)
  row.contextMenu = ctm
  row
}

This worked nicely until I tried to disable/enable some items based on the status of the displayed package:

  ...
  val pkg: SomeObject = row.item.value
  val isInstalled: Boolean = /* determine installation status of pkg */
  val installMI = new MenuItem("Install") { 
    disable = isInstalled
    onAction = /* use row.item.value */
  }

What I did here is just pull the shown package, get its installation status, and disable the Install context menu entry if it is already installed.

All good and fine I thought, but somehow reality was different. First there where NullPointerExceptions (rare occurrence in Scala for me), and then somehow that didn’t work out at all.

The explanation is quite simple to be found by printing something in the rowFactory function. There are only as many rows made as fit into the current screen size (plus a bit), and their content is dynamically updated when one scrolls. But the enable/disable status of the context menu entries were not properly updated.

To fix this one needs to add a callback on the displayed item, which is exposed in row.item. So the correct code is (assuming that a SomeObject has a BooleanProperty installed):

table.rowFactory = { p =>
  val row = new TreeTableRow[SomeObject] {}
  val infoMI = new MenuItem("Info") { onAction = /* use row.item.value */ }
  val installMI = new MenuItem("Install") { onAction = /* use row.item.value */ } 
  val removeMI = new MenuItem("Remove") { onAction = /* use row.item.value */ }
  val ctm = new ContextMenu(infoMI, installMI, removeMI)
  row.item.onChange { (_,_,newContent) =>
    if (newContent != null) {
      val isInstalled: /* determine installation status from newContent */
      installMI.disable = is_installed
      removeMI.disable = !is_installed
    }
  }
  row.contextMenu = ctm
  row
}

The final output then gives me:

That’s it, the context menus are now correctly adapted to the displayed content. If there is a simpler way, please let me know.

1 Response

  1. 2017/11/12

    […] ScalaFX: dynamic update of context menu of table rows […]

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>