ComboBox 选定项的事件过滤器

EventFilter for ComboBox selected Item

如何为 ComboBox 的 SelectedItem 属性 编写 EventFilter? This 文章仅针对 MouseEvent 之类的用户事件描述了它,我似乎无法找出 selectedItem 属性 更改的事件类型。

我问是因为我在对话框中有一个 3D 应用程序,它在插槽上显示材料。该插槽可以用我的 Combobox 切换,但我希望能够在选择发生实际更改之前进行过滤,看看我是否有任何未保存的更改,并在用户想要保存更改或中止时显示一个对话框。而且由于我在组合框上有各种侦听器,当组合框中的选择发生变化时,它们会切换 3D 中的材质,因此不容易实现该对话框的中止功能。

我也对可能更适合的 "Do you want to save Changes?" 实施的其他方法持开放态度。

考虑创建另一个 属性 来表示组合框中的值,并且仅在用户确认时更新它。然后您的应用程序的其余部分可以观察到 属性.

所以,例如

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);
    }
}

为此,您需要将 ChangeListener 添加到 ComboBox

valueProperty()

这是一个例子:

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);
             }
        }
    }    
});