【问题标题】:JavaFX 2 TableView how to update cell when object is changedJavaFX 2 TableView如何在对象更改时更新单元格
【发布时间】:2014-04-04 14:45:58
【问题描述】:

我正在创建一个 TableView 来显示有关自定义对象 (EntityEvent) 列表的信息。

表格视图必须有 2 列。 第一列显示相应 EntityEvent 的名称。 第二列将显示一个按钮。按钮文本取决于 EntityEvent 的属性。如果属性为零,则为“创建”,否则为“编辑”。

我设法把这一切都做得很好,只是当相应的 EntityEvent 对象发生更改时,我找不到更新 TableView 行的方法。

非常重要:我无法更改 EntityEvent 类以使用 JavaFX 属性,因为它们不在我的控制之下。此类使用 PropertyChangeSupport 在被监控的属性发生更改时通知侦听器。

注意: 我意识到向 List 添加新元素可能会导致 TableView 重新绘制自身,但这不是我所需要的。我说可能是因为我已经阅读了一些影响这种行为的错误。 我尝试使用这种方法来强制重绘,但我无法让它工作。

有人知道怎么做吗?

非常感谢。

下面是一个简化的代码示例来说明该场景:

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;

import javafx.application.Application;
import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableColumn.CellDataFeatures;
import javafx.scene.control.TableView;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.util.Callback;

public class Main extends Application {

    //=============================================================================================
    public class EntityEvent {
        private String m_Name;
        private PropertyChangeSupport m_NamePCS = new PropertyChangeSupport(this);
        private int m_ActionCounter;
        private PropertyChangeSupport m_ActionCounterPCS = new PropertyChangeSupport(this);

        public EntityEvent(String name, int actionCounter) {
            m_Name = name;
            m_ActionCounter = actionCounter;
        }

        public String getName() {
            return m_Name;
        }

        public void setName(String name) {
            String lastName = m_Name;
            m_Name = name;
            System.out.println("Name changed: " + lastName + " -> " + m_Name);
            m_NamePCS.firePropertyChange("Name", lastName, m_Name);
        }

        public void addNameChangeListener(PropertyChangeListener listener) {
            m_NamePCS.addPropertyChangeListener(listener);
        }   

        public int getActionCounter() {
            return m_ActionCounter;
        }

        public void setActionCounter(int actionCounter) {
            int lastActionCounter = m_ActionCounter;
            m_ActionCounter = actionCounter;
            System.out.println(m_Name + ": ActionCounter changed: " + lastActionCounter + " -> " + m_ActionCounter);
            m_ActionCounterPCS.firePropertyChange("ActionCounter", lastActionCounter, m_ActionCounter);
        }

        public void addActionCounterChangeListener(PropertyChangeListener listener) {
            m_ActionCounterPCS.addPropertyChangeListener(listener);
        }   
    }

    //=============================================================================================
    private class AddPersonCell extends TableCell<EntityEvent, String> {
        Button m_Button = new Button("Undefined");
        StackPane m_Padded = new StackPane();

        AddPersonCell(final TableView<EntityEvent> table) {
            m_Padded.setPadding(new Insets(3));
            m_Padded.getChildren().add(m_Button);

            m_Button.setOnAction(new EventHandler<ActionEvent>() {
                @Override public void handle(ActionEvent actionEvent) {
                    // Do something
                }
            });
        }

        @Override protected void updateItem(String item, boolean empty) {
            super.updateItem(item, empty);
            if (!empty) {
                setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
                setGraphic(m_Padded);
                m_Button.setText(item);
            }
        }
    }

    //=============================================================================================
    private ObservableList<EntityEvent> m_EventList;

    //=============================================================================================
    @SuppressWarnings("unchecked")
    @Override
    public void start(Stage primaryStage) {
        primaryStage.setTitle("Table View test.");

        VBox container = new VBox();

        m_EventList = FXCollections.observableArrayList(
                new EntityEvent("Event 1", -1),
                new EntityEvent("Event 2", 0),
                new EntityEvent("Event 3", 1)
                );
        final TableView<EntityEvent> table = new TableView<EntityEvent>();
        table.setItems(m_EventList);            

        TableColumn<EntityEvent, String> eventsColumn = new TableColumn<>("Events");
        TableColumn<EntityEvent, String> actionCol = new TableColumn<>("Actions");
        actionCol.setSortable(false);

        eventsColumn.setCellValueFactory(new Callback<CellDataFeatures<EntityEvent, String>, ObservableValue<String>>() {
            public ObservableValue<String> call(CellDataFeatures<EntityEvent, String> p) {
                EntityEvent event = p.getValue();
                event.addActionCounterChangeListener(new PropertyChangeListener() {
                    @Override
                    public void propertyChange(PropertyChangeEvent event) {
                        // TODO: I'd like to update the table cell information.
                    }
                });
                return new ReadOnlyStringWrapper(event.getName());
            }
        });

        actionCol.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<EntityEvent, String>, ObservableValue<String>>() {
            @Override
            public ObservableValue<String> call(TableColumn.CellDataFeatures<EntityEvent, String> ev) {
                String text = "NONE";
                if(ev.getValue() != null) {
                    text = (ev.getValue().getActionCounter() != 0) ? "Edit" : "Create";
                }
                return new ReadOnlyStringWrapper(text);
            }
        });

        // create a cell value factory with an add button for each row in the table.
        actionCol.setCellFactory(new Callback<TableColumn<EntityEvent, String>, TableCell<EntityEvent, String>>() {
            @Override
            public TableCell<EntityEvent, String> call(TableColumn<EntityEvent, String> personBooleanTableColumn) {
                return new AddPersonCell(table);
            }
        });

