如何监听 TableCell 中动态生成的按钮的点击?

How to listen for clicks on dynamically generated buttons in TableCell?

我有一个 class 任务,它从数据库中获取字段值并在 table 中显示为一行。

package com.example.javafxapp.models;

import javafx.scene.control.Button;
import javafx.scene.layout.HBox;

public class Task {
    private String id;
    private String task;
    private Button done;
    private Button cancel;
    private Button remove;
    private HBox edit;


    public Task(String id, String task, HBox edit, Button done, Button cancel, Button remove) {
        this.id = id;
        this.task = task;
        this.edit = edit;
        this.done = done;
        this.cancel = cancel;
        this.remove = remove;

        edit.getChildren().addAll(done, cancel, remove);
        edit.setSpacing(5);
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getTask() {
        return task;
    }

    public void setTask(String task) {
        this.task = task;
    }
    public HBox getEdit() {
        return edit;
    }

    public void setEdit(HBox edit) {
        this.edit = edit;
    }

    public Button getDone() {
        return done;
    }

    public void setDone(Button done) {
        this.done = done;
    }

    public Button getCancel() {
        return cancel;
    }

    public void setCancel(Button cancel) {
        this.cancel = cancel;
    }

    public Button getRemove() {
        return remove;
    }

    public void setRemove(Button remove) {
        this.remove = remove;
    }
}

有一个控制器用任务 class 的对象填充 table 的行。

package com.example.javafxapp.controllers;

import com.example.javafxapp.models.DatabaseHandler;
import com.example.javafxapp.models.Task;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;

import java.io.IOException;
import java.net.URL;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Optional;
import java.util.ResourceBundle;

public class AppController {

    @FXML
    private ResourceBundle resources;

    @FXML
    private URL location;

    @FXML
    private AnchorPane appPane;

    @FXML
    private TableView<Task> currentTable;

    @FXML
    private Tab currentTab;

    @FXML
    private AnchorPane currentTabAnchorPane;

    @FXML
    private TableColumn<Task, String> currentIdColumn;

    @FXML
    private TableColumn<Task, String> currentTasksColumn;

    @FXML
    private TableColumn<Task, HBox> editColumn;


    ObservableList<Task> currentTasks = FXCollections.observableArrayList();

    @FXML
    void initialize() {

        getAllTasks();
        initCols();

        //Вutton mouse click listener from Tableview cells.

    }

    private void initCols(){
        currentIdColumn.setCellValueFactory(new PropertyValueFactory<Task, String>("id"));
        currentTasksColumn.setCellValueFactory(new PropertyValueFactory<Task, String>("task"));
        editColumn.setCellValueFactory(new PropertyValueFactory<Task, HBox>("edit"));

        currentTable.getItems().clear();
        currentTable.setItems(currentTasks);
    }

