JavaFX表格选择单元格时高亮所在行功能实现

使用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);
                        }
                    }));
        }
    }
}

你可能感兴趣的:(JavaFX)