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