EventFilter для выбранного элемента ComboBox

Как я могу написать EventFilter для свойства SelectedItem ComboBox? Эта статья описывает это только для пользовательских событий, таких как a MouseEvent, и я не могу понять, какой тип EventType является изменением свойства selectedItem.

Я спрашиваю, потому что у меня есть 3D-приложение в диалоговом окне, которое отображает материалы в слоте. Этот слот можно переключить с помощью моего поля со списком, но я хочу иметь возможность фильтровать ДО того, как произойдет фактическое изменение выбора, посмотреть, есть ли у меня какие-либо несохраненные изменения, и показать диалоговое окно, хочет ли пользователь сохранить изменения или прервать. И поскольку у меня есть множество слушателей в поле со списком, которые переключают материалы в 3D при изменении выбора в поле со списком, функциональность прерывания в этом диалоговом окне не легко достигается.

Я также открыт для других подходов «Хотите ли вы сохранить изменения?» реализация, которая может быть лучше подходящей.


person 404KnowledgeNotFound    schedule 27.03.2017    source источник


Ответы (2)


Рассмотрите возможность создания другого свойства для представления значения в поле со списком и обновления его только в том случае, если пользователь подтвердит. Тогда остальная часть вашего приложения может просто наблюдать за этим свойством.

So, e.g.

private ComboBox<MyData> combo = ... ;

private boolean needsConfirmation = true ;

private final ReadOnlyObjectWrapper<MyData> selectedValue = new ReadOnlyObjectWrapper<>();

public ReadOnlyObjectProperty<MyData> selectedValueProperty() {
    return selectedValue.getReadOnlyProperty() ;
}

public final MyData getSelectedValue() {
    return selectedValueProperty().get();
}

// ...

combo.valueProperty().addListener((obs, oldValue, newValue) -> {

    if (needsConfirmation) {
        // save changes dialog:
        Dialog<ButtonType> dialog = ... ;
        Optional<ButtonType> response = dialog.showAndWait();
        if (response.isPresent()) {
            if (response.get() == ButtonType.YES) {
                // save changes, then:
                selectedValue.set(newValue);
            } else if (response.get() == ButtonType.NO) {
                // make change without saving:
                selectedValue.set(newValue);
            } else if (response.get() == ButtonType.CANCEL) {
                // revert to old value, make sure we don't display dialog again:
                // Platform.runLater() is annoying workaround required to avoid
                // changing contents of list (combo's selected items) while list is processing change:
                Platform.runLater(() -> {
                    needsConfirmation = false ;
                    combo.setValue(oldValue);
                    needsConfirmation = true ;
                });
            }
        } else {
            needsConfirmation = false ;
            combo.setValue(oldValue);
            needsConfirmation = true ;
        }
    }
});

Теперь ваше приложение может просто наблюдать за selectedValueProperty() и реагировать, если оно изменится:

selectionController.selectedValueProperty().addListener((obs, oldValue, newValue) -> {
    // respond to change...
});

Вот (очень простой) SSCCE:

import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.ButtonType;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Dialog;
import javafx.scene.control.DialogPane;
import javafx.scene.control.Label;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;

public class InterceptComboBox extends Application {

    private ComboBox<String> combo ;
    private boolean needsConfirmation = true ;
    private Label view ;
    private final ReadOnlyObjectWrapper<String> selectedValue = new ReadOnlyObjectWrapper<String>();

    public ReadOnlyObjectProperty<String> selectedValueProperty() {
        return selectedValue.getReadOnlyProperty();
    }

    public final String getSelectedValue() {
        return selectedValueProperty().get();
    }

    @Override
    public void start(Stage primaryStage) {
        combo = new ComboBox<>();
        combo.getItems().addAll("One", "Two", "Three");
        combo.setValue("One");
        selectedValue.set("One");
        view = new Label();
        view.textProperty().bind(Bindings.concat("This is view ", selectedValue));

        combo.valueProperty().addListener((obs, oldValue, newValue) -> {
            if (needsConfirmation) {
                SaveChangesResult saveChanges = showSaveChangesDialog();
                if (saveChanges.save) {
                    saveChanges();
                }
                if (saveChanges.proceed) {
                    selectedValue.set(newValue);
                } else { 
                    Platform.runLater(() -> {
                        needsConfirmation = false ;
                        combo.setValue(oldValue);
                        needsConfirmation = true ;
                    });
                }
            }
        });

        BorderPane root = new BorderPane(view);
        BorderPane.setAlignment(combo, Pos.CENTER);
        BorderPane.setMargin(combo, new Insets(5));
        root.setTop(combo);

        primaryStage.setScene(new Scene(root, 400, 400));
        primaryStage.show();
    }

    private void saveChanges() {
        System.out.println("Save changes");
    }

    private SaveChangesResult showSaveChangesDialog() {
        DialogPane dialogPane = new DialogPane();
        dialogPane.setContentText("Save changes?");
        dialogPane.getButtonTypes().setAll(ButtonType.YES, ButtonType.NO, ButtonType.CANCEL);
        Dialog<SaveChangesResult> dialog = new Dialog<>();
        dialog.setDialogPane(dialogPane);
        dialog.setResultConverter(button -> {
            if (button == ButtonType.YES) return SaveChangesResult.SAVE_CHANGES ;
            else if (button == ButtonType.NO) return SaveChangesResult.PROCEED_WITHOUT_SAVING ;
            else return SaveChangesResult.CANCEL ;
        });
        return dialog.showAndWait().orElse(SaveChangesResult.CANCEL);
    }

    enum SaveChangesResult {
        SAVE_CHANGES(true, true), PROCEED_WITHOUT_SAVING(true, false), CANCEL(false, false) ;

        private boolean proceed ;
        private boolean save ;

        SaveChangesResult(boolean proceed, boolean save) {
            this.proceed = proceed ;
            this.save = save ;
        }

    }

    public static void main(String[] args) {
        launch(args);
    }
}
person James_D    schedule 27.03.2017
comment
Побей меня и лучше подойди. +1 - person Jonah; 27.03.2017
comment
Спасибо, Джеймс, как всегда, ты отвечаешь на все мои вопросы лучшими ответами, о которых я даже не думал! +1 - person 404KnowledgeNotFound; 28.03.2017

Для этого вы хотите добавить ChangeListener к valueProperty() из ComboBox

Вот пример:

comboBox.valueProperty().addListener(new ChangeListener<Object>()
{
    @Override
    public void changed(ObservableValue observable, Object oldValue, Object newValue)
    {
        Optional<ButtonType> result = saveAlert.showAndWait();

        if(result.isPresent())
        {
             if(result.get() == ButtonType.YES)
             {
                 //Your Save Functionality
                 comboBox.valueProperty().setValue(newValue);
             }
             else
             {
                 //Whatever
                 comboBox.valueProperty().setValue(oldValue);
             }
        }
    }    
});
person Jonah    schedule 27.03.2017