如何使自定义 TableColumn 显示单元格可观察?

问题描述 投票:0回答:1

我想实现一个解决方案,避免使用 PropertyValueFactory,并且还能够在 TableCell updateItem 方法中引用所有类的属性,而不仅仅是绑定属性显示值。

注:

我专门将 col_2 声明为 public TableColumnmyclass> col_2;

这允许类的所有属性在单元格 updateItem 方法中可用。 正如您将看到的,单元格 2 的显示输出是 2 个属性的组合。

按下设置按钮会显示此内容

按下 GO 按钮会更改 List origlist 的 fld2 属性值。

按下中断按钮可向控制台确认 List origlist 中的值已更改。

但是,更改不会传播到显示屏。

使用 PropertyValueFactory 设置的列会更新。

总而言之,第 2 列所做的事情除了无法观察到绑定数据的变化之外。

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>

<VBox alignment="CENTER" prefHeight="473.0" prefWidth="923.0" spacing="20.0" xmlns="http://javafx.com/javafx/17.0.2-ea" xmlns:fx="http://javafx.com/fxml/1" fx:controller="org.test.tableview.HelloController">
    <padding>
        <Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
    </padding>
   <TableView fx:id="TableView1" editable="true" prefHeight="200.0" prefWidth="400.0">
     <columns>
        <TableColumn fx:id="col_1" prefWidth="200.0" text="C1" />
        <TableColumn fx:id="col_2" prefWidth="200.0" text="C2" />
        <TableColumn fx:id="col_3" prefWidth="200.0" text="C3" />
        <TableColumn fx:id="col_4" prefWidth="200.0" text="C4" />
     </columns>
   </TableView>

    <Button onAction="#onsetup" text="setup" />
    <Button onAction="#ongoClick" text="GO" />
    <Button onAction="#onbreak" text="break" />
</VBox>

package org.test.tableview;

import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;

public class myclass
{
    private IntegerProperty fld1 = new SimpleIntegerProperty();
    private StringProperty fld2 = new SimpleStringProperty();
    private StringProperty fld3 = new SimpleStringProperty();
    private StringProperty fld4 = new SimpleStringProperty();

    public myclass(Integer fld1,String fld2,String fld3,String fld4)
    {
        this.fld1.set(fld1);
        this.fld2.set(fld2);
        this.fld3.set(fld3);
        this.fld4.set(fld4);
    }

    // Getter methods
    public IntegerProperty fld1Property() {
        return fld1;
    }

    public StringProperty fld2Property() {
        return fld2;
    }

    public StringProperty fld3Property() {
        return fld3;
    }

    public StringProperty fld4Property() {
        return fld4;
    }
}
package org.test.tableview;

import javafx.beans.InvalidationListener;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;

import java.util.ArrayList;
import java.util.List;

public class HelloController
{
    public TableView<myclass> TableView1;
    public TableColumn<myclass,Integer> col_1;
    public TableColumn<myclass,myclass>  col_2;
    public TableColumn<myclass,String>  col_3;
    public TableColumn<myclass,String>  col_4;

    List<myclass> origlist = new ArrayList<>();


    public HelloController()
    {
        init();
    }

    private void init()
    {
        origlist.clear();
        myclass m = new myclass(1,"A1","B1","C1");
        origlist.add(m);
        m= new myclass(2,"A2","B2","C2");
        origlist.add(m);
        m= new myclass(3,"A3","B3","C3");
        origlist.add(m);
        m= new myclass(4,"A4","B4","C4");
        origlist.add(m);
    }

    @FXML
    public void onsetup(ActionEvent actionEvent)
    {
        init();

        col_1.setCellValueFactory(new PropertyValueFactory<>("fld1"));

        col_2.setCellValueFactory(data -> new ObservableValue<>()
        {
            @Override
            public void addListener(ChangeListener<? super myclass> listener) {}

            @Override
            public void removeListener(ChangeListener<? super myclass> listener) {}

            @Override
            public myclass getValue()
            {
                return data.getValue();
            }

            @Override
            public void addListener(InvalidationListener listener) {}

            @Override
            public void removeListener(InvalidationListener listener) {

            }
        });

        col_3.setCellValueFactory(new PropertyValueFactory<>("fld3"));
        col_4.setCellValueFactory(new PropertyValueFactory<>("fld4"));

        col_2.setCellFactory(p ->
        {
            TableCell<myclass,myclass> cell = new TableCell<>()
            {
                @Override
                protected void updateItem(myclass item, boolean empty)
                {
                    if (item != null)
                    {
                        Label l = new Label();
                        l.setText("combination display " + item.fld2Property().getValue() + " " + item.fld4Property().getValue());
                        setGraphic(l);
                    }
                    else
                    {
                        setGraphic(null);
                        setText(null);
                    }
                }
            };
            return cell;
        });

        final ObservableList<myclass> olpc = FXCollections.observableArrayList();

        olpc.addAll(origlist.stream().toList());

        TableView1.setItems(olpc);
    }

    @FXML
    public void ongoClick(ActionEvent actionEvent)
    {
        origlist.get(0).fld2Property().setValue("Z");
        origlist.get(0).fld4Property().setValue("ZZZ");
    }

    @FXML
    public void onbreak(ActionEvent actionEvent)
    {
        System.out.println(origlist.get(0).fld2Property().get());
    }


}
java javafx observable tableview tablecolumn
1个回答
0
投票

