将 CheckComboBox 添加到 PropertySheet JavaFX

Add CheckComboBox to PropertySheet JavaFX

我想将 CheckComboBox 添加到 controlsfx 库中的 PropertySheet。默认编辑器仅包含 ComboBox 实现。是否可以添加 CheckComboBox?我尝试使用 AbstractPropertyEditor 实现 PropertyEditor 但出现异常。

    public static final <T> PropertyEditor<?> createCheckComboBoxEditor(PropertySheet.Item property,
        final Collection<T> choices) {
    final ObservableList<T> result = FXCollections.observableArrayList();
    result.addAll(choices);
    CheckComboBox<T> comboBox = new CheckComboBox<T>(result);
    return new AbstractPropertyEditor<ObservableList<T>, CheckComboBox<T>>(property, comboBox) {

        {
            getEditor().getCheckModel().getCheckedItems().setAll(FXCollections.observableArrayList(choices));                
        }
        @Override
        public void setValue(ObservableList<T> value) {
            getEditor().getCheckModel().getCheckedItems().setAll(value);
        }

        @Override
        protected ObservableValue<ObservableList<T>> getObservableValue() {
            return (ObservableValue<ObservableList<T>>) getEditor().getItems();
        }
    };
}

异常:

Exception in thread "JavaFX Application Thread" java.lang.ClassCastException: com.sun.javafx.collections.ObservableListWrapper cannot be cast to javafx.beans.value.ObservableValue
at configurator.ComboBoxEditor.getObservableValue(ComboBoxEditor.java:109)
at org.controlsfx.property.editor.AbstractPropertyEditor.<init>(AbstractPropertyEditor.java:83)
at org.controlsfx.property.editor.AbstractPropertyEditor.<init>(AbstractPropertyEditor.java:67)
at configurator.ComboBoxEditor.<init>(ComboBoxEditor.java:85)
at configurator.ComboBoxEditor.createCheckComboBoxEditor(ComboBoxEditor.java:85)
at configurator.ConfiguratorController.lambda$setPropertySheetEditors(ConfiguratorController.java:273)
at impl.org.controlsfx.skin.PropertySheetSkin$PropertyPane.getEditor(PropertySheetSkin.java:321)
at impl.org.controlsfx.skin.PropertySheetSkin$PropertyPane.setItems(PropertySheetSkin.java:301)
at impl.org.controlsfx.skin.PropertySheetSkin$PropertyPane.<init>(PropertySheetSkin.java:269)
at impl.org.controlsfx.skin.PropertySheetSkin$PropertyPane.<init>(PropertySheetSkin.java:261)
at impl.org.controlsfx.skin.PropertySheetSkin.buildPropertySheetContainer(PropertySheetSkin.java:223)
at impl.org.controlsfx.skin.PropertySheetSkin.refreshProperties(PropertySheetSkin.java:188)
at impl.org.controlsfx.skin.PropertySheetSkin.lambda$new(PropertySheetSkin.java:140)

这是一种可行的方法,遵循您使用 CheckComboBox 控件实现 AbstractPropertyEditor 的方法。

基于 HelloCheckComboBox,我使用 Person class、Musicians bean 和自定义编辑器创建了这个示例。

public static class Person {

    private final StringProperty firstname = new SimpleStringProperty();
    private final StringProperty lastname = new SimpleStringProperty();
    private final ReadOnlyStringWrapper fullName = new ReadOnlyStringWrapper();

    public Person(String firstname, String lastname) {
        this.firstname.set(firstname);
        this.lastname.set(lastname);
        fullName.bind(Bindings.concat(firstname, " ", lastname));
    }

    public static final ObservableList<Person> createDemoList() {
        final ObservableList<Person> result = FXCollections.observableArrayList();
        result.add(new Person("Paul", "McCartney"));
        result.add(new Person("Andrew Lloyd", "Webber"));
        result.add(new Person("Herb", "Alpert"));
        result.add(new Person("Emilio", "Estefan"));
        result.add(new Person("Bernie", "Taupin"));
        result.add(new Person("Elton", "John"));
        result.add(new Person("Mick", "Jagger"));
        result.add(new Person("Keith", "Richerds"));
        return result;
    }

    public final StringProperty firstnameProperty() {
        return this.firstname;
    }

    public final java.lang.String getFirstname() {
        return this.firstnameProperty().get();
    }

    public final void setFirstname(final String firstname) {
        this.firstnameProperty().set(firstname);
    }

