【学习笔记】C++ GUI Qt4 第四章 4.1 *窗口部件和4.2子类化QTableWidget
第四章 实现应用程序的功能
前两章说明了如何创建Spreadsheet应用程序的用户界面。在这一章中,将通过编写它的底层功能函数来完成这个程序。此外,还将看到如何载入和保存文件,如何在内存中存储数据,如何实现剪贴板操作,以及如何向QTableWidget中添加对电子制表软件公式的支持等功能。
4.1 *窗口部件
QMainWindow的*区域可以被任意种类的窗口部件所占用。下面给出的是对所有可能情形的概述。
1、使用一个标准的Qt窗口部件
像QTableWidget或者QTextEdit这样的标准窗口部件可以用作*窗口部件。在这种情况下,这个应用程序的功能,如文件的载入和保存,必须在其他地方实现(例如,在QMainWindow的子类中)。
2、使用一个自定义窗口部件
特殊的应用程序通常需要在自定义窗口部件中显示数据。例如,一个图标编辑器程序就应当使用一个IconEditor窗口部件作为自己的*窗口部件。第5章将会说明如何在Qt中编写自定义窗口部件。
3、使用一个带布局管理器的普通QWidget
有时,应用程序的*区域会被许多窗口部件所占用。这时可以通过使用一个作为所有这些其他窗口部件父对象的QWidget,以及通过使用布局管理器管理这些子窗口部件的大小和位置来完成这一特殊情况。
4、使用切分窗口(splitter)
多个窗口部件一起使用的另一种方法是使用QSplitter。 QSplitter会在水平方向或者竖直方向上排列它的子窗口部件,用户可以利用切分条(splitter handle)控制它们的尺寸大小。切分窗口可以包含所有类型的窗口部件,包括其他切分窗口。
5、使用多文档界面工作空间
如果应用程序使用的是多文档界面,那么它的*区域就会被QMdiArea窗口部件所占据,并且每个多文档界面窗口都是它的一个子窗口部件。
布局、切分窗口和多文档界面工作空间都可以与标准的Qt窗口部件或者自定义窗口部件组合使用。第6章将会进一步深入地讲解这些类。对于Spreadsheet应用程序,会使用一个QTableWidget子类作为它的*窗口部件。类QTableWidget已经提供了我们所需要的绝大多数电子制表软件的功能,但是它还不支持剪贴板操作,并且也不能理解诸如“= A1+A2+A3"这样的电子制表软件公式的意义。我们将会在Spreadsheet 类中实现这些缺少的功能。
4.2 子类化QTableWidget
类Spreadsheet派生自QTableWidget,如图4.1 所示。QTableWidget是一组格子,可以非常有效地用来表达二维稀疏数组。它可以在规定的维数内显示用户滚动到的任一单元格。当用户在一个空单元格内输入一些文本的时候,QTableWidget会自动创建一个用来存储这些文本的QTableWidgetItem。QTableWidget派生自QTableView ,它是模型/视图类之一,我们将在第10章进一步了解它。
//Spreadsheet.h
#ifndef SPREADSHEET_H
#define SPREADSHEET_H
#include <QTableWidget>
class Cell;
class SpreadsheetCompare;
/* 头文件是从Cell和SpreadsheetCompare 类的前置声明开始的。
* QTableWidget单元格的属性,比如它的文本和对齐方式等,都存储在QTableWidgetItem中。
* 与QTableWidget不同的是,QTableWidgetItem不是一个窗口部件类,而是一个纯粹的数据类。
* Cell类派生自QTableWidgetltem,会在本章的最后一节对这个Cell类进行解释。
*/
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);
/* 之所以把autoRecalculate()函数实现为内联函数,是因为无论自动重新计算的标识符生效与否,它都必须要有返回值。
* 在第3章中,当实现MainWindow时,我们依赖于Spreadsheet中的一些公有函数。
* 例如,我们从MainWindow::newFile()中调用clear()来重置电子制表软件。
* 也使用了一些从QTableWidget中继承而来的函数,特别是setCurrentCell()和setShowGrid()。
*/
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();
/* Spreadsheet提供了许多实现Edit、Tols和Options菜单中的动作的槽;
* 并且它也提供了一个modified()信号,用来告知用户可能已经发生的任何变化。
* 还定义了一个由Spreadsheet类内部使用的私有槽:
*/
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;
//在这个类的私有段中,声明了3个常量、4个函数和1个变量。
};
class SpreadsheetCompare
{
public:
bool operator()(const QStringList &row1,
const QStringList &row2) const;
enum { KeyCount = 3 };
int keys[KeyCount];
bool ascending[KeyCount];
//在这个头文件的最后,给出了SpreadsheetCompare类的定义。
//当查看Spreaseet:sort()时,会解释这个类。
};
#endif // SPREADSHEET_H
//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();
/* 通常情况下,当用户在一个空单元格中输入一些文本的时候, QTableWidget将会自动创建一个QTableWidgtItem来保存这些文本。
* 在电子制表软件中,我们想利用将要创建的Cell项来代替QTableWidgetItem。
* 这可以通过在构造函数中调用setItemPrototype()来完成。
* 实际上, QTableWidget会在每次需要新项的时候把所传递的项以原型的形式克隆出来。
* 同样是在构造函数中,我们将选择模式设置为QAbstractemView::ContiguousSelection,从而可以允许简单矩形选择框方法。
* 我们把表格窗口部件的ienChanged()信号连接到私有槽somethingChanged()上,
* 这可以确保在用户编辑一个单元格的时候, somethingChanged()槽可以得到调用。
* 最后,调用clear()来重新调整表格的尺寸大小并且设置列标题。
*/
}
void Spreadsheet::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提供了一个可以滚动的视口和两个可以打开或关闭的滚动条。第6章将讲述QScrollArea子类。
*/
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);
}
Cell *Spreadsheet::cell(int row, int column) const
{
//cell()私有函数可以根据给定的行和列返回一个Cell对象。
//它几乎和QTableWidget::item()函数的作用一样,只不过它返回的是一个Cell指针,而不是一个QTableWidgetItem指针。
return static_cast<Cell *>(item(row, column));
}
QString Spreadsheet::text(int row, int column) const
{
//tex()私有函数可以返回给定单元格中的文本。如果cell( )返回的是一个空指针,则表示该单元格是空的,因而返回一个空字符串。
Cell *c = cell(row, column);
if (c) {
return c->text();
} else {
return "";
}
}
QString Spreadsheet::formula(int row, int column) const
{
/* formula()函数返回给定单元格中的公式。在很多情况下,公式和文本是相同的。
* 例如,公式“Hello"等价于字符串“Hello",所以如果用户在单元格中输入“Hello"并且按下回车键;
* 那么该单元格就会显示文本“Hello"。但是还有一些例外的情况:
* ● 如果公式是一个数字那么它就会被认为是一个数字。
* 例如,公式“1.50”等价于双精度实数(double)的1.5,它在电子制表软件中会被显示为右对齐的“1.5”。
* ● 如果公式以单引号开始,那么公式的剩余部分将会被认为是文本。
* 例如,公式“12345"等价于字符串“12345”。
* ● 如果公式以等号开始,那么公式将会被认为是一个算术公式。
* 例如, 如果单元格A1包含“12”并且单元格A2包含“6”,那么公式“=A1+A2”就会等于18。
* 把公式转换成值的任务是由Cell类完成的。
* 这时,要记住的事情是显示在单元格内的文本是公式的结果,而不是公式本身。
*/
Cell *c = cell(row, column);
if (c) {
return c->formula();
} else {
return "";
}
}
void Spreadsheet::setFormula(int row, int column,
const QString &formula)
{
/* setFormula()私有函数可以设置用于给定单元格的公式。
* 如果该单元格已经有一个Cell对象,那么我们就重新使用它。
* 否则,可以创建一个新的Cell对象并且调用QTableWidget::setItem()把它插入到表中。
* 最后,调用该单元格自己的setFormula()函数,但如果这个单元格已经显示在屏幕上,那么就重新绘制它。
* 我们不需要担心随后对这个Cell对象的删除操作,因为QTableWidget会得到这个单元格的所有权,并且会在正确的时候自动将其删除。
*/
Cell *c = cell(row, column);
if (!c) {
c = new Cell;
setItem(row, column, c);
}
c->setFormula(formula);
}
QString Spreadsheet::currentLocation() const
{
//currentLocation()函数返回当前单元格的位置,它是按照电子制表软件的通常格式,也就是一个列字母后跟上行号的形式来表示这个位置的值。
//MainWindow::updateSatusBar()使用它把这个单元格的位置显示在状态栏上。
return QChar('A' + currentColumn())
+ QString::number(currentRow() + 1);
}
QString Spreadsheet::currentFormula() const
{
//curentFomula()函数返回当前单元格的公式。它是从MainWindow::upatetatusBar()中得到调用的。
return formula(currentRow(), currentColumn());
}
void Spreadsheet::somethingChanged()
{
//如果启用了"auto-recalculate"(自动重新计算) ,那么somethingChanged()私有槽就会重新计算整个电子制表软件。
//它也会发射modified()信号。
if (autoRecalc)
recalculate();
emit modified();
}