Qt-spreadsheet>>gotocelldialog&spreadsheet&cell

    科技2024-10-04  24

    spreadsheet.h

    #ifndef SPREADSHEET_H #define SPREADSHEET_H #include <QTableWidget> class Cell; class SpreadsheetCompare; class Spreadsheet: public QTableWidget { public: Spreadsheet(QWidget *parent=nullptr); bool autoRecalculate() const { return autoRecalc; } QString currentLocation() const; QString currentFormula() const; QTableWidgetSelectionRange selectedRange() const; void clear(); bool readFile(const QString &fileName); bool writeFile(const QString &fileName); void sort(const SpreadsheetCompare &compare); public slots: void cut(); void copy(); void paste(); void del(); void selectCurrentRow(); void selectCurrentColumn(); void recalculate(); void setAutoRecalculate(bool recalc); void findNext(const QString &str, Qt::CaseSensitivity cs); void findPrevious(const QString &str, Qt::CaseSensitivity cs); signals: void modified(); private slots: void somethingChanged(); private: enum { MagicNumber = 0x7F51C883, RowCount = 999, ColumnCount = 26 }; Cell *cell(int row, int column) const; QString text(int row, int column) const; QString formula(int row, int column) const; void setFormula(int row, int column, const QString &formula); bool autoRecalc; }; class SpreadsheetCompare { public: bool operator()(const QStringList &row1, const QStringList &row2) const; enum { KeyCount = 3 }; int keys[KeyCount]; bool ascending[KeyCount]; }; #endif // SPREADSHEET_H

    spreadsheet.cpp

    #include "spreadsheet.h" #include <QtWidgets> #include "cell.h" Spreadsheet::Spreadsheet(QWidget *parent) : QTableWidget(parent) { autoRecalc = true; ///将要创建的Cell项来代替QTableWidgetItem setItemPrototype(new Cell); //ContiguousSelection允许简单矩形选择 //Contiguous相连的 //其实就是说shift按键起作用 而SingleSelection就是单一选择 setSelectionMode(ContiguousSelection); //QTableWidget中的一个信号itemChanged //用cellChanged()与itemchanged实际上是一致的 connect(this, SIGNAL(itemChanged(QTableWidgetItem *)), this, SLOT(somethingChanged())); clear(); } QString Spreadsheet::currentLocation() const { ///默认都是从0开始 QString类的处理 return QChar('A' + currentColumn()) + QString::number(currentRow() + 1); } QString Spreadsheet::currentFormula() const { return formula(currentRow(), currentColumn()); } QTableWidgetSelectionRange Spreadsheet::selectedRange() const { //这是一个模板类 必须先显示指定是哪个模版才可以 QList<QTableWidgetSelectionRange> ranges = selectedRanges(); if (ranges.isEmpty()) return QTableWidgetSelectionRange(); return ranges.first(); } void Spreadsheet::clear() { setRowCount(0); setColumnCount(0); //QTableWidget::clear(); setRowCount(RowCount); setColumnCount(ColumnCount); for (int i = 0; i < ColumnCount; ++i) { QTableWidgetItem *item = new QTableWidgetItem; item->setText(QString(QChar('A' + i))); //把水平方向上的标题进行修改 setHorizontalHeaderItem(i, item); } setCurrentCell(0, 0); } bool Spreadsheet::readFile(const QString &fileName) { QFile file(fileName); if (!file.open(QIODevice::ReadOnly)) { QMessageBox::warning(this, tr("Spreadsheet"), tr("Cannot read file %1:\n%2.") .arg(file.fileName()) .arg(file.errorString())); return false; } QDataStream in(&file); in.setVersion(QDataStream::Qt_4_3); quint32 magic; in >> magic; if (magic != MagicNumber) { QMessageBox::warning(this, tr("Spreadsheet"), tr("The file is not a Spreadsheet file.")); return false; } clear(); quint16 row; quint16 column; QString str; QApplication::setOverrideCursor(Qt::WaitCursor); while (!in.atEnd()) { in >> row >> column >> str; setFormula(row, column, str); } QApplication::restoreOverrideCursor(); return true; } bool Spreadsheet::writeFile(const QString &fileName) { QFile file(fileName); if (!file.open(QIODevice::WriteOnly)) { QMessageBox::warning(this, tr("Spreadsheet"), tr("Cannot write file %1:\n%2.") .arg(file.fileName()) .arg(file.errorString())); return false; } QDataStream out(&file); out.setVersion(QDataStream::Qt_4_3); out << quint32(MagicNumber); //变为等待光标 QApplication::setOverrideCursor(Qt::WaitCursor); for (int row = 0; row < RowCount; ++row) { for (int column = 0; column < ColumnCount; ++column) { QString str = formula(row, column); if (!str.isEmpty()) out << quint16(row) << quint16(column) << str; } } //恢复光标 QApplication::restoreOverrideCursor(); return true; } void Spreadsheet::sort(const SpreadsheetCompare &compare) { QList<QStringList> rows; QTableWidgetSelectionRange range = selectedRange(); int i; for (i = 0; i < range.rowCount(); ++i) { QStringList row; for (int j = 0; j < range.columnCount(); ++j) row.append(formula(range.topRow() + i, range.leftColumn() + j)); rows.append(row); } //使用的是归并排序 必须是容器 qStableSort(rows.begin(), rows.end(), compare); 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(); somethingChanged(); } void Spreadsheet::cut() { copy(); del(); } /* * *int bottomRow () const int columnCount () const int leftColumn () const int rightColumn () const int rowCount () const int topRow () const */ void Spreadsheet::copy() { QTableWidgetSelectionRange range = selectedRange(); QString 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); } void Spreadsheet::paste() { QTableWidgetSelectionRange range = selectedRange(); QString str = QApplication::clipboard()->text(); //利用\n进行分离 QStringList rows = str.split('\n'); int numRows = rows.count(); int numColumns = rows.first().count('\t') + 1; if (range.rowCount() * range.columnCount() != 1 && (range.rowCount() != numRows || range.columnCount() != numColumns)) { QMessageBox::information(this, tr("Spreadsheet"), tr("The information cannot be pasted because the copy " "and paste areas aren't the same size.")); return; } for (int i = 0; i < numRows; ++i) { QStringList columns = rows[i].split('\t'); for (int j = 0; j < numColumns; ++j) { int row = range.topRow() + i; int column = range.leftColumn() + j; if (row < RowCount && column < ColumnCount) setFormula(row, column, columns[j]); } } somethingChanged(); } void Spreadsheet::del() { //selectedRange是选择范围 //selectedItems是选择某一项 QList<QTableWidgetItem *> items = selectedItems(); if (!items.isEmpty()) { //define Q_FOREACH(variable,container) ///关键字foreach, 这是一个Qt中独有的,如果你想顺序遍历一个容器的所有元素, ///那么你就可以使用这个关键字 foreach (QTableWidgetItem *item, items) delete item; ///迭代器删除的只是指针 // QList<QTableWidgetItem *>::Iterator iter = items.begin(); // for(iter = items.begin();iter != items.end();) // { // iter = items.erase(iter); // } somethingChanged(); } } void Spreadsheet::selectCurrentRow() { selectRow(currentRow()); } void Spreadsheet::selectCurrentColumn() { selectColumn(currentColumn()); } void Spreadsheet::recalculate() { for (int row = 0; row < RowCount; ++row) { for (int column = 0; column < ColumnCount; ++column) { if (cell(row, column)) cell(row, column)->setDirty(); } } ///对options->auto-recalculate做出反应 启用这个特性就会重绘,在somethingchanged()中得到调用 viewport()->update(); } void Spreadsheet::setAutoRecalculate(bool recalc) { autoRecalc = recalc; if (autoRecalc) recalculate(); } void Spreadsheet::findNext(const QString &str, Qt::CaseSensitivity cs) { int row = currentRow(); int column = currentColumn() + 1; while (row < RowCount) { while (column < ColumnCount) { //调用了QString中的contains方法 if (text(row, column).contains(str, cs)) { //移动相应的光标 clearSelection(); setCurrentCell(row, column); activateWindow(); return; } ++column; } column = 0; ++row; } QApplication::beep(); } void Spreadsheet::findPrevious(const QString &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; } QApplication::beep(); } void Spreadsheet::somethingChanged() { if (autoRecalc) recalculate(); //emit modified(); } Cell *Spreadsheet::cell(int row, int column) const { //强制类型转换 向下转换是安全的 /* *当派生类以public方式继承基类时,编译器可自动执行的转换(向上转型 upcasting 安全转换) 1、派生类对象指针自动转化为基类对象指针 2、派生类对象引用自动转化为基类对象引 用 3、派生类对象自动转换为基类对象(特有的成员消失) 当派生类以private/protected方式继承基类时 1、派生类对象指针(引用)转化为基类对象指针(引用)需用强制类型转化。但不能用static_cast,要用reinterpret_cast 2、不能把派生类对象强制转换为基类对象 基类对象指针(引用)可用强制类型转换为派生类对象指针(引用), 而基类对象无法执行这类转换. 向下转型不安全,没有自动转换的机制 */ //如果item是为空的话 返回空指针!并不是马上就分配好item return static_cast<Cell *>(item(row, column)); } void Spreadsheet::setFormula(int row, int column, const QString &formula) { Cell *c = cell(row, column); if (!c) { c = new Cell; //setItem将其插入到表中 setItem(row, column, c); } c->setFormula(formula); } ///返回公式 ///数字 字幕 =A1+A2 可以进行改动 QString Spreadsheet::formula(int row, int column) const { Cell *c = cell(row, column); if (c) { return c->formula(); } else { return ""; } } QString Spreadsheet::text(int row, int column) const { Cell *c = cell(row, column); if (c) { return c->text(); } else { return ""; } } bool SpreadsheetCompare::operator()(const QStringList &row1, const QStringList &row2) const { ///提供了三个比较 如果第一个比较相等 则跳过比较第二个 for (int i = 0; i < KeyCount; ++i) { int column = keys[i]; if (column != -1) { ///明显是比较每一列 if (row1[column] != row2[column]) { if (ascending[i]) { return row1[column] < row2[column]; } else { return row1[column] > row2[column]; } } } } return false; }

    cell.h

    #ifndef CELL_H #define CELL_H #include <QTableWidgetItem> class Cell : public QTableWidgetItem { public: Cell(); QTableWidgetItem *clone() const; void setData(int role, const QVariant &value); QVariant data(int role) const; void setFormula(const QString &formula); QString formula() const; void setDirty(); private: QVariant value() const; QVariant evalExpression(const QString &str, int &pos) const; QVariant evalTerm(const QString &str, int &pos) const; QVariant evalFactor(const QString &str, int &pos) const; //添加的两个私有变量 ///使用mutable 就可以在const函数中修改这个参数 mutable QVariant cachedValue; mutable bool cacheIsDirty; }; #endif // CELL_H

    cell.cpp

    #include "cell.h" #include <QtWidgets> #include "cell.h" Cell::Cell() { setDirty(); } QTableWidgetItem *Cell::clone() const { //调用默认的构造函数 return new Cell(*this); } void Cell::setData(int role, const QVariant &value) { QTableWidgetItem::setData(role, value); if (role == Qt::EditRole) //以确保下一次调用text()的时候可以重新计算该单元格 setDirty(); } QVariant Cell::data(int role) const { if (role == Qt::DisplayRole) { //Valid是有效的 if (value().isValid()) { return value().toString(); } else { return "####"; } } else if (role == Qt::TextAlignmentRole) { if (value().type() == QVariant::String) { return int(Qt::AlignLeft | Qt::AlignVCenter); } else { return int(Qt::AlignRight | Qt::AlignVCenter); } } else { return QTableWidgetItem::data(role); } } void Cell::setFormula(const QString &formula) { setData(Qt::EditRole, formula); } QString Cell::formula() const { return data(Qt::EditRole).toString(); } void Cell::setDirty() { cacheIsDirty = true; } //使用无效变量 const QVariant Invalid; QVariant Cell::value() const { if (cacheIsDirty) { cacheIsDirty = false; QString formulaStr = formula(); if (formulaStr.startsWith('\'')) { cachedValue = formulaStr.mid(1); } else if (formulaStr.startsWith('=')) { cachedValue = Invalid; QString expr = formulaStr.mid(1); expr.replace(" ", ""); expr.append(QChar::Null); int pos = 0; cachedValue = evalExpression(expr, pos); if (expr[pos] != QChar::Null) cachedValue = Invalid; } else { bool ok; double d = formulaStr.toDouble(&ok); if (ok) { cachedValue = d; } else { cachedValue = formulaStr; } } } return cachedValue; } QVariant Cell::evalExpression(const QString &str, int &pos) const { QVariant result = evalTerm(str, pos); while (str[pos] != QChar::Null) { QChar op = str[pos]; if (op != '+' && op != '-') return result; ++pos; QVariant term = evalTerm(str, pos); if (result.type() == QVariant::Double && term.type() == QVariant::Double) { if (op == '+') { result = result.toDouble() + term.toDouble(); } else { result = result.toDouble() - term.toDouble(); } } else { result = Invalid; } } return result; } QVariant Cell::evalTerm(const QString &str, int &pos) const { QVariant result = evalFactor(str, pos); while (str[pos] != QChar::Null) { QChar op = str[pos]; if (op != '*' && op != '/') return result; ++pos; QVariant factor = evalFactor(str, pos); if (result.type() == QVariant::Double && factor.type() == QVariant::Double) { if (op == '*') { result = result.toDouble() * factor.toDouble(); } else { if (factor.toDouble() == 0.0) { result = Invalid; } else { result = result.toDouble() / factor.toDouble(); } } } else { result = Invalid; } } return result; } QVariant Cell::evalFactor(const QString &str, int &pos) const { QVariant result; bool negative = false; if (str[pos] == '-') { negative = true; ++pos; } if (str[pos] == '(') { ++pos; result = evalExpression(str, pos); if (str[pos] != ')') result = Invalid; ++pos; } else { ///regExp 正则表达式 QRegExp regExp("[A-Za-z][1-9][0-9]{0,2}"); QString token; while (str[pos].isLetterOrNumber() || str[pos] == '.') { token += str[pos]; ++pos; } if (regExp.exactMatch(token)) { int column = token[0].toUpper().unicode() - 'A'; int row = token.mid(1).toInt() - 1; Cell *c = static_cast<Cell *>( tableWidget()->item(row, column)); if (c) { result = c->value(); } else { result = 0.0; } } else { bool ok; result = token.toDouble(&ok); if (!ok) result = Invalid; } } if (negative) { if (result.type() == QVariant::Double) { result = -result.toDouble(); } else { result = Invalid; } } return result; }

    gotocelldialog.h

    #ifndef GOTOCELLDIALOG_H #define GOTOCELLDIALOG_H #include <QDialog> class QLabel; class QLineEdit; class QPushButton; class GoToCellDialog:public QDialog { Q_OBJECT public: GoToCellDialog(QDialog *parent =nullptr); QLabel *label; QLineEdit *lineEdit; QPushButton *okButton; QPushButton *cancelButton; private slots: void on_lineEdit_textChanged(); }; #endif // GOTOCELLDIALOG_H

    gotocelldialog.cpp

    #include "gotocelldialog.h" #include <QtWidgets> GoToCellDialog::GoToCellDialog(QDialog *parent) :QDialog(parent) { label = new QLabel(tr("&Cell Location")); lineEdit = new QLineEdit; label->setBuddy(lineEdit); okButton = new QPushButton(tr("OK")); okButton->setEnabled(false); okButton->setDefault(true); cancelButton = new QPushButton(tr("Cancel")); QHBoxLayout *topLayout = new QHBoxLayout; topLayout->addWidget(label); topLayout->addWidget(lineEdit); QHBoxLayout *lowerLayout = new QHBoxLayout; lowerLayout->addStretch(); lowerLayout->addWidget(okButton); lowerLayout->addWidget(cancelButton); QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addLayout(topLayout); mainLayout->addLayout(lowerLayout); setLayout(mainLayout); setWindowTitle(tr("Go to Cell")); setFixedHeight(sizeHint().height()); QRegExp regExp("[A-Za-z][1-9][0-9]{0,2}"); lineEdit->setValidator(new QRegExpValidator(regExp,this)); connect(okButton,SIGNAL(clicked()),this,SLOT(accept())); connect(cancelButton,SIGNAL(clicked()),this,SLOT(reject())); connect(lineEdit,SIGNAL(textChanged(const QString &)),this,SLOT(on_lineEdit_textChanged())); } void GoToCellDialog::on_lineEdit_textChanged() { okButton->setEnabled(lineEdit->hasAcceptableInput()); }
    Processed: 0.015, SQL: 8