JavaFX: Prevent selection of a different tab if the data validation of the selected tab fails

坚强是说给别人听的谎言 提交于 2019-12-12 10:18:05

问题


I'm creating a CRUD application that store data in a local h2 DB. I'm pretty new to JavaFX. I've created a TabPane to with 3 Tab using an jfxml created with Scene Builder 2.0. Each Tab contains an AncorPane that wrap all the controls: Label, EditText, and more. Both the TabPane and the Tabs are managed using one controller. This function is used to create and to update the data. It's called from a grid that display all the data. A pretty basic CRUD app.

I'm stuck in the validation phase: when the user change the tab, by selecting another tab, it's called a validation method of the corresponding tab. If the validation of the Tab fails, I want that the selection remains on this tab.

To achieve this I've implemented the following ChangeListener on the SelectionModel of my TabPane:

boolean processingTabValidationOnChange = false;

tabPane.getSelectionModel().selectedIndexProperty().addListener(new ChangeListener<Number>() {

            @Override
            public void changed(ObservableValue<? extends Number> ov, Number t, Number t1) {
                if (processingTabValidationOnChange == false) {
                    boolean success;

                    switch (t.intValue()) {
                        case 0: success = validationTab1Passed();
                                break;
                        case 1: success = validationTab2Passed();
                                break;
                        case 1: success = validationTab3Passed();
                                break;
                        default: success = false;    
                    }
                    if (success == false) {
                        processingTabValidationOnChange = true;
                        // select the previous tab
                        tabPane.getSelectionModel().select(t.intValue());
                        processingTabValidationOnChange = false;
                    }
                }
            }
        });

I'm not sure that this is the right approach because:

  1. The event changed is fired two times, one for the user selection and one for the .select(t.intValue()). To avoid this I've used a global field boolean processingTabValidationOnChange... pretty dirty I know.
  2. After the .select(t.intValue()) the TabPane displays the correctly Tab as selected but the content of the tab is empty as if the AnchorPane was hidden. I cannot select again the tab that contains the errors because it's already selected.

Any help would be appreciated.

Elvis


回答1:


As I commented to the James's answer, I was looking for a clean solution to the approach that I've asked. In short, to prevent the user to change to a different tab when the validation of the current tab fails. I proposed a solution implementing the ChangeListener but, as I explained: it's not very "clean" and (small detail) it doesn't work!

Ok, the problem was that the code used to switch back the previous tab:

tabPane.getSelectionModel().select(t.intValue());

is called before the process of switching of the tab itself it's completed, so it ends up selected... but hidden.

To prevent this I've used Platform.runLater(). The code .select() is executed after the change of tab. The full code becomes:

//global field, to prevent validation on .select(t.intValue());
boolean skipValidationOnTabChange = false;

tabPane.getSelectionModel().selectedIndexProperty().addListener(new ChangeListener<Number>() {

    @Override
    public void changed(ObservableValue<? extends Number> ov, Number t, Number t1) {
        if (skipValidationOnTabChange == false) {
            boolean success;

            switch (t.intValue()) {
                case 0:
                    success = validationTab1Passed();
                    break;
                case 1:
                    success = validationTab2Passed();
                    break;
                case 1:
                    success = validationTab3Passed();
                    break;
                default:
                    success = false;
            }
            if (success == false) {
                Platform.runLater(new Runnable() {

                    @Override
                    public void run() {
                        skipValidationOnTabChange = true;
                        tabPane.getSelectionModel().select(t.intValue());
                        skipValidationOnTabChange = false;
                    }
                });
            }
        }
    }
});

Anyway, if anyone has a better solution to accomplish this, you're welcome. In example using a method like consume() to prevent the tab to be selected two times. This way I can eliminated the global field skipValidationOnTabChange.

Elvis




回答2:


I would approach this very differently. Instead of waiting for the user to select a different tab, and reverting if the contents of the current tab are invalid, prevent the user from changing tabs in the first place.

The Tab class has a disableProperty. If it is set to true, the tab cannot be selected.

Define a BooleanProperty or BooleanBinding representing whether or not the data in the first tab is invalid. You can create such bindings based on the state of the controls in the tab. Then bind the second tab's disableProperty to it. That way the second tab automatically becomes disabled or enabled as the data in the first tab becomes valid or invalid.

You can extend this to as many tabs as you need, binding their properties as the logic dictates.

Here's a simple example.

Update: The example linked above is a bit less simple now. It will dynamically change the colors of the text fields depending on whether the field is valid or not, with validation rules defined by bindings in the controller. Additionally, there are titled panes at the top of each page, with a title showing the number of validation errors on the page, and a list of messages when the titled pane is expanded. All this is dynamically bound to the values in the controls, so it gives constant, clear, yet unobtrusive feedback to the user.




回答3:


I needed to achieve the similar thing. I've done this by changing the com.sun.javafx.scene.control.behavior.TabPaneBehaviour class by overriding selectTab method:

class ValidatingTabPaneBehavior extends TabPaneBehavior {

        //constructors etc...

        @Override
        public void selectTab(Tab tab) {
            try {
                Tab current = getControl().getSelectionModel().getSelectedItem();
                if (current instanceof ValidatingTab) {
                    ((ValidatingTab) current).validate();
                }
                //this is the method we want to prevent from running in case of error in validation
                super.selectTab(tab);
            }catch (ValidationException ex) {
                //show alert or do nothing tab won't be changed
            }
        }
    });

The ValidatingTab is my own extension to Tab:

public class ValidatingTab extends Tab {
       public void validate() throws ValidationException {
           //validation
       }
}

This is the "clean part" of the trick. Now we need to place ValidatingTabPaneBehavior into TabPane.

First you need to copy (!) the whole com.sun.javafx.scene.control.skin.TabPaneSkin to the new class in order to change its constructor. It is quite long class, so here is only the part when I switch the Behavior class:

public class ValidationTabPaneSkin extends BehaviorSkinBase<TabPane, TabPaneBehavior> {

//copied private fields

public ValidationTabPaneSkin(TabPane tabPane) {
    super(tabPane, new ValidationTabPaneBehavior(tabPane));

    //the rest of the copied constructor
}

The last thing is to change the skin in your tabPane instance:

 tabPane.setSkin(new ValidationTabPaneSkin(tabPane));


来源:https://stackoverflow.com/questions/22772364/javafx-prevent-selection-of-a-different-tab-if-the-data-validation-of-the-selec

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!