4-2 从QTableWidget继承(Subclassing QTableWidget)
类Spreadsheet从QTableWidget继承。QTableWidget是一个表示二维离散数组的表格。它根据给定坐标显示当前用户指定的网格。当用户在一个空的网格中输入一些文本时,QTableWidget自动创建一个QTableWidgetItem对象保存输入的文本。
现在我们来实现这个类,首先是头文件spreadsheet.h,首先前向声明两个类Cell和SpreadsheetCompare。
#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
Figure 4.1. Inheritance trees for Spreadsheet and Cell
文本,对齐等这个QTableWidget网格的属性存储在QTableWidgetItem类里。QTableWidgetItem类不是一个控件类,而是一个单纯保存数据的类。类Cell从QTableWidgetItem继承的,将在下一节介绍。
在第三章我们实现MainWindow类的时候我们用到了Spreadsheet的一些函数。如在MainWindow::newFile中调用clear()将表格置空。我们也用到了QTableWidget的一些函数,如setCurrentCell()和setShowGrid()就多次调用过。
Spreadsheet提供了很多槽函数来相应Edit,Tools和Options等菜单的动作。信号modified()在表格发生变化时给出通知。
私有槽函数somethingChanged()在Speadsheet类内部使用。
在类的私有部分,我们声明了三个常数,四个函数和一个变量。
在头文件的最后定义了类SpreadsheetCompare
现在我们看一下源文件
spreadsheet.cpp
:
#include
<
QtGui
>
#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 );
}
Cell * Spreadsheet::cell( int row, int column) const
{
return static_cast < Cell *> (item(row, column));
}
QString Spreadsheet::text( int row, int column) const
{
Cell * c = cell(row, column);
if (c) {
return c -> text();
} else {
return "" ;
}
}
QString Spreadsheet::formula( int row, int column) const
{
Cell * c = cell(row, column);
if (c) {
return c -> formula();
} else {
return "" ;
}
}
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);
}
QString Spreadsheet::currentLocation() const
{
return QChar( ' A ' + currentColumn())
+ QString::number(currentRow() + 1 );
}
QString Spreadsheet::currentFormula() const
{
return formula(currentRow(), currentColumn());
}
void Spreadsheet::somethingChanged()
{
if (autoRecalc)
recalculate();
emit modified();
}
#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 );
}
Cell * Spreadsheet::cell( int row, int column) const
{
return static_cast < Cell *> (item(row, column));
}
QString Spreadsheet::text( int row, int column) const
{
Cell * c = cell(row, column);
if (c) {
return c -> text();
} else {
return "" ;
}
}
QString Spreadsheet::formula( int row, int column) const
{
Cell * c = cell(row, column);
if (c) {
return c -> formula();
} else {
return "" ;
}
}
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);
}
QString Spreadsheet::currentLocation() const
{
return QChar( ' A ' + currentColumn())
+ QString::number(currentRow() + 1 );
}
QString Spreadsheet::currentFormula() const
{
return formula(currentRow(), currentColumn());
}
void Spreadsheet::somethingChanged()
{
if (autoRecalc)
recalculate();
emit modified();
}
通常,用户在一个空的网格中输入文本时,
QTableWidget
将会自动创建
QTableWidgetItem
对象来保存这些文本。在
spreadsheet
程序中,我们使用
Cell
代替
QTableWidgetItem
。在构造函数中,
setItemProtoType()
完成这个替换。实现方式是当需要创建一个新的项目时,
QTableWidget
克隆传递给
setItemProtoType()
函数中的项目。
在构造函数中,我们设置选择方式
QAbstractItemView::ContiguousSelection
使表格能够选择一个单一的网格。连接表格控件的信号
itemChanged()
和
somethingChanged()
槽函数,这样当用户编辑了一个网格时,
somethingChanged()
能够被调用。最后,我们调用
clear()
清空表格,设置列标头。
在构造函数中调用
clear()
用来初始化表格。在
MainWindow::newFile()
中也调用了这个函数。如果使用函数
QTableWidget::clear()
也可清除所有的网格和选择,但这样不能改变标题头的个数。我们首先把表格从新定义为
0
×
0
,这样全部清除了表格和标题头。然后把表格重新定义为
ColumnCount
×
RowCount
(
26
×
999
),让水平标题头为
QTableWidgetItem
类型,文本为
"
A
"
到
"
Z
"
。垂直标题栏会自动设置为
1
,
2
,到
999
。最后把光标移动到
A1
。
QTableWidget
由几个子控件组成。它在最上面有一个水平的
QHeaderView
,最左边有一个垂直的
QHeaderView
和两个
QScrollBars
。中间区域是一个特殊的
viewport
控件,这个控件可以显示网格。这些组成控件可以通过
QTableView
和
QAbstractScrollArea
的函数进行操作。
QAbstractScrollArea
提供了一个可以滚动的
viewport
和两个滚动条。它的子类
QScrollArea
会在第六章介绍到。
Figure 4.2.
QTableWidget
's constituent widgets
在
Items
中保存数据:
在
Spreadsheet
应用程序中,每一个非空的网格都是一个独立的
QTableWidgetItem
对象。这种在
Item
中保存数据的方法被
QListWidget
和
QTreeWidget
所采用,对应这两个控件的
Item
类分别为
QListWidgetItem
和
QTreeWidgetItem
。
Qt
的
Item
类还可以作为数据存储使用。比如,
QTableWidgetItem
也保存了一些属性如文本,字体,颜色,图标等,还有一个指向
QTableWidget
的指针。这个
Item
还可以保存
QVariant
类型的数据,包括注册的自定义类型。把这个类作为基类,我们还可以提供其他功能。
其他的工具是在
item
类可以提供一个空指针来保存用户数据。在
Qt
中更加好用的方法是使用
setData()
,把
QVariant
类型的数据保存起来。如果需要一个空类型指针,也可以继承
item
类,添加一个空类型指针成员数据。
对于那些更为复杂的数据处理,如大量的数据,复杂的数据项,数据库数据和多种数据显示方式,
Qt
提供了一套
model/view
类将数据和显示分离出来,第十章介绍了这个特性。
私有函数
cell()
返回给定的行数和列数的
Cell
对象。它和
QTableWidget::item()
是一样的,只是它返回的是
Cell
类型的指针,
QTableWidget::item()
返回的是
QTableWidgetItem
类型的指针。
私有函数
text()
返回给定的网格的文本。如果
cell()
返回空指针,网格为空,则返回空字符。
函数
formula()
返回的是网格的公式。大多数情况下,网格的公式和文本是一样的。例如,公式
"
hello
"
和字符
"
hello
"
是一样的,如果用户输入了
"
hello
"
,网格的文本就显示为
hello
。但是下面会是例外:
1
、如果公式是一个数字,那么网格的文本也是数字。
2
、如果公式是单引号开头,公式的其他部分就是文本。如公式
'12345
,网格公式就是
"
12345
"
。
3
、如果公式由等号
"
=
"
开头,代表一个数学公式。如果
A1
为
12
,
A2
为
6
,那么公式
"
=A1+A2
"
就是
18
。
把公式转换为值的任务是由类
Cell
完成的。此时需要记住的是网格中显示的文本是经过公式计算的结果,而不是公式本身。
私有函数
setFormula()
用来给一个指定的网格设置公式。如果网格有
Cell
对象,那就使用这个对象。否则,我们创建一个新的
Cell
对象。最后我们调用
Cell
自己的
setFormula()
函数,在网格上显示公式结果。我们不用删除
Cell
对象,在适当的时候,
QTableWidget
会自动删除这些对象。
函数
currentLocation()
返回当前网格的位置,字母显示的列和行号。在
MainWindow::updateStatusBar()
调用在状态条上显示位置。
函数
currentFormula()
返回当前网格的公式。
MainWindow::updateStatusBar()
调用了这个函数。
私有槽函数
somethingChanged()
中,如果
auto-recalculate
为真,那么重新计算整个表格。然后发送
modified()
信号。