JavaFX PropertyView mit dynamischer EditCell erstellen?

lam_tr

Top Contributor
Hallo zusammen,

ich bin dabei eine PropertyView wie in Eclipse zu implementieren. Wenn eine Selektierung irgendwo vorgenommen wurde, sollte die PropertyView das Selektierte annehemen und per Reflections alle Attribute holen und anzeigen.

Das Ganze funktioniert auch schon, man kann auch schon editieren und den Wert annehmen. Hier mein Code Snippet
Code:
tableView = new TableView<>();
        TableColumn<EAttribute, String> propertyColumn = new TableColumn<>("Property");
        TableColumn<EAttribute, String> valueColumn = new TableColumn<>("Value");

        propertyColumn.setPrefWidth(200);
        valueColumn.setPrefWidth(400);
       
        propertyColumn.setCellValueFactory(param -> new SimpleStringProperty(param.getValue().getName()));
        valueColumn.setCellFactory(TextFieldTableCell.<EAttribute>forTableColumn());
        valueColumn.setOnEditCommit(evt -> {
          // Wert wird hier gesetzt
        });
        valueColumn.setCellValueFactory(param ->{
            if (cuurentSelection==null || param.getValue()==null) {
                return new SimpleStringProperty("");
            }
            return new SimpleStringProperty(cuurentSelection.eGet(param.getValue())+"");
        });

        tableView.getColumns().add(propertyColumn);
        tableView.getColumns().add(valueColumn);

        tableView.setItems(filteredProperties);
        tableView.setEditable(true);

Mein Problem ist diese Stelle valueColumn.setCellFactory(TextFieldTableCell.<EAttribute>forTableColumn());. Meine PropertyView zeigt nur Textfeld im Editiermodus an. Wie kann ich das etwas dynamischer machen wenn ich zum Beispiel:
* Enum ->ChoiceBoxTableCell
* Boolean -> CheckBoxTableCell
* Date -> DatePickerTableCell
etc.

Oder ist da an der Stelle einfacher den cellFactory selber zu schreiben?

@dzim hast du da etwas auf Lager?

Grüße
lam
 

lam_tr

Top Contributor
Habe jetzt mal angefangen mit Custom Cell Factory, aber irgendwas mache ich noch falsch.


Code:
public class PropertyValueTableCell extends TableCell<EAttribute, String> {

    private EditingDomain editingDomain;

    public PropertyValueTableCell(EditingDomain editingDomain) {
        this.editingDomain = editingDomain;
    }

    @Override
    protected void updateItem(String item, boolean empty) {
        super.updateItem(item, empty);
        if (item == null || empty) {
            setText(null);
            setGraphic(null);
        } else {
            Object obj = getTableRow().getItem();
            if (obj instanceof EAttribute) {
                EAttribute attribute = (EAttribute) obj;
                if (attribute instanceof EEnum) {
                    setGraphic(new ComboBox<>());
                }else {
                    setGraphic(new TextField());
                }

                setText(String.valueOf(attribute.eGet(attribute)));

                if (editableProperty().get()) {
                    setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
                } else {
                    setContentDisplay(ContentDisplay.TEXT_ONLY);
                }
            }
        }
    }
}

Warum bin ich immer im Editiermodus?
 

dzim

Top Contributor
Puh. Vielleicht weil die Table grundsätzlich editable ist? Aber muss man nicht auch Columns editable machen?
Man, TableView ist auch für mich jedes mal (vor allem, wenn man es länger nicht machen musste), herausfordernd...

Kannst du irgendwie ein kleines und komplettes Beispiel zusammenfrickeln, damit man damit spielen könnte?
 

lam_tr

Top Contributor
Puh. Vielleicht weil die Table grundsätzlich editable ist? Aber muss man nicht auch Columns editable machen?
Man, TableView ist auch für mich jedes mal (vor allem, wenn man es länger nicht machen musste), herausfordernd...

Kannst du irgendwie ein kleines und komplettes Beispiel zusammenfrickeln, damit man damit spielen könnte?
Okay ich mach mal ein Prototyp und meld mich noch mal bei dir :)
 

lam_tr

Top Contributor
Okay ich mach mal ein Prototyp und meld mich noch mal bei dir :)
Hi dzim,

beim aufstellen eines Prototyps ist mir aufgefallen, wie man es machen kann.

Zuerst mal der Protoyp:
Code:
package tableview;

import java.lang.reflect.Field;

