JavaFX带复选框的TableView:获取复选框选中的行
JavaFX TableView复选框选中行获取问题修复
问题背景
我找了很久相关方案,但大多过时无效,现在遇到的问题是:TableView列头有全选复选框,每行也有独立复选框,需要获取所有复选框选中的行来执行后续操作,但当前代码里的获取方法都不生效。
现有控制器代码
package com.comparador.controller; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.List; import java.util.ResourceBundle; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import com.comparador.ComparadorPreciosApplication; import com.comparador.entity.Commerce; import com.comparador.entity.Items; import com.comparador.entity.ShoppingListPrices; import com.comparador.repository.CommerceRepository; import com.comparador.repository.ProductRepository; import com.comparador.service.ShoppingService; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; import javafx.fxml.Initializable; import javafx.scene.Scene; import javafx.scene.SceneAntialiasing; import javafx.scene.control.Button; import javafx.scene.control.CheckBox; import javafx.scene.control.Label; import javafx.scene.control.SelectionMode; import javafx.scene.control.TableColumn; import javafx.scene.control.TableColumn.CellEditEvent; import javafx.scene.control.TableView; import javafx.scene.control.cell.PropertyValueFactory; import javafx.scene.control.cell.TextFieldTableCell; import javafx.stage.Stage; import javafx.util.converter.IntegerStringConverter; @Component public class ShoppingController implements Initializable { // @Autowired // @Qualifier("lblTitulo") private String titulo = "Productos"; @Autowired private ProductRepository productRepository; @Autowired private CommerceRepository commerceRepository; @Autowired private ShoppingService shoppingService; @FXML private Label lblTitulo; @FXML private Button btBack; @FXML private TableView<Items> tvProducts; @FXML private TableColumn<Items, CheckBox> colSelected; //THE CHECKBOX COLUMN @FXML private TableColumn<Items, String> colName; @FXML private TableColumn<Items, Integer> colAmount; @FXML private TableView<ShoppingListPrices> tvTotalPrices; @FXML private TableColumn<ShoppingListPrices, String> colCommerce; @FXML private TableColumn<ShoppingListPrices, Double> colTotal; private CheckBox selectAll; List<ShoppingListPrices> shoppingList = new ArrayList<>(); @Override public void initialize(URL location, ResourceBundle resources) { colName.setCellValueFactory(new PropertyValueFactory<>("name")); colAmount.setCellValueFactory(new PropertyValueFactory<>("amount")); colAmount.setCellFactory(TextFieldTableCell.forTableColumn(new IntegerStringConverter())); // colSelected.setCellFactory(CheckBoxTableCell.forTableColumn(colSelected)); // colSelected.setCellValueFactory(cellData -> new ReadOnlyBooleanWrapper(cellData.getValue().getChecked())); colSelected.setCellValueFactory(new PropertyValueFactory<>("selected")); colCommerce.setCellValueFactory(new PropertyValueFactory<>("commerceName")); colTotal.setCellValueFactory(new PropertyValueFactory<>("total")); lblTitulo.setText(titulo); tvProducts.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); reloadTableViewProducts(); selectAll = new CheckBox(); selectAll.setOnAction(event -> { event.consume(); tvProducts.getItems().forEach(item -> { item.getSelected().setSelected(selectAll.isSelected()); }); }); setShoppingList(); colSelected.setGraphic(selectAll); } @FXML public void editAmount(CellEditEvent<Items, Integer> event) { Items item = event.getRowValue(); if(event.getTableColumn().getText().equals("Cantidad")) { item.setAmount(event.getNewValue()); } setShoppingList(); } /* * CLICKING ON A CHECKBOX SHOULD CALL THIS METHOD AND ADD THE ROW TO "selectedItems" */ @FXML public void setShoppingList() { List<Items> selectedItems = new ArrayList<>(); //Before trying this I was selecting each row by Ctrl + Clicking on it // List<Items> selectedItems = tvProducts.getSelectionModel().getSelectedItems(); //This didn't seem to work // List<ShoppingListItems> selectedItems = tvProducts.getItems().filtered(x->x.getSelected() == true); List<Commerce> commerces = commerceRepository.findByNameContaining(""); ShoppingListPrices pricesMixingCommerces = shoppingService.getCheapestShoppingList(commerces, selectedItems); List<ShoppingListPrices> pricesByCommerce = shoppingService.getShoppingListsPerCommerce(commerces, selectedItems); shoppingList = new ArrayList<>(); shoppingList.add(pricesMixingCommerces); shoppingList.addAll(pricesByCommerce); ObservableList<ShoppingListPrices> resultOL = FXCollections.observableArrayList(); resultOL.addAll(shoppingList); tvTotalPrices.setItems(resultOL); } @FXML public void openShoppingList() throws IOException { FXMLLoader loader = new FXMLLoader(getClass().getResource("/shoppingList.fxml")); ShoppingListController shoppingListController = new ShoppingListController(); loader.setControllerFactory(ComparadorPreciosApplication.applicationContext::getBean); loader.setController(shoppingListController); shoppingListController.setup(tvTotalPrices.getSelectionModel().getSelectedItem()); try { Scene scene = new Scene(loader.load(), 800, 400, true, SceneAntialiasing.BALANCED); Stage stage = new Stage();//(Stage) btBack.getScene().getWindow(); stage.setUserData(tvTotalPrices.getSelectionModel().getSelectedItem()); stage.setScene(scene); stage.show(); } catch (IOException e) { e.printStackTrace(); } } @FXML public void goBack() { FXMLLoader loader = new FXMLLoader(ComparadorPreciosApplication.class.getResource("/index.fxml")); loader.setControllerFactory(ComparadorPreciosApplication.applicationContext::getBean); try { Scene scene = new Scene(loader.load(), 800, 800, false, SceneAntialiasing.BALANCED); Stage stage = (Stage) btBack.getScene().getWindow(); stage.setScene(scene); stage.show(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } private void reloadTableViewProducts() { List<String> productNames = productRepository.findOnProductPerName(""); List<Items> items = new ArrayList<>(); for(String name : productNames) { //items.add(new Items(new SimpleBooleanProperty(false), name, 1)); Items item = new Items((CheckBox) new CheckBox(), name, 1); item.getSelected().setSelected(false); items.add(item); } ObservableList<Items> itemsOL = FXCollections.observableArrayList(); itemsOL.addAll(items); tvProducts.setItems(itemsOL); } }
问题分析与修复方案
1. 核心问题:Items类的复选框设计错误
当前在Items类中直接存储CheckBox对象,无法监听复选框状态变化,不符合JavaFX的响应式设计。正确做法是用BooleanProperty维护选中状态,自动触发UI更新和状态监听。
修改Items类:
import javafx.beans.property.BooleanProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleIntegerProperty; import javafx.beans.property.SimpleStringProperty; public class Items { private final BooleanProperty selected = new SimpleBooleanProperty(false); private final SimpleStringProperty name; private final SimpleIntegerProperty amount; public Items(String name, int amount) { this.name = new SimpleStringProperty(name); this.amount = new SimpleIntegerProperty(amount); } // 属性Getter/Setter public BooleanProperty selectedProperty() { return selected; } public boolean isSelected() { return selected.get(); } public void setSelected(boolean selected) { this.selected.set(selected); } public String getName() { return name.get(); } public SimpleStringProperty nameProperty() { return name; } public int getAmount() { return amount.get(); } public void setAmount(int amount) { this.amount.set(amount); } public SimpleIntegerProperty amountProperty() { return amount; } }
2. 修复TableColumn配置
在控制器initialize方法中,替换colSelected的配置,使用CheckBoxTableCell绑定到selected属性:
// 替换原colSelected配置 colSelected.setCellFactory(CheckBoxTableCell.forTableColumn(colSelected)); colSelected.setCellValueFactory(cellData -> cellData.getValue().selectedProperty());
3. 添加选中状态监听器
在reloadTableViewProducts方法中,给每个Items的selectedProperty添加监听器,触发setShoppingList更新:
private void reloadTableViewProducts() { List<String> productNames = productRepository.findOnProductPerName(""); ObservableList<Items> itemsOL = FXCollections.observableArrayList(); for(String name : productNames) { Items item = new Items(name, 1); // 监听选中状态变化,自动更新列表 item.selectedProperty().addListener((obs, oldVal, newVal) -> setShoppingList()); itemsOL.add(item); } tvProducts.setItems(itemsOL); }
4. 完善全选复选框逻辑
更新全选事件处理,同时监听TableView列表变化,确保新行同步全选状态:
selectAll = new CheckBox(); selectAll.setOnAction(event -> { event.consume(); tvProducts.getItems().forEach(item -> item.setSelected(selectAll.isSelected())); }); // 监听列表变化,同步全选状态 tvProducts.getItems().addListener((ListChangeListener<Items>) c -> { boolean allSelected = tvProducts.getItems().stream().allMatch(Items::isSelected); selectAll.setSelected(allSelected); }); // 监听单个项选中状态,更新全选框状态 tvProducts.getItems().forEach(item -> { item.selectedProperty().addListener((obs, oldVal, newVal) -> { boolean allSelected = tvProducts.getItems().stream().allMatch(Items::isSelected); selectAll.setSelected(allSelected); }); });
5. 修复选中项获取逻辑
修改setShoppingList中的选中项过滤代码:
@FXML public void setShoppingList() { // 过滤所有选中的Items List<Items> selectedItems = tvProducts.getItems().filtered(Items::isSelected); List<Commerce> commerces = commerceRepository.findByNameContaining(""); ShoppingListPrices pricesMixingCommerces = shoppingService.getCheapestShoppingList(commerces, selectedItems); List<ShoppingListPrices> pricesByCommerce = shoppingService.getShoppingListsPerCommerce(commerces, selectedItems); shoppingList = new ArrayList<>(); shoppingList.add(pricesMixingCommerces); shoppingList.addAll(pricesByCommerce); ObservableList<ShoppingListPrices> resultOL = FXCollections.observableArrayList(shoppingList); tvTotalPrices.setItems(resultOL); }
修复后效果
- 点击单行复选框,自动触发
setShoppingList更新右侧表格 - 全选复选框可同步所有行的选中状态
- 正确获取所有选中行数据,传递给后续业务逻辑
内容的提问来源于stack exchange,提问作者Calfa
相关产品推荐
相关产品推荐

