【问题标题】:Can't get JavaFX Tableview with flashing cell background on update to work properly更新时单元格背景闪烁的 JavaFX Tableview 无法正常工作
【发布时间】:2020-07-11 16:51:30
【问题描述】:

我正在学习 JavaFX,现在正在研究 TableView。我想将股票价格放在表格中,并在更新时闪烁/闪烁单元格背景。

我进行了很多搜索,并看到了一些建议,但是无法找到我要查找的内容。此外,我似乎无法理解 TableView 的底层逻辑,特别是单元格的创建和更新时间和方式。

考虑下面的代码(大部分是从http://jaakkola.net/juhani/blog/?p=233借来的):

public class FlashingTableCell<S, T> extends TableCell<S, T> {

    private static final Color INCREASE_HIGHLIGHT_COLOR = Color.rgb(0, 255, 0, 0.8);
    private static final Color DECREASE_HIGHLIGHT_COLOR = Color.rgb(255, 0, 0, 0.8);
    private static final Color HIGHLIGHT_COLOR = Color.rgb(0, 255, 0, 0.8);
    private static final Duration HIGHLIGHT_TIME = Duration.millis(600);

    private final Background bgIncrease = new Background(new BackgroundFill(INCREASE_HIGHLIGHT_COLOR, CornerRadii.EMPTY, Insets.EMPTY));
    private final Background bgDecrease = new Background(new BackgroundFill(DECREASE_HIGHLIGHT_COLOR, CornerRadii.EMPTY, Insets.EMPTY));
    private final Background bgChange = new Background(new BackgroundFill(HIGHLIGHT_COLOR, CornerRadii.EMPTY, Insets.EMPTY));

    private final BorderPane background = new BorderPane();
    private final Label lblText = new Label("");
    private final FadeTransition animation = new FadeTransition(HIGHLIGHT_TIME, background);

    private final StackPane container = new StackPane();

    private T prevValue;
    private S prevItem;

    final private Comparator<T> comparator;


    public FlashingTableCell(Comparator<T> comparator, Pos alignment) {
        super();
        this.comparator = comparator;

        lblText.textProperty().bindBidirectional(textProperty());
        this.setContentDisplay(ContentDisplay.GRAPHIC_ONLY);

        setPadding(Insets.EMPTY);
        container.getChildren().addAll(background, lblText);
        container.setAlignment(alignment);
        setGraphic(container);
    }

    @Override
    protected void updateItem(T value, boolean empty) {
        super.updateItem(value, empty);

        System.out.println("updateItem " + this.hashCode() + " " + getIndex() + " value=" + value + " (" + prevValue + ")" + empty);

        S currentItem = getTableRow() != null && getTableRow().getItem() != null ? (S) getTableRow().getItem() : null;

        /*
         * We check that the value has been updated and that the row model/item
         * under the cell is the same. JavaFX table reuses cells so item is not
         * always the same!
         */
        boolean valueChanged = (prevValue == null && value != null) || (value != null && (prevValue.hashCode() != value.hashCode()));
        boolean sameItem = currentItem != null && prevItem != null && currentItem == prevItem;

        if (valueChanged && sameItem) {

            if (comparator != null) {
                int compare = comparator.compare(value, prevValue);
                if (compare > 0) {
                    background.setBackground(bgIncrease);
                } else if (compare < 0) {
                    background.setBackground(bgDecrease);
                }
            } else {
                background.setBackground(bgChange);
            }

            lblText.setText(String.format("%1.2f", value));

            animation.setFromValue(1);
            animation.setToValue(0);
            animation.setCycleCount(1);
            animation.setAutoReverse(false);
            animation.playFromStart();
        }

        prevValue = value;
        prevItem = currentItem;
    }
}

还有这个细胞工厂:

public class FlashingTableCellFactory<S, T> implements Callback<TableColumn<S, T>, TableCell<S, T>> {
    @Override
    public TableCell<S, T> call(TableColumn<S, T> p) {
        System.out.println("************** CREATING FLASHING TABLE CELL **************");
        FlashingTableCell<S,T> cell = new FlashingTableCell<S,T>(null, Pos.CENTER);
        return cell;
    }
}

我是这样使用它的:

public class Main extends Application {

    Timer timer = new Timer();

    @Override
    public void start(Stage primaryStage) {

        TableView<InstrumentPrice> table = new TableView<>();
        ObservableList<InstrumentPrice> data = getInitialTableData();
        table.setItems(data);

        TableColumn<InstrumentPrice, String> nameCol = new TableColumn<>("Name");
        nameCol.setCellValueFactory(new PropertyValueFactory<>("instrumentName"));

        TableColumn<InstrumentPrice, Double> openCol = new TableColumn<>("Open");
        openCol.setCellValueFactory(new PropertyValueFactory("open"));
        // Flashing table cell
        FlashingTableCellFactory<tabletest1.InstrumentPrice, Double> ftc2 = new FlashingTableCellFactory<tabletest1.InstrumentPrice, Double>();
        openCol.setCellFactory(ftc2);

        table.getColumns().setAll(nameCol, openCol);

        timer.schedule(new TimerTask() {
            @Override
            public void run() {
            // Get and update first item in data array
            InstrumentPrice p = data.get(0);
            p.setOpen(p.getOpen()+1.0);
            data.set(0, p);
            }
        }, 10*1000, 5*1000); // 10 seconds

        StackPane rootLayout = new StackPane(table);
        Scene scene = new Scene(rootLayout, 1000, 300);
        primaryStage.setTitle("Example");
        primaryStage.setScene(scene);
        primaryStage.setX(0);
        primaryStage.setY(0);
        primaryStage.show();
    }

     private ObservableList<InstrumentPrice> getInitialTableData() {
        List list = new ArrayList<InstrumentPrice>();
        list.add(new InstrumentPrice("ABC", 0.0, 0.0, 0.0, 0.0, 0.0, 0, 0.0, 0, 0.0, 0));
        list.add(new InstrumentPrice("DEF", 0.0, 0.0, 0.0, 0.0, 0.0, 0, 0.0, 0, 0.0, 0));
        list.add(new InstrumentPrice("GHI", 0.0, 0.0, 0.0, 0.0, 0.0, 0, 0.0, 0, 0.0, 0));
        list.add(new InstrumentPrice("JKL", 0.0, 0.0, 0.0, 0.0, 0.0, 0, 0.0, 0, 0.0, 0));
        list.add(new InstrumentPrice("MNO", 0.0, 0.0, 0.0, 0.0, 0.0, 0, 0.0, 0, 0.0, 0));
        list.add(new InstrumentPrice("PQR", 0.0, 0.0, 0.0, 0.0, 0.0, 0, 0.0, 0, 0.0, 0));
        list.add(new InstrumentPrice("STU", 0.0, 0.0, 0.0, 0.0, 0.0, 0, 0.0, 0, 0.0, 0));
        list.add(new InstrumentPrice("VWX", 0.0, 0.0, 0.0, 0.0, 0.0, 0, 0.0, 0, 0.0, 0));
        ObservableList<InstrumentPrice> data = FXCollections.observableList(list);
        return data;
    }


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

我在控制台打印了一些调试信息,下面是其中的一些:

************** CREATING FLASHING TABLE CELL 1299017468
updateItem 1299017468 0 value=0.0 (null)false
updateItem 1299017468 1 value=0.0 (0.0)false
updateItem 1299017468 2 value=0.0 (0.0)false
updateItem 1299017468 3 value=0.0 (0.0)false
updateItem 1299017468 4 value=0.0 (0.0)false
updateItem 1299017468 5 value=0.0 (0.0)false
updateItem 1299017468 6 value=0.0 (0.0)false
updateItem 1299017468 7 value=0.0 (0.0)false
updateItem 1299017468 -1 value=null (0.0)true
************** CREATING FLASHING TABLE CELL 312035237
updateItem 312035237 0 value=0.0 (null)false
updateItem 312035237 -1 value=null (0.0)true
************** CREATING FLASHING TABLE CELL 616040193
updateItem 616040193 0 value=0.0 (null)false
************** CREATING FLASHING TABLE CELL 1836880566
updateItem 1836880566 1 value=0.0 (null)false
************** CREATING FLASHING TABLE CELL 1717614984
updateItem 1717614984 2 value=0.0 (null)false
************** CREATING FLASHING TABLE CELL 114981818
updateItem 114981818 3 value=0.0 (null)false
************** CREATING FLASHING TABLE CELL 151715918
updateItem 151715918 4 value=0.0 (null)false
************** CREATING FLASHING TABLE CELL 1690114806
updateItem 1690114806 5 value=0.0 (null)false
************** CREATING FLASHING TABLE CELL 397552694
updateItem 397552694 6 value=0.0 (null)false
************** CREATING FLASHING TABLE CELL 705642570
updateItem 705642570 7 value=0.0 (null)false
************** CREATING FLASHING TABLE CELL 575848070
updateItem 575848070 8 value=null (null)true
************** CREATING FLASHING TABLE CELL 349657094
updateItem 349657094 9 value=null (null)true
************** CREATING FLASHING TABLE CELL 221445895
updateItem 221445895 10 value=null (null)true
************** CREATING FLASHING TABLE CELL 666967962
updateItem 666967962 11 value=null (null)true
updateItem 616040193 -1 value=null (0.0)true
updateItem 1836880566 -1 value=null (0.0)true
updateItem 1717614984 -1 value=null (0.0)true
updateItem 114981818 -1 value=null (0.0)true
updateItem 151715918 -1 value=null (0.0)true
updateItem 1690114806 -1 value=null (0.0)true
updateItem 397552694 -1 value=null (0.0)true
updateItem 705642570 -1 value=null (0.0)true
updateItem 666967962 0 value=1.0 (null)false
updateItem 666967962 0 value=1.0 (1.0)false
updateItem 221445895 1 value=0.0 (null)false
updateItem 221445895 1 value=0.0 (0.0)false
updateItem 349657094 2 value=0.0 (null)false
updateItem 349657094 2 value=0.0 (0.0)false
updateItem 575848070 3 value=0.0 (null)false
updateItem 575848070 3 value=0.0 (0.0)false
updateItem 705642570 4 value=0.0 (0.0)false
updateItem 705642570 4 value=0.0 (0.0)false
updateItem 397552694 5 value=0.0 (0.0)false
updateItem 397552694 5 value=0.0 (0.0)false
updateItem 1690114806 6 value=0.0 (0.0)false
updateItem 1690114806 6 value=0.0 (0.0)false
updateItem 151715918 7 value=0.0 (0.0)false
updateItem 151715918 7 value=0.0 (0.0)false
updateItem 114981818 8 value=null (0.0)true
updateItem 114981818 8 value=null (0.0)true
updateItem 1717614984 9 value=null (0.0)true
updateItem 1717614984 9 value=null (0.0)true
updateItem 1836880566 10 value=null (0.0)true
updateItem 1836880566 10 value=null (0.0)true
updateItem 616040193 11 value=null (0.0)true
updateItem 616040193 11 value=null (0.0)true

有很多问题,但主要是我想知道:

  • 为什么每次更新都会调用两次 updateItem() 方法?
  • 当我只更新第 0 行时,为什么会收到第 1..n 行的 updateItem() 调用?我没有调整表格或其他任何东西的大小。
  • “sameItem”逻辑似乎根本不起作用
  • 当 updateItem 被调用为 empty == true 时我应该怎么做?

不知何故,我相信所有这些问题都是相关的,并且不知何故是由于我对细胞工厂和细胞创造的误解造成的。我在许多其他语言/框架中做过类似的事情,从来没有像现在这样困惑......

如果我能提供任何关于我做错了什么的信息,我将不胜感激!

【问题讨论】:

  • 无论您做什么,您不得从 fx 应用程序线程更新场景图中的任何属性(就像您在 timertask 中所做的那样)
  • 您无法控制何时/是否/如何重新使用单元格 - 因此在单元格内实现任何动画迟早都会中断。相反,在外部(在数据中或数据周围的包装器中)执行此操作,使表知道更改并让单元格根据状态自行更新。盐粒:并没有真正深入研究您的代码,只是看到了 Timertask 和动画;)
  • 我认为 TableView 在 JavaFX 中是最先进的,可以做高级网格的东西(我在 JavaFX 中看到了很多高级动画演示 - 虽然不是在表格中),如果我理解正确地说,这些功能(在我看来真的很基本)是完全不可能使用 TableView 实现的?这让我大吃一惊!那么我应该朝哪个方向去获得闪烁更新的基本表呢?
  • @kleopatra 您提到了 timertask,但我只更新数据,而不是“场景图”中的任何内容(我真的不知道它实际上是什么)。否则我将如何模拟来自外部源的实时更新?
  • _使用TableView完全不可能实现_不,只是说单元格是不正确的地方来实现它。 但我只更新数据,而不是将“场景图”中的任何内容数据设置为在场景图中处于活动状态的表 - 所以实际上您确实更新了场景图(即 ui 中节点的层次结构)脱离 fx 应用程序线程。请做一些研究.. fx 具有广泛的并发支持(f.i. Task),使用它而不是普通的 java Timer/-Task

标签: java javafx tableview


【解决方案1】:

你的细胞工厂一定是这样的。

sizeBuy.setCellFactory(new Callback<TableColumn<buyOrderBook, Double>, TableCell<buyOrderBook, Double>>() {
    public TableCell<buyOrderBook, Double> call(TableColumn<buyOrderBook, Double> column) {

        Comparator<Double> comparator = new Comparator<Double>() {
                    @Override
                    public int compare(Double o1, Double o2) {
                        return o1.compareTo(o2);
                    }
                };
                
                FlashingTableCell cell = new FlashingTableCell(comparator, Pos.CENTER_RIGHT);

                return label;
    }
});

sizeBuy 是我的列名。 buyOrderBook 是该表视图的类,而 Double 是该列的值类型。 因为我现在正在开发这种类型的应用程序,所以我只是改进了 FlashingTableCell 类并使其正确运行, 这是 FlashingTableCell 类的完整代码:

class FlashingTableCell<S, T> extends TableCell<S, T> {

private static final Color INCREASE_HIGHLIGHT_COLOR = Color.rgb(0, 255, 0, 0.4);
private static final Color DECREASE_HIGHLIGHT_COLOR = Color.rgb(255, 0, 0, 0.4);
private static final Color NOTCHANGE_COLOR = Color.rgb(0, 0, 0, 0.0);
private static final Color HIGHLIGHT_COLOR = Color.rgb(255, 255, 0, 0.7);
private static final Duration HIGHLIGHT_TIME = Duration.millis(500);

private final Background bgIncrease = new Background(new BackgroundFill(INCREASE_HIGHLIGHT_COLOR, CornerRadii.EMPTY, Insets.EMPTY));
private final Background bgDecrease = new Background(new BackgroundFill(DECREASE_HIGHLIGHT_COLOR, CornerRadii.EMPTY, Insets.EMPTY));
private final Background bgNotChange = new Background(new BackgroundFill(NOTCHANGE_COLOR, CornerRadii.EMPTY, Insets.EMPTY));
private final Background bgChange = new Background(new BackgroundFill(HIGHLIGHT_COLOR, CornerRadii.EMPTY, Insets.EMPTY));

private final BorderPane background = new BorderPane();
private final LabeledText lblText = new LabeledText(this);
private final FadeTransition animation = new FadeTransition(HIGHLIGHT_TIME, background);

private final StackPane container = new StackPane();

private T prevValue;
private S prevItem;

final private Comparator<T> comparator;


public FlashingTableCell(Comparator<T> comparator, Pos alignment) {
    super();
    this.comparator = comparator;
    lblText.textProperty().bindBidirectional(textProperty());
    this.setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
    lblText.setStyle("-fx-text-fill: lightgray ;");

    setPadding(new Insets(0,10,0,0));
    container.getChildren().addAll(background, lblText);
    container.setAlignment(alignment);

    setGraphic(container);

    animation.setFromValue(1.0);
    animation.setToValue(0);
  // animation.setCycleCount(Timeline.INDEFINITE);
    animation.setCycleCount(0);
    animation.setAutoReverse(false);


    itemProperty().addListener((obs, oldItem, newItem) -> {

       
        if (newItem != null && oldItem != null && getIndex() >= 0  ) {
            int compare = comparator.compare(newItem , oldItem);               
            if (compare > 0) {
                background.setBackground(bgIncrease);
                animation.playFromStart();
            } else if (compare < 0) {
                background.setBackground(bgDecrease);
                animation.playFromStart();
            }


        }
    });
}
@Override
protected void updateItem(T item, boolean empty) {
    super.updateItem(item, empty);
    if(!isEmpty()) {
        lblText.setText(String.format("%1.2f", item));
    }
}

}

我正在使用来自交易所的 websocket 处理实时数据。最佳实践你也需要使用实时数据,因为就像我现在的项目一样,我将这种方法用于订单簿。并且订单簿有时会在 200 毫秒以下发生变化。所以我们可以看看这种方法是否有很多延迟。

这里是我的项目 SS: Flashing impelementation on javafx orderbook

【讨论】:

  • java 命名约定,拜托!
  • .. 我在您的实现中没有看到任何动画,那么与问题有什么关系?
  • @kleopatra 我刚刚更新了我的解释,原来的 FlashingTableCell 类实际上是错误的获取 newItem 和 oldItem 的方法。侦听器对每个索引显示进行 1 逐 1 更新。因此,如果使用循环手动获取 oldItem,它将比较不同的值。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-12-11
  • 2013-04-17
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多