import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Callback;
import sun.font.EAttribute;

public class TableViewSample extends Application {

    private TableView<Property> table = new TableView<>();
    private final ObservableList<Person> data = FXCollections.observableArrayList(
            new Person("Jacob", "Smith", "jacob.smith@example.com", Person.Type.MAN),
            new Person("Isabella", "Johnson", "isabella.johnson@example.com",  Person.Type.BABY),
            new Person("Ethan", "Williams", "ethan.williams@example.com",  Person.Type.MAN),
            new Person("Emma", "Jones", "emma.jones@example.com",  Person.Type.WOMAN),
            new Person("Michael", "Brown", "michael.brown@example.com", Person.Type.BABY));

    private final ObservableList<Property> properties = FXCollections.observableArrayList();
   
    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage stage) {
        ListView<Person> listView = new ListView<TableViewSample.Person>();
        listView.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
        listView.setItems(data);
        listView.setCellFactory(param -> new ListCell<TableViewSample.Person>() {
            @Override
            protected void updateItem(Person item, boolean empty) {
                super.updateItem(item, empty);
                if (item==null || empty) {
                    setText(null);
                }else {
                    setText(item.getFirstName()+", "+item.getLastName());
                }
            }
        });
       
        VBox.setVgrow(listView, Priority.ALWAYS);
       
        table.setEditable(true);

        TableColumn colProperty = new TableColumn("Property");
        colProperty.setMinWidth(100);
        colProperty.setCellValueFactory(new PropertyValueFactory<Property, String>("name"));

        TableColumn<Property, String> colValue = new TableColumn("value");
        colValue.setMinWidth(100);
        colValue.setCellValueFactory(new PropertyValueFactory<Property, String>("value"));
        colValue.setCellFactory(new Callback<TableColumn<Property,String>, TableCell<Property,String>>() {
            @Override
            public TableCell<Property, String> call(TableColumn<Property, String> param) {
                return new TableCell<TableViewSample.Property, String>(){
                    protected void updateItem(String item, boolean empty) {
                        super.updateItem(item, empty);
                        if (empty || item == null) {
                            setGraphic(null);
                            setText(null);
                        }else {
                            Object obj = getTableRow().getItem();
                            if (obj instanceof Property) {
                                Property property = (Property) obj;
                                Class<?> type = property.getField().getType();
                               
                                if (type instanceOf Enum) {
                                    setGraphic(new ComboBox<>());
                                }else {
                                    setGraphic(new TextField());
                                }

                                setText(property.getValue());

                                if (editableProperty().get()) {
                                    setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
                                } else {
                                    setContentDisplay(ContentDisplay.TEXT_ONLY);
                                }
                            }
                        }
                    };
                };
            }
        });

        table.setItems(properties);
        table.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
        table.getColumns().addAll(colProperty, colValue);

        listView.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<Person>() {
            @Override
            public void changed(ObservableValue<? extends Person> observable, Person oldValue, Person newValue) {
                if (newValue!=null) {
                    properties.clear();
                    for (Field field : newValue.getClass().getDeclaredFields()) {
                        try {
                            field.setAccessible(true);
                            Object object = field.get(newValue);
                            properties.add(new Property(field.getName(), String.valueOf(object), field));
                        } catch (IllegalArgumentException | IllegalAccessException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        });
       
        final VBox vbox = new VBox();
        vbox.setSpacing(5);
        vbox.getChildren().addAll(listView, table);

        Scene scene = new Scene(vbox);
        stage.setTitle("Table View Sample");
        stage.setWidth(1200);
        stage.setHeight(800);

        stage.setScene(scene);
        stage.show();
    }

    public static class Property {
        private String name;
        private String value;
        private Field field;
        public Property(String name, String value, Field field) {
            this.name = name;
            this.value = value;
            this.field = field;
        }

        public String getName() {
            return name;
        }

        public String getValue() {
            return value;
        }

        public Field getField() {
            return field;
        }
    }

    public static class Person {

        public static enum Type{
            MAN,
            WOMAN,
            BABY
        }
       
        private final String firstName;
        private final String lastName;
        private final String email;
        private Type type;

        private Person(String fName, String lName, String email, Type type) {
            this.firstName = fName;
            this.lastName = lName;
            this.email = email;
            this.type = type;
        }

        public String getFirstName() {
            return firstName;
        }

        public String getLastName() {
            return lastName;
        }

        public String getEmail() {
            return email;
        }

        public Type getType() {
            return type;
        }
    }
}

Ich muss anstatt instanceof Enum dieses Vorgehen type.isEnum() machen, danach wird die Zelle auch wie gewünscht gerendert. Jetzt muss ich das Ganze nur noch in der Anwendung hinbekommen :)

Jetzt benötige ich an der Stelle nur noch die Hilfe wie ich von editable -> false (Text), editable -> true (Graphics) anzeigen kann. Hast du eine Idee?
 
Zuletzt bearbeitet:

lam_tr

Top Contributor
Guten morgen zusammen,

jetzt bin ich noch mal ein Schritt weitergekommen, der CellFactory ist das anscheidene. Ich muss die TableCell#startEdit überschreiben, damit das gewünschte Verhalten kommt. Mit ComboBox und TextField konnte ich über viel Copy Paste und Verwendung von gegebene Sachen wiederverwenden.

Code:
public class EmfPropertyValueTableCell extends TableCell<EAttribute, String> {

    private TextField textField;
    private ComboBox comboBox;
    private CheckBox checkBox;
    private DatePicker datePicker;

    SimpleDateFormat smp = new SimpleDateFormat("dd-MM-yyyy");

    public EmfPropertyValueTableCell() {
        if (datePicker == null) {
            createDatePicker();
        }

        Platform.runLater(new Runnable() {
            @Override
            public void run() {
                datePicker.requestFocus();
            }
        });
    }

    private void createDatePicker() {
        this.datePicker = new DatePicker();
        datePicker.setPromptText("dd-MM-yyyy");
        datePicker.setEditable(true);

        datePicker.setOnAction(new EventHandler() {
            public void handle(Event t) {
                LocalDate date = datePicker.getValue();
                int index = getIndex();

                Calendar cal = Calendar.getInstance();
                cal.set(Calendar.DAY_OF_MONTH, date.getDayOfMonth());
                cal.set(Calendar.MONTH, date.getMonthValue() - 1);
                cal.set(Calendar.YEAR, date.getYear());

                setText(smp.format(cal.getTime()));
                commitEdit(smp.format(cal.getTime()));
                setContentDisplay(ContentDisplay.TEXT_ONLY);
            }
        });

    }

    private void setDatepikerDate(String dateAsStr) {
        LocalDate ld = null;
        int year, month, day;

        year = month = day = 0;
        try {
            year = Integer.parseInt(dateAsStr.substring(0, 2));
            month = Integer.parseInt(dateAsStr.substring(3, 5));
            day = Integer.parseInt(dateAsStr.substring(6, dateAsStr.length()));
        } catch (NumberFormatException e) {
            System.out.println("setDatepikerDate / unexpected error " + e);
        }

        ld = LocalDate.of(day, month, year);
        datePicker.setValue(ld);
    }

    @Override
    protected void updateItem(String item, boolean empty) {
        super.updateItem(item, empty);
        if (empty) {
            setText(null);
            setGraphic(null);
        }else {
            Object obj = getTableRow().getItem();
            if (obj instanceof EAttribute) {
                EAttribute attribute = (EAttribute) obj;
                if (attribute.getEType() instanceof EEnum) {
                    CellUtils.updateItem(this, new DefaultStringConverter(), null, null, comboBox);
                } else if (attribute.getEType().getName().equals("EBooleanObject")
                        || attribute.getEType().getName().equals("EBoolean")) {
                    CellUtils.updateItem(this, new DefaultStringConverter(), null, null, comboBox);
//                } else if (attribute.getEType().getName().equals("EDate")) {
//                    if (isEditing()) {
//                        setContentDisplay(ContentDisplay.TEXT_ONLY);
//                    } else {
//                        if (item.equals("null")) {
//                          
//                        } else {
//                            setDatepikerDate(smp.format(item));
//                            setText(smp.format(item));
//                        }
//                        setGraphic(this.datePicker);
//                        setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
//                    }
                } else {
                    CellUtils.updateItem(this, new DefaultStringConverter(), null, null, textField);
                }
            }
        }
    }

    @Override
    public void startEdit() {
        if (!isEditable() || !getTableView().isEditable() || !getTableColumn().isEditable()) {
            return;
        }

        Object obj = getTableRow().getItem();
        if (obj instanceof EAttribute) {
            EAttribute attribute = (EAttribute) obj;
            if (attribute.getEType() instanceof EEnum) {
                EEnum enumt = (EEnum) attribute.getEType();
                if (comboBox == null) {
                    comboBox = CellUtils.createComboBox(this,
                            FXCollections.observableArrayList(
                                    enumt.getELiterals().stream().map(e -> e.getName()).collect(Collectors.toList())),
                            new SimpleObjectProperty(new DefaultStringConverter()));
                }

                comboBox.getSelectionModel().select(getItem());

                super.startEdit();
                setText(null);
                setGraphic(comboBox);
            } else if (attribute.getEType().getName().equals("EBooleanObject")
                    || attribute.getEType().getName().equals("EBoolean")) {
                if (comboBox == null) {
                    comboBox = CellUtils.createComboBox(this, FXCollections.observableArrayList("true", "false"),
                            new SimpleObjectProperty(new DefaultStringConverter()));
                }

                comboBox.getSelectionModel().select(getItem());

                super.startEdit();
                setText(null);
                setGraphic(comboBox);
//            } else if (attribute.getEType().getName().equals("EDate")) {
//                setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
//                super.startEdit();
//                setText(null);
//                setGraphic(datePicker);
            } else {
                super.startEdit();
                if (isEditing()) {
                    if (textField == null) {
                        textField = CellUtils.createTextField(this, new DefaultStringConverter());
                    }

                    CellUtils.startEdit(this, new DefaultStringConverter(), null, null, textField);
                }
            }
        }
    }

    @Override
    public void cancelEdit() {
        super.cancelEdit();

        Object obj = getTableRow().getItem();
        if (obj instanceof EAttribute) {
            EAttribute attribute = (EAttribute) obj;
            if (attribute.getEType() instanceof EEnum) {
                setText(new DefaultStringConverter().toString(getItem()));
                setGraphic(null);
//            } else if (attribute.getEType().getName().equals("EDate")) {
//                setContentDisplay(ContentDisplay.TEXT_ONLY);
            } else {
                CellUtils.cancelEdit(this, new DefaultStringConverter(), null);
            }
        }
    }
}

Für CheckBox und DatePicker musste ich selber noch anpassen.

Irgendwie kommt man erst auf Lösungswege, wenn man versucht etwas einfacheres darzustellen.

Trotzdem, danke dir!
 

dzim

Top Contributor
Hahaha... Ok. Gut, dass du selbst weitergekommen bist! Ich hab das mit den Enums seinerzeit über ein Interface gelöst (class.isAssignableOf(Interface.class) oder so ähnlich). Da wusste ich was kommt und konnte Übersetzungen, Bilder etc. darstellen. Also primär weil ich wusste, dass das Interface einfach nur in dem Umfeld verwendet wird.
Was den edit-Modus angeht. Musst du da nicht auch commitEdit eventuell überschreiben? Ich müsste auch erst mal durch ein komplexes Programm bei uns gehen und es raussuchen... Im Moment mache ich kaum JavaFX sondern fast nur Flutter/Dart, Kotlin/Android und ein wenig Swift/iOS... :rolleyes:
 

lam_tr

Top Contributor
Hahaha... Ok. Gut, dass du selbst weitergekommen bist! Ich hab das mit den Enums seinerzeit über ein Interface gelöst (class.isAssignableOf(Interface.class) oder so ähnlich). Da wusste ich was kommt und konnte Übersetzungen, Bilder etc. darstellen. Also primär weil ich wusste, dass das Interface einfach nur in dem Umfeld verwendet wird.
Was den edit-Modus angeht. Musst du da nicht auch commitEdit eventuell überschreiben? Ich müsste auch erst mal durch ein komplexes Programm bei uns gehen und es raussuchen... Im Moment mache ich kaum JavaFX sondern fast nur Flutter/Dart, Kotlin/Android und ein wenig Swift/iOS... :rolleyes:
Ihr macht echt coole Sachen, zuerst viel JavaFX und jetzt auch noch Flutter 👍
 

dzim

Top Contributor
Glaub mir: Von der API her ist mir JavaFX mit Kotlin lieber als Flutter. Aber das ist jammern auf hohem Niveau.

Und ja, es ist cool, wenn man einfach mal festlegen kann, mit welcher Technologie man arbeiten möchte und einem niemanden mit "aber nativ", "was ist mit React.Native", "aber ionic ist doch auch super", oder dazwischenquatscht! 🤣
 

Ähnliche Java Themen

Neue Themen


Oben