此类继承于QTableWidget,代码如下。

package jqt;

import io.qt.core.*;
import io.qt.widgets.*;

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

public class QSpreadsheet extends QTableWidget{
    //private variables
    private boolean autoRecal;
    private final int MagicNumber = 0x7F51C883, RowCount = 999, ColumnCount = 26;
    public QSpreadsheet(){
        this(null);
    }
    public QSpreadsheet(QWidget parent){
        super(parent);
        autoRecal = false;
        setItemPrototype(new QCell());
        setSelectionMode(SelectionMode.ContiguousSelection);
        this.itemChanged.connect(this::contentChanged);
        init();
    }
    //public methods
    public void init(){
        setRowCount(0);
        setColumnCount(0);
        setRowCount(RowCount);
        setColumnCount(ColumnCount);
        for (int i = 0; i < ColumnCount; i++){
            QTableWidgetItem item = new QTableWidgetItem();
            item.setText(String.valueOf((char) ('A' + i)));
            setHorizontalHeaderItem(i, item);
        }
        setCurrentCell(0, 0);
    }
    public boolean autoRecal(){
        return autoRecal;
    }
    public String currentLocation(){
        return (char)('A' + currentColumn()) + String.valueOf(currentRow() + 1);
    }
    public String currentFormula(){
        return formula(currentRow(), currentColumn());
    }
    public boolean readFile(String fileName){
        QFile file = new QFile(fileName);
        if (!file.open(QIODeviceBase.OpenModeFlag.ReadOnly)){
            QMessageBox.warning(this, "表格", "文件%1不可读".arg(file.fileName()));
            return false;
        }
        QDataStream in = new QDataStream(file);
        in.setVersion(QDataStream.Version.Qt_6_7.value());
        int magic = in.readInt();
        if (magic != MagicNumber){
            QMessageBox.warning(this, "表格", "%1不是有效格式".arg(file.fileName()));
            return false;
        }
        init();
        QApplication.setOverrideCursor(Qt.CursorShape.WaitCursor);
        int row, column;
        String formula;
        while (!in.atEnd()){
            row = in.readInt();
            column = in.readInt();
            formula = in.readString();
            if (row >= 0 && row < rowCount() && column >=0 && column < columnCount())
                setFormula(row, column, formula);
        }
        QApplication.restoreOverrideCursor();
        file.close();
        return true;
    }
    public boolean writeFile(String fileName){
        QFile file = new QFile(fileName);
        if (!file.open(QIODeviceBase.OpenModeFlag.WriteOnly)){
            QMessageBox.warning(this, "表格","文件%1不可写".arg(file.fileName()));
            return false;
        }
        QDataStream out = new QDataStream(file);
        out.setVersion(QDataStream.Version.Qt_6_7.value());
        out.writeInt(MagicNumber);
        QApplication.setOverrideCursor(Qt.CursorShape.WaitCursor);
        for (int i = 0; i < rowCount(); i++)
            for (int j = 0; j < columnCount(); j++){
                String str = formula(i, j);
                if (!str.isEmpty()){
                    out.writeInt(i);
                    out.writeInt(j);
                    out.writeString(str);
                }
            }
        QApplication.restoreOverrideCursor();
        file.close();
        return true;
    }
    public QTableWidgetSelectionRange selectedRange(){
        List<QTableWidgetSelectionRange> ranges = selectedRanges();
        if (ranges.isEmpty())
            return new QTableWidgetSelectionRange();
        return ranges.first();
    }
    public void sort(SpreadsheetComparator comparator){
        List<QStringList> rows = new ArrayList<>();
        QTableWidgetSelectionRange range = selectedRange();
        int i;

        for (i = 0; i < range.rowCount(); ++i) {
            QStringList row = new QStringList();
            for (int j = 0; j < range.columnCount(); ++j) {
                row.append(formula(range.topRow() + i, range.leftColumn() + j));
            }
            rows.add(row);
        }

        Collections.sort(rows, comparator);

        for (i = 0; i < range.rowCount(); ++i) {
            for (int j = 0; j < range.columnCount(); ++j) {
                setFormula(range.topRow() + i, range.leftColumn() + j, rows[i][j]);
            }
        }

        clearSelection();
        contentChanged();
    }
    //private methods
    private QCell cell(int row, int col){
        return (QCell) item(row, col);
    }
    private String text(int row , int col){
        QCell c = cell(row, col);
        if (c != null)
            return c.text();
        return "";
    }
    private String formula(int row , int col){
        QCell c = cell(row, col);
        if (c != null)
            return c.formula();
        return "";
    }
    private void setFormula(int row, int col, String formula){
        QCell c = cell(row, col);
        if (c == null){
            c = new QCell();
            setItem(row, col, c);
        }
        c.setFormula(formula);
    }
    //public signals
    public final Signal0 modified = new Signal0();
    //pulbic slots
    public void cut(){
        copy();
        del();
    }
    public void copy(){
        QTableWidgetSelectionRange range = selectedRange();
        if (range == null)
            return;
        String str = "";
        for (int i = 0; i< range.rowCount(); i++){
            if (i > 0)
                str += '\n';
            for (int j = 0; j < range.columnCount(); j++){
                if (j > 0)
                    str += '\t';
                str += formula(range.topRow() + i, range.leftColumn() + j);
            }
        }
        QApplication.clipboard().setText(str);
    }
    public void paste() {
        final QTableWidgetSelectionRange range = selectedRange();
        if (range == null)
            return;
        final String clipboardText = QApplication.clipboard().text().trim();
        if (clipboardText.isEmpty())
            return;
        // 预解析剪贴板数据(兼容所有换行符)
        final String[] rows = clipboardText.split("\\R");
        if (rows.length == 0)
            return;
        // 单次遍历完成列数校验+数据解析
        final List<String[]> parsedData = new ArrayList<>(rows.length);
        int expectedColumns = -1;
        for (String row : rows) {
            final String[] columns = row.split("\t", -1);
            if (expectedColumns == -1) {
                expectedColumns = columns.length;
                parsedData.add(columns);
            } else if (columns.length == expectedColumns) {
                parsedData.add(columns);
            } else {
                QMessageBox.information(this, "表格", "粘贴失败,剪贴板中的列数不一致");
                return;
            }
        }
        // 动态计算粘贴范围(智能适应单/多单元格模式)
        final int startRow = range.topRow();
        final int startCol = range.leftColumn();
        final int maxRow = Math.min(startRow + parsedData.size(), RowCount);
        final int maxCol = Math.min(startCol + expectedColumns, ColumnCount);

        // 仅当选区为多单元格时才校验范围匹配性
        if (range.rowCount() * range.columnCount() != 1) {
            if (parsedData.size() != range.rowCount() || expectedColumns != range.columnCount()) {
                QMessageBox.information(this, "表格", "粘贴失败,所选范围不匹配");
                return;
            }
        }
        // 批量写入非空数据(优化内存访问)
        for (int i = 0; i < parsedData.size() && startRow + i < maxRow; i++) {
            final String[] rowData = parsedData.get(i);
            for (int j = 0; j < expectedColumns && startCol + j < maxCol; j++) {
                final String value = rowData[j];
                if (!value.isEmpty()) {
                    setFormula(startRow + i, startCol + j, value);
                }
            }
        }
        contentChanged();
    }
    public void del(){
        QList<QTableWidgetItem> items = selectedItems();
        if (!items.isEmpty()){
            for (QTableWidgetItem item : items)
                item.dispose();
            contentChanged();
        }
    }
    public void selectCurrentRow(){
        selectRow(currentRow());
    }
    public void selectCurrentColumn(){
        selectColumn(currentColumn());
    }
    public void recalculate(){
        for (int row = 0; row < RowCount; row++){
            for (int col = 0; col < ColumnCount; col++){
                if (cell(row, col) != null)
                    cell(row, col).setDirty();
            }
        }
        viewport().update();
    }
    public void setAutoRecal(boolean recalc){
        autoRecal = recalc;
        if (autoRecal)
            recalculate();
    }
    public void findNext(String str, Qt.CaseSensitivity cs){
        int row = currentRow();
        int column = currentColumn() + 1;
        while (row < RowCount){
            while (column < ColumnCount){
                if (text(row, column).contains(str, cs)){
                    clearSelection();
                    setCurrentCell(row, column);
                    activateWindow();
                    return;
                }
                column++;
            }
            column = 0;
            row++;
        }
    }
    public void findPrevious(String str, Qt.CaseSensitivity cs){
        int row = currentRow();
        int column = currentColumn() - 1;
        while (row >= 0){
            while (column >= 0){
                if (text(row, column).contains(str, cs)){
                    clearSelection();
                    setCurrentCell(row, column);
                    activateWindow();
                    return;
                }
                column--;
            }
            column = ColumnCount - 1;
            row--;
        }
    }
    //private slots
    private void contentChanged(){
        if (autoRecal)
            recalculate();
        modified.emit();
    }
}

