检测JavaFX TableView中行的双击事件

69

我需要检测TableView行的双击。

我如何监听行的任何部分上的双击,并获取此行的所有数据以将其打印到控制台?


使用双击单元格的解决方案是可行的,因为您始终可以在单元格上调用getTableRow().getItem()来获取该行的项目。不过更好的方法是直接向表行注册侦听器(请参见答案)。 - James_D
7个回答

147
TableView<MyType> table = new TableView<>();

//...

table.setRowFactory( tv -> {
    TableRow<MyType> row = new TableRow<>();
    row.setOnMouseClicked(event -> {
        if (event.getClickCount() == 2 && (! row.isEmpty()) ) {
            MyType rowData = row.getItem();
            System.out.println(rowData);
        }
    });
    return row ;
});

这里是一个完整的可运行示例:

import java.util.Random;
import java.util.function.Function;

import javafx.application.Application;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableRow;
import javafx.scene.control.TableView;
import javafx.stage.Stage;

public class TableViewDoubleClickOnRow extends Application {

    @Override
    public void start(Stage primaryStage) {
        TableView<Item> table = new TableView<>();
        table.setRowFactory(tv -> {
            TableRow<Item> row = new TableRow<>();
            row.setOnMouseClicked(event -> {
                if (event.getClickCount() == 2 && (! row.isEmpty()) ) {
                    Item rowData = row.getItem();
                    System.out.println("Double click on: "+rowData.getName());
                }
            });
            return row ;
        });
        table.getColumns().add(column("Item", Item::nameProperty));
        table.getColumns().add(column("Value", Item::valueProperty));

        Random rng = new Random();
        for (int i = 1 ; i <= 50 ; i++) {
            table.getItems().add(new Item("Item "+i, rng.nextInt(1000)));
        }

        Scene scene = new Scene(table);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    private static <S,T> TableColumn<S,T> column(String title, Function<S, ObservableValue<T>> property) {
        TableColumn<S,T> col = new TableColumn<>(title);
        col.setCellValueFactory(cellData -> property.apply(cellData.getValue()));
        return col ;
    }

    public static class Item {
        private final StringProperty name = new SimpleStringProperty();
        private final IntegerProperty value = new SimpleIntegerProperty();

        public Item(String name, int value) {
            setName(name);
            setValue(value);
        }

        public StringProperty nameProperty() {
            return name ;
        }

        public final String getName() {
            return nameProperty().get();
        }

        public final void setName(String name) {
            nameProperty().set(name);
        }

        public IntegerProperty valueProperty() {
            return value ;
        }

        public final int getValue() {
            return valueProperty().get();
        }

        public final void setValue(int value) {
            valueProperty().set(value);
        }
    }

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

2
@Aubin,我认为既然这个答案已经被接受并且有80多个赞,那么可以合理地假设如果它对你不起作用,那么你实现的方式有问题。发布你自己的问题,加上 [MCVE]。 - James_D
如果我想要使用单元格工厂怎么办?我已经有使用PropertyValueFactory来设置每个字段的代码了,如何扩展您的方法以监听双击事件?此外,我不理解您的行工厂的这一部分:您创建一个新的TableRow,对其调用getItem,然后打印出该项目的名称?实际数据什么时候涉及进来呢? - Hey
1
@Arno 一个 PropertyValueFactory 被用于 cellValueFactory,而不是 cellFactory。你总是需要在你的 TableColumn 上有一个 cellValueFactory(请注意表中的两列也都定义了 cellValueFactory)。如果需要,你还可以在一些或所有列上另外定义 cellFactory,它们是独立的。我不确定我理解你最后的问题;你提到的代码位于鼠标处理程序中。因此,当鼠标在行上双击时,处理程序将检索该行的值并显示其名称。 - James_D
我刚理解了ValueFactoryCellFactory之间的区别,谢谢。至于我的第二个问题,我刚才没注意到上下文,现在我明白了。 - Hey

22

例子:

table.setOnMousePressed(new EventHandler<MouseEvent>() {
    @Override 
    public void handle(MouseEvent event) {
        if (event.isPrimaryButtonDown() && event.getClickCount() == 2) {
            System.out.println(table.getSelectionModel().getSelectedItem());                   
        }
    }
});
如果您正在使用自定义选择模型,那么您可以从事件中获取行,例如:

如果您使用自定义选择模型,则可以从事件中获取行,示例:

table.setOnMousePressed(new EventHandler<MouseEvent>() {
    @Override 
    public void handle(MouseEvent event) {
        if (event.isPrimaryButtonDown() && event.getClickCount() == 2) {
            Node node = ((Node) event.getTarget()).getParent();
            TableRow row;
            if (node instanceof TableRow) {
                row = (TableRow) node;
            } else {
                // clicking on text part
                row = (TableRow) node.getParent();
            }
            System.out.println(row.getItem());
        }
    }
});

3
尽管这个方法可行(因为双击的第一次点击会选择整行),但是附加一个鼠标处理程序到行上的选择语义上是不同的。如果出于某些原因需要使用自定义的选择模型,这可能会破坏这段代码的效果。 - James_D
@James_D 在问题中没有写下一个自定义选择模型的情况,但是答案也适用于这种情况。 - Alexander.Berg
这取决于一个特定的节点层次结构(该结构未记录,且故意如此)。如果JavaFX的未来版本中表格视图布局机制的实现发生更改怎么办?只需在创建表格行时直接将侦听器附加到行即可。 - James_D
你有点误解我的意思。问题不在于你的代码是否支持自定义选择模型,而是“选择”和“注册鼠标监听器”是两个独立的事情。使用“所选内容”作为确定鼠标单击所在节点的机制会引入两个API部分之间以前不存在的耦合(依赖关系)。 - James_D
3
谢谢,但我不得不使用event.getButton().equals(MouseButton.PRIMARY)代替event.isPrimaryButtonDown()才能使我的代码正常工作。 - Johis
显示剩余2条评论

5
这对我有用:
table.setOnMouseClicked((MouseEvent event) -> {
            if (event.getButton().equals(MouseButton.PRIMARY) && event.getClickCount() == 2){
                System.out.println(table.getSelectionModel().getSelectedItem());
            }
        });
    }

3

如果您正在使用 SceneBuilder,您可以将表格的 OnMouseClicked 设置为下面所示的 handleRowSelect() 方法:

MyType temp;
Date lastClickTime;
@FXML
private void handleRowSelect() {
    MyType row = myTableView.getSelectionModel().getSelectedItem();
    if (row == null) return;
    if(row != temp){
        temp = row;
        lastClickTime = new Date();
    } else if(row == temp) {
        Date now = new Date();
        long diff = now.getTime() - lastClickTime.getTime();
        if (diff < 300){ //another click registered in 300 millis
             System.out.println("Edit dialog");
        } else {
            lastClickTime = new Date();
        }
    }
}

您可以使用事件handleRowSelect(MouseEvent event)来检测主鼠标按钮的双击,就像@Abdul.Moqueet的答案中所示。 - Adam Silenko

1

在上一个答案的基础上进行扩展:

额外的检查确保所选行被双击 - 忽略空行或列标题的双击

table.setRowFactory(param -> {
            TableRow<MyType> row = new TableRow<>();
            row.setOnMouseClicked(event -> Optional.ofNullable(row.getItem()).ifPresent(rowData-> {
                if(event.getClickCount() == 2 && rowData.equals(table.getSelectionModel().getSelectedItem())){
                    System.out.println(rowData);
                }
            }));
            return row;
        });
```

0

我曾经遇到过类似的问题,无法在TableView上检测到鼠标双击事件。 所有的示例都能够完美地工作,但我的应用程序却根本无法检测到双击事件。

后来我发现,如果TableView处于可编辑状态,就无法检测到鼠标双击事件!!

请检查您的应用程序是否像这样处于可编辑状态。

tableView.setEditable( true );

如果条件成立,则双击事件仅在选择了相同的行时触发。


0

这个答案已经经过测试:

table.setOnMouseClicked( event -> {
   if( event.getClickCount() == 2 ) {
      System.out.println( table.getSelectionModel().getSelectedItem());
   }});

table.getSelectionModel().getSelectedItem() 可以用于双击事件处理。在第一次单击时会移动选择,而在第二次时执行此处理程序。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接