要将OP下的评论压缩为答案,实际上有三种方法可以创建一个表列,该列显示的值取决于模型类中的多个其他值

  1. 创建与要显示的复合值相对应的可观察值。这可能并不像您想象的那么具有侵入性;如果您有一个现有的模型类,您可以随时为其创建一个包装器,仅用于您的表(如果需要)。
  2. 在单元格值工厂中创建一个绑定,生成所需的复合值。
  3. 使用整个表格行模型作为单元格的值,并在单元格工厂创建的单元格中显示复合值。为了确保单元保持更新,请在单元实现中使用绑定。

要进行演示,请从标准 Oracle 文档中表示例的变体开始:

表格行模型类:

package org.jamesd.example.compositecell;

import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;

public class Person {
    private final StringProperty firstName = new SimpleStringProperty();
    private final StringProperty lastName = new SimpleStringProperty();

    public Person(String firstName, String lastName) {
        this.firstName.set(firstName);
        this.lastName.set(lastName);
    }

    public StringProperty firstNameProperty() {
        return firstName;
    }

    public StringProperty lastNameProperty() {
        return lastName;
    }
}

和应用程序:

package org.jamesd.example.compositecell;

import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;

public class App extends Application {
    @Override
    public void start(Stage stage) throws Exception {
        TableView<Person> table = new TableView<>();
        table.setEditable(true);
        TableColumn<Person, String> firstNameColumn = new TableColumn<>("First Name");
        firstNameColumn.setCellValueFactory(data -> data.getValue().firstNameProperty());
        firstNameColumn.setCellFactory(TextFieldTableCell.forTableColumn());
        TableColumn<Person, String> lastNameColumn = new TableColumn<>("Last Name");
        lastNameColumn.setCellValueFactory(data -> data.getValue().lastNameProperty());
        lastNameColumn.setCellFactory(TextFieldTableCell.forTableColumn());

        /* TODO:
        Create full name column
         */

        table.getColumns().add(firstNameColumn);
        table.getColumns().add(lastNameColumn);
//        table.getColumns().add(fullNameColumn);

        ObservableList<Person> data = createPersonList();
        populateData(data);
        table.setItems(data);

        BorderPane root = new BorderPane(table);
        Scene scene = new Scene(root);
        stage.setScene(scene);
        stage.show();
    }

    private ObservableList<Person> createPersonList() {
        return FXCollections.observableArrayList();
    }

    private void populateData(ObservableList<Person> personList) {
        personList.addAll(
                new Person("Jacob", "Smith"),
                new Person("Isabella", "Johnson"),
                new Person("Ethan", "Williams"),
                new Person("Emma", "Jones"),
                new Person("Michael", "Brown")
        );
    }

    public static void main(String[] args) {
        Application.launch(args);
    }
}

对于第一个选项,我们可以向模型添加一个新的只读属性,以获取全名:

public class Person {

    // existing code...

    private final ReadOnlyStringWrapper fullName = new ReadOnlyStringWrapper();

    public Person(String firstName, String lastName) {
        this.firstName.set(firstName);
        this.lastName.set(lastName);
        fullName.bind(this.firstName.concat(" ").concat(this.lastName));
    }
    
    public ReadOnlyStringProperty fullNameProperty() {
        return fullName.getReadOnlyProperty();
    }

    // existing code
}

然后像往常一样创建全名列:

        /*
        Create full name column
         */
        TableColumn<Person, String> fullNameColumn = new TableColumn<>("Full Name");
        fullNameColumn.setCellValueFactory(data -> data.getValue().fullNameProperty());

对于第二个选项,保留

Person
类的原始形式,并使用创建适当绑定的单元格值工厂:

        /*
        Create full name column
         */
        TableColumn<Person, String> fullNameColumn = new TableColumn<>("Full Name");
        fullNameColumn.setCellValueFactory(data -> {
            Person person = data.getValue();
            return person.firstNameProperty().concat(" ").concat(person.lastNameProperty());
        });

或者(也许更普遍):

        /*
        Create full name column
         */
        TableColumn<Person, String> fullNameColumn = new TableColumn<>("Full Name");
        fullNameColumn.setCellValueFactory(data -> {
            Person person = data.getValue();
            return Bindings.createStringBinding(
                () -> person.firstNameProperty().get() + " " + person.lastNameProperty().get(),
                person.firstNameProperty(),
                person.lastNameProperty()
            );
        });

对于选项 3,再次使用原始

Person
类,并将复合列创建为:

        /*
        Create full name column
         */
        TableColumn<Person, Person> fullNameColumn = new TableColumn<>("Full Name");
        fullNameColumn.setCellValueFactory(data -> new SimpleObjectProperty<>(data.getValue()));
        fullNameColumn.setCellFactory(tc ->  new TableCell<>() {
            // not sure you need a label here, but to match OP:
            private final Label label = new Label();
            @Override
            protected void updateItem(Person person, boolean empty) {
                super.updateItem(person, empty);
                if (empty || person == null) {
                    label.textProperty().unbind();
                    setGraphic(null);
                } else {
                    label.textProperty().bind(
                            person.firstNameProperty().concat(" ").concat(person.lastNameProperty())
                    );
                    setGraphic(label);
                }
            }
        });

这里的要点是,当各个属性发生变化时,不会调用

updateItem()
方法。要强制更新标签的文本(您可以直接使用单元格的
textProperty()
执行相同操作),您需要将其显式绑定到其他属性。

© www.soinside.com 2019 - 2024. All rights reserved.