感觉这本书的顺序设计的太不合理了,出现的最多的一句话就是后面会讲。按照使用的顺序讲不行吗?搞得代码都运行不了。
我决定先直接跳到73页,子类化QTableWidgetItem这一节。因为前面功能的实现都依赖于这一部分。
预备知识:
C++关键字 mutable:
mutalbe的中文意思是“可变的,易变的”,跟constant(既C++中的const)是反义词。
在C++中,mutable也是为了突破const的限制而设置的。被mutable修饰的变量,将永远处于可变的状态,即使在一个const函数中。
我们知道,如果类的成员函数不会改变对象的状态,那么这个成员函数一般会声明成const的。但是,有些时候,我们需要在const的函数里面修改一些跟类状态无关的数据成员,那么这个数据成员就应该被mutalbe来修饰。
----------------------------------------------------------------------------------------------------------------
Start:
QTableWidget是一个QT已经实现了的二维表格的类,每一个单元格的文本都用一个自动创建的QTableWidgetItem来存储。
为了实现更多的功能,我们自己创建一个类Cell来扩展QTableWidgetItem的功能。
QVariant 类: The QVariant class acts like a union for the most common Qt data types.
在Cell类中,用两个私有变量来扩展功能。
mutable QVariant cachedValue;
mutable bool cacheIsDirty;
cachedValue 缓存单元格的值
若单元格的值不是最新的 cacheIsDirty 设为 true
好吧 后面变成了纯粹的看这一部分的代码,计算表达式的值时,三个函数的循环调用有些难度。
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 QVariant cachedValue; mutable bool cacheIsDirty; }; #endif // CELL_H
cell.cpp
#include <QtGui> #include "cell.h" Cell::Cell() { setDirty(); } //新建一个单元格时调用 QTableWidgetItem *Cell::clone() const { return new Cell(*this); } 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; } void Cell::setData(int role, const QVariant &value) { QTableWidgetItem::setData(role, value); if (role == Qt::EditRole) setDirty(); //如果有新的公式就把cacheIsDirty设为True 以保证下次调用text时重新计算值 } QVariant Cell::data(int role) const //重新实现QTableWidgetItem::data { if (role == Qt::DisplayRole) { //如果是DisplayRole调用这个函数 返回应该显示的文本 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 { //如果 EditRole调用 返回该单元格的公式 return QTableWidgetItem::data(role); } } void Cell::setFormula(const QString &formula) { setData(Qt::EditRole, formula); //对编辑角色调用setData } QString Cell::formula() const { return data(Qt::EditRole).toString(); //重新获得该项的EditRole数据 } //对于下面三个函数的循环套用没完全看懂 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; //这里的return 使得evalFactor中调用该函数成为可能 ++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 { 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; }
然后回去看spreadsheet部分,把定义和clear()实现后终于可以显示一个像样子的界面了。虽然没有实现功能,但也不错了。