在给头文件filedialog.h添加include保护(include guard)后的内容,包含了FileDialog类的声明,我们包含(#include)每一个uic从UI文件生成的类定义。前向类声明使得免于读相应类的头文件(这招貌似只在头文件里有用):
// filedialog/filedialog.h
#ifndef FILEDIALOG_H
#define FILEDIALOG_H
#include "ui_filedialog.h"
class QModelIndex;
class QDirModel;
class QItemSelectionModel;
class FileDialog: public QDialog, private Ui::FileDialog {
Q_OBJECT
public:
FileDialog(QWidget *parent = 0);
...
private:
QItemSelectionModel *selModel;
QDirModel *dirModel;
};
#endif // FILEDIALOG_H
就如同QDirModel一样,我们现在需要一个选择模型,能够管理、比较views返回给它的选择。相应地,我们在FileDialog的构造函数里面,除了目录模型,再创建一个QItemSelectionModel,代码位于在我们调用setupUi()初始化Designer生成的widget之后。要实现这个功能,我们把目录模型指定给QItemSelectionModel的构造函数。这样,选择模型识别了数据源,即可管理条目。
现在我们指定所有的view以相同的模型,通过setModel()并且指定相应的选择模型通过setSelectionModel()。后一个步骤保证了所有三个view打开了相同的选项:如果你在一个view选择了几个文件,这些文件自动在其他两个视图里面也亮了:
// filedialog/filedialog.cpp
#include <QtGui>
#include "filedialog.h"
FileDialog::FileDialog(QWidget *parent)
: QDialog(parent)
{
setupUi(this);
dirModel =new QDirModel;
selModel =new QItemSelectionModel(dirModel);
listView->setModel(dirModel);
treeView->setModel(dirModel);
iconView->setModel(dirModel);
listView->setSelectionModel(selModel);
treeView->setSelectionModel(selModel);
iconView->setSelectionModel(selModel);
QModelIndex cwdIndex = dirModel->index(QDir::rootPath());
listView->setRootIndex(cwdIndex);
treeView->setRootIndex(cwdIndex);
iconView->setRootIndex(cwdIndex);
这些Views任然需要模型的入口点,所以,我们使用setRootIndex()设置。这个函数需要一个QModelIndex作为参数,但是我们在这里使用了文件系统的语义(dirModel)。要调和这两个“世界”(model中的条目和filesystem中的条目),我们将QDirModel中的index()方法重载,作为一个过渡者:它接收一个文件路径,寻找model中匹配的索引,并将它返回。
然后我们把Designer中生成的组合框用根目录填充起来。因为QDir::rootPath()包含了Windows的c盘,问题就来了,我们如何得到可用的盘符的表?作为答案,我们建议说点简短的题外话,关于通常而言的model函数和QDirModel独特的功能。
一个model基本上包含两维结构:在QDirModel中,每一行表示一个文件条目,而每一列则包含了一个文件的属性(名称、大小、创建日期)。如果一个文件条目又指向了一个有效地QModelIndex,这表示一个子目录。就像图8.9所示的一样,这就形成了一个第三维度,就像图中表示的一样。尽管list和table views不能显示出这种附加的结构层次,tree views在视觉上从源表示数据项目(也就是文件系统),也就是说他们自己允许QModelIndex对象作为子树。这就解释了file dialog构造函数中的下一部分代码:
// filedialog/filedialog.cpp (continued)
for(intr= 0; r <dirModel->rowCount(QModelIndex());++r) {
QModelIndex index =dirModel->index(r,0, QModelIndex());
if (index.isValid())
comboBox->addItem(dirModel->fileIcon(index),dirModel->filePath(index));
}
一个无效的(也就是空的)QModelIndex意味着model应当选择文件系统的根级作为开始的index。在我们的案例中,这一级包含了Windows下所有的分区,在Linux里面,只有目录树。我们通过rowCount()决定分区条目的数量,因此有了这些知识,我们就能迭代出来所有条目。为此目的,我们使用第零列,既然QModelIndex向我们提供了这些位置需要的信息。为了保险起见,我们检查index是否真的有效。在这个案例中,我们向组合框添加一条条目以对应分区。
要能够让model工作,我们需要一些槽,我们在头文件里声明他们,在构造函数的后面:
// filedialog/filedialog.h(replenished)
...
protected slots:
void switchToDir(const QModelIndex& index);
void syncActive(const QModelIndex& index);
void switchView();
...
switchToDir()应当响应鼠标点击并且更新其它的list view,因此他们也能显示选择的目录。syncActive()比较活动的条目,换句话说,所有三个view中颜色高亮显示的那个,并且在treeView的对应分支打开,而switchView()响应鼠标开关,并且在vews间切换,把被stacked的放到最顶上来
我们现在需要连接构造函数里每个新槽跟三个view中的activated()信号。switchToDir()和syncActive()需要QmodelIndex作为参数,引用的是新的目录。最终我们指挥Qt,如果点击了Designer中定义的Switch按钮就调用switchView()槽:
// filedialog/filedialog.cpp (continued)
connect(listView, SIGNAL(activated(const QModelIndex&)),
SLOT(switchToDir(const QModelIndex&)));
connect(treeView, SIGNAL(activated(const QModelIndex&)),
SLOT(switchToDir(const QModelIndex&)));
connect(iconView, SIGNAL(activated(const QModelIndex&)),
SLOT(switchToDir(const QModelIndex&)));
connect(listView, SIGNAL(clicked(const QModelIndex&)),
SLOT(syncActive(const QModelIndex&)));
connect(treeView, SIGNAL(clicked(const QModelIndex&)),
SLOT(syncActive(const QModelIndex&)));
connect(iconView, SIGNAL(clicked(const QModelIndex&)),
SLOT(syncActive(const QModelIndex&)));
connect(switchButton, SIGNAL(clicked()),SLOT(switchView()));
}
构造函数现在完成了,我们把注意力放在槽函数的实现上:在switchToDir()我们首先检查传递来的index是否真的是一个目录。QDirModel自身就包含了合适的方法。如果是的话,我们设置model中的开始index到新的目录。注意:我们并不需要在treeView中切换,因为这种view应该总是显示所有的分区内容。既然tree view使用了相同的selection model和其他的views,它自动地显示选择的条目:
// filedialog/filedialog.cpp (continued)
void FileDialog::switchToDir(const QModelIndex& index)
{
if (dirModel->isDir(index)) {
listView->setRootIndex(index);
iconView->setRootIndex(index);
}
}
syncActive()在所有的3个viewsonic中比较活动的条目。对应的QAbstractItemView中的API是CurrentIndex():
// filedialog/filedialog.cpp (continued)
void FileDialog::syncActive(const QModelIndex& index)
{
listView->setCurrentIndex(index);
treeView->setCurrentIndex(index);
iconView->setCurrentIndex(index);
}
在views间切换的槽函数只有一行长度:它需要当前Widget的index,然后加1,要保证index不超出上限,我们添加一个取摸操作:
// filedialog/filedialog.cpp (continued)
void FileDialog::switchView()
{
stackedWidget->setCurrentIndex(
(stackedWidget->currentIndex()+1)%stackedWidget->count());
}
最终,我们使得选中的文件对用户有效。要完成这个,我们定义一个叫做selectedFiles()的方法。在dialog结束——当用户按下打开建(Designer自动跟accept()槽联系起来了)——你可以读出选择的文件名,以QStringList格式,使用FileDialog方法:
// filedialog/filedialog.cpp (continued)
QStringList FileDialog::selectedFiles()
{
QStringList fileNames;
QModelIndexList indexes = selModel->selectedIndexes();
foreach( QModelIndex index,indexes)
fileNames.append( dirModel->filePath(index) );
returnfileNames;
}
返回的条目反映了selection model:selectedIndexes()返回从QDirModel实例中选择的QmodelIndex条目。有了模型提供的FilePath()方法,我们可以得到文件路径。