使用Qt的项目视图便利的子类通常比定义一个自己的模型更简单,适合无需分离模型和视图的操作。在第四章中我们使用了该技术,我们从QTableWidget和QTableWidgetItem派生子类来实现列表功能。
本节我们将展示如何使用便利的项目视图子类来显示项目。第一个例子展示了一个只读的QListWidget,第二个例子展示了一个可编辑的QTableWidget,第三个例子展示了一个只读的QtreeWidget.
我们首先来看一个简单对话框,它允许用户从一个列表中选取一个流程图符号。每个项目由一个图标、文本及唯一的ID号组成。
Figure 10.3. The Flowchart Symbol Picker application
首先来看一下头文件的一段摘录:
class FlowChartSymbolPicker : public QDialog
{
Q_OBJECT
public:
FlowChartSymbolPicker(const QMap<int, QString> &symbolMap,
QWidget *parent = 0);
int selectedId() const { return id; }
void done(int result);
...
};
当我们创建该对话框时,我们必须传递一个QMap<int, QString>,并且它执行后我们能调用selectedId()得到选择的ID(或者如果用户没有选择任何项目,返回-1)。
FlowChartSymbolPicker::FlowChartSymbolPicker(
const QMap<int, QString> &symbolMap, QWidget *parent)
: QDialog(parent)
{
id = -1;
listWidget = new QListWidget;
listWidget->setIconSize(QSize(60, 60));
QMapIterator<int, QString> i(symbolMap);
while (i.hasNext()) {
i.next();
QListWidgetItem *item = new QListWidgetItem(i.value(),
listWidget);
item->setIcon(iconForSymbol(i.value()));
item->setData(Qt::UserRole, i.key());
}
...
}
我们初始化id(最近选择的ID)为-1。接着我们创建了一个QListWidget对象,它是一个便利的项目视图控件。我们对流程图符号的map进行跌代遍历每个项目且创建一个QListWidgetItem来表示每项。QListWidgetItem构造函数接收一个QString参数来表示要显示的文本,后面接着是父亲QListWidget。
接下来我们设置项目的图标,接着在QListWidgetItem中我们调用setData()来保存我们任意的ID号。iconForSymbol()私有函数返回一个指定符号名的QIcon对象。
QListWidgetItem有多种角色,每种角色有一个相关的QVariant。最常用的角色是Qt::DisplayRole, Qt::EditRole, 及 Qt::IconRole,对于这些常用角色,有便利的设置及获取函数(setText(), setIcon()),但还有一些其它的角色。我们也可以通过指定Qt::UserRole的一个数字值来定义自己的角色。在此例中,我们使用了Qt::UserRole来保存每个项目的ID。
构造函数的省略部分是关于创建按钮、排列控件及设置窗体标题。
void FlowChartSymbolPicker::done(int result)
{
id = -1;
if (result == QDialog::Accepted) {
QListWidgetItem *item = listWidget->currentItem();
if (item)
id = item->data(Qt::UserRole).toInt();
}
QDialog::done(result);
}
done()函数是对QDialog的done()函数的重载。当用户点击OK或Cancel时调用该函数。如果点击的OK,我们获得相关项目并使用data()函数提取ID。如果我们对项目的文本感兴趣,我们可以调用item->data(Qt::DisplayRole).toString() 或更方便地调用item->text()来获得。
默认,QListWidget是只读的。如果我们想让用户编辑项目,可以使用QAbstractItemView::setEditTriggers()来设置视图的编辑触发器。例如,QAbstractItemView::AnyKeyPressed设置表示用户仅通过开始输入就能够开始编辑一个项目。同样,提供一个Edit按钮(也许是Add 和 Delete按钮)并把它们与槽连接以便我们能通过程序来处理编辑操作。
既然我们已经明白了如何使用一个便利的项目视图类来浏览和选择数据,我们将看一个能编辑数据的例子。我们再次使用一个对话框,这次对话框表示一组用户能编辑的(x, y)坐标。
Figure 10.4. The Coordinate Setter application
从构造函数开始,我们先看看项目视图相关的代码。
CoordinateSetter::CoordinateSetter(QList<QPointF> *coords,
QWidget *parent)
: QDialog(parent)
{
coordinates = coords;
tableWidget = new QTableWidget(0, 2);
tableWidget->setHorizontalHeaderLabels(
QStringList() << tr("X") << tr("Y"));
for (int row = 0; row < coordinates->count(); ++row) {
QPointF point = coordinates->at(row);
addRow();
tableWidget->item(row, 0)->setText(QString::number(point.x()));
tableWidget->item(row, 1)->setText(QString::number(point.y()));
}
...
}
QTableWidget构造函数接收要显示的表的初始行列数。QTableWidget中的每一项用QTableWidgetItem表示,包含水平和垂直头部项目。函数setHorizontalHeaderLabels()对每个水平的表控件项目将文本设置为函数所传递的串列表中的文本。默认时,QTableWidget提供了一个带有从1开始标号的行的垂直头部,这正是我们所要的,因此我们不必手工设置垂直的头标签。
一旦我们创建并进入列标签,我们遍历所传进的坐标数据。对每个(x, y)对,我们创建与x和y坐标相关的两个QTableWidgetItems。使用QTableWidget::setItem()将这两项加入到表中,该函数除了项外还接收一个行一个列。
默认,QTableWidget允许编辑。用户通过浏览时按F2或只是录入就可以编辑表中的任何单元格。视图中用户所做的改变会自动反应到QTableWidgetItem中。为了阻止编辑,我们可以调用setEditTriggers(QAbstractItemView:: NoEditTriggers)。
void CoordinateSetter::addRow()
{
int row = tableWidget->rowCount();
tableWidget->insertRow(row);
QTableWidgetItem *item0 = new QTableWidgetItem;
item0->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter);
tableWidget->setItem(row, 0, item0);
QTableWidgetItem *item1 = new QTableWidgetItem;
item1->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter);
tableWidget->setItem(row, 1, item1);
tableWidget->setCurrentItem(item0);
}
当用户点击Add Row按钮时会调用addRow()槽。我们使用insertRow()来追加一个新行。如果用户想编辑该新行的一个单元格,QTableWidget会自动创建一个新的QTableWidgetItem。
void CoordinateSetter::done(int result)
{
if (result == QDialog::Accepted) {
coordinates->clear();
for (int row = 0; row < tableWidget->rowCount(); ++row) {
double x = tableWidget->item(row, 0)->text().toDouble();
double y = tableWidget->item(row, 1)->text().toDouble();
coordinates->append(QPointF(x, y));
}
}
QDialog::done(result);
}
最后,当用户点击OK时,我们清除传入对话框中的所有坐标,然后根据QTableWidget的项中的坐标创建一个新的坐标集。
QtreeWidget默认情况是只读的。
Figure 10.5. The Settings Viewer application
SettingsViewer::SettingsViewer(QWidget *parent)
: QDialog(parent)
{
organization = "Trolltech";
application = "Designer";
treeWidget = new QTreeWidget;
treeWidget->setColumnCount(2);
treeWidget->setHeaderLabels(
QStringList() << tr("Key") << tr("Value"));
treeWidget->header()->setResizeMode(0, QHeaderView::Stretch);
treeWidget->header()->setResizeMode(1, QHeaderView::Stretch);
...
setWindowTitle(tr("Settings Viewer"));
readSettings();
}
为了访问应用程序的设置,必须创建一个带有组织的名字及程序的名字作为参数的QSettings对象。我们设置默认的名字("Designer" by "Trolltech")然后创建一个新的QtreeWidget。最后调用readSettings()函数。
void SettingsViewer::readSettings()
{
QSettings settings(organization, application);
treeWidget->clear();
addChildSettings(settings, 0, "");
treeWidget->sortByColumn(0);
treeWidget->setFocus();
setWindowTitle(tr("Settings Viewer - %1 by %2")
.arg(application).arg(organization));
}
应用程序的设置被保存在一个层次结构的键值里。私有函数addChildSettings()接收一个settings对象,一个父亲QtreeWidgetItem和当前"group"。组是一个等同于文件系统目录的QSettings。函数addChildSettings()递归调用自己来遍历一个随机树结构。从readSettings()函数最初的调用传递0作为父项目表示根。
void SettingsViewer::addChildSettings(QSettings &settings,
QTreeWidgetItem *parent, const QString &group)
{
QTreeWidgetItem *item;
settings.beginGroup(group);
foreach (QString key, settings.childKeys()) {
if (parent) {
item = new QTreeWidgetItem(parent);
} else {
item = new QTreeWidgetItem(treeWidget);
}
item->setText(0, key);
item->setText(1, settings.value(key).toString());
}
foreach (QString group, settings.childGroups()) {
if (parent) {
item = new QTreeWidgetItem(parent);
} else {
item = new QTreeWidgetItem(treeWidget);
}
item->setText(0, group);
addChildSettings(settings, item, group);
}
settings.endGroup();
}
addChildSettings()函数用于创建所有的QtreeWidgetItem。它遍历在settings结构中当前级别的所有的键,并且为每个键创建一个QTableWidgetItem。如果传递0作为父项目,我们就创建该项目作为QtreeWidget自己的孩(使其成为顶级项);否则我们就创建该项目作为它父亲的孩。第一列被设为键名,第二列设为对应的值。
接着,此函数遍历当前级别的每个组。对每个组,创建一个新的QtreeWidgetItem,第一列设为组名。接着该函数使用组项作为父亲递归调用自己来创建带组的孩项的QTReeWidget。
这节所说的项目视图控件允许我们使用一种类似于早期版本所用的编程风格:把整个数据集读进一个项目视图控件,使用项目对象来表示数据元素,(如果项目是可编辑的)然后写回数据源。在后面的小节中,我们将充分利用 Qt 的模型 / 视图架构。