类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();
}
通常,用户在一个空的单元格中输入文本时,
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
对象,然后我们调用
QTableWidget::setItem()
函数把它插入到表格中,最后调用单元格自己的setFormula()函数,在单元格上显示公式结果。我们不用删除
Cell
对象,在适当的时候,
QTableWidget
会自动删除这些对象。
函数
currentLocation()
返回当前单元格的位置,字母显示的列和行号,被
MainWindow::updateStatusBar()
调用在状态条上显示位置。
函数
currentFormula()
返回当前单元格的公式。
MainWindow::updateStatusBar()
调用了这个函数。
私有槽函数
somethingChanged()
中,如果
auto-recalculate
为真,那么重新计算整个表格。然后发送
modified()
信号。