此类继承于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]; }
浙公网安备 33010602011771号