kotlinjavafxtornadofx

Tornadofx - Keeping globally accessed properties in a ViewModel?


Reasoning:

Hello guys. I'm building an evolution simulator as personal project. I have a few parameters set on textfields, such as the speed of the simulation and the number of "organisms". These are going to be accessed by multiple components of the application. Because i would also like to use validation on a few parameters, I set up a ViewModel like such:

class ParametersModel: ViewModel() {
    // These properties would likely be DoubleProperty, but for simplicity lets pretend they are strings
    val simulationSpeed = bind { SimpleStringProperty() }
    val organismsGenerated = bind { SimpleStringProperty() }
}

... and then perform the validation tests on the textfields:

val model = ParametersModel()
textfield(model.simulationSpeed).required()

This works alright, but the issue with it is that I'm defining the model properties as a bind to an empty SimpleDoubleProperty, which is redundant since I'm never commiting this model (the program should always read changes as they are typed). At the same time, I cant define the model properties as simply:

class ParametersModel: ViewModel() {
    val simulationSpeed = SimpleStringProperty()
    val organismsGenerated = SimpleStringProperty()
}

Because I then get an error about the validation:

The addValidator extension can only be used on inputs that are already bound bidirectionally to a property in a Viewmodel. Use validator.addValidator() instead or make the property's bean field point to a ViewModel.

The other option I could take would be to make a class named something like GlobalProperties, which would keep my properties and also a ValidationContext. I could then add validators by using validationContext.addValidator and pass the textfields. But at this point I feel I'm just coding a ViewModel equivalent.

Question:

Is ViewModel the correct way of keeping "globally" accessed parameters set by textfields? If so, is there a way to not have to set the model properties as a bind of an empty one, since i dont ever need to commit anything?


Solution

  • Usually you would use a ViewModel with some sort of model. Then you can use the ViewModel to handle user input, which stores the current state of the user input, and the backing model will only be update when the ViewModel is committed, assuming validation passes (which seems at odds with your claim that you "dont ever need to commit anything").

    Something like this:

    class Parameters {
        val simulationSpeedProperty = SimpleStringProperty(...)
        var simulationSpeed by simulationSpeedProperty
    
        val organismsGeneratedProperty = SimpleStringProperty(...)
        var organismsGenerated by organismsGeneratedProperty
    }
    
    class ParametersModel(parameters: Parameters): ItemViewModel<Parameters>(parameters) {
        val simulationSpeed = bind(Parameters::simulationSpeedProperty)
        val organismsGenerated = bind(Parameters::organismsGeneratedProperty)
    }
    

    Then you can be sure that the Parameters backing the ParametersModel always has valid values in it (assuming of course it was initialized with valid values).