使用JavaFX表格进行数据展示时,想要将当前所选择的单元格所在的行进行高亮,经过研究,实现方案见下文。
主要的思路:为Table定制TableCell,在TableCell中的RowIndex发生变更后,检查Cell所在行是否被选中,根据是否选中来设置单元格的背景色。
但按这种方式有一个问题,TableCell的RowIndex时,一般只有表格的数据发生变化的时候才会变更,如果直接在监听RowIndex的变更事件,并在其中进行处理,实际上点击某个单元格的时候是不会触发这部分处理逻辑的。很自然的,又想到监听表格的选择事件发生后去进行检查并处理,但矛盾的是,在表格的点击事件中又没法修改所选择的TableCell的背景色。
最终采取了一种折中的方式:在RowIndex变更事件中,对表格的选择事件进行监听,判断选择行中是否包含有当前Cell所在的行,根据检查结果设置单元格背景色。
关键代码如下(其中MapTable是继承自TableView的一个子类):
public class DragSelectionCell extends TextFieldTableCell<Map<String, Object>, String> {
public DragSelectionCell() {
super(new DefaultStringConverter());
setOnDragDetected(e -> {
startFullDrag();
startRow.setValue(getIndex());
startCol.setValue(getTableColumn());
});
setOnMouseDragEntered(event -> {
endRow.setValue(getIndex());
endCol.setValue(getTableColumn());
getTableView().getSelectionModel().clearSelection();
getTableView().getSelectionModel().selectRange(startRow.getValue(), startCol.getValue(), endRow.getValue(), endCol.getValue());
});
// 对行的index进行监听,如果index发生变更,则监听表格的选择事件,当选择的行发生变化时,修改当前行的背景色
this.tableRowProperty().addListener((observable, oldValue, newValue) ->
MapTable.this.getSelectionModel().getSelectedCells().addListener((ListChangeListener<? super TablePosition>) l -> {
ObservableList<? extends TablePosition> list = l.getList();
List<Integer> rowList = list.stream().map(TablePosition::getRow).collect(Collectors.toList());
if (rowList.contains(newValue.getIndex())) {
if (this.isSelected()) {
this.setBackground(new Background(new BackgroundFill(Color.valueOf("#0C739F"), null, null)));
} else {
this.setBackground(new Background(new BackgroundFill(Color.LIGHTGRAY, null, null)));
}
} else {
this.setBackground(null);
}
}));
}
}
然后在MapTable中,创建列的时候指定新列的TableCell:
private TableColumn<Map<String, Object>, String> createColumn(String title, double prefWidth, double maxWidth) {
TableColumn<Map<String, Object>, String> tableColumn = new TableColumn<>(title);
tableColumn.setCellValueFactory(param -> {
Map<String, Object> map = param.getValue();
return new ReadOnlyObjectWrapper<>(MapUtils.getString(map, title));
});
tableColumn.setMaxWidth(maxWidth);
if (0 != prefWidth) {
tableColumn.setPrefWidth(prefWidth);
}
tableColumn.setCellFactory(column -> new DragSelectionCell());
return tableColumn;
}
完整的MapTable代码如下所示,这个Table实现除了上述所说的功能,还包含有通过拖动选择多个单元格、按CTRL+C复制所选择的单元格到粘贴板等功能:
package com.liuqi.tools.mysqlmanager.ui;
import com.liuqi.common.util.SingleValue;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.scene.control.*;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.input.Clipboard;
import javafx.scene.input.DataFormat;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.paint.Color;
import javafx.util.converter.DefaultStringConverter;
import org.apache.commons.collections.MapUtils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* @author LiuQi 2019/8/26-16:44
* @version V1.0
**/
public class MapTable extends TableView<Map<String, Object>> {
private SingleValue<Integer> startRow = new SingleValue<>();
private SingleValue<Integer> endRow = new SingleValue<>();
private SingleValue<TableColumn> startCol = new SingleValue<>();
private SingleValue<TableColumn> endCol = new SingleValue<>();
private List<EditRowInfo> editingRows = new ArrayList<>(16);
public MapTable() {
this.setCopyAction();
}
public void addColumn(String title) {
this.getColumns().add(createColumn(title));
}
public void addColumn(String title, double prefWidth, double maxWidth) {
this.getColumns().add(createColumn(title, prefWidth, maxWidth));
}
public void addData(Map<String, Object> map) {
this.getItems().add(map);
}
public void addDataToFirst(Map<String, Object> map) {
this.getItems().add(0, map);
}
public void addData(List<Map<String, Object>> list) {
this.getItems().addAll(list);
}
public void resetData(List<Map<String, Object>> list) {
this.getItems().clear();
this.getItems().addAll(list);
}
private TableColumn<Map<String, Object>, String> createColumn(String title) {
return createColumn(title, 0D, 800D);
}
private TableColumn<Map<String, Object>, String> createColumn(String title, double prefWidth, double maxWidth) {
TableColumn<Map<String, Object>, String> tableColumn = new TableColumn<>(title);
tableColumn.setCellValueFactory(param -> {
Map<String, Object> map = param.getValue();
return new ReadOnlyObjectWrapper<>(MapUtils.getString(map, title));
});
tableColumn.setMaxWidth(maxWidth);
if (0 != prefWidth) {
tableColumn.setPrefWidth(prefWidth);
}
tableColumn.setCellFactory(column -> new DragSelectionCell());
tableColumn.setOnEditCommit(e -> {
Map<String, Object> rowValue = e.getRowValue();
String cellValue = e.getNewValue();
String oldValue = e.getOldValue();
if (cellValue.equals(oldValue)) {
return;
}
int column = e.getTablePosition().getColumn();
int row = e.getTablePosition().getRow();
this.editingRows.add(new EditRowInfo()
.rowData(rowValue)
.newValue(cellValue)
.title(title)
.row(row)
.column(column)
);
rowValue.put(title, cellValue);
MapTable.this.getSelectionModel().clearSelection();
});
return tableColumn;
}
public List<EditRowInfo> getEditingRows() {
return editingRows;
}
/**
* 按ctrl+C时将所选择的内容复制到系统粘贴板
*/
private void setCopyAction() {
// 设置选择单元格
this.getSelectionModel().setCellSelectionEnabled(true);
this.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
this.setOnKeyPressed(event -> {
if (event.getCode() == KeyCode.C && event.isControlDown()) {
// ctrl+c复制单元格,shift+ctrl+c复制整行
copyToClipboard(event.isShiftDown());
}
});
}
/**
* 复制选中的数据
*
* @param copyLines 是否复制整行,如果为true,那么即使只是选择了某一个单元格也会复制这个单元格所在的整行
*/
public void copyToClipboard(boolean copyLines) {
ObservableList<TablePosition> selectedCells = this.getSelectionModel().getSelectedCells();
Map<Integer, StringBuilder> rowValues = new HashMap<>(16);
if (null != selectedCells && 0 != selectedCells.size()) {
selectedCells.forEach(position -> {
int row = position.getRow();
if (!copyLines) {
String label = (String) position.getTableColumn().getCellObservableValue(row).getValue();
if (null != label) {
rowValues.computeIfAbsent(row, n -> new StringBuilder()).append(label.trim()).append("\t");
}
} else {
if (rowValues.containsKey(row)) {
return;
}
StringBuilder stringBuilder = new StringBuilder();
Map<String, Object> map = this.getItems().get(row);
this.getColumns().forEach(column -> {
String field = column.getText();
String value = MapUtils.getString(map, field);
stringBuilder.append(value).append("\t");
});
rowValues.put(row, stringBuilder);
}
});
}
String value = rowValues.values()
.stream()
.reduce((s1, s2) -> s1.append("\r\n").append(s2))
.map(StringBuilder::toString)
.orElse("");
if (value.endsWith("\t")) {
value = value.substring(0, value.length() - 1);
}
Clipboard.getSystemClipboard().clear();
Map<DataFormat, Object> map = new HashMap<>(2);
map.put(DataFormat.PLAIN_TEXT, value);
Clipboard.getSystemClipboard().setContent(map);
}
public class DragSelectionCell extends TextFieldTableCell<Map<String, Object>, String> {
public DragSelectionCell() {
super(new DefaultStringConverter());
setOnDragDetected(e -> {
startFullDrag();
startRow.setValue(getIndex());
startCol.setValue(getTableColumn());
});
setOnMouseDragEntered(event -> {
endRow.setValue(getIndex());
endCol.setValue(getTableColumn());
getTableView().getSelectionModel().clearSelection();
getTableView().getSelectionModel().selectRange(startRow.getValue(), startCol.getValue(), endRow.getValue(), endCol.getValue());
});
// 对行的index进行监听,如果index发生变更,则监听表格的选择事件,当选择的行发生变化时,修改当前行的背景色
this.tableRowProperty().addListener((observable, oldValue, newValue) ->
MapTable.this.getSelectionModel().getSelectedCells().addListener((ListChangeListener<? super TablePosition>) l -> {
ObservableList<? extends TablePosition> list = l.getList();
List<Integer> rowList = list.stream().map(TablePosition::getRow).collect(Collectors.toList());
if (rowList.contains(newValue.getIndex())) {
if (this.isSelected()) {
this.setBackground(new Background(new BackgroundFill(Color.valueOf("#0C739F"), null, null)));
} else {
this.setBackground(new Background(new BackgroundFill(Color.LIGHTGRAY, null, null)));
}
} else {
this.setBackground(null);
}
}));
}
}
}