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

时间:2014-08-10 14:47:03

标签: java javafx tableview

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

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

2 个答案:

答案 0 :(得分:4)

我认为您的解决方案似乎比使用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类)不同,这也避免了反射,因此它可能表现得更好。

答案 1 :(得分: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。