【问题标题】:JavaFX TableView not triggering selection callbacksJavaFX TableView 不触发选择回调
【发布时间】:2018-01-01 14:30:36
【问题描述】:

我遇到了 JavaFX TableView 没有触发选择回调的问题。我设置了这两个回调:

tableView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
tableView.getSelectionModel().selectedItemProperty().addListener((observable) -> {
    System.out.println(LocalDateTime.now() + " Item selection changed.");
});
tableView.getSelectionModel().selectedIndexProperty().addListener(observable -> {
    System.out.println(LocalDateTime.now() + " Item index changed.");
});

有一件事让我感到困惑。选择更改事件仅在第一次选择和开始多选时触发。每次更改似乎都会发生索引更改。这是为什么呢?

然而,我最大的问题是,当我选择多个条目,然后选择我点击的最后一个条目时,因此,撤消多选但不更改焦点项目时,不会触发任何事件。希望这能说明问题:

任何想法为什么会发生这种情况?我怎么知道选择什么时候发生了这样的变化?

我的最小示例是四个文件:

Main.java:

package sample;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class Main extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception {
        Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));
        primaryStage.setTitle("Hello World");
        primaryStage.setScene(new Scene(root, 300, 275));
        primaryStage.show();
    }


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

记录.java

package sample;

import javafx.beans.property.SimpleStringProperty;

public class Record {
    private SimpleStringProperty c1 = new SimpleStringProperty();
    private SimpleStringProperty c2 = new SimpleStringProperty();

    public Record(String c1, String c2) {
        this.c1.setValue(c1);
        this.c2.setValue(c2);
    }

    public String getC1() {
        return c1.get();
    }

    public SimpleStringProperty c1Property() {
        return c1;
    }

    public void setC1(String c1) {
        this.c1.set(c1);
    }

    public String getC2() {
        return c2.get();
    }

    public SimpleStringProperty c2Property() {
        return c2;
    }

    public void setC2(String c2) {
        this.c2.set(c2);
    }

    @Override
    public String toString() {
        return String.format("Record{c1=%s, c2=%s}", c1, c2);
    }
}

Controller.java

package sample;

import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.control.SelectionMode;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;

import java.time.LocalDateTime;

public class Controller {
    private ObservableList<Record> records = FXCollections.observableArrayList();

    @FXML
    private TableView<Record> tableView;

    @FXML
    private TableColumn<Record, String> c1Column;

    @FXML
    private TableColumn<Record, String> c2Column;

    @FXML
    private void initialize() {
        records.add(new Record("c1 - 1", "c2 - 1"));
        records.add(new Record("c1 - 2", "c2 - 2"));
        records.add(new Record("c1 - 3", "c2 - 3"));
        records.add(new Record("c1 - 4", "c2 - 4"));
        records.add(new Record("c1 - 5", "c2 - 5"));

        tableView.setItems(records);
        c1Column.setCellValueFactory(new PropertyValueFactory<>("c1"));
        c2Column.setCellValueFactory(new PropertyValueFactory<>("c2"));

        tableView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
        tableView.getSelectionModel().selectedItemProperty().addListener((observable) -> {
            System.out.println(LocalDateTime.now() + " Item selection changed.");
        });
        tableView.getSelectionModel().selectedIndexProperty().addListener(observable -> {
            System.out.println(LocalDateTime.now() + " Item index changed.");
        });
    }
}

和sample.fxml

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

<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.layout.StackPane?>
<StackPane xmlns="http://javafx.com/javafx/8.0.141" xmlns:fx="http://javafx.com/fxml/1"
           fx:controller="sample.Controller">
    <TableView fx:id="tableView" prefHeight="200.0" prefWidth="200.0">
        <columns>
            <TableColumn fx:id="c1Column" prefWidth="75.0" text="C1"/>
            <TableColumn fx:id="c2Column" prefWidth="75.0" text="C2"/>
        </columns>
    </TableView>
</StackPane>

【问题讨论】:

    标签: java events javafx tableview


    【解决方案1】:

    问题是您使用的是InvalidationListeners。每当可观察值变为无效时都会触发这些侦听器:也就是说,最后计算的值可能不是最新的。

    JavaFX 属性可以延迟计算。如果它们变得无效,则通知失效侦听器,但不一定要计算它们的新值。如果它们所依赖的值在重新计算之前再次更改,则它们已经无效,因此不会再次通知失效侦听器。如果它们在中间时间被重新计算,那么它们将在重新计算时变为有效,并且将再次通知失效侦听器。

    此机制是出于性能原因而设计的:您可以想象,例如大窗格的布局取决于其中许多项目的大小和位置;但您不想在每次更改时都重新计算:您只需要知道何时需要重新计算(何时无效)。

    从您的代码来看,它的工作方式似乎是selectedItem 声明了对selectedIndex 的依赖。当用户选择某些东西时,直接设置selectedIndex(使其无效并更改它),这导致selectedItem变得无效。但是,selectedItem 在重新计算之前不会显式更改。

    如果您实际重新计算侦听器中的值,您可以看到所有这些:

        tableView.getSelectionModel().selectedItemProperty().addListener((observable) -> {
            System.out.println(LocalDateTime.now() + " Item selection changed: "+tableView.getSelectionModel().getSelectedItem());
        });
        tableView.getSelectionModel().selectedIndexProperty().addListener((observable) -> {
            System.out.println(LocalDateTime.now() + " Item index changed: " + tableView.getSelectionModel().getSelectedIndex());
        });
    

    典型的用法是用这些属性注册ChangeListeners,而不是InvalidationListeners。 ChangeListeners 通知属性的先前值和新值,因此无论如何它们都会强制重新计算值:

        tableView.getSelectionModel().selectedItemProperty().addListener((observable, oldItem, newItem) -> {
            System.out.println(LocalDateTime.now() + " Item selection changed: "+ oldItem + " -> " + newItem);
        });
        tableView.getSelectionModel().selectedIndexProperty().addListener((observable, oldIndex, newInde) -> {
            System.out.println(LocalDateTime.now() + " Item index changed: " + oldIndex + " -> " + newIndex);
        });
    

    最后,请注意,对于多选,selectedItemselectedIndex 属性仅引用最后一个选择。可以在不更改最后选择的情况下更改所选项目的 列表。在这种情况下,属性根本不会更改,因此不会通知更改侦听器和失效侦听器。出于这个原因,对于多项选择,在项目(或索引)列表上使用监听器比在项目或索引本身上使用更自然:

        tableView.getSelectionModel().getSelectedItems().addListener((Change<? extends Record> c) -> {
                System.out.println("Selected items: "+tableView.getSelectionModel().getSelectedItems());
        });
    

    (或与tableView.getSelectionModel().getSelectedIndices().addListener((Change&lt;? extends Number&gt; c) ...);类似)。

    有关InvalidationListenerChangeListener 之间区别的更多信息,请参阅Javadocs for ObservableObservableValue

    【讨论】:

    • 谢谢@James_D。最后一点确实很有帮助,因为我描述为最有问题的行为发生在selectedItemProperty() 上的两种类型的听众身上。
    • @Pablo selectedItemselectedIndex 属性仅指最后选择的项目:因此在某些情况下,您可以更改所选项目列表而不更改最后选择:在这种情况下,这两个属性不会改变(因此不会触发任何侦听器)。我会在答案中添加对此效果的评论。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-02-07
    • 1970-01-01
    • 1970-01-01
    • 2023-04-08
    相关资源
    最近更新 更多