        table.getColumns().setAll(eventsColumn, actionCol);
        table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);

        // Add Resources Button
        Button btnInc = new Button("+");
        btnInc.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent ev) {
                System.out.println("+ clicked.");
                EntityEvent entityEvent = table.getSelectionModel().getSelectedItem();
                if (entityEvent == null) {
                    System.out.println("No Event selected.");
                    return;
                }
                entityEvent.setActionCounter(entityEvent.getActionCounter() + 1);
                // TODO: I expected the TableView to be updated since I modified the object.
            }
        });

        // Add Resources Button
        Button btnDec = new Button("-");
        btnDec.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent ev) {
                System.out.println("- clicked.");
                EntityEvent entityEvent = table.getSelectionModel().getSelectedItem();
                if (entityEvent == null) {
                    System.out.println("No Event selected.");
                    return;
                }
                entityEvent.setActionCounter(entityEvent.getActionCounter() - 1);
                // TODO: I expected the TableView to be updated since I modified the object.
            }
        });

        container.getChildren().add(table);
        container.getChildren().add(btnInc);
        container.getChildren().add(btnDec);

        Scene scene = new Scene(container, 300, 600, Color.WHITE);        

        primaryStage.setScene(scene);

        primaryStage.show();
    }

    //=============================================================================================
    public Main() {
    }

    //=============================================================================================
    public static void main(String[] args) {
        launch(Main.class, args);
    }

}

【问题讨论】:

    标签: javafx-2


    【解决方案1】:

    试试javafx.beans.property.adapter 类,尤其是JavaBeanStringProperty 和JavaBeanIntegerProperty。我没有用过这些,但我认为你可以做类似的事情

    TableColumn<EntityEvent, Integer> actionCol = new TableColumn<>("Actions");
    actionCol.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<EntityEvent, Integer> ev) {
        return new JavaBeanIntegerPropertyBuilder().bean(ev.getValue()).name("actionCounter").build();
    });
    
    // ...
    
    public class AddPersonCell extends TableCell<EntityEvent, Integer>() {
        final Button button = new Button();
    
        public AddPersonCell() {
            setPadding(new Insets(3));
            setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
            button.setOnAction(...);
        }
    
        @Override
        public void updateItem(Integer actionCounter, boolean empty) {
            if (empty) {
                setGraphic(null);
            } else {
                if (actionCounter.intValue()==0) {
                    button.setText("Create");
                } else {
                    button.setText("Add");
                }
                setGraphic(button);
            }
        }
    }
    

    正如我所说,我没有使用 Java bean 属性适配器类,但想法是它们将属性更改事件“转换”为 JavaFX 更改事件。我只是在这里输入了这个没有测试,但它至少应该给你一些开始。

    更新:经过一些试验,如果您的 EntityEvent 真的按照您在代码示例中显示的方式设置,我认为这种方法不会起作用。标准的 Java bean 绑定属性模式(JavaFX 属性适配器所依赖的)具有单个属性更改侦听器和一个 addPropertyChangeListener(...) 方法。 (侦听器可以查询事件以查看更改了哪个属性。)

    我想如果你这样做

    public class EntityEvent {
        private String m_Name;
        private PropertyChangeSupport pcs = new PropertyChangeSupport(this);
        private int m_ActionCounter;
    
        public EntityEvent(String name, int actionCounter) {
            m_Name = name;
            m_ActionCounter = actionCounter;
        }
    
        public String getName() {
            return m_Name;
        }
    
        public void setName(String name) {
            String lastName = m_Name;
            m_Name = name;
            System.out.println("Name changed: " + lastName + " -> " + m_Name);
            pcs.firePropertyChange("name", lastName, m_Name);
        }
    
        public void addPropertyChangeListener(PropertyChangeListener listener) {
            pcs.addPropertyChangeListener(listener);
        }   
    
        public void removePropertyChangeListener(PropertyChangeListener listener) {
            pcs.removePropertyChangeListener(listener);
        }
    
        public int getActionCounter() {
            return m_ActionCounter;
        }
    
        public void setActionCounter(int actionCounter) {
            int lastActionCounter = m_ActionCounter;
            m_ActionCounter = actionCounter;
            System.out.println(m_Name + ": ActionCounter changed: " + lastActionCounter + " -> " + m_ActionCounter);
            pcs.firePropertyChange("ActionCounter", lastActionCounter, m_ActionCounter);
        }
    
    }
    

    它将与上面的适配器类一起使用。显然,如果您有调用 addActionChangeListener 和 addNameChangeListener 方法的现有代码,您会希望保留这些现有方法和现有属性更改侦听器,但我认为您没有理由不能同时拥有这两者。

    【讨论】:

    • 其实EntityEvent是可以改变的。但是我不能向其中添加 JavaFX 属性。它必须独立于 UI 域。我要做几个测试。如果有什么我会告诉你的。
    • 由于 JavaFX 现在是核心 JDK 的一部分,并且 JavaFX 属性不依赖于任何本机 UI 资源(即它们以无头模式运行),您可以相当有说服力地争辩说引入 JavaFX 属性是UI 域独立更改。
    • 我同意你的观点,但是这次我不能将 JavaFX 属性添加到 EntityEvent 类中。我正在做一些测试,很快就会回到这里。非常感谢。
    • 我无法让它工作。该表根本没有更新。你确定这会强制重新粉刷桌子吗?
    • 很奇怪!我的代码不起作用,而你的代码起作用了。可能这里有一些小错误。这完美地解决了我的问题。再次非常感谢您。
    猜你喜欢
    • 2018-10-24
    • 1970-01-01
    • 2012-09-24
    • 1970-01-01
    • 2023-03-28
    • 2019-02-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多