QT学习之路2 学习笔记
1.Qt 是一个著名的 C++ 应用程序框架。你并不能说它只是一个 GUI 库,因为 Qt 十分庞大,并不仅仅是 GUI 组件。使用 Qt,在一定程度上你获得的是一个“一站式”的解决方案:不再需要研究 STL,不再需要 C++ 的
2.Qt 是一个跨平台的框架。跨平台 GUI 通常有三种实现策略:
API 映射;API 模拟;GUI 模拟;
3.Qt 和 wxWidgets 一样,也是一个标准的 C++ 库。但是它的语法类似于 Java 的 Swing,十分清晰,而且使用信号槽(signal/slot)机制,让程序看起来很明白——这也是很多人优先选择 Qt 的一个很重要的原因。不过,所谓“成也萧何,败也萧何”。这种机制虽然很清楚,但是它所带来的后果是你需要使用 Qt 的 moc 对程序进行预处理,才能够再使用标准的 make 或者 nmake 进行正常的编译,并且信号槽的调用要比普通的函数调用慢大约一个数量级
4.main()函数一般以创建 application 对象(GUI 程序是QApplication,非 GUI 程序是QCoreApplication。QApplication实际上是QCoreApplication的子类。)
5.信号槽:
// !!! Qt 5
connect(sender, signal,
receiver, slot);
这是我们最常用的形式。connect()一般会使用前面四个参数,第一个是发出信号的对象,第二个是发送对象发出的信号,第三个是接收信号的对象,第四个是接收对象在接收到信号之后所需要调用的函数。也就是说,当 sender 发出了 signal 信号之后,会自动调用 receiver 的 slot 函数。QT5有五个重载connect,QT4有三个重载connect
参考自定义信号槽函数,写一个;
6.信号槽QT4和QT5的区别
Newspaper类没有什么区别。
Reader类,receiveNewspaper()函数放在了 public slots 块中。在 Qt 4 中,槽函数必须放在由 slots 修饰的代码块中,并且要使用访问控制符进行访问控制。其原则同其它函数一样:默认是 private 的,如果要在外部访问,就应该是 public slots;如果只需要在子类访问,就应该是 protected slots。
main()函数中,QObject::connect()函数,第二、第四个参数需要使用SIGNAL和SLOT这两个宏转换成字符串(具体事宜我们在上一节介绍过)。注意SIGNAL和SLOT的宏参数并不是取函数指针,而是除去返回值的函数声明,并且 const 这种参数修饰符是忽略不计的。
QT4: QObject::connect(&newspaper, SIGNAL(newPaper(QString)),
&reader, SLOT(receiveNewspaper(QString)));
QT5: QObject::connect(&newspaper, &Newspaper::newPaper,
&reader, &Reader::receiveNewspaper);
7.Qt 5 与 Qt 4 最大的一个区别之一是底层架构有了修改。Qt 5 引入了模块化的概念,将众多功能细分到几个模块之中。Qt 4 也有模块的概念,
8. QT预处理MOC
标准 C++ 对象模型在运行时效率方面卓有成效,但是在某些特定问题域下的静态特性就显得捉襟见肘。GUI 界面需要同时具有运行时的效率以及更高级别的灵活性。为了解决这一问题,Qt “扩展”了标准 C++。所谓“扩展”,实际是在使用标准 C++ 编译器编译 Qt 源程序之前,Qt 先使用一个叫做 moc(Meta Object Compiler,元对象编译器)的工具,先对 Qt 源代码进行一次预处理(注意,这个预处理与标准 C++ 的预处理有所不同。Qt 的 moc 预处理发生在标准 C++ 预处理器工作之前,并且 Qt 的 moc 预处理不是递归的。),生成标准 C++ 源代码,然后再使用标准 C++ 编译器进行编译。
Qt 使用 moc,为标准 C++ 增加了一些特性:
信号槽机制,用于解决对象之间的通讯,这个我们已经了解过了,可以认为是 Qt 最明显的特性之一;
可查询,并且可设计的对象属性;
强大的事件机制以及事件过滤器;
基于上下文的字符串翻译机制(国际化),也就是 tr() 函数,我们简单地介绍过;
复杂的定时器实现,用于在事件驱动的 GUI 中嵌入能够精确控制的任务集成;
层次化的可查询的对象树,提供一种自然的方式管理对象关系。
智能指针(QPointer),在对象析构之后自动设为 0,防止野指针;
能够跨越库边界的动态转换机制
9. 每创建一个对象QOBJECT就会创建对象树:当一个QObject对象在堆上创建的时候,Qt 会同时为其创建一个对象树。不过,对象树中对象的顺序是没有定义的。这意味着,销毁这些对象的顺序也是未定义的。Qt 保证的是,任何对象树中的 QObject对象 delete 的时候,如果这个对象有 parent,则自动将其从 parent 的children()列表中删除;如果有孩子,则自动 delete 每一个孩子。Qt 保证没有QObject会被 delete 两次,这是由析构顺序决定的。
如果QObject在栈上创建,Qt 保持同样的行为
10 QT对象模型章节主要是注意对象树模型,每个QOBJECT继承都会创建对象树,对象析构顺序对应对象树规则;
11.布局管理器: QSpinBox *spinBox = new QSpinBox(&window);
QSlider *slider = new QSlider(Qt::Horizontal, &window);
12.菜单栏、工具栏和状态栏:Qt 将用户与界面进行交互的元素抽象为一种“动作”,使用QAction类表示。QAction可以添加到菜单上、工具栏上。
QMenu *file = menuBar()->addMenu(tr("&File"));
file->addAction(openAction);
QToolBar *toolBar = addToolBar(tr("&File"));
toolBar->addAction(openAction);
QOBJECT是所有父类窗口,Widget是object子类,QMENU,QToolBar是QWidget子类;
13.对话框:对话框分为模态对话框和非模态对话框,Qt 有两种级别的模态对话框:应用程序级别的模态和窗口级别的模态,默认是应用程序级别的模态。应用程序级别的模态是指,当该种模态的对话框出现时,用户必须首先对对话框进行交互,直到关闭对话框,然后才能访问程序中其他的窗口。窗口级别的模态是指,该模态仅仅阻塞与对话框关联的窗口,但是依然允许用户与程序中其它窗口交互,Qt 使用QDialog::exec()实现应用程序级别的模态对话框,使用QDialog::open()实现窗口级别的模态对话框,使用QDialog::show()实现非模态对话框。我们将 dialog 改成堆上建立,在对话框出现的时候可以与主窗口交互,因此我们可以建立多个相同的对话框。
void MainWindow::open()
{
QDialog *dialog = new QDialog;
dialog->setAttribute(Qt::WA_DeleteOnClose); //setAttribute()函数设置对话框关闭时,自动销毁对话框
dialog->setWindowTitle(tr("Hello, dialog!"));
dialog->show();
}
14.对话框数据传递,由于非模态对话框在关闭时可以调用QDialog::accept()或者QDialog::reject()或者更通用的QDialog::done()函数,所以我们可以在这里发出信号。另外,如果找不到合适的信号发出点,我们可以重写QDialog::closeEvent()函数,在这里发出信号。在需要接收数据的窗口(这里是主窗口)连接到这个信号即可。
15.标准对话框 QMessageBox:
if (QMessageBox::Yes == QMessageBox::question(this,
tr("Question"),
tr("Are you OK?"),
QMessageBox::Yes | QMessageBox::No,
QMessageBox::Yes)) {
QMessageBox::information(this, tr("Hmmm..."), tr("I'm glad to hear that!"));
} else {
QMessageBox::information(this, tr("Hmmm..."), tr("I'm sorry!"));
}
16 深入 Qt5 信号槽新语法,信号槽函数出现重载,那么再connect连接信号槽函数时,可以用QT4也可以用指定匿名函数参数来指定重载的信号槽函数
17. 文件对话框: 打开和保存按钮
//构建动作
openAction = new QAction(QIcon(":/images/file-open"), tr("&Open..."), this);
openAction->setShortcuts(QKeySequence::Open);
openAction->setStatusTip(tr("Open an existing file"));
//点击信号槽函数
connect(openAction, &QAction::triggered, this, &MainWindow::openFile);
//添加菜单,工具栏
QMenu *file = menuBar()->addMenu(tr("&File"));
file->addAction(openAction);
QToolBar *toolBar = addToolBar(tr("&File"));
toolBar->addAction(openAction);
//信号槽函数编写
void MainWindow::saveFile()
void MainWindow::openFile()
QString path = QFileDialog::getOpenFileName(this,
tr("Open File"),
".",
tr("Text Files(*.txt)"));
18.事件:Qt 中的事件和信号槽却并不是可以相互替代的。信号由具体的对象发出,然后会马上交给由connect()函数连接的槽进行处理;而对于事件,Qt 使用一个事件队列对所有发出的事件进行维护,当新的事件产生时,会被追加到事件队列的尾部。前一个事件完成后,取出后面的事件进行处理。但是,必要的时候,Qt 的事件也可以不进入事件队列,而是直接处理。信号一旦发出,对应的槽函数一定会被执行。但是,事件则可以使用“事件过滤器”进行过滤,对于有些事件进行额外的处理,另外的事件则不关心。总的来说,如果我们使用组件,我们关心的是信号槽;如果我们自定义组件,我们关心的是事件。因为我们可以通过事件来改变组件的默认操作。比如,如果我们要自定义一个能够响应鼠标事件的EventLabel,我们就需要重写QLabel的鼠标事件,做出我们希望的操作,有可能还得在恰当的时候发出一个类似按钮的clicked()信号(如果我们希望让这个EventLabel能够被其它组件使用)或者其它的信号。
19 事件的接受与忽略:当重写事件回调函数时,时刻注意是否需要通过调用父类的同名函数来确保原有实现仍能进行!事件的传播是在组件层次上面的,而不是依靠类继承机制。
通过调用父类的同名函数,我们可以把 Qt 的事件传递看成链状:如果子类没有处理这个事件,就会继续向其父类传递。Qt 的事件对象有两个函数:accept()和ignore()。正如它们的名字一样,前者用来告诉 Qt,这个类的事件处理函数想要处理这个事件;后者则告诉 Qt,这个类的事件处理函数不想要处理这个事件。在事件处理函数中,可以使用isAccepted()来查询这个事件是不是已经被接收了。具体来说:如果一个事件处理函数调用了一个事件对象的accept()函数,这个事件就不会被继续传播给其父组件;如果它调用了事件的ignore()函数,Qt 会从其父组件中寻找另外的接受者.为了避免自己去调用accept()和ignore()函数,而是尽量调用父类实现,Qt 做了特殊的设计:事件对象默认是 accept 的,而作为所有组件的父类QWidget的默认实现则是调用ignore()。这么一来,如果你自己实现事件处理函数,不调用QWidget的默认实现,你就等于是接受了事件;如果你要忽略事件,只需调用QWidget的默认实现。
20 event() :事件对象创建完毕后,Qt 将这个事件对象传递给QObject的event()函数。event()函数并不直接处理事件,而是将这些事件对象按照它们不同的类型,分发给不同的事件处理器(event handler);注意,在event()函数中,调用事件对象的accept()和ignore()函数是没有作用的;
QEvent::type()函数可以检查事件的实际类型,其返回值是QEvent::Type类型的枚举。我们处理过自己感兴趣的事件之后,可以直接返回 true,表示我们已经对此事件进行了处理;对于其它我们不关心的事件,则需要调用父类的event()函数继续转发,否则这个组件就只能处理我们定义的事件了
event()函数中实际是通过事件处理器来响应一个具体的事件。这相当于event()函数将具体事件的处理“委托”给具体的事件处理器。而这些事件处理器是 protected virtual 的,因此,我们重写了某一个事件处理器,即可让 Qt 调用我们自己实现的版本。
21 事件过滤器:QObject有一个eventFilter()函数,用于建立事件过滤器
virtual bool QObject::eventFilter ( QObject * watched, QEvent * event );事件过滤器也是如此:它会检查接收到的事件。如果这个事件是我们感兴趣的类型,就进行我们自己的处理;如果不是,就继续转发。这个函数返回一个 bool 类型,如果你想将参数 event 过滤出来,比如,不想让它继续转发,就返回 true,否则返回 false。事件过滤器的调用时间是目标对象(也就是参数里面的watched对象)接收到事件对象之前。也就是说,如果你在事件过滤器中停止了某个事件,那么,watched对象以及以后所有的事件过滤器根本不会知道这么一个事件.安装过滤器需要调用QObject::installEventFilter()函数
22.事件总结:Qt 的事件是整个 Qt 框架的核心机制之一;
现在我们可以总结一下 Qt 的事件处理,实际上是有五个层次:
重写paintEvent()、mousePressEvent()等事件处理函数。这是最普通、最简单的形式,同时功能也最简单。
重写event()函数。event()函数是所有对象的事件入口,QObject和QWidget中的实现,默认是把事件传递给特定的事件处理函数。
在特定对象上面安装事件过滤器。该过滤器仅过滤该对象接收到的事件。
在QCoreApplication::instance()上面安装事件过滤器。该过滤器将过滤所有对象的所有事件,因此和notify()函数一样强大,但是它更灵活,因为可以安装多个过滤器。全局的事件过滤器可以看到 disabled 组件上面发出的鼠标事件。全局过滤器有一个问题:只能用在主线程。
重写QCoreApplication::notify()函数。这是最强大的,和全局事件过滤器一样提供完全控制,并且不受线程的限制。但是全局范围内只能有一个被使用(因为QCoreApplication是单例的)。
顺序:全局事件过滤器被第一个调用,之后是该对象上面的事件过滤器,其次是event()函数,最后是特定的事件处理函数
23. 自定义事件
//注册一个事件 static int QEvent::registerEventType ( int hint = -1 );
//两种方式发送事件给对象:
1.static bool QCoreApplication::sendEvent(QObject *receiver,
QEvent *event);
2.static void QCoreApplication::postEvent(QObject *receiver,
QEvent *event);
//重写事件处理函数
24 Qt 绘制系统简介
Qt 的绘图系统实际上是,使用QPainter在QPainterDevice上进行绘制,它们之间使用QPaintEngine进行通讯(也就是翻译QPainter的指令,我们可以把QPainter理解成画笔;把QPaintDevice理解成使用画笔的地方,比如纸张、屏幕等;而对于纸张、屏幕而言,肯定要使用不同的画笔绘制,为了统一使用一种画笔,我们设计了QPaintEngine类,这个类让不同的纸张、屏幕都能使用一种画笔。
25. QPainter的逻辑坐标与QPaintDevice的物理坐标进行映射的工作,是由QPainter的变换矩阵(transformation matrix)、视口(viewport)和窗口(window)完成的。Qt 使用 viewport-window 机制将我们提供的逻辑坐标转换成绘制设备使用的物理坐标,方法是,在逻辑坐标和物理坐标之间提供一层“窗口”坐标。视口是由任意矩形指定的物理坐标;窗口则是该矩形的逻辑坐标表示。默认情况下,物理坐标和逻辑坐标是一致的,都等于设备矩形。
视口坐标(也就是物理坐标)和窗口坐标是一个简单的线性变换。我们传给QPainter的是逻辑坐标(也称为世界坐标),逻辑坐标可以通过变换矩阵转换成窗口坐标,窗口坐标通过 window-viewport 转换成物理坐标(也就是设备坐标)。translate()函数只是简单地将坐标原点重新设置,而setWindow()则是将整个坐标系进行了修改
26. Graphics View Framework:Graphics View 提供了一种接口,用于管理大量自定义的 2D 图形元素,并与之进行交互;还提供了用于将这些元素进行可视化显示的观察组件,并支持缩放和旋转。Graphics View 是一个基于元素(item)的 MV 架构的框架。它可以分成三个部分:元素 item、场景 scene 和视图 view。
Graphics View Framework 有三个主要部分:
QGraphicsScene:能够管理元素的非 GUI 容器;
QGraphicsItem:能够被添加到场景的元素;
QGraphicsView:能够观察场景的可视化组件视图。
29.贪吃蛇
对于游戏而言,我们需要一个QGraphicsScene,作为游戏发生的舞台;一个QGraphicsView,作为观察游戏舞台的组件;以及若干元素,用于表示游戏对象,比如蛇、食物以及障碍物等。我们的地图是建立在QGraphicsScene的基础之上的,所以,里面的对象应该是QGraphicsItem实例。通常,我们会把所有的图形元素(这里便是游戏中需要的对象,例如蛇、食物等)设计为QGraphicsItem的子类,在这个类中添加绘制自身的代码以及动画逻辑。这也是面向对象的开发方式:封装自己的属性和操作。在我们的游戏中,应该有三个对象:蛇 Snake、食物 Food 以及墙 Wall。
35 文件:
QFile file("in.txt");
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
qDebug() << "Open file failed.";
return -1;
}
有关文件本身的信息,比如文件名、文件所在目录的名字等,则是通过QFileInfo获取
36 二进制文件:
我们定义的二进制格式通常具有一个魔术数字,用于标识文件的合法性。QDataStream的相关内容。我们用魔术数字标识文件的类型,从而判断文件是不是合法的。但是,文件的不同版本之间也可能存在差异:我们可能在第一版保存整型,第二版可能保存字符串。那么,既然QIODevice提供了read()、readLine()之类的函数,为什么还要有QDataStream呢?QDataStream同QIODevice有什么区别?区别在于,QDataStream提供流的形式,性能上一般比直接调用原始 API 更好一些。
37 文本文件:
QTextStream和QDataStream的使用类似,只不过它是操作纯文本文件的。以文本形式写入数据,是没有数据之间的分隔的,QTextStream同QDataStream的使用基本一致,为方便起见,QTextStream同std::cout一样提供了很多描述符
38:存储容器:
Qt 提供了顺序存储容器:QList,QLinkedList,QVector,QStack和QQueue。对于绝大多数应用程序,QList是最好的选择。虽然它是基于数组实现的列表,但它提供了快速的向前添加和向后追加的操作。如果你需要链表,可以使用QLinkedList。如果你希望所有元素占用连续地址空间,可以选择QVector。QStack和QQueue则是 LIFO 和 FIFO 的。
Qt 还提供了关联容器:QMap,QMultiMap,QHash,QMultiHash和QSet。带有“Multi”字样的容器支持在一个键上面关联多个值。“Hash”容器提供了基于散列函数的更快的查找,而非 Hash 容器则是基于二分搜索的有序集合
我们将 Qt 提供的各个容器类总结如下:
QList
QLinkedList
QVector
QStack
QQueue
QSet
QMap
QMultiMap
QHash
QMultiHash
39 遍历容器:Qt 的容器类提供了两种风格的遍历器:Java 风格和 STL 风格。这两种风格的遍历器在通过非 const 函数对集合进行修改时都是不可用的。
40. 隐式数据共享:当对象即将被修改,并且其引用计数大于 1 时,隐式数据共享自动将数据从共享块中拿出。隐式共享类必须控制其内部数据,在任何修改其数据的函数中,将数据自动取出。在我们操作共享数据时,实际有两种拷贝对象的方法:我们通常称其为深拷贝和浅拷贝。深拷贝意味着要重新构造一个全新的对象;浅拷贝则仅仅复制引用,也就是上面所说的那个指向共享数据块的指针。深拷贝对内存和 CPU 资源都是很昂贵的;浅拷贝则非常快速,因为它仅仅是设置一个新的指针,然后将引用计数加 1。具有隐式数据共享的对象,其赋值运算符使用的是浅拷贝来实现的。
对于QList或者QVector,我们应该使用at()函数而不是 [] 操作符进行只读访问。原因是 [] 操作符既可以是左值又可以是右值,这让 Qt 容器很难判断到底是左值还是右值,这意味着无法进行隐式数据共享;而at()函数不能作左值,因此可以进行隐式数据共享。另外一点是,对于begin(),end()以及其他一些非 const 遍历器,由于数据可能改变,因此 Qt 会进行深复制。为了避免这一点,要尽可能使用const_iterator、constBegin()和constEnd()。
41:model/view 架构:视图从模型获取模型索引,这种索引就是数据项的引用。通过将这个模型索引反向传给模型,视图又可以从数据源获取数据。在标准视图中,委托渲染数据项;在需要编辑数据时,委托使用直接模型索引直接与模型进行交互。
总的来说,model/view 架构将传统的 MV 模型分为三部分:模型、视图和委托。每一个组件都由一个抽象类定义,这个抽象类提供了基本的公共接口以及一些默认实现。模型、视图和委托则使用信号槽进行交互:
来自模型的信号通知视图,其底层维护的数据发生了改变;
来自视图的信号提供了有关用户与界面进行交互的信息;
来自委托的信号在用户编辑数据项时使用,用于告知模型和视图编辑器的状态。
所有的模型都是QAbstractItemModel的子类。这个类定义了供视图和委托访问数据的接口。模型并不存储数据本身。这意味着,你可以将数据存储在一个数据结构中、另外的类中、文件中、数据库中,或者其他你所能想到的东西中。
42 QListWidget、QTreeWidget 和 QTableWidget
详见Qt 学习之路 2(42)
43 QStringListModel
也就是说,我们直接对数据进行操作,当模型检测到数据发生了变化,会立刻通知视图进行刷新。这样,我们就可以把精力集中到对数据的操作上,而不用担心视图的同步显示问题。这正是 model/view 模型所带来的一个便捷之处。
44.QFileSystemModel
Qt 内置了两种模型:QStandardItemModel和QFileSystemModel。QStandardItemModel是一种多用途的模型,能够让列表、表格、树等视图显示不同的数据结构。这种模型会将数据保存起来。试想一下,列表和表格所要求的数据结构肯定是不一样的:前者是一维的,后者是二维的。因此,模型需要保存有实际数据,当视图是列表时,以一维的形式提供数据;当视图是表格时,以二维的形式提供数据。QFileSystemModel则是另外一种方式。它的作用是维护一个目录的信息。因此,它不需要保存数据本身,而是保存这些在本地文件系统中的实际数据的一个索引。我们可以利用QFileSystemModel显示文件系统的信息、甚至通过模型来修改文件系统。
45. 模型
为了能够使得数据的显示同存储分离,我们引入模型索引(model index)的概念。通过索引,我们可以访问模型的特定元素的特定部分。视图和委托使用索引来请求所需要的数据。由此可以看出,只有模型自己需要知道如何获得数据,模型所管理的数据类型可以使用通用的方式进行定义。索引保存有创建的它的那个模型的指针,这使得同时操作多个模型成为可能。
总结一下:
模型使用索引来提供给视图和委托有关数据项的位置的信息,这样做的好处是,模型之外的对象无需知道底层的数据存储方式;
数据项通过行号、列号以及父项三个坐标进行定位;
模型索引由模型在其它组件(视图和委托)请求时才会被创建;
如果使用index()函数请求获得一个父项的可用索引,该索引会指向模型中这个父项下面的数据项。这个索引指向该项的一个子项;如果使用index()函数请求获得一个父项的不可用索引,该索引指向模型的最顶级项;
角色用于区分数据项的不同类型的数据。
模型的数目信息可以通过rowCount()和columnCount()获得。这些函数需要制定父项;
索引用于访问模型中的数据。我们需要利用行号、列号以及父项三个参数来获得该索引;
当我们使用QModelIndex()创建一个空索引使用时,我们获得的就是模型中最顶级项;
数据项包含了不同角色的数据。为获取特定角色的数据,必须指定这个角色。
46. 视图和委托
我们使用QAbstractItemModel提供标准的模型接口,使用 QAbstractItemView提供标准的视图接口,而结合这两者,就可以将数据同表现层分离,在视图中利用前面所说的模型索引。视图管理来自模型的数据的布局:既可以直接渲染数据本身,也可以通过委托渲染和编辑数据。
视图不仅仅用于展示数据,还用于在数据项之间的导航以及数据项的选择。另外,视图也需要支持很多基本的用户界面的特性,例如右键菜单以及拖放。视图可以提供数据编辑功能,也可以将这种编辑功能交由某个委托完成。视图可以脱离模型创建,但是在其进行显示之前,必须存在一个模型。也就是说,视图的显示是完全基于模型的,这是不能脱离模型存在的。对于用户的选择,多个视图可以相互独立,也可以进行共享。
委托就是供视图实现某种高级的编辑功能。不同于经典的 Model-View-Controller(MVC)模式,model/view 没有将用户交互部分完全分离。一般地,视图将数据向用户进行展示并且处理通用的输入。但是,对于某些特殊要求(比如这里的要求必须输入数字),则交予委托完成。这些组件提供输入功能,同时也能渲染某些特殊数据项。委托的接口由QAbstractItemDelegate定义。在这个类中,委托通过paint()和sizeHint()两个函数渲染用户内容(也就是说,你必须自己将渲染器绘制出来)。为使用方便,从 4.4 开始,Qt 提供了另外的基于组件的子类:QItemDelegate和QStyledItemDelegate。默认的委托是QStyledItemDelegate
47. 视图选择
Qt 使用QItemSelectionModel类获取视图中项目的选择情况。这个模型保持有项目的索引,并且独立于任何视图。这意味着,我们可以让不同的视图共享同一个选择模型,从来达到一种同步操作的目的。选择由选择区域组成。模型只将选区的开始和结束的索引位置记录下来,以保证对于很大的选区也有很好的性能。非连续选区则由多个连续选择组成。
48 QSortFilterProxyModel
QSortFilterProxyModel。之所以将这个类放在这里,是因为在一定程序上,我们可以使用QSortFilterProxyModel获得一些可能必须自定义才能达到的效果。QSortFilterProxyModel并不能单独使用。顾名思义,它是一个“代理”,其真正的数据需要另外的一个模型提供。它的作用是对数据进行排序和过滤。排序很好理解,而过滤,则是按照输入的内容对数据及进行筛选,很像 Excel 里面的过滤器。不过 Qt 提供的过滤功能是基于正则表达式的,功能很强大。
49. 自定义只读模型
在开始自定义模型之前,我们首先需要思考这样一个问题:我们的数据结构适合于哪种视图的显示方式?是列表,还是表格,还是树?如果我们的数据仅仅用于列表或表格的显示,那么QAbstractListModel或者QAbstractTableModel 已经足够,它们为我们实现了很多默认函数。但是,如果我们的数据具有层次结构,并且必须向用户显示这种层次,我们只能选择QAbstractItemModel。不管底层数据结构是怎样的格式,最好都要直接考虑适应于标准的QAbstractItemModel的接口,这样就可以让更多视图能够轻松访问到这个模型