    private void getAllTasks() {
        ResultSet rs = new DatabaseHandler().getTaskTableFromDb();
            try {
                while (rs.next()) {
                    if(rs.getString("state").equals("current")) {
                        currentTasks.add(new Task(rs.getString("idtask"),
                                rs.getString("task"),
                                rs.getString("state"),
                                new HBox(),
                                new Button("done"),
                                new Button("cancel"),
                                new Button("remove")));
                    }
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
    }
}

我需要监听单元格中按钮的点击。 但是这些按钮不会作为控制器中的字段出现。如何在不引用特定单元格的情况下执行此操作?

您的模型 class Task 不应包含任何 Ui 组件。它应该看起来像

public class Task {
    private String id;
    private String task;


    public Task(String id, String task) {
        this.id = id;
        this.task = task;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getTask() {
        return task;
    }

    public void setTask(String task) {
        this.task = task;
    }
}

您应该强烈考虑使用 JavaFX Properties 实现模型 class,而不是将其作为普通 Java bean 实现。

要在 table 的单元格中显示 UI 控件,您应该实现自定义 TableCell 并在单元格工厂中生成它的实例。可能最方便的方法是让包含这些单元格的列将整个模型作为其值。这样,单元格的 getItem() 方法将 return 其特定行的模型。

@FXML
private TableColumn<Task, Task> editColumn ;

@FXML
private void initialize() {

    // ...

    editColumn.setCellValueFactory(cellData -> new SimpleObjectProperty<>(cellData.getValue()));
    editColumn.setCellFactory(col -> new TaskActionCell());

    // ...
}

单元实现(这里是控制器中的内部 class)看起来像

public class TaskActionCell extends TableCell<Task, Task> {

    private final HBox graphic;

    public TaskActionCell() {
        Button done = new Button("Done");
        Button cancel = new Button("Cancel");
        Button remove = new Button("Remove");
        graphic = new HBox(5, done, cancel remove);

        remove.setOnAction(event -> {
            Task task = getItem();
            currentTasks.remove(task);
        });

        // other event handlers are similar

    }

    @Override
    protected void updateItem(Task task, boolean empty) {
        super.updateItem(task, empty);
        if (empty) {
            setGraphic(null);
        } else {
            setGraphic(graphic);
        }
    }
}

另一种方法是只让单元格值为空(例如使用 TableColumn<Task, Void> editColumn 等),并使用 getTableView().getItems().get(getIndex()) 访问单元格中的行值,但这似乎更少优雅。


这是一个完整的工作示例,使用我在上面定义的 Task class。

Table.fxml

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

<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.control.TableColumn?>
<BorderPane xmlns:fx="http://javafx.com/fxml"
            fx:controller="org.jamesd.examples.actiontable.TableController">
    <center>
    <TableView fx:id="table">
        <columns>
            <TableColumn text="Id" fx:id="idColumn"/>
            <TableColumn text="Task" fx:id="taskColumn"/>
            <TableColumn text="Edit" fx:id="editColumn"/>
        </columns>
    </TableView>
    </center>
</BorderPane>

TableController.java

import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.control.*;
import javafx.scene.layout.HBox;

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

public class TableController {

    @FXML
    private TableView<Task> table ;

    @FXML
    private TableColumn<Task, Task> editColumn ;

    @FXML
    private TableColumn<Task, String> idColumn ;

    @FXML
    private TableColumn<Task, String> taskColumn ;

    private ObservableList<Task> currentTasks ;

    @FXML
    private void initialize() {

        idColumn.setCellValueFactory(cellData -> new SimpleStringProperty(cellData.getValue().getId()));
        taskColumn.setCellValueFactory(cellData -> new SimpleStringProperty(cellData.getValue().getTask()));

        editColumn.setCellValueFactory(cellData -> new SimpleObjectProperty<>(cellData.getValue()));
        editColumn.setCellFactory(col -> new TaskActionCell());

        currentTasks = loadTasks();

        table.setItems(currentTasks);
    }

    private ObservableList<Task> loadTasks() {
        List<Task> tasks = new ArrayList<>();
        for (int i = 1 ; i <= 20; i++) {
            tasks.add(new Task(Integer.toString(i), "Task "+i));
        }
        return FXCollections.observableList(tasks);
    }

    public class TaskActionCell extends TableCell<Task, Task> {

        private final HBox graphic;

        public TaskActionCell() {
            Button done = new Button("Done");
            Button cancel = new Button("Cancel");
            Button remove = new Button("Remove");
            graphic = new HBox(5, done, cancel, remove);

            remove.setOnAction(event -> {
                Task task = getItem();
                currentTasks.remove(task);
            });

            // other event handlers are similar

        }

        @Override
        protected void updateItem(Task task, boolean empty) {
            super.updateItem(task, empty);
            if (empty) {
                setGraphic(null);
            } else {
                setGraphic(graphic);
            }
        }
    }
}

申请class:

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

import java.io.IOException;

public class App extends Application {
    @Override
    public void start(Stage stage) throws IOException {
        FXMLLoader fxmlLoader = new FXMLLoader(App.class.getResource("Table.fxml"));
        Scene scene = new Scene(fxmlLoader.load());
        stage.setScene(scene);
        stage.show();
    }

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

申请运行:

按了很多次“删除”按钮后: