javajavafxcontrolsfxpropertysheet

Add CheckComboBox to PropertySheet JavaFX


I want to add CheckComboBox to PropertySheet in controlsfx library. Default editor contains only ComboBox implementation. Is it possible to add CheckComboBox? I tried to implement PropertyEditor with AbstractPropertyEditor but getting exception.

    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:

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$3.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$3.<init>(ComboBoxEditor.java:85)
at configurator.ComboBoxEditor.createCheckComboBoxEditor(ComboBoxEditor.java:85)
at configurator.ConfiguratorController.lambda$setPropertySheetEditors$7(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$65(PropertySheetSkin.java:140)

Solution

  • This is one possible way to do it, following your approach of implementing AbstractPropertyEditor with a CheckComboBox control.

    Based on the HelloCheckComboBox, I've created this sample, using a Person class, a Musicians bean, and the custom editor.

    Person

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

    Musicians

    This class contains a StringProperty field that will be edited with a TextField, and a ListProperty<Person> field, that will be edited with a CheckComboBox<Person> control:

    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

    Now we provide our own PropertyEditorFactory, making use of the same text editor as the DefaultEditorFactory for the String fields, and adding the CheckComboBox implementation.

    Note that we have to populate the CheckComboBox list of items, and this will be done in this case with 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);
                }
            };
        }
    }
    

    Finally, we can make use of this custom factory in our application:

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

    checkcombobox