10-1 使用数据视图便捷类(Using the Item View Convenience Classes)

通常使用Qt提供的数据视图便捷类(item view convenience class)要比定义一个模型简单的多,适合无需分离模型和视图的操作。在第四章中我们使用了QTableWidgetQTableWidgetItem实现了表格的功能。

在这一节中,我们将介绍这些便捷类的使用。第一个例子是一个只读的QListWidget,第二个例子是一个可编辑的QTableWidget,第三个例子显示的是一个只读的QTreeWidget

首先我们显示一个简单的对话框,用户通过鼠标点击选中列表中的一个流程图符号,每一个项目包含一个图标,一个文字说明和一个唯一ID

Figure 10.3. The Flowchart Symbol Picker application

10-1 使用数据视图便捷类(Using the Item View Convenience Classes)_第1张图片

 

下面是头文件中声明的类:

class FlowChartSymbolPicker : public QDialog

{

    Q_OBJECT

public:

    FlowChartSymbolPicker(const QMap &symbolMap,

                          QWidget *parent = 0);

    int selectedId() const { return id; }

    void done(int result);

    ...

};

 

当我们构造对话框时,要传递一个QMap参数,这个参数保存了一个项目的ID。调用函数selectedId()能够得到选中的ID,如果用户没有选择,返回的ID为-1

FlowChartSymbolPicker::FlowChartSymbolPicker(

        const QMap &symbolMap, QWidget *parent)

    : QDialog(parent)

{

    id = -1;

    listWidget = new QListWidget;

    listWidget->setIconSize(QSize(60, 60));

    QMapIterator 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,一个便捷类。我们遍历流程图符号映射中的所有项目,每一个项目用一个QListWidgetItem类对象表示。新建一个QListWidgetItem所需的第一个参数为显示的文本,另一个参数为QListWidget作为其他父容器。

然后我们设置了这个项目的图标,调用setData()函数把每一个流程符号的ID保存在QListWidgetItem中。函数iconForSymbol()返回表示当前流程符号的图标。

QListWidgetItem有很多角色(role),每一个角色由一个QVariant类型的数据进行读取和设置。最常用的角色是Qt::DisplayRoleQt::EditRoleQt::IconRole。这些角色的数值可以通过settergetter函数获得。如(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

10-1 使用数据视图便捷类(Using the Item View Convenience Classes)_第2张图片

 

和前一个例子一样,我们主要关注和视图有关的代码,首先从构造函数开始。

CoordinateSetter::CoordinateSetter(QList *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)数据对,我们创建两个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

10-1 使用数据视图便捷类(Using the Item View Convenience Classes)_第3张图片

 

下面是构造函数的一部分。

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是可编辑的,就把更新的数据写回到数据源。接下来的几节将改变这种简单的方式,充分利用Qtmodel/view结构。

 

 

 

你可能感兴趣的:(C++,GUI,Programming,with,Qt,4)