使用预定义模型
Qt提供了几种可以在视图类中使用的预定义模型,见下表。
Team Leaders
我们从一个简单的对话框开始,用户可以使用它添加、删除和编辑一个 QStringList,其中每个字符串都代表一个团队领导。
TeamLeadersDialog.h
#ifndef TEAMLEADERSDIALOG_H
#define TEAMLEADERSDIALOG_H
#include <QDialog>
class QDialogButtonBox;
class QListView;
class QStringListModel;
class TeamLeadersDialog : public QDialog
{
Q_OBJECT
public:
TeamLeadersDialog(const QStringList &leaders, QWidget *parent = 0);
QStringList leaders() const;
private slots:
void insert();
void del();
private:
QListView *listView;
QDialogButtonBox *buttonBox;
QStringListModel *model; // 存储一个字符串列表
};
#endif
TeamLeadersDialog.cpp
#include <QtGui>
#include "teamleadersdialog.h"
TeamLeadersDialog::TeamLeadersDialog(const QStringList &leaders,
QWidget *parent)
: QDialog(parent)
{
model = new QStringListModel(this);
model->setStringList(leaders);
listView = new QListView;
listView->setModel(model);
listView->setEditTriggers(QAbstractItemView::AnyKeyPressed /* 按任意键编辑 */
| QAbstractItemView::DoubleClicked); /* 双击编辑 */
buttonBox = new QDialogButtonBox();
QPushButton *insertButton = buttonBox->addButton(tr("&Insert"),
QDialogButtonBox::ActionRole);
QPushButton *deleteButton = buttonBox->addButton(tr("&Delete"),
QDialogButtonBox::ActionRole);
buttonBox->addButton(QDialogButtonBox::Ok);
buttonBox->addButton(QDialogButtonBox::Cancel);
connect(insertButton, SIGNAL(clicked()), this, SLOT(insert()));
connect(deleteButton, SIGNAL(clicked()), this, SLOT(del()));
connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject()));
QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->addWidget(listView);
mainLayout->addWidget(buttonBox);
setLayout(mainLayout);
setWindowTitle(tr("Team Leaders"));
}
QStringList TeamLeadersDialog::leaders() const
{
return model->stringList();
}
void TeamLeadersDialog::insert()
{
int row = listView->currentIndex().row();
model->insertRows(row, 1);
QModelIndex index = model->index(row);
listView->setCurrentIndex(index);
listView->edit(index);
}
void TeamLeadersDialog::del()
{
model->removeRows(listView->currentIndex().row(), 1);
}
TeamLeadersDialog()
我们从创建并且组装一个 QStringListModel 开始。接下来创建一个 ListView 并且把刚才创建的那个模型作为它的模型;还设置了一些编辑触发器以允许用户简单地通过开始输入或者双击进人编辑字符串的状态。默认情况下,ListView 中没有任何编辑触发器,这样就使这个视图只读。
insert()
当用户单击由此按钮的时候, insert()槽就会得到调用这个糟从获得到表视图的当前项的行数开始。模型中的每一个数据项都对应一个"模型索引",它是由一个QModelIndex 对象表示的。会在下一节中看到有关模型索引的细节,但是现在我们需要知道一个索引有三个主要组成部分:行、列和它所属的模型的指针。在一个一维的列表视图中,列总为0。
一旦得到行数,就可以在那个位置插入一个新行。这个插入是在模型中完成的,并且模型会自动更新列表视图。然后我们设置刚刚插入的空白行为列表视图的当前索引。最后,设置列表视图在当前行进入编辑的状态,就好像用户已经按下一个键或者双击进编辑状态一样。
del()
在构造函数中, Delete 按钮的 clicked() 信号会连接到这个 del() 槽。因为我们只是想删除当前行,所以可以使用当前索引位置调用 removeRows() ,并且把要删除的行数设置为 1。就像刚才的插入操作一样,我们需要依赖于模型才能够相应地更新这个视图。
leaders()
最后,当关闭这个对话框的时候, leader() 函数提供了一种读回这些被编辑的宇符串的方式。
main.cpp
#include <QApplication>
#include "teamleadersdialog.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QStringList leaders;
leaders << "Stooge Viller" << "Littleface" << "B-B Eyes"
<< "Pruneface" << "Mrs. Pruneface" << "The Brow"
<< "Vitamin Flintheart" << "Flattop Sr." << "Shakey"
<< "Breathless Mahoney" << "Mumbles" << "Shoulders"
<< "Sketch Paree";
TeamLeadersDialog dialog(leaders);
dialog.show();
return app.exec();
}
通过简单地改变 TeamLeadersDialog 的窗口标题,它就可以成为一个通用的字符串列表编辑对话框。我们通常所需要的另外一个通用对话框是向用户显示一个文件或者目录的列表。
Directory Viewer
这个例子使用了QDirModel类,它封装了计算机的文件系统并且可以显示(或者隐藏)不同的文件属性。可以为这个模型应用过滤器,这样就可以根据自己的需要显示不同类型的文件系统条目,并且使用不同的方式对这些条目进行排序。
DirectoryViewer.h
#ifndef DIRECTORYVIEWER_H
#define DIRECTORYVIEWER_H
#include <QDialog>
class QDialogButtonBox;
class QDirModel;
class QTreeView;
class DirectoryViewer : public QDialog
{
Q_OBJECT
public:
DirectoryViewer(QWidget *parent = 0);
private slots:
void createDirectory();
void remove();
private:
QTreeView *treeView;
QDirModel *model;
QDialogButtonBox *buttonBox;
};
#endif
DirectoryViewer.cpp
#include <QtGui>
#include "directoryviewer.h"
DirectoryViewer::DirectoryViewer(QWidget *parent)
: QDialog(parent)
{
model = new QDirModel;
model->setReadOnly(false);
model->setSorting(QDir::DirsFirst | QDir::IgnoreCase | QDir::Name);
treeView = new QTreeView;
treeView->setModel(model);
treeView->header()->setStretchLastSection(true);
treeView->header()->setSortIndicator(0, Qt::AscendingOrder);
treeView->header()->setSortIndicatorShown(true);
treeView->header()->setClickable(true);
QModelIndex index = model->index(QDir::currentPath());
treeView->expand(index); // ???
treeView->scrollTo(index); // 打开父对象,到根节点 滚动到当前项
treeView->resizeColumnToContents(0);
buttonBox = new QDialogButtonBox(Qt::Horizontal);
QPushButton *mkdirButton = buttonBox->addButton(
tr("&Create Directory..."), QDialogButtonBox::ActionRole);
QPushButton *removeButton = buttonBox->addButton(tr("&Remove"),
QDialogButtonBox::ActionRole);
buttonBox->addButton(tr("&Quit"), QDialogButtonBox::AcceptRole);
connect(mkdirButton, SIGNAL(clicked()), this, SLOT(createDirectory()));
connect(removeButton, SIGNAL(clicked()), this, SLOT(remove()));
connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->addWidget(treeView);
mainLayout->addWidget(buttonBox);
setLayout(mainLayout);
setWindowTitle(tr("Directory Viewer"));
}
void DirectoryViewer::createDirectory()
{
QModelIndex index = treeView->currentIndex();
if (!index.isValid())
return;
QString dirName = QInputDialog::getText(this,
tr("Create Directory"),
tr("Directory name"));
if (!dirName.isEmpty()) {
if (!model->mkdir(index, dirName).isValid())
QMessageBox::information(this, tr("Create Directory"),
tr("Failed to create the directory"));
}
}
void DirectoryViewer::remove()
{
QModelIndex index = treeView->currentIndex();
if (!index.isValid())
return;
bool ok;
if (model->fileInfo(index).isDir()) {
ok = model->rmdir(index); // 删除文件夹
} else {
ok = model->remove(index); // 删除文件
}
if (!ok)
QMessageBox::information(this, tr("Remove"),
tr("Failed to remove %1").arg(model->fileName(index)));
}
DirectoryViewer()
一旦模型构造完成,我们就让它可编辑并且设置不同的初始排序属性。然后创建用于显示这个模型的数据的 QTreeView。这个 QTreeVìew 的头可以用来提供用户控制的排序功能。通过让头可以点击,用户就能够按他们所点击的列进行排序。当这个树视图的头被设置完毕之后,就得到了当前目录的模型索引,然后通过调用 expand(),如果需要就打开它的父对象一直到根节点,并且调用 scrollTo()滚动到当前项,这样就确保它是可见的。然后我们确保第一列足够宽,可以显示它所有的条目,而不是使用省略号。
这里没有给出的构造函数中的部分代码中,把 Create Directory Remove 按钮和实现这些操作的槽连接起来。我们不需要 Rename 按钮,这是因为用户可以通过按下凹键并键入字母来直接进行重命名。
createDirectory()
如果用户在输入对话框中输入的是一个目录名称,就试图会在当前目录下用这个名称创建一个子目录。QDirModel::mkdir() 函数可以使用父目录的索引和新目录的名称,然后返回它所创建的目录的模型索引。如果操作失败,就返回一个无效的模型索引。
remove()
如果用户单击Remove,就试图移除当前项所对应的文件或者目录。也可以使用QDir来完成这项操作,但是QDirModel提供了一些对QModelIndex起作用的方便函数。
main.cpp
#include <QApplication>
#include "directoryviewer.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
DirectoryViewer directoryViewer;
directoryViewer.show();
return app.exec();
}
Color Names
这个实例显示了如何使用 QsortFilterProxyModel。和其他预定义模型不同,这个模型封装了一个已经存在的模型并且对在底层模型和视图之间的传递的数据进行操作。在我们的实例中,底层模型是一个由Qt所认识的颜色名称[通过调用 QColor::colorNames()得到]初始化的 QStringListModel。用户可以在 QLineEdit 中输人一个过滤器宇符串并且使用组合框指定这个字符串被如何解释(作为正则表达式、通配符模式或者固定字符串)。
ColorNamesDialog.h
#ifndef COLORNAMESDIALOG_H
#define COLORNAMESDIALOG_H
#include <QDialog>
class QComboBox;
class QLabel;
class QLineEdit;
class QListView;
class QSortFilterProxyModel;
class QStringListModel;
class ColorNamesDialog : public QDialog
{
Q_OBJECT
public:
ColorNamesDialog(QWidget *parent = 0);
private slots:
void reapplyFilter();
private:
QStringListModel *sourceModel;
QSortFilterProxyModel *proxyModel;
QListView *listView;
QLabel *filterLabel;
QLabel *syntaxLabel;
QLineEdit *filterLineEdit;
QComboBox *syntaxComboBox;
};
#endif
ColorNamesDialog.cpp
#include <QtGui>
#include "colornamesdialog.h"
ColorNamesDialog::ColorNamesDialog(QWidget *parent)
: QDialog(parent)
{
sourceModel = new QStringListModel(this);
sourceModel->setStringList(QColor::colorNames());
proxyModel = new QSortFilterProxyModel(this);
proxyModel->setSourceModel(sourceModel);
proxyModel->setFilterKeyColumn(0);
listView = new QListView;
listView->setModel(proxyModel);
listView->setEditTriggers(QAbstractItemView::NoEditTriggers);
filterLabel = new QLabel(tr("&Filter:"));
filterLineEdit = new QLineEdit;
filterLabel->setBuddy(filterLineEdit);
syntaxLabel = new QLabel(tr("&Pattern syntax:"));
syntaxComboBox = new QComboBox;
syntaxComboBox->addItem(tr("Regular expression"), QRegExp::RegExp);
syntaxComboBox->addItem(tr("Wildcard"), QRegExp::Wildcard);
syntaxComboBox->addItem(tr("Fixed string"), QRegExp::FixedString);
syntaxLabel->setBuddy(syntaxComboBox);
connect(filterLineEdit, SIGNAL(textChanged(const QString &)),
this, SLOT(reapplyFilter()));
connect(syntaxComboBox, SIGNAL(currentIndexChanged(int)),
this, SLOT(reapplyFilter()));
QGridLayout *mainLayout = new QGridLayout;
mainLayout->addWidget(listView, 0, 0, 1, 2);
mainLayout->addWidget(filterLabel, 1, 0);
mainLayout->addWidget(filterLineEdit, 1, 1);
mainLayout->addWidget(syntaxLabel, 2, 0);
mainLayout->addWidget(syntaxComboBox, 2, 1);
setLayout(mainLayout);
setWindowTitle(tr("Color Names"));
}
void ColorNamesDialog::reapplyFilter()
{
QRegExp::PatternSyntax syntax =
QRegExp::PatternSyntax(syntaxComboBox->itemData(
syntaxComboBox->currentIndex()).toInt());
QRegExp regExp(filterLineEdit->text(), Qt::CaseInsensitive, syntax);
proxyModel->setFilterRegExp(regExp);
}
ColorNamesDialog()
我们使用常规的方式创建并且组装这个 QStringListModel。接下来就是对 QSortFilterProxyModel的构造。我们使用setSourceModel() 传递底层模型并且告诉这个代理过滤器被应用在初始模型的第0列。QComboBox::addItem()函数接受一个类型为 QVariant 的可选"数据"参数。我们使用它存储和每一个项文本对应的 QRegExp::PattemSyntax值。
reapplyFilter()
只要用户改变过滤器字符串或者模式语法组合框,就会调用这个 reapplyFilter()槽。我们使用行编辑器中的文本创建一个 QRegExp,然后设置它的模式语法为保存在组合框中当前项中保存的值。当调用 setFilterRegExp() 时,会激活新的过滤器并且会自动更新视图。
main.cpp
#include <QApplication>
#include "colornamesdialog.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
ColorNamesDialog dialog;
dialog.show();
return app.exec();
}