通常使用Qt提供的数据视图便捷类(item view convenience class)要比定义一个模型简单的多,适合无需分离模型和视图的操作。在第四章中我们使用了QTableWidget和QTableWidgetItem实现了表格的功能。
在这一节中,我们将介绍这些便捷类的使用。第一个例子是一个只读的QListWidget,第二个例子是一个可编辑的QTableWidget,第三个例子显示的是一个只读的QTreeWidget。
首先我们显示一个简单的对话框,用户通过鼠标点击选中列表中的一个流程图符号,每一个项目包含一个图标,一个文字说明和一个唯一ID。
Figure 10.3. The Flowchart Symbol Picker application
下面是头文件中声明的类:
class FlowChartSymbolPicker : public QDialog
{
Q_OBJECT
public:
FlowChartSymbolPicker(const QMap
QWidget *parent = 0);
int selectedId() const { return id; }
void done(int result);
...
};
当我们构造对话框时,要传递一个QMap
FlowChartSymbolPicker::FlowChartSymbolPicker(
const QMap
: QDialog(parent)
{
id = -1;
listWidget = new QListWidget;
listWidget->setIconSize(QSize(60, 60));
QMapIterator
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,一个便捷类。我们遍历流程图符号映射中的所有项目,每一个项目用一个QListWidgetItem类对象表示。新建一个QListWidgetItem所需的第一个参数为显示的文本,另一个参数为QListWidget作为其他父容器。
然后我们设置了这个项目的图标,调用setData()函数把每一个流程符号的ID保存在QListWidgetItem中。函数iconForSymbol()返回表示当前流程符号的图标。
QListWidgetItem有很多角色(role),每一个角色由一个QVariant类型的数据进行读取和设置。最常用的角色是Qt::DisplayRole,Qt::EditRole和Qt::IconRole。这些角色的数值可以通过setter和getter函数获得。如(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()的重写。用户点击了对话框上的确定或者取消按钮后调用这个函数。如果用户点击了确定按钮,我们调用data()函数得到选中项目的ID。如果需要得到的是项目显示的文本,可以用函数item->data(Qt::DisplayRole).toString(),或者item->text()更方便。
缺省状态下,QListWidget是只读的。如果我们希望用户能够编辑这个项目,可以调用QAbstractItemView::setEditTriggers()。例如,设置为QAbstractItemView::AnyKeyPressed说明用户敲击键盘就可以进行编辑。另外我们也可以提供一个编辑按钮(也许是增加或者删除按钮),把他们的信号和槽函数关联起来,在程序中实现编辑。
现在我们已经知道怎么使用一个便捷类查看和选择数据。我们将要实现一个可以进行编辑的例子。也是用到了对话框,这个例子中要表现的是一对(x,y)坐标,用户对这些坐标可以编辑。
Figure 10.4. The Coordinate Setter application
和前一个例子一样,我们主要关注和视图有关的代码,首先从构造函数开始。
CoordinateSetter::CoordinateSetter(QList
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)数据对,我们创建两个QTableWidgetItem实例,分别表示x坐标和y坐标值。调用函数QTableWidget::setItem()把QTableWidgetItem实例添加到表格中去。
在缺省情况下,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);
}
当用户点击AddRow按钮时,函数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查看一下一个Qt应用程序的设置文件。缺省情况下,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对象,并且把组织名称和程序名称作为参数。我们使用了一个缺省名称(”Desinger” 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));
}
程序设置保存在一个有层次关系的属性关键字和对应值当的文件中(windows平台下也可能保存在注册表中)。函数addChildSettings()读取一个属性组的信息,需要的参数分别是一个QSettings对象,父项目QTreeWidgetItem和当前的属性组名,在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。它遍历应用设置层次树当前节点的所有属性,为每一个属性创建一个QTableWidgetItem。如果传递的父项目为0,新创建的QTreeWidgetItem作为QTreeWdiget的子项目(树的根节点),如果父项目不为0,在父项目下面创建一个子项目。控件QTreeWidget的第一列为属性关键字名称,第二列为属性值。
然后函数开始遍历当前节点下的属性组,为每一个组创建一个QTreeWidgetItem对象,第一列为属性的名称。函数递归调用自己得到所有的属性组,把每一个组的子项目放到QTreeWidget控件中。
在本节所介绍的控件中,这些控件的编程方式和Qt的早期版本很像。把所有数据读到控件中,使用item代表一个数据,如果item是可编辑的,就把更新的数据写回到数据源。接下来的几节将改变这种简单的方式,充分利用Qt的model/view结构。