首页 文章

JavaFX:没有JavaFX样式属性的可编辑TableView

提问于
浏览
3

在阅读了大量涉及setOnEditCommit的可编辑TableView解决方案之后,我今天对Oracle非常生气,这不是正确的方法 .

以下是我在挖掘JavaFX源代码后发现的更好,更简单的解决方案:

2 回答

  • 3

    我认为您的解决方案似乎比使用 setOnEditCommit 复杂得多 . 例如(使用Oracle使用的常用联系表类型示例),给定标准 Person JavaBean:

    public class Person {
        private String firstName ;
        private String lastName ;
    
        public Person(String firstName, String lastName) {
            this.firstName = firstName;
            this.lastName = lastName;
        }
        public String getFirstName() {
            return firstName;
        }
        public void setFirstName(String firstName) {
            this.firstName = firstName;
        }
        public String getLastName() {
            return lastName;
        }
        public void setLastName(String lastName) {
            this.lastName = lastName;
        }
    
        @Override
        public String toString() {
            return firstName + " " + lastName ;
        }
    }
    

    此代码创建一个可更新Java bean的可编辑表:

    import javafx.application.Application;
    import javafx.beans.property.ReadOnlyStringWrapper;
    import javafx.scene.Scene;
    import javafx.scene.control.Button;
    import javafx.scene.control.TableColumn;
    import javafx.scene.control.TableView;
    import javafx.scene.control.cell.TextFieldTableCell;
    import javafx.scene.layout.BorderPane;
    import javafx.scene.layout.HBox;
    import javafx.stage.Stage;
    
    
    public class Main extends Application {
        @Override
        public void start(Stage primaryStage) {
            BorderPane root = new BorderPane();
            TableView<Person> table = new TableView<>();
    
            TableColumn<Person, String> firstNameCol = new TableColumn<>("First Name");
    
            firstNameCol.setCellFactory(TextFieldTableCell.forTableColumn());
            firstNameCol.setOnEditCommit(
                    event -> event.getRowValue().setFirstName(event.getNewValue()));
            firstNameCol.setCellValueFactory(data -> new ReadOnlyStringWrapper(data.getValue().getFirstName()));
    
            TableColumn<Person, String> lastNameCol = new TableColumn<>("Last Name");
            lastNameCol.setCellFactory(TextFieldTableCell.forTableColumn());
            lastNameCol.setOnEditCommit(
                    event -> event.getRowValue().setLastName(event.getNewValue()));
            lastNameCol.setCellValueFactory(data -> new ReadOnlyStringWrapper(data.getValue().getLastName()));
    
            table.getColumns().addAll(firstNameCol, lastNameCol);
            table.setEditable(true);
    
            Button button = new Button("Show data");
            button.setOnAction(event -> table.getItems().forEach(System.out::println));
    
            HBox controls = new HBox(5, button);
    
            root.setCenter(table);
            root.setBottom(controls);
    
            table.getItems().addAll(
                    new Person("Jacob", "Smith"),
                    new Person("Isabella", "Johnson"),
                    new Person("Ethan", "Williams"),
                    new Person("Emma", "Jones"),
                    new Person("Michael", "Brown")
            );
    
            Scene scene = new Scene(root, 600, 400);
            primaryStage.setScene(scene);
            primaryStage.show();
        }
    
        public static void main(String[] args) {
            launch(args);
        }
    }
    

    此代码还避免了任何非公共API类 . 我同意构建器类更优雅,但它们已经deprecated for good reason .

    此外,与 WritablePropertyValueFactory 类(或 PropertyValueFactory API类)不同,这也避免了反射,因此它可能表现更好 .

  • 0

    要创建TableView:

    BorderPaneBuilder.create()
        .top(ToolBarBuilder.create()
            .items(ButtonBuilder.create()
                .text("Add New Row")
                .onAction(e -> backendItemList.add(new MyItem("<NEW>", "<NEW>")))
                .build())
            .build())
        .center(
            TableViewBuilder.<CustomColumnNameMapping> create()
                .items(backendItemList)
                .editable(true)
                .columns(
                    TableColumnBuilder.<CustomColumnNameMapping, String> create()
                        .text("Column1 for property1")
                        .cellValueFactory(new WritablePropertyValueFactory<>("property1"))
                        .cellFactory(TextFieldTableCell.forTableColumn())
                        .editable(true)
                        .build(),
                    TableColumnBuilder.<CustomColumnNameMapping, String> create()
                        .text("Column2 for property2")
                        .cellValueFactory(new WritablePropertyValueFactory<>("property2"))
                        .cellFactory(TextFieldTableCell.forTableColumn())
                        .editable(true)
                        .build())
                .build())
        .build();
    

    WritablePropertyValueFactory.java:

    import javafx.beans.NamedArg;
    import javafx.beans.value.ObservableValue;
    import javafx.scene.control.TableColumn.CellDataFeatures;
    import javafx.scene.control.cell.PropertyValueFactory;
    import javafx.util.Callback;
    import sun.util.logging.PlatformLogger;
    import sun.util.logging.PlatformLogger.Level;
    
    import com.sun.javafx.property.PropertyReference;
    import com.sun.javafx.scene.control.Logging;
    
    // Original code from PropertyValueFactory
    // Replacing ReadOnlyObjectWrapper with new ReadableWritableObservableValue
    public class WritablePropertyValueFactory<S, T> implements Callback<CellDataFeatures<S, T>, ObservableValue<T>>
    {
        private final String property;
    
        private Class<?> columnClass;
        private String previousProperty;
        private PropertyReference<T> propertyRef;
    
        public WritablePropertyValueFactory(@NamedArg("property") String property)
        {
            this.property = property;
        }
    
        @Override
        @SuppressWarnings("unchecked")
        public ObservableValue<T> call(CellDataFeatures<S, T> param)
        {
            return getCellDataReflectively((T) param.getValue());
        }
    
        public final String getProperty()
        {
            return this.property;
        }
    
        private ObservableValue<T> getCellDataReflectively(T rowData)
        {
            if (getProperty() == null || getProperty().isEmpty() || rowData == null)
                return null;
            try
            {
                if (this.columnClass == null || this.previousProperty == null ||
                        !this.columnClass.equals(rowData.getClass()) ||
                        !this.previousProperty.equals(getProperty()))
                {
                    this.columnClass = rowData.getClass();
                    this.previousProperty = getProperty();
                    this.propertyRef = new PropertyReference<T>(rowData.getClass(), getProperty());
                }
                if (this.propertyRef.hasProperty())
                {
                    return this.propertyRef.getProperty(rowData);
                }
                else
                {
                    // Create ReadableWritableObservableValue instead of ReadOnlyObjectWrapper
                    return new ReadableWritableObservableValue<T>(
                            () -> this.propertyRef.get(rowData),
                            (value) -> this.propertyRef.set(rowData, value));
                }
            }
            catch (IllegalStateException e)
            {
                final PlatformLogger logger = Logging.getControlsLogger();
                if (logger.isLoggable(Level.WARNING))
                {
                    logger.finest("Can not retrieve property '" + getProperty() +
                            "' in PropertyValueFactory: " + this +
                            " with provided class type: " + rowData.getClass(), e);
                }
            }
            return null;
        }
    }
    

    ReadableWritableObservableValue.java

    import java.util.function.Consumer;
    import java.util.function.Supplier;
    
    import javafx.beans.InvalidationListener;
    import javafx.beans.value.ChangeListener;
    import javafx.beans.value.ObservableValue;
    import javafx.beans.value.WritableValue;
    
    public class ReadableWritableObservableValue<T> implements ObservableValue<T>, WritableValue<T>
    {
        protected final Supplier<T> getter;
        protected final Consumer<T> setter;
    
        public ReadableWritableObservableValue(Supplier<T> getter, Consumer<T> setter)
        {
            this.getter = getter;
            this.setter = setter;
        }
    
        @Override
        public void addListener(InvalidationListener listener)
        {
            // useless (no property to listen)
        }
    
        @Override
        public void removeListener(InvalidationListener listener)
        {
            // useless (no property to listen)
        }
    
        @Override
        public void addListener(ChangeListener<? super T> listener)
        {
            // useless (no property to listen)
        }
    
        @Override
        public void removeListener(ChangeListener<? super T> listener)
        {
            // useless (no property to listen)
        }
    
        @Override
        public T getValue()
        {
            return this.getter.get();
        }
    
        @Override
        public void setValue(T value)
        {
            this.setter.accept(value);
        }
    }
    

    PS:关键是从回调中返回WritableValue,参见TableColumn#DEFAULT_EDIT_COMMIT_HANDLER .

相关问题