使用项视图的简便类
使用。的项视图中那些方便的子类通常要比定义一个自定义模型简单得多,并且姐果我们不需
要由区分模型和视图所带来的好处时,这种方法也是比较合适的。
Flowchart Symbol Picker
演示了一个只读的QListWidget。先从一个允许用户在列表中选择流程图符号的简单对话框开始。每个项都由一个图标、一段文本和一个唯一的ID组成。
FlowChartSymbolPicker.h
#ifndef FLOWCHARTSYMBOLPICKER_H
#define FLOWCHARTSYMBOLPICKER_H
#include <QDialog>
#include <QMap>
class QDialogButtonBox;
class QIcon;
class QListWidget;
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);
private:
QIcon iconForSymbol(const QString &symbolName);
QListWidget *listWidget;
QDialogButtonBox *buttonBox;
int id;
};
#endif
当构造这个对话框时,我们必须给它传递一个QMap<int, QString>,在它运行之后,可以通过调用selectedId()获得一个选中的ID(或者如果用户没有选中任何一项,就返回-1)。
FlowChartSymbolPicker.cpp
#include <QtGui>
#include "flowchartsymbolpicker.h"
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());
}
buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok
| QDialogButtonBox::Cancel);
connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject()));
QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->addWidget(listWidget);
mainLayout->addWidget(buttonBox);
setLayout(mainLayout);
setWindowTitle(tr("Flowchart Symbol Picker"));
}
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);
}
QIcon FlowChartSymbolPicker::iconForSymbol(const QString &symbolName)
{
QString fileName = ":/images/" + symbolName.toLower();
fileName.replace(' ', '-');
return QIcon(fileName);
}
FlowChartSymbolPicker
我们把id(最后被选择的ID)初始化为-1。接下来构造一个方便的项视图窗口部件QListWidget。我们遍历这个流程图符号映射中的每一个项,并且为了显示每一个项而创建一个QListWidgetItem。QListWidgetItem构造函数都会在父对象QListWidget之后获得一个代表将要显示的文本QString。
然后,设置项的图标并且调用setData()函数把任意ID保存到QListWidgetItem中。iconForSymbol()私有函数为每一个给定的符号名称返回一个QIcon。
QListWidgetItem有几个角色(role),每一个角色都有一个关联的QVariant。最常用的角色有Qt::DisplayRole、Qt::EditRole和Qt::IconRole,并且这些角色都有方便的设置和获取函数[setText()和setIcon()],另外还有其他几个角色。通过指定一个大于等于Qt::UserRole的值,就可以定义自定义的角色。在例子中,使用Qt::UserRole存储每一个项的ID。
构造函数中省略的部分包括创建按钮、对这些窗口部件进行摆放以及设置这个窗口的标题栏。
done()
这个 done() 函数是由 QDialog 重新实现的。当用户单击 OK 或者 Cancel 按钮时,就会调用它。如果用户单击 OK ,就获得相应的项并且使用 data() 函数提取 ID。如果对项的文本感兴趣,则可以通过调用 item->data(Qt::DisplayRole).toString() 或者更为方便的 item->text()来获取文本。
默认情况下, QListWidget 是只读的。如果想让用户编辑这些项,则可以使用 AbstractItemView::setEditTriggers() 设置这个视图的编辑触发器(edit trigger) 。例如,AbstractItemVjew::AnyKeyPressed 这个设置值的意思是:用户只要一开始输入就进入项的编辑状态。类似地,也可以提供一个Edit(编辑)按钮(还可以提供 Add 按钮和 Delete 按钮) ,同时使用信号-槽连接,这样就可以使用程序来控制编辑操作了。
main.cpp
#include <QApplication>
#include "flowchartsymbolpicker.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QMap<int, QString> symbolMap;
symbolMap.insert(132, QObject::tr("Data"));
symbolMap.insert(135, QObject::tr("Decision"));
symbolMap.insert(137, QObject::tr("Document"));
symbolMap.insert(138, QObject::tr("Manual Input"));
symbolMap.insert(139, QObject::tr("Manual Operation"));
symbolMap.insert(141, QObject::tr("On Page Reference"));
symbolMap.insert(142, QObject::tr("Predefined Process"));
symbolMap.insert(145, QObject::tr("Preparation"));
symbolMap.insert(150, QObject::tr("Printer"));
symbolMap.insert(152, QObject::tr("Process"));
FlowChartSymbolPicker picker(symbolMap);
picker.show();
return app.exec();
}
Coordinate Setter
这个例子演示一个可编辑的QTableWidget。现在已经看到了如何使用方便的项视图类来查看和选择数据,接下来将会看一个可以编辑数据的例子。再次使用一个对话框,它显示自是用户可以编辑的一对(x, y) 坐标。
CoordinateSetter.h
#ifndef COORDINATESETTER_H
#define COORDINATESETTER_H
#include <QDialog>
#include <QList>
#include <QPointF>
class QDialogButtonBox;
class QTableWidget;
class CoordinateSetter : public QDialog
{
Q_OBJECT
public:
CoordinateSetter(QList<QPointF> *coords, QWidget *parent = 0);
void done(int result);
private slots:
void addRow();
private:
QTableWidget *tableWidget;
QDialogButtonBox *buttonBox;
QList<QPointF> *coordinates;
};
#endif
CoordinateSetter.cpp
#include <QtGui>
#include "coordinatesetter.h"
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()));
}
buttonBox = new QDialogButtonBox(Qt::Horizontal);
QPushButton *addRowButton = buttonBox->addButton(tr("&Add Row"),
QDialogButtonBox::ActionRole);
buttonBox->addButton(QDialogButtonBox::Ok);
buttonBox->addButton(QDialogButtonBox::Cancel);
connect(addRowButton, SIGNAL(clicked()), this, SLOT(addRow()));
connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject()));
QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->addWidget(tableWidget);
mainLayout->addWidget(buttonBox);
setLayout(mainLayout);
setWindowTitle(tr("Coordinate Setter"));
}
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);
}
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);
}
CoordinateSetter()
这个 QTableWidget 构造函数得到要在这个表格中显示的行和列的初始数字。在 QTableWidget 中的每一个项都使用一个 QTableWidgetItem 表示,包括水平方向和垂宜方向的表头项。 setHorizontalHeaderLabels ()函数则设置每一个表窗口部件的水平表头项的文本为所传递的字符串列表中的相应文本。默认情况下,QTableWidget 会提供一个垂直表头,这个列的标签从1开始,这正是我们想要的,所以不需要手工设置垂直表头的标签。
一旦创建完列标签,就可以遍历传递进来的坐标数据。对于每一个(x, y)坐标对,都添加一个新行[使用私有函数 addRow()],同时在每一行的列中设置合适的文本。
默认情况下, QTableWidget 允许编辑。用户可以在遍历一个表的时候,按下F2或者任意简单的输入,来编辑这个表的任意单元格。用户在这个视图中所做的任何修改都会自动影响这些 QTableWidgetltem。为了防止编辑,可以调用setEditTriggers(QAbstractItemView::NoEditTriggers)。
addRow()
当用户单击 Add Row 按钮时,就会调用这个 addRow()槽,这种方式在函数构造中也经常使用到。我们使用QTableWidget::insertRow() 插人一个新的行,然后创建两个 QTableWidgetItem()项,并利用 QTableWidget::insertRow()把它们添加到表中。除了该项外, QTableWidget: : setItem()还需要一行及一列。最后,我们设置当前项,这样用户就可以开始编辑新的一行的第一项了。
done()
最后,当用户单击 OK 时,就清空传递给这个对话框的坐标,并且根据这个 QTableWidget 的所有项创建一个新的坐标集。
main.cpp
#include <QtGui>
#include "coordinatesetter.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QList<QPointF> coordinates;
coordinates << QPointF(0.0, 0.9)
<< QPointF(0.2, 11.0)
<< QPointF(0.4, 15.4)
<< QPointF(0.6, 12.9)
<< QPointF(0.8, 8.5)
<< QPointF(1.0, 7.1)
<< QPointF(1.2, 4.0)
<< QPointF(1.4, 13.6)
<< QPointF(1.6, 22.2)
<< QPointF(1.8, 22.2);
CoordinateSetter coordinateSetter(&coordinates);
coordinateSetter.show();
return app.exec();
}
Settings Viewer
这个例子演示了一个只读的QTreeWidget。我们将会查看一些应用程序的代码片段,它使用 QTreeWidget 显示 Qt 应用程序设置。 QTreeWidget 默认是只读的。
SettingsViewer.h
#ifndef SETTINGSVIEWER_H
#define SETTINGSVIEWER_H
#include <QDialog>
class QDialogButtonBox;
class QSettings;
class QTreeWidget;
class QTreeWidgetItem;
class SettingsViewer : public QDialog
{
Q_OBJECT
public:
SettingsViewer(QWidget *parent = 0);
private slots:
void open();
private:
void readSettings();
void addChildSettings(QSettings &settings, QTreeWidgetItem *item,
const QString &group);
QTreeWidget *treeWidget;
QDialogButtonBox *buttonBox;
QString organization;
QString application;
};
#endif
SettingsViewer.cpp
#include <QtGui>
#include "settingsviewer.h"
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);
buttonBox = new QDialogButtonBox(QDialogButtonBox::Open
| QDialogButtonBox::Close);
connect(buttonBox, SIGNAL(accepted()), this, SLOT(open()));
connect(buttonBox, SIGNAL(rejected()), this, SLOT(close()));
QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->addWidget(treeWidget);
mainLayout->addWidget(buttonBox);
setLayout(mainLayout);
setWindowTitle(tr("Settings Viewer"));
readSettings();
}
void SettingsViewer::open()
{
QDialog dialog(this);
QLabel *orgLabel = new QLabel(tr("&Organization:"));
QLineEdit *orgLineEdit = new QLineEdit(organization);
orgLabel->setBuddy(orgLineEdit);
QLabel *appLabel = new QLabel(tr("&Application:"));
QLineEdit *appLineEdit = new QLineEdit(application);
appLabel->setBuddy(appLineEdit);
QDialogButtonBox *buttonBox = new QDialogButtonBox(
QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
connect(buttonBox, SIGNAL(accepted()), &dialog, SLOT(accept()));
connect(buttonBox, SIGNAL(rejected()), &dialog, SLOT(reject()));
QGridLayout *gridLayout = new QGridLayout;
gridLayout->addWidget(orgLabel, 0, 0);
gridLayout->addWidget(orgLineEdit, 0, 1);
gridLayout->addWidget(appLabel, 1, 0);
gridLayout->addWidget(appLineEdit, 1, 1);
QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->addLayout(gridLayout);
mainLayout->addWidget(buttonBox);
dialog.setLayout(mainLayout);
dialog.setWindowTitle(tr("Choose Settings"));
if (dialog.exec()) {
organization = orgLineEdit->text();
application = appLineEdit->text();
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));
}
void SettingsViewer::addChildSettings(QSettings &settings,
QTreeWidgetItem *parent, const QString &group)
{
if (!parent)
parent = treeWidget->invisibleRootItem();
QTreeWidgetItem *item;
settings.beginGroup(group);
foreach (QString key, settings.childKeys()) {
item = new QTreeWidgetItem(parent);
item->setText(0, key);
item->setText(1, settings.value(key).toString());
}
foreach (QString group, settings.childGroups()) {
item = new QTreeWidgetItem(parent);
item->setText(0, group);
addChildSettings(settings, item, group);
}
settings.endGroup();
}
SettingsViewer()
为了访问应用程序的设置,必须使用组织名称和应用程序名称作为参数创建 QSettings 对象。我们设置了默认的名称(Trolltech的Designer),然后构造一个新的QTreeWidget。树形窗口部件的头视图控制了树形列队的大小。我们设置两列的重定义模式为 Stretch。这就告诉了头视图总是确保列能够填充有用的空间。在这种模式下,用户或者程序都不能重新调整列的大小。在构造函数的最后,我们调用这个 readSetting()函数来构成树形窗口部件。
readSettings()
应用程序的设置会存储在一个由键和值组成的分层结构中。 addChildSettings()私有函数需要一个设置对象、一个父对象的 QTreeWidgetItem 和当前"组" (group) 。QSettings群组相当于文件系统目录。 addChildSettings()函数可以递归调用自己来遍历任意一个树状结构。readSettings() 里是初始
调用,并且传递一个作为父项的空指针,表示这是根。
addChildSettings()
addChildSettings() 函数用于创建所有的 QTreeWidgetItem。它在设置树的当前层次中遍历所有键并且为每个键创建一个 QTableWidgetItem。如果空指针被作为 parent 项传递进来,就创建一个以QTreeWidget::invisibleRootItem()为父对象的项,这样它就成为了顶层项。第一列设置为键的名称,第二列设置为相对应的值。
接下来,这个函数在当前层中遍历每一个组。对于每一个组,都会创建一个新的 QTreeWidgetItem ,并且把它的第一列设置为这个组的名称。然后这个函数会把这个群组项作为父对象递归调用自己,这样就可以用这个组的子项来构成这个 QTreeWidget 了。
main.cpp
#include <QApplication>
#include "settingsviewer.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
SettingsViewer settingsViewer;
settingsViewer.show();
return app.exec();
}