子类化QTableWidget
类Spreadsheet派生自QTableWidget,如图所示。QTableWidget是一组格子,可以非常有效地用来表达二维稀疏数组。它可以在规定的维数内显示用户滚动到的任一单元格。当用户在一个空单元格内输入一些文本的时候,QTableWidget会自动创建一个用来存储这些文本的QTableWidgetItem。QTableWidget派生自QTableView ,它是模型/视图类之一。
Spreadsheet.h
#ifndef SPREADSHEET_H
#define SPREADSHEET_H
#include <QTableWidget>
class Cell;
class SpreadsheetCompare;
class Spreadsheet : public QTableWidget
{
Q_OBJECT
public:
Spreadsheet(QWidget *parent = 0);
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
头文件是从Cell和SpreadsheetCompare 类的前置声明开始的。
QTableWidget单元格的属性,比如它的文本和对齐方式等,都存储在QTableWidgetItem中。与QTableWidget不同的是,QTableWidgetItem不是一个窗口部件类,而是一个纯粹的数据类。Cell类派生自QTableWidgetltem。
autoRecalculate()
autoRecalculate()函数实现为内联函数,是因为无论自动重新计算的标识符生效与否,它都必须要有返回值。
当实现MainWindow时,我们依赖于Spreadsheet中的一些公有函数。例如,我们从MainWindow::newFile()中调用clear()来重置电子制表软件。也使用了一些从QTableWidget中继承而来的函数,特别是setCurrentCell()和setShowGrid()。
Spreadsheet提供了许多实现Edit、Tools和Options菜单中的动作的槽;并且它也提供了一个modified()信号,用来告知用户可能已经发生的任何变化。
somethingChanged()
还定义了一个由Spreadsheet类内部使用的私有槽。
private
在这个类的私有段中,声明了3个常量、4个函数和1个变量。
class SpreadsheetCompare
在这个头文件的最后,给出了SpreadsheetCompare类的定义。当查看Spreaseet:sort()时,会解释这个类。
spreadsheet.cpp
#include <QtWidgets>
#include "cell.h"
#include "spreadsheet.h"
Spreadsheet::Spreadsheet(QWidget *parent)
: QTableWidget(parent)
{
autoRecalc = true;
setItemPrototype(new Cell);
setSelectionMode(ContiguousSelection);
connect(this, SIGNAL(itemChanged(QTableWidgetItem *)),
this, SLOT(somethingChanged()));
clear();
}
void Spreadsheet::clear()
{
setRowCount(0);
setColumnCount(0);
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);
}
通常情况下,当用户在一个空单元格中输入一些文本的时候,QTableWidget将会自动创建一个QTableWidgtItem来保存这些文本。在电子制表软件中,我们想利用将要创建的Cell项来代替QTableWidgetItem。这可以通过在构造函数中调用setItemPrototype()来完成。实际上,QTableWidget会在每次需要新项的时候把所传递的项以原型的形式克隆出来。
同样是在构造函数中,我们将选择模式设置为QAbstractemView::ContiguousSelection。从而可以允许简单矩形选择框方法。我们把表格窗口部件的ienChanged()信号连接到私有槽somethingChanged()上,这可以确保在用户编辑一个单元格的时候,somethingChanged()槽可以得到调用。最后,调用clear()来重新调整表格的尺寸大小并且设置列标题。
clear()
clear()函数是从Spreadsheet构造函数中得到调用的,用来初始化电子制表软件。它也会在MainWindow::newFile()中得到调用。
我们原本使用QTableWidget::clear()来清空所有项和任意选择,但是那样做的话,这些标题将会以当前大小的尺寸而被留下。相反的是,我们要把表格向下调整为0x0。这样就可以完全清空整个表格,包括这些标题。
然后,重新调整表的大小为ColumnCount * RowCount(26 * 999),并且把QTableWidgetItem水平方向上的标题修改为列名"A",“B” ,…,“Z”。不需要设置垂直标题的标签,因为这些标签的默认值是“1”,“2”,.,”999“。后,把单元格光标移动到单元格A1处。
QTableWidget由多个子窗口部件构成。在它的顶部有一个水平的QHeaderView,左侧有一个垂直的QHeaderView,还有两个QScrollBar在它的中间区域被一个名为视口(viewport)的特殊窗口部件所占用,QTableWidget可以在它上面绘制单元格。
通过从QTableView和QAbstraetScrollArea中继承的一些函数,可以访问这些不同的子窗口部件(参见图4.2)。
QAbstractSrollArea提供了一个可以滚动的视口和两个可以打开或关闭的滚动条。
Cell *Spreadsheet::cell(int row, int column) const
{
return static_cast<Cell *>(item(row, column));
}
cell()私有函数可以根据给定的行和列返回一个Cell对象。它几乎和QTableWidget::item()函数的作用一样,只不过它返回的是一个Cell指针,而不是一个QTableWidgetItem指针。
QString Spreadsheet::text(int row, int column) const
{
Cell *c = cell(row, column);
if (c) {
return c->text();
} else {
return "";
}
}
tex()私有函数可以返回给定单元格中的文本。如果cell( )返回的是一个空指针,则表示该单元格是空的,因而返回一个空字符串。
QString Spreadsheet::formula(int row, int column) const
{
Cell *c = cell(row, column);
if (c) {
return c->formula();
} else {
return "";
}
}
formula()函数返回给定单元格中的公式。在很多情况下,公式和文本是相同的。例如,公式“Hello"等价于字符串“Hello",所以如果用户在单元格中输入"Hello"并且按下回车键,那么该单元格就会显示文本“Hello"。但是还有一些例外的情况:
● 如果公式是一个数字那么它就会被认为是一个数字。例如,公式"1.50"等价于双精度实数(double)的1.5,它在电子制表软件中会被显示为右对齐的"1.5"。
● 如果公式以单引号开始,那么公式的剩余部分将会被认为是文本。例如,公式“12345"等价于字符串"12345"。
● 如果公式以等号开始,那么公式将会被认为是一个算术公式。例如, 如果单元格A1包含"12"并且单元格A2包含"6",那么公式“=A1+A2”就会等于18。
把公式转换成值的任务是由Cell类完成的。这时,要记住的事情是显示在单元格内的文本是公式的结果,而不是公式本身。
void Spreadsheet::setFormula(int row, int column, const QString &formula)
{
Cell *c = cell(row, column);
if (!c) {
c = new Cell;
setItem(row, column, c);
}
c->setFormula(formula);
}
setFormula()私有函数可以设置用于给定单元格的公式。如果该单元格已经有一个Cell对象,那么我们就重新使用它。否则,可以创建一个新的Cell对象并且调用QTableWidget::setItem()把它插入到表中。最后,调用该单元格自己的setFormula()函数,但如果这个单元格已经显示在屏幕上,那么就重新绘制它。我们不需要担心随后对这个Cell对象的删除操作,因为QTableWidget会得到这个单元格的所有权,并且会在正确的时候自动将其删除。
QString Spreadsheet::currentLocation() const
{
return QChar('A' + currentColumn())
+ QString::number(currentRow() + 1);
}
currentLocation()函数返回当前单元格的位置,它是按照电子制表软件的通常格式,也就是一个列字母后跟上行号的形式来表示这个位置的值。MainWindow::updateSatusBar()使用它把这个单元格的位置显示在状态栏上。
QString Spreadsheet::currentFormula() const
{
return formula(currentRow(), currentColumn());
}
curentFomula()函数返回当前单元格的公式。它是从MainWindow::upatetatusBar()中得到调用的。
void Spreadsheet::somethingChanged()
{
if (autoRecalc)
recalculate();
emit modified();
}
如果启用了"auto-recalculate"(自动重新计算) ,那么somethingChanged()私有槽就会重新计算整个电子制表软件。它也会发射modified()信号。
把数据存储位项
在Spreadsheet应用程序中,每一个非空单元格都被当做一个独立的QTableWidgetItem对象而保存到内存中。把数据存储为项(item)是一种对QListWidgetItem和QTreeWidgetItem进行操作的方法,该方法也可用于QListWidget和QTreeWidget。
Qt的项类可以用作非常规的数据持有者。例如,一个QTableWigetItem已经存储了一些属性,其中包括一个字符串、一种字体、一种颜色和一个图标,以及一个返回到QTableWidget的指针。项也可以保存数据(QVariant型),包括一些已经注册过的自定义类型,以及通过项类的子类化,我们还可以提供其他功能。
许多老一点的工具包在它们的项类中提供一个void指针来存储自定义数据。在Qt中,更为自然的方法是使用带QVariant的setData(),但如果需要一个void指针,那么可以通过子类化一个项类并且添加一个void指针成员变量来简单实现这一点。
对于更具有挑战性的数据处理需求,比如大数据集、复杂数据项,数据库集成以及多数据视图等,Qt提供了一套模型/视图(mode/view)类,利用这些类可以把数据从它们的直观表示中分离出来。