Featured Post

Applying Email Validation to a JavaFX TextField Using Binding

This example uses the same controller as in a previous post but adds a use case to support email validation.  A Commons Validator object is ...

Tuesday, August 18, 2015

Disabling MenuItems With Binding

Disabling controls for invalid operations is far better for your user than displaying an error or simply not doing anything.  The user may even use disabled controls to help understand the operation by scanning what's available at different points in the application.  Outside of JavaFX, I've made explicit calls like setDisabled() on MenuItems when I need to enable or disable and item.  This blog post uses binding to link the MenuItem with a flag for more concise code and better readability.

This video demonstrates the MenuItem behavior.  Initially, the Save Profile MenuItem is disabled.  Typing into a TextField enables it.  Performing the Save operation disables the MenuItem again.

In my FXML, I have named the MenuItem "miSave".  This is defined as a field in the Controller class.  In SceneBuilder, the MenuItem's Disabled setting is checked, making it disabled when the program is started.

    @FXML
    MenuItem miSave;

I have a flag in the application called needsSave which is very clear in its purpose.  It is also a field.
    
    private final BooleanProperty needsSave = new SimpleBooleanProperty(false);

JavaFX binding let's me link the disabledProperty of miSave to the needsSave property.  For readbility, I'm actually binding to the needsSave.not() property.  I feel this is more readable than defining my flag as "doesNotNeedSave", especially later on when I call "needsSave(true)".  This code appears in the @FXML initialize() method.

    miSave.disableProperty().bind(needsSave.not());

This binding means that when the needsSave.not property changes to true, disable on the MenuItem will also be true.  In English, if the document doesn't need to be saved, no save MenuItem will be available.

When the user types into the TextFields, and InvalidationListener fires that sets needsSave to true.  This will make the disabledProperty false (needsSave.not = false).  In the class definition,

    private final InvalidationListener needsSaveListener = (evt) -> needsSave.set(true);

In the @FXML initialize(),

    tfSourceFile.textProperty().addListener(new WeakInvalidationListener(needsSaveListener));
    tfTargetFile.textProperty().addListener(new WeakInvalidationListener(needsSaveListener));

There is a needsSave.set(false) call made after the save operation takes place.

While you can skip binding and directly call miSave.setDisabled(), binding is more readable and scalable.  Binding's improved readability comes from linking the presentation (disabledProperty) to the intent (needsSave.not).  The scalability comes from being able to bind other non-MenuItem disabled properties like an alternate Button to the needsSave property.

The code in this post is hosted at GitHub.  See the class "ResignatorMainViewController".

No comments:

Post a Comment