kotlinjavafxtornadofx

How to update ItemFragment when item property changes


I'm quite confused about the relationship between an ItemViewModel and its underlying item. I'm trying to show a badge on the screen, and then if the badge is "achieved" on another thread, the badge should update on screen.

Right now, the badge code looks like this:

class Badge {
  val achievedProperty = SimpleBooleanProperty(this, "achieved", false)
  var achieved by achievedProperty
}

class BadgeModel: ItemViewModel<Badge>() {
  val achieved = bind(Badge::achievedProperty)
}

Next, I want to display it on the screen, in an ItemFragment subclass:

class BadgeFragment(badge: Badge): ItemFragment<Badge>() {

  private val model: BadgeModel by inject()
  
  init {
    model.item = badge
  }

  override val root = rectangle {
    width = 50
    height = 50
    stroke = Color.BLACK
    fill = if (model.achieved) Color.RED else Color.GREEN
  }
}

This works fine to start, but if I then set badge.achieved = true (in my controller), the color of the badge on screen doesn't change.

I'm clearly missing something about the relationship between the object and the model, but I'm having a lot of trouble figuring it out from documentation. Can someone help me get my fragment working the way I want it to?


Solution

  • Your problem isn't specific to models or almost anything TornadoFX-related. The way you've written it only checks for the color once upon creation. You need to use properties or bindings to listen to property changes:

    class Test : Fragment() {
        val modelAchieved = SimpleBooleanProperty(true) // pretend this is model.achieved
        val achievedColor = modelAchieved.objectBinding { isAchieved ->
            if (isAchieved == true) Color.RED
            else Color.GREEN
        }
    
        override val root = vbox {
            togglebutton("Toggle") {
                selectedProperty().bindBidirectional(modelAchieved)
            }
            rectangle(width = 50, height = 50) {
                stroke = Color.BLACK
                fillProperty().bind(achievedColor)
            }
        }
    }
    

    I would also bidirectionally bind the item properties of the viewmodel and fragment together so they stay in sync. Or just use a regular fragment, and reference the model's item property by itself.