sialcasa / mvvmFX

an Application Framework for implementing the MVVM Pattern with JavaFX
Apache License 2.0
489 stars 104 forks source link

Validation does not show Visualization in TableView (TextFieldTableCell) #546

Open Siedlerchr opened 6 years ago

Siedlerchr commented 6 years ago

I have created a very simple TableView with two columns and added a ValidiationVisualizer to the TextFieldTableCell. The validation triggers correctly, but there is no visual clue shown. I tried different Decorators etc. but I could not figure it out. Any help would be appreciated!

grafik

This is the code I used to setup the table:

private final ControlsFxVisualizer visualizer = new ControlsFxVisualizer();

        colContent.setCellFactory(column -> {

            TextFieldTableCell<StringViewModel, String> cell = new TextFieldTableCell<>(new DefaultStringConverter());
            column.setCellValueFactory(cellData -> {

                visualizer.initVisualization(cellData.getValue().contentValidation(), cell);
                return cellData.getValue().getContent();
            });
            return cell;

        });
        colContent.setOnEditCommit((CellEditEvent<StringViewModel, String> cell) -> {
                                       cell.getRowValue().setContent(cell.getNewValue());
                                   });

        tblStrings.itemsProperty().bindBidirectional(viewModel.allStringsProperty());
        tblStrings.setEditable(true);

https://github.com/JabRef/jabref/pull/3735/files#diff-117aa7fc99c0b7380bdaf11022bb3575

In my ViewModel I have the Validator:

 private final Function<String, ValidationMessage> function = input -> {
        if (input == null) {
            System.out.println("Null checker");
            return ValidationMessage.error("May not be null2");
        } else if (input.trim().isEmpty()) {
            System.out.println("Should not empty");
            return ValidationMessage.warning("Should not be empty");
        } else {
            return null; // everything is ok
        }
    };

    public StringViewModel(String label, String content) {

        this.label.setValue(label);
        this.content.setValue(content);

        labelValidator = new FunctionBasedValidator<>(this.label, function);
        contentValidator = new FunctionBasedValidator<>(this.content, function);
    }

https://github.com/JabRef/jabref/pull/3735/files#diff-df16f2f707ba7bfd296b444ac7962c20

manuel-mauky commented 6 years ago

Your validator setup looks good but I think the setup of the cellfactory is not correct.

You are initializing the visualizer for one cell-value and one specific cell. However, JavaFX is reusing cells for different values. It will only create as many cells as needed for one view of elements in the table and it will reuse them when the user scrolls down.

The setup of a cellFactory and cellValueFactory is unconnected in JavaFX. The cellValueFactory only defines how a value to display is resolved. The cellFactory defines which type of cells is used. However, the cellFactory doesn't know anything about actual values. Your setup is only possible because you are nesting both factory methods. Normally you don't define the cellValueFactory inside a cellFactory method.

Ok, so how to fix this? I have to admit that I haven't used the validation within a TableView yet. The basic assumption of the validation module is that there is a one-to-one connection between a value and a control in the view (which is typically the case for forms). This assumption isn't true for Tables so we have to find a workaround.

In TableCell there is a method updateItem which is invoked everytime the value of a cell is updated. A typicall pattern is to create a subclass (can be anonymous inner class) and override this method. This would probably be the correct place to update the validation but I don't know hot to access the actual value (the StringViewModel instance in your example) inside of this method. And I don't know if this would create any sideeffects.

I don't have a ready-to-use solution but I'm trying to find one.

manuel-mauky commented 6 years ago

I found a solution that seems to work but I haven't fully tested it and I don't know if there are any negative side-effects. But you could try it out:


colContent.setCellValueFactory(cellData -> cellData.getValue.getContent());

colContent.setCellFactory(column -> new TextFieldTableCell<StringViewModel, String>(new DefaultStringConverter()) {
    @Override
    public void updateItem(String item, boolean empty) {
        super.updateItem(item, empty);

        if(! empty) {
            if(getTableRow() != null) {
                Object rowItem = getTableRow().getItem();

                if(rowItem != null && rowItem instanceof StringViewModel) {
                    StringViewModel vm = (StringViewModel) rowItem;

                    visualizer.initVisualization(vm.contentValidation(), this);
                }
            }
        }
    }
});

With this setup I get the validation marks in the table cells. However, the default styling of ControlsFX seems to be not optimal for this usecase. Due to the fact that TextFieldTableCell renders a Label when the value was entered, there is no red border but only a small mark at the buttom left corner of the TableCell. grafik

Another limitation is that it doesn't validate the inital values but only after the user has changed the value in the table.

Siedlerchr commented 6 years ago

Yeah cool! Thanks for the help, I will try this out. In JabRef we have a custom icon decorater that hopefully looks better.

Siedlerchr commented 6 years ago

The only odd effect I encounter is the following: Clear the first column cell -> icon shows Clear the second column cell -> The first icon is gone , the icon shows for the second cell But, when I now click the button to enter a new row, then both icons are shown again. Seems like a redraw is then triggered.

grafik

manuel-mauky commented 6 years ago

I don't know the internals of how and when re-render is done in JavaFX TableViews. Maybe there are examples or documentation of how the ControlsFX validation can be used with Tables? The visualization part of mvvmFX validation is quite similar to the one from ControlsFX, just with a different API. So maybe if they have any hints on how this works with pure ControlsFX then we probably can adapt the solution.