    public final StringProperty lastnameProperty() {
        return this.lastname;
    }

    public final String getLastname() {
        return this.lastnameProperty().get();
    }

    public final void setLastname(final String lastname) {
        this.lastnameProperty().set(lastname);
    }

    public final ReadOnlyStringProperty fullNameProperty() {
        return this.fullName.getReadOnlyProperty();
    }

    public final String getFullName() {
        return this.fullNameProperty().get();
    }

    @Override
    public String toString() {
        return getFullName(); 
    }

    @Override
    public int hashCode() {
        int hash = 3;
        hash = 79 * hash + Objects.hashCode(this.firstname);
        hash = 79 * hash + Objects.hashCode(this.lastname);
        hash = 79 * hash + Objects.hashCode(this.fullName);
        return hash;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final Person other = (Person) obj;
        if (!Objects.equals(getFirstname(), other.getFirstname())) {
            return false;
        }
        if (!Objects.equals(getLastname(), other.getLastname())) {
            return false;
        }
        return Objects.equals(this.getFullName(), other.getFullName());
    }

}

音乐家

此 class 包含将使用 TextField 编辑的 StringProperty 字段和将使用 [=22= 编辑的 ListProperty<Person> 字段] 控制:

public static class Musicians {

    private final StringProperty category = new SimpleStringProperty();
    private final ListProperty<Person> persons = new SimpleListProperty<>(FXCollections.observableArrayList());

    public Musicians() { }

    public String getCategory() {
        return category.get();
    }

    public void setCategory(String category) {
        this.category.set(category);
    }

    public StringProperty categoryProperty() {
        return category;
    }

    public void setPersons(ObservableList<Person> value) {
        this.persons.set(value);
    }

    public ObservableList<Person> getPersons() {
        return persons.get();
    }

    public ListProperty<Person> personsProperty() {
        return persons;
    }
}

CustomPropertyEditorFactory

现在我们提供自己的 PropertyEditorFactory,使用与 DefaultEditorFactory 相同的文本编辑器来处理字符串字段,并添加 CheckComboBox 实现。

请注意,我们必须填充 CheckComboBox 项列表,在本例中将使用 Person.createDemoList() 完成。

public class CustomPropertyEditorFactory implements Callback<Item, PropertyEditor<?>> {

    @Override public PropertyEditor<?> call(Item item) {
        Class<?> type = item.getType();

        if (type == String.class) {
            return createTextEditor(item);  
        }

        if (type != null && type == javafx.collections.ObservableList.class) {
            return createCheckComboBoxEditor(item, Person.createDemoList());
        }

        return null; 
    }

    public final PropertyEditor<?> createTextEditor(PropertySheet.Item property) {

        return new AbstractPropertyEditor<String, TextField>(property, new TextField()) {

            @Override protected StringProperty getObservableValue() {
                return getEditor().textProperty();
            }

            @Override public void setValue(String value) {
                getEditor().setText(value);
            }
        };
    }

    public final <T> PropertyEditor<?> createCheckComboBoxEditor(PropertySheet.Item property, final Collection<T> choices) {

        return new AbstractPropertyEditor<ObservableList<T>, CheckComboBox<T>>(property, new CheckComboBox<>()) {

            private ListProperty<T> list;

            { 
                getEditor().getItems().setAll(choices);
            } 

            @Override
            protected ListProperty<T> getObservableValue() {
                if (list == null) {
                    list = new SimpleListProperty<>(getEditor().getCheckModel().getCheckedItems());
                }
                return list;
            }

            @Override
            public void setValue(ObservableList<T> checked) {
                checked.forEach(getEditor().getCheckModel()::check);
            }
        };
    }
}

最后,我们可以在我们的应用程序中使用这个自定义工厂:

@Override
public void start(Stage primaryStage) {

    PropertySheet propertySheet = new PropertySheet();

    propertySheet.setPropertyEditorFactory(new CustomPropertyEditorFactory());

    Musicians address = new Musicians();

    // 1: set initial selected values:
    address.getPersons().add(new Person("Paul", "McCartney"));

    // 2: listen to changes in selection:
    address.personsProperty().addListener((ors, ov, nv) -> {
        System.out.println("Selected persons:");
        nv.forEach(System.out::println);
    });

    propertySheet.getItems().setAll(BeanPropertyUtils.getProperties(address));

    Scene scene = new Scene(propertySheet, 500, 500);
    primaryStage.setScene(scene);
    primaryStage.show();
}