其中的SpreadsheetComparator类实现如下。

package jqt;

import io.qt.core.QStringList;
import java.util.Comparator;

public class SpreadsheetComparator implements Comparator<QStringList>
{
    public SpreadsheetComparator(SortDialog sortDialog){
        keys[0] = sortDialog.ui.primaryColumnCombo.currentIndex();
        keys[1] = sortDialog.ui.secondaryColumnCombo.currentIndex() - 1;
        keys[2] = sortDialog.ui.tertiaryColumnCombo.currentIndex() - 1;
        ascending[0] = sortDialog.ui.primaryOrderCombo.currentIndex() == 0;
        ascending[1] = sortDialog.ui.secondaryOrderCombo.currentIndex() == 0;
        ascending[2] = sortDialog.ui.tertiaryOrderCombo.currentIndex() == 0;
    }
    public int compare(QStringList row1, QStringList row2) {
        for (int i = 0; i < KeyCount; i++) {
            int column = keys[i];
            if (column != -1) {
                String s1 = row1[column];
                String s2 = row2[column];

                boolean s1Empty = s1.isEmpty();
                boolean s2Empty = s2.isEmpty();

                // 处理空单元格的情况
                if (s1Empty || s2Empty) {
                    if (s1Empty && s2Empty) {
                        continue; // 都为空则继续下一个排序键
                    }
                    // 确保空单元格排在最后
                    if (s1Empty) {
                        return 1; // s1为空,排到后面
                    } else {
                        return -1; // s2为空,排到后面
                    }
                }

                // 非空时正常比较
                int cmp = s1.compareTo(s2);
                if (cmp != 0) {
                    return ascending[i] ? cmp : -cmp;
                }
            }
        }
        return 0;
    }
    private final int KeyCount = 3;
    private int[] keys = new int[KeyCount];
    private boolean[] ascending  = new boolean[KeyCount];
}

 

posted on 2025-04-03 12:49  dalgleish  阅读(27)  评论(0)    收藏  举报