欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

javafx tableview中checkbox的全选/反选

程序员文章站 2024-02-05 12:00:40
...

第一次写文章,质量请海涵!
最近有被要求做一个简单的javafx桌面应用程序,需要检索目录下的文件,勾选进行文件处理。

javafx tableview中checkbox的全选/反选

这里用到了tableview,可以看到表格的两列check state和File Name都是string类的,点击某列之后check state会改为选定/非选定,同时将选中行的File Name存入list,代码如下:

public class HelloWorld extends Application {
	@FXML
	private final TableView<FileSearch> table = new TableView<>();
	@FXML
	private final ObservableList<FileSearch> data = FXCollections.observableArrayList();
	
	public static void main(String[] args) {
		launch(args);
	}

	@Override
	public void start(Stage primaryStage) {
		GridPane grid = new GridPane();		
		Button btSelect = new Button("全選択");
		btSelect.setId("btSelect");
		grid.add(btSelect, 0, 1);

		final VBox vbox = new VBox();
		vbox.getChildren().addAll(table);
		grid.add(vbox, 0, 2);

		TableColumn firstNameCol = new TableColumn("checkState");
		//checkState以及下面的lastName都是和FileSearch中的属性对应的!
		//这句的完全写法应该是 TableColumn<FileSearch,String> firstNameCol = new TableColumn<FileSearch,String>("checkState"); 这样看起来好多了。
		firstNameCol.setCellValueFactory(new PropertyValueFactory<>("checkState"));
		
		TableColumn<FileSearch,String> lastNameCol = new TableColumn<FileSearch,String>("File Name");
		lastNameCol.setCellValueFactory(new PropertyValueFactory<>("lastName"));
		table.getColumns().addAll(firstNameCol, lastNameCol);
		
		//表格绑定的点击事件
		//changeList存放选中行的文件名
		List<String> changeList = new ArrayList<>();
		table.setRowFactory(tv -> {
			TableRow<FileSearch> row = new TableRow<>();
			row.setOnMouseClicked(event -> {
				if(!row.isEmpty()) {
					//取到当前选定行的文件名
					String rowData = row.getItem().lastName.getValue();
					//下面的rowData是我对路径格式进行的处理,可忽略
					String[] list = rowData.split("\\\\" );
					rowData = '"' + list[list.length-1].split("\\.")[0] + '"';
					if (row.getItem().getCheckState()== "disSelected") {
						data.get(row.getIndex()).setCheckState("selected");
						//必须刷新一下表,否则值改变了但是表中不动态显示
						table.refresh();
						changeList.add(rowData);
					}else {
					//再次点击从changeList移除选定文件
						row.getItem().setCheckState("disSelected");
						table.refresh();
						remove(changeList, rowData);
					}
				}
			});
			
			//全选/反选按钮点击事件
			btSelect.setOnMouseClicked(event -> {
				if(btSelect.getText() == "全選択") {
					btSelect.setText("全選択解除");
				}else {
					btSelect.setText("全選択");
				}
				table.getItems().forEach(e ->{
					if (e.getCheckState()== "disSelected") {
						e.setCheckState("selected");
						table.refresh();
						changeList.add(e.lastName.getValue());
					}else {
						e.setCheckState("disSelected");
						table.refresh();
						remove(changeList, e.lastName.getValue());
					}
				});
			});
			return row;
		});
		
		primaryStage.setResizable(false);// 窗体缩放(默认为true)
		primaryStage.setScene(scene);
		primaryStage.show();
	}

	public static class FileSearch {
//		private SimpleBooleanProperty box = new SimpleBooleanProperty();
		private SimpleStringProperty checkState = new SimpleStringProperty();
		private SimpleStringProperty lastName = new SimpleStringProperty();
		
		private FileSearch(Boolean cBox, String lName) {
			this.checkState = new SimpleStringProperty(fName);
//			this.box = new SimpleBooleanProperty(cBox);
			this.lastName = new SimpleStringProperty(lName);
		}

		public String getCheckState() {
			return checkState.get();
		}

		public void setCheckState(String fName) {
			checkState.set(fName);
		}
		
//		public Boolean getBox() {
//			return box.get();
//		}

//		public void setBox(Boolean cBox) {
//			box.set(cBox);
//		}

		public String getLastName() {
			return lastName.get();
		}

		public void setLastName(String lName) {
			lastName.set(lName);
		}
	}
}

看起来是完成了我要的需求,但是,故事还没结束。。。。。。
这个check state太出戏了,怎么可能不用checkBox呢?!
其实我一开始是打算直接checkBox的,但是tableview里放插件真的好不舒服,一点没有js那种随便定位到目标元素然后操作的感觉。我们拿check state开刀,加了一句

	firstNameCol.setCellFactory(CheckBoxTableCell.forTableColumn(firstNameCol));

这行代码非常直接地给我的表格第一列画上了漂亮的checkBox(还是自动居中的):
javafx tableview中checkbox的全选/反选
然后我凉了,花了一天也搞不定这个checkBox的定位,按道理来说应该是非常简单的,毕竟table的点击事件确定第几行后直接给checkBox设定true和false即可,但是现在我怎么点也没反应。搞不定,注掉它!

可能你已经注意到了FileSearch中注释部分,那正是我接下来要干的事情。
我们干掉checkState,加一个Boolean类的Box。

public static class FileSearch {

		private SimpleBooleanProperty box = new SimpleBooleanProperty();
//		private SimpleStringProperty checkState = new SimpleStringProperty();
		private SimpleStringProperty lastName = new SimpleStringProperty();
		
		private FileSearch(Boolean cBox, String lName) {
//			this.checkState = new SimpleStringProperty(fName);
			this.box = new SimpleBooleanProperty(cBox);
			this.lastName = new SimpleStringProperty(lName);
		}

//		public String getCheckState() {
//			return checkState.get();
//		}
//
//		public void setCheckState(String fName) {
//			checkState.set(fName);
//		}
		
		public Boolean getBox() {
			return box.get();
		}

		public void setBox(Boolean cBox) {
			box.set(cBox);
		}

		public String getLastName() {
			return lastName.get();
		}

		public void setLastName(String lName) {
			lastName.set(lName);
		}
	}

对应的firstNameCol也换成box

Callback<TableColumn<FileSearch, Boolean>, TableCell<FileSearch, Boolean>> booleanCellFactory = 
	            new Callback<TableColumn<FileSearch, Boolean>, TableCell<FileSearch, Boolean>>() {
	            @Override
	                public TableCell<FileSearch, Boolean> call(TableColumn<FileSearch, Boolean> p) {
	                    return new BooleanCell();
	            }
	        };
	        firstNameCol.setCellValueFactory(new PropertyValueFactory<FileSearch,Boolean>("box"));
	        firstNameCol.setCellFactory(booleanCellFactory);
class BooleanCell extends TableCell<FileSearch, Boolean> {
        private CheckBox checkBox;
        public BooleanCell() {
            checkBox = new CheckBox();
            checkBox.setDisable(true);
            checkBox.selectedProperty().addListener(new ChangeListener<Boolean> () {
                public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
                    if(isEditing())
                        commitEdit(newValue == null ? false : newValue);
                }
            });
            this.setGraphic(checkBox);
            this.setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
            this.setEditable(true);
        }
        @Override
        public void startEdit() {
            super.startEdit();
            if (isEmpty()) {
                return;
            }
            checkBox.setDisable(false);
            checkBox.requestFocus();
        }
        @Override
        public void cancelEdit() {
            super.cancelEdit();
            checkBox.setDisable(true);
        }
        public void commitEdit(Boolean value) {
            super.commitEdit(value);
            checkBox.setDisable(true);
        }
        @Override
        public void updateItem(Boolean item, boolean empty) {
            super.updateItem(item, empty);
            if (!isEmpty()) {
                checkBox.setSelected(item);
            }
        }
    }

当然,点击事件的get set方法记得一起改了:

table.setRowFactory(tv -> {
			TableRow<FileSearch> row = new TableRow<>();
			row.setOnMouseClicked(event -> {
				if(!row.isEmpty()) {
					String rowData = row.getItem().lastName.getValue();
					String[] list = rowData.split("\\\\" );
					rowData = '"' + list[list.length-1].split("\\.")[0] + '"';
					if (row.getItem().getBox()== false) {
						data.get(row.getIndex()).setBox(true);
						table.refresh();
						changeList.add(rowData);
					}else {
						row.getItem().setBox(false);
						table.refresh();
						remove(changeList, rowData);
					}
				}
			});
			btSelect.setOnMouseClicked(event -> {
				if(btSelect.getText() == "全選択") {
					btSelect.setText("全選択解除");
				}else {
					btSelect.setText("全選択");
				}
				table.getItems().forEach(e ->{
					if (e.getBox()== false) {
						e.setBox(true);
						table.refresh();
						changeList.add(e.lastName.getValue());
					}else {
						e.setBox(false);
						table.refresh();
						remove(changeList, e.lastName.getValue());
					}
				});
			});
			return row;
		});