首页资源分类嵌入式系统 > QT入门之选

QT入门之选

已有 445125个资源

下载专区

上传者其他资源

    文档信息举报收藏

    标    签:QT

    分    享:

    文档简介

    嵌入式QT之新手学习的好帮手

    文档预览

    Qt 学习之路 ---整理:DZY 献给自强不息的自学者 Qt 学习之路 DZY 整理 目录 QT 学习之路(1) 前言.......................................................................................................................................................4 QT 学习之路(2):HELLO, WORLD!............................................................................................................................6 QT 学习之路(3):HELLO, WORLD!(续)...................................................................................................................13 QT 学习之路(4):初探信号槽......................................................................................................................................16 QT 学习之路(5):组件布局..........................................................................................................................................18 QT 学习之路(6): API 文档的使用................................................................................................................................22 QT 学习之路(7): 创建一个对话框(上)........................................................................................................................23 QT 学习之路(8): 创建一个对话框(下)........................................................................................................................26 QT 学习之路(9):深入了解信号槽 ...........................................................................................................................31 QT 学习之路(10): META-OBJECT 系统 ...................................................................................................................34 QT 学习之路(11): MAINWINDOW..............................................................................................................................35 QT 学习之路(12): 菜单和工具条.................................................................................................................................38 QT 学习之路(13): 菜单和工具条(续)..........................................................................................................................42 QT 学习之路(14): 状态栏..............................................................................................................................................47 QT 学习之路(15): QT 标准对话框之 QFILEDIALOG.............................................................................................49 QT 学习之路(16): QT 标准对话框之 QCOLORDIALOG........................................................................................50 QT 学习之路(17): QT 标准对话框之 QMEAGEBOX...............................................................................................52 QT 学习之路(18): QT 标准对话框之 QIUTDIALOG................................................................................................53 QT 学习之路(19): 事件(EVENT)..................................................................................................................................56 QT 学习之路(20): 事件接收与忽略.............................................................................................................................57 QT 学习之路(21): EVENT()...........................................................................................................................................59 QT 学习之路(22): 事件过滤器......................................................................................................................................61 QT 学习之路(23): 自定义事件......................................................................................................................................63 QT 学习之路(24): QPAINTER.......................................................................................................................................66 QT 学习之路(25): QPAINTER(续)................................................................................................................................67 QT 学习之路(26): 反走样..............................................................................................................................................71 QT 学习之路(27): 渐变填充 ........................................................................................................................................74 QT 学习之路(28): 坐标变换 ........................................................................................................................................77 QT 学习之路(29): 绘图设备 ........................................................................................................................................81 第 2 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 QT 学习之路(30): GRAPHICS VIEW FRAMEWORK ............................................................................................85 QT 学习之路(31): 一个简易画板的实现(QWIDGET) .............................................................................................89 QT 学习之路(32): 一个简易画板的实现(GRAPHICS VIEW) ..............................................................................100 QT 学习之路(33): 国际化(上) ....................................................................................................................................109 QT 学习之路(34): 国际化(下) .....................................................................................................................................116 QT 学习之路(35): QT 容器类之顺序存储容器 .....................................................................................................119 QT 学习之路(36): QT 容器类之遍历器和隐式数据共享 ....................................................................................124 QT 学习之路(37): QT 容器类之关联存储容器 .....................................................................................................129 QT 学习之路(38): MODEL-VIEW 架构 ..................................................................................................................133 QT 学习之路(39): QLISTWIDGET ...........................................................................................................................135 QT 学习之路(40): QTREEWIDGET ..........................................................................................................................138 QT 学习之路(41): QTABLEWIDGET .......................................................................................................................142 QT 学习之路(42): QSTRINGLISTMODEL ..............................................................................................................144 QT 学习之路(43): QDIRMODEL ...............................................................................................................................150 QT 学习之路(44): QSORTFILTERPROXYMODEL ...............................................................................................157 QT 学习之路(45): 自定义 MODEL 之一 .................................................................................................................161 QT 学习之路(46): 自定义 MODEL 之二 .................................................................................................................169 QT 学习之路(47): 自定义 MODEL 之三 .................................................................................................................175 QT 学习之路(48): 自定义委托 .................................................................................................................................184 QT 学习之路(49): 通用算法 ......................................................................................................................................193 QT 学习之路(50): QSTRING ......................................................................................................................................196 QT 学习之路(51): QBYTEARRAY 和 QVARIANT .................................................................................................200 QT 学习之路(52): 拖放技术之一 .............................................................................................................................203 QT 学习之路(53): 拖放技术之二 .............................................................................................................................208 QT 学习之路(54): 自定义拖放数据对象 ................................................................................................................214 QT 学习之路(55): 剪贴板操作 .................................................................................................................................221 QT 学习之路(56): 二进制文件读写 .........................................................................................................................227 QT 学习之路(57): 文本文件读写 .............................................................................................................................231 QT 学习之路(58): 进程间交互 .................................................................................................................................234 QT 学习之路(59): 编写跨平台的程序 ....................................................................................................................239 第 3 页 共 243 页 整理:DZY Qt 学习之路(1) 前言 我们所使用的 Qt,确切地说也就是它的 GUI 编程部分。C++的 GUI 编程同 Java 不同:GUI 并不是 C++标准的一部分。所以,如果使用 Java,那么你最好的选择就是 AWT/Swing,或者 也可以使 SWT/JFace,但是,C++的 GUI 编程给了你更多的选择:wxWidget, gtk++以及 Qt。 这几个库我都有接触,但是接触都不是很多,只能靠一些资料和自己的一点粗浅的认识说一 下它们之间的区别(PS: 更详尽的比较在前面的文章中有)。   首先说 wxWidget,这是一个标准的 C++库,和 Qt 一样庞大。它的语法看上去和 MFC 类似, 有 大量的宏。据说,一个 MFC 程序员可以很容易的转换到 wxWidget 上面来。wxWidget 有一 个很大的优点,就是它的界面都是原生风格的。这是其他的库所不能做到的。wxWidget 的运 行效率很高, 据说在 Windows 平台上比起微软自家的 MFC 也不相上下。   gtk++其实是一个 C 库,不过由于 C++和 C 之间的关系,这点并没有很 大的关系。但是, gtk++是一个使用 C 语言很优雅的实现了面向对象程序设计的范例。不过,这也同样带来了一 个问题——它的里面带有大量的类型转换的宏来 模拟多态,并且它的函数名“又臭又长(不 过这点我倒是觉得无所谓,因为它的函数名虽然很长,但是同样很清晰)”,使用下划线分 割单词,看上去和 Linux 如出一辙。由于它是 C 语言实现,因此它的运行效率当然不在话下 。 gtk++并不是模拟的原生界面,而有它自己的风格,所以有时候就会和操作系统的界面显得 格格不入。   再来看 Qt,和 wxWidget 一样,它也是一个标准的 C++库。但是它的语法很类似于 Java 的 Swing,十分清晰,而 且 SIGNAL/SLOT 机制使得程序看起来很明白——这也是我首先选 择 Qt 的一个很重要的方面,因为我是学 Java 出身的 :) 。不过,所谓“成也萧何,败也萧 何”,这种机制虽然很清楚,但是它所带来的后果是你需要使用 Qt 的 qmake 对程序进行预 处理,才能够再使用 make 或者 nmake 进行编译。并且它的界面也不是原生风格的,尽管 Qt 使用 style 机制十分巧妙的模拟了本地界面。另外值得一提的是,Qt 不仅仅运行在桌面环境 中,Qt 已经被 Nokia 收购,它现在已经会成为 Symbian 系列的主要界面技术——Qt 是能够 运行于嵌入式平台的。 Qt 学习之路 DZY 整理   以往人们对 Qt 的授权多有诟病。因为 Qt 的商业版本价格不菲,开源版本使用的是 GPL 协议。但是现在 Qt 的开源协议已经变成 LGPL。这意味着,你可以将 Qt 作为一个库 连接到一 个闭源软件里面。可以说,现在的 Qt 协议的争议已经不存在了——因为 wxWidgets 或者 gtk+ 同样使用的是类似的协议发布的。    在 本系 列文 章中 ,我 们 将 使用 Qt4 进 行 C++ GUI 的 开发 。我 是 参 照着 《 C++ GUI Programming with Qt4》一书进行学习的。其实,我也只是初学 Qt4,在这里将这个学习笔记 记下来,希望能够方便更多的朋友学习 Qt4。我是一个 Java 程序员,感觉 Qt4 的一些命名规 范以及约束同 Java 有异曲同工之妙,因而从 Java 迁移到 Qt4 似乎困难不大。不过,这也主 要是因为 Qt4 良好的设计等等。    闲话少说,还是尽快开始下面的学习吧!   第 5 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 Qt 学习之路(2):Hello, world!   任何编程技术的学习第一课基本上都会是 Hello, world!,我也不想故意打破这个惯例 ——照理说,应该首先回顾一下 Qt 的历史,不过即使不说这些也并无大碍。   或许有人总想知 道,Qt 这个单词是什么意思。其实,这并不是一个缩写词,仅仅是因 为 它 的 发 明 者 , TrollTech 公 司 的 CEO , Haarard Nord 和 Trolltech 公 司 的 总 裁 Eirik Chambe-Eng 在联合发明 Qt 的时候并没有一个很好的名字。在这里,字母 Q 是 Qt 库中所有类 的前缀——这仅仅是因为在 Haarard 的 emacs 的 字体中,这个字母看起来特别的漂亮;而 字母 t 则代表“toolkit”,这是在 Xt( X toolkit )中得到的灵感。   顺便说 句,Qt 原始的公司就是上面提到的 Trolltech,貌似有一个中文名字是奇趣科 技——不过现在已经被 Nokia 收购了。因此,一些比较旧的文章里面会 提到 Trolltech 这个 名字。   好了,闲话少说,先看看 Qt 的开发吧!事先说明一下,我是一个比较懒的人,不喜欢 配置很多的东西,而 Qt 已经提供了一个轻量级的 IDE,并且它的网站上也有 for Eclipse 和 VS 的开发插件,不过在这里我并不想用这些大块头 :)    Qt 有两套协议——商业版本和开源的 LGPL 版本。不同的是前者要收费,而后者免费, 当然,后者还要遵循 LGPL 协议的规定,这是题外话。    Qt 的网址是 https://qt.nokia.com/downloads,不过我打开这个站点总是很慢,不知 道为什么。你可以找到大大的 LGPL/Free 和 Commercial,好了,我选的是 LGPL 版本的,下 载包蛮大,但是下载并不会很慢。下载完成后安装就可以了,其它不用管了。这样,整个 Qt 的开发环 境就装好了——如果你需要的话,也可以把 qmake 所在的目录添加进环境变量, 不过我就不做了。   安装完成后会有个 Qt Creator 的东西,这就是官方提供的一个轻量级 IDE,不过它的功 能还是蛮强大的。运行这个就会发现,其实 Qt 不仅仅是 Linux KDE 桌面的底层实现库。而且 是这个 IDE 的实现 :) 这个 IDE 就是用 Qt 完成的。     Qt Creator 左 面 从 上 到 下 依 次 是 Welcome( 欢 迎 页 面 , 就 是 一 开 始 出 现 的 那 个 ) ; Edit(我们的代码编辑窗口);Debug(调试窗 口);Projects(工程窗口);Help(帮助,这个 第 6 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 帮助完全整合的 Qt 的官方文档,相当有用);Output(输出窗口)。   下 面我们来试试我们的 Hello, world! 吧!     在 Edit 窗 口 空 白 处 点 右 键 , 有 New project... 这 里 我 们 选 第 三 项 , Qt Gui Application。      然后点击 OK,来到下一步,输入工程名字和保存的位置。 第 7 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理   点击 Next,来到选择库的界面。这里我们系统默认为我们选择了 Qt core 和 GUI,还记 得我们建的是 Gui Application 吗?嗯,就是这里啦,它会自动为我们加上 gui 这个库。现 在 应 该 就 能 看 出 , Qt 是 多 么 庞 大 的 一 个 库 , 它 不 仅 仅 有 Gui , 而 且 有 Network,OpenGL,XML 之类。不过,现在在这里我们不作修改,直接 Next。 第 8 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理      下一个界面需要我们定义文件名,我们不修改默认的名字,只是为了清除起见,把 generate form 的那个勾去掉即可。 第 9 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理      Next 之后终于到了 Finish 了——漫长的一系列啊!检查无误后 Finish 就好啦! 第 10 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理      之后可以看到,IDE 自动生成了四个文件,一个.pro 文件,两个.cpp 和一个.h。这 里说 明一下,.pro 就是工程文件 (project),它是 qmake 自动生成的用于生产 makefile 的配置 文件。这里我们先不去管它。main.cpp 里面就是一个 main 函数,其 他两个文件就是先前我们 曾经指定的文件名的文件。 第 11 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理   现在,我们把 main.cpp 中的代码修改一下: #include #include   int main(int argc, char *argv[]) {         QApplication a(argc, argv);         QLabel *label = new QLabel("Hello, world!");          label->show();         return a.exec(); }    修改完成后保存。点击左下角的绿色三角键,Run。一个小小的窗口出现了——   好了!我们的第一个 Qt 程序已经完成了。   PS:截了很多图,说得详细些,以后可就没这么详 细的步骤啦,嘿嘿…相信很多朋友 应该一下子就能看明白这个 IDE 应该怎么使用了的,无需我多费口舌。呵呵。 下一篇中,将会对这个 Hello, world!做一番逐行解释! 第 12 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 Qt 学习之路(3):Hello, world!(续)   下面来逐行解释一下前面的那个 Hello, world!程序,尽管很简单,但却可以对 Qt 程序 的结构有一个清楚的认识。现在再把代码贴过来: #include #include int main(int argc, char *argv[]) {         QApplication app(argc, argv);          QLabel *label = new QLabel("Hello, world!");         label->show();         return app.exec(); }   第 1 行和 第 2 行就是需要引入的头文件。和普通的 C++程序没有什么两样,如果要使用 某个组件,就必须要引入相应的头文件,这类似于 Java 的 import 机制。值 得说明的是,Qt 中头文件和类名是一致的。也就是说,如果你要使用某个类的话,它的类名就是它的头文件 名。   第 3 行是空行 :)    第 4 行是 main 函数函数头。这与普通的 C++程序没有什么两样,学过 C++的都明白。因 此你可以看到,实际上,Qt 完全通过普通的 main 函数进入, 这不同于 wxWidgets,因为 wxWidgets 的 Hello, world 需要你继承它的一个 wxApp 类,并覆盖它的 wxApp::OnInit 方法, 系统会自动将 OnInit 编译成入口函数。不过在 Qt 中,就不需 要这些了。   第 5 行,噢噢,大括号…   第 6 行,创建一个 QApplication 对象。这个对象用于管理应用程序级别的 资源 。 QApplication 的构造函数要求两个参数,分别来自 main 的那两个参数,因此,Qt 在一定程 度上是支持命令行参数的。    第 7 行,创建一个 QLabel 对象,并且能够显示 Hello, world!字符串。和其他库的 第 13 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 Label 控件一样,这是用来显示文本的。在 Qt 中,这被称为一个 widget(翻译出来是小东西, 不 过 这 个 翻 译 并 不 好 … ) , 它 等 同 于 Windows 技 术 里 面 的 控 件 (controls) 和 容 器 (containers)。也就是说,widget 可以放置其他的 widget,就像 Swing 的组件。大多数 Qt 程 序使用 QMainWindow 或者 QDialog 作为顶级组件,但 Qt 并不强制要求这点。在这个例子 中, 顶级组件就是一个 QLabel。   第 8 行,使这个 label 可见。组件创建出来之后通常是不可见的,要求我们手动的使它 们可见。这 样,在创建出组建之后我们就可以对它们进行各种定制,以避免出现之后在屏幕 上面会有闪烁。   第 9 行,将应用程序的控制权移交给 Qt。这 时,程序的事件循环就开始了,也就是说, 这时可以相应你发出的各种事件了。这类似于 gtk+最后的一行 gtk_main()。   第 10 行,大括号……程序结束了。   注意,我们并没有使用 delete 去删除创建的 QLabel,因为在程序结束后操作系统会回 收这个空间—— 这只是因为这个 QLabel 占用的内存比较小,但有时候这么做会引起麻烦的, 特别是在大程序中,因此必须小心。   好了,程序解释完了。按 照正常的流程,下面应该编译。前面也提过,Qt 的编译不能使 用普通的 make,而必须先使用 qmake 进行预编译。所以,第一步应该是在工程目录下使用    qmake -project   命令创建.pro 文件(比如说是叫 helloworld.pro)。然后再在.pro 文件目录下使用    qmake helloworld.pro  (make)   或者   qmake -tp vc helloworld.pro  (nmake)   生成 makefile,然后才能调用 make 或者是 nmake 进行编译。不过因为我们 使用的是 IDE,所以这些步骤就不需要我们手动完成了。   值得说明一点的是,这个 qmake 能够生成标准的 makefile 文件,因此完 全可以利用 qmake 自动生成 makefile——这是题外话。   好了,下面修改一下源代码,把 QLabel 的创建一句改成 第 14 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理     QLabel *label = new QLabel("

    Hello, world!

    ");   运行一下:   同 Swing 的 JLabel 一样,Qt 也是支持 HTML 解析的。   好了,这个 Hello, world 就说到这里!明确一下 Qt 的程序结构,在一个 Qt 源代码中, 一下两条语句是必不可少的: QApplication app(argc, argv); //... return app.exec(); 第 15 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 Qt 学习之路(4):初探信号槽   看过了简单的 Hello, world! 之后,下面来看看 Qt 最引以为豪的信号槽机制!   所谓信号槽,简单来说,就像 是插销一样:一个插头和一个插座。怎么说呢?当某种事 件发生之后,比如,点击了一下鼠标,或者按了某个按键,这时,这个组件就会发出一个信 号。就像是广播 一样,如果有了事件,它就漫天发声。这时,如果有一个槽,正好对应上这 个信号,那么,这个槽的函数就会执行,也就是回调。就像广播发出了,如果你感兴趣, 那 么你就会对这个广播有反应。干巴巴的解释很无力,还是看代码: #include #include int main(int argc, char *argv[]) {         QApplication a(argc, argv);         QPushButton *button = new QPushButton("Quit");          QObject::connect(button, SIGNAL(clicked()), &a, SLOT(quit()));          button->show();         return a.exec(); }    这是在 Qt Creator 上面新建的文件,因为前面已经详细的说明怎么新建工程,所以这 里就不再赘述了。这个程序很简单,只有一个按钮,点击之后程序退出。(顺便说 一句,Qt 里面的 button 被叫做 QPushButton,真搞不明白为什么一个简单的 button 非得加上 push 呢? 呵呵)   主要 是看这一句:   QObject::connect(button, SIGNAL(clicked()), &a, SLOT(quit()));   QObject 是所有类的根。Qt 使用这个 QObject 实现了一个单根继承的 C++。它里面有一个 connect 静态函数,用于连接信号槽。 第 16 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理   当一个按钮被点击时,它会发出一个 clicked 信号,意思是,向周围的组件们声明:我 被点 击啦!当然,其它很多组件都懒得理他。如果对它感兴趣,就告诉 QObject 说,你帮我 盯着点,只要 button 发出 clicked 信号,你就告诉我—— 想了想之后,说,算了,你也别 告诉我了,直接去执行我的某某某函数吧!就这样,一个信号槽就形成了。具体来说呢,这 个例子就是 QApplication 的 实例 a 说,如果 button 发出了 clicked 信号,你就去执行我的 quit 函数。所以,当我们点击 button 的时候,a 的 quit 函数被调用,程序 退出了。所以, 在这里,clicked()就是一个信号,而 quit()就是槽,形象地说就是把这个信号插进这个槽 里面去。   Qt 使用信 号槽机制完成了事件监听操作。这类似与 Swing 里面的 listener 机制,只是 要比这个 listener 简单得多。以后我们会看到,这种信号槽的定义 也异常的简单。值得注意 的是,这个信号槽机制仅仅是使用的 QObject 的 connect 函数,其他并没有什么耦合——也 就是说,完全可以利用这种机制实 现你自己的信号监听!不过,这就需要使用 qmake 预处 理一下了!   细心的你或许发现,在 Qt Creator 里面,SIGNAL 和 SLOT 竟然变颜色了!没错,Qt 确 实把它们当成了关键字!实际上,Qt 正是利用它们扩展了 C++语言,因此才需要使 用 qmake 进行预处理,比便使普通的 C++编译器能够顺利编译。另外,这里的 signal 和 Unix 系统里面 的 signal 没有任何的关系!哦哦,有一 点关系,那就是名字是一样的! 信号槽机制是 Qt 关键部分之一,以后我们还会再仔细的探讨这个问题的。 第 17 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 Qt 学习之路(5):组件布局   顾名思义,绝对定位就是使用最原始的定位方法,给出这个组件的坐标和长宽值。这样 Qt 就知道该把组件放在哪里,以及怎么设置组件的大小 了。但是这样做的一个问题是,如 果用户改变了窗口大小,比如点击了最大化或者拖动窗口边缘,这时,你就要自己编写相应 的函数来响应这些变化,以避免那些组 件还只是静静地呆在一个角落。或者,更简单的方法 是直接禁止用户改变大小。   不过,Qt 提供了另外的一种机制,就是布局,来解决这个问 题。你只要把组件放入某 一种布局之中,当需要调整大小或者位置的时候, Qt 就知道该怎样进行调整。这类似于 Swing 的布局管理器,不过 Qt 的布局没有那 么多,只有有限的几个。   来看一下下面的例子: #include #include #include #include #include int main(int argc, char *argv[]) {          QApplication app(argc, argv);         QWidget *window = new QWidget;         window->setWindowTitle("Enter your age");          QSpinBox *spinBox = new QSpinBox;         QSlider *slider = new QSlider(Qt::Horizontal);         spinBox->setRange(0, 130);          slider->setRange(0, 130); 第 18 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理               QObject::connect(slider, SIGNAL(valueChanged(int)), spinBox, SLOT(setValue(int)));               QObject::connect(spinBox, SIGNAL(valueChanged(int)), slider, SLOT(setValue(int)));         spinBox->setValue(35);         QHBoxLayout *layout = new QHBoxLayout;         layout->addWidget(spinBox);         layout->addWidget(slider);         window->setLayout(layout);          window->show();         return app.exec(); }     这 里 使 用 了 两 个 新 的 组 件 : QSpinBox 和 QSlider , 以 及 一 个 新 的 顶 级 窗 口 QWidget。QSpinBox 是一个有上下箭头的 微调器,QSlider 是一个滑动杆,只要运行一下就 会明白到底是什么东西了。   代码并不是那么难懂,还是来简单的看一下。首先创建了一 个 QWidget 的实例,调用 setWindowTitle 函数来设置窗口标题。然后创建了一个 QSpinBox 和 QSlider,分别设置了它 们值的范 围,使用的是 setRange 函数。然后进行信号槽的链接。这点后面再详细说明。然后 是一个 QHBoxLayout,就是一个水平布局,按照从左到右的顺 序进行添加,使用 addWidget 添加好组件后,调用 QWidget 的 setLayout 把 QWidget 的 layout 设置为我们定义的这个 Layout,这样,程序就完成了!   编译运行一下,可以看到效 果: 第 19 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理    如果最大化的话:      虽然我并没有添加任何代码,但是那个 layout 就已经明白该怎样进行布局。   或许你发现,那两个信号槽的链接操作 会不会产生无限递归?因为 steValue 就会引发 valueChanged 信号!答案是不会。这两句语句实现了,当 spinBox 发出 valueChanged 信号 的时候,会回调 slider 的 setValue,以更新 slider 的值;而 slider 发出 valueChanged 信 号的时候,又会回调 slider 的 setValue。但是,如果新的 value 和旧的 value 是一样的话, 是不会发出这个信 号的,因此避免了无限递归。     迷 糊 了 吧 ? 举 个 例 子 看 。 比 如 下 面 的 spinBox->setValue(35) 执 行 的 时 候 , 首 先 , spinBox 会将自己的值设为 35,这样,它的值与原来的不一样了(在没有 setValue 之前的时 候,默认值是 0),于是它发出了 valueChanged 信号。slider 接收到 这个信号,于是回调自 己的 setValue 函数,将它的值也设置成 35,它也发出了 valueChanged 信号。当然,此时 spinBox 又收到了,不 过它发现,这个 35 和它本身的值是一样的,于是它就不发出信号, 所以信号传递就停止了。 第 20 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理   那么,你会问,它们是怎么知道值的呢?答案很简 单,因为你的信号和槽都接受了一 个 int 参数!新的值就是通过这个进行传递的。实际上,我们利用 Qt 的信号槽机制完成了一 个数据绑定,使两个组件或者更多 组件的状态能够同步变化。   Qt 一共有三种主要的 layout,分别是:   QHBoxLayout- 按照水平方向从左到右布局;   QVBoxLayout- 按照竖直方向从上到下布局;   QGridLayout- 在一个网格中进行布局,类似于 HTML 的 table。   layout 使用 addWidget 添加组件,使用 addLayout 可以添加子 布局,因此,这就有了 无穷无尽的组合方式。   我是在 Windows 上面进行编译的,如果你要是在其他平台上面,应用程序就会有不同的 样 子:    还记得前面曾经说过,Qt 不是使用的原生组件,而是自己绘制模拟的本地组件的样子, 第 21 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 不过看看这个截图,它模拟的不能说百分百一致,也可说是惟妙惟肖了… :) Qt 学习之路(6): API 文档的使用   今天来说一下有关 Qt API 文档的使用。因为 Qt 有一个商业版本,因此它的文档十分健 全,而且编写良好。对于开发者来说,查看文档时开发必修课之一——没有人能够记住那么 多 API 的使用!   在 Qt 中查看文档是一件很简单的事情。如果你使用 QtCreator,那么左侧的 Help 按钮就 是文档查看入口。否则的 话,你可以在 Qt 的安装目录下的 bin 里面的 assistant.exe 中看 到 Qt 的文档。在早期版本中,Qt 的文档曾以 HTML 格式发布,不过在 2009.03 版中我没有找 到 HTML 格式的文档,可能 Qt 已经把它全部换成二进制格式的了吧?——当然,如果你全部 安装了 Qt 的组件,是可以在开始菜单中找到 assistant 的!    assistant 里面的文档有很多项:   其中,第一个是帮助的帮助:-);第二个是 Qt Designer 的帮助;第三个是 Qt Linguist 的帮助;第四个是 QMake 的帮助;最后一个是 Qt 的 API 文档,在 QtCreator 中默认打开的就 是这部分。 不 过,关于文档的内容这里实在不好阐述,因为整个文档太大了,我也并没有看过多 少,很多时候都是随用随查,就好像是字典一样——谁也不会天天没事抱着本字典 去看不 是?还有就是这里的文档都是英文的,不过如果是做开发的话,了解一些英文还是很有帮助 的,不是吗? 第 22 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 Qt 学习之路(7): 创建一个对话框(上)   首先说明一点,在 C++ GUI Programming with Qt4, 2nd 中,这一章连同以后的若干章 一起,完成了一个比较完整的程序——一个模仿 Excel 的电子表格。不过这个程序挺大的, 而且书中也没有给出完整的源代 码,只是分段分段的——我不喜欢这个样子,我想要看到 我写出来的是什么东西,这是最主要的,而不是慢慢的过上几章的内容才能看到自己的作品 所以,我打算 换一种方式,每章只给出简单的知识,但是每章都能够运行出东西来。好了, 扯完了,下面开始!   以前说的主要是一些基础知识,现在我们来真 正做一个东西——一个查找对话框。什么? 什么叫查找对话框?唉唉,先看看我们的最终作品吧!   好了,首先新建一个工程,就叫 FindDialog 吧!嗯,当然还是 Qt Gui Application, 然后最后一步注意,Base Dialog 选择 QDialog,而不是默认的 QMainWindow,因为我们要学 习建立对话框嘛!名字随便起,不过我就叫 finddialog 啦!Ganarate form 还是不要的。然 后 Finish 就好了。   打开 finddialog.h,开始编写头文件。 #ifndef FINDDIALOG_H #define FINDDIALOG_H #include 第 23 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 class QCheckBox; class QLabel; class QLineEdit; class QPushButton; class FindDialog : public QDialog {         Q_OBJECT public:         FindDialog(QWidget *parent = 0);         ~FindDialog(); signals:          void findNext(const QString &str, Qt::CaseSensitivity cs);          void findPrevious(const QString &str, Qt::CaseSensitivity cs); private slots:         void findClicked();         void enableFindButton(const QString &text); private:         QLabel *label;         QLineEdit *lineEdit;         QCheckBox *caseCheckBox;         QCheckBox *backwardCheckBox;         QPushButton *findButton;         QPushButton *closeButton; }; #endif // FINDDIALOG_H 第 24 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理   大家都是懂得 C++的啊,所以什么#ifndef,#define 和#endif 的含 义和用途就不再赘 述了。   首先,声明四个用到的类。这里做的是前向声明,否则的话是编译不过的,因为编译器 不知道这些类是否存在。简单来 说,所谓前向声明就是告诉编译器,我要用这几个类,而且 这几个类存在,你就不要担心它们存不存在的问题啦!   然后是我们的 FindDialog,继承自 QDialog。   下面是一个重要的东西:Q_OBJECT。这是一个宏。凡是定义信号槽的类都必须声明这 个 宏。至于为什么,我们以后再说。   然后是 public 的构造函数和析构函数声明。   然后是一个 signal:,这是 Qt 的关键字——还记得前面说过的嘛?Qt 扩展了 C++语言, 因此它有自己的关键字——这是对信号的定义,也就是说, FindDialog 有两个 public 的信 号,它可以在特定的时刻发出这两个信号,就这里来说,如果用户点击了 Find 按钮,并且 选中了 Search backward,就会发出 findPrevious(),否则发出 findNext()。   紧接着是 private slots:的定义,和前面的 signal 一样,这是私有的槽的定义。也就是 说,FindDialog 具有两个槽,可以接收某些信号,不过这两个槽都是私 有的。   为了 slots 的定义,我们需要访问 FindDialog 的组件,因此,我们把其中的组件定义 为成员变量以便访问。正是因为需要 定义这些组件,才需要对它们的类型进行前向声明。因 为我们仅仅使用的是指针,并不涉及到这些类的函数,因此并不需要 include 它们的头文件 ——当然, 你想直接引入头文件也可以,不过那样的话编译速度就会慢一些。   好了,头文件先说这些,下一篇再说源代码啦! 第 25 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 Qt 学习之路(8): 创建一个对话框(下)   接着前一篇,下面是源代码部分: #include #include "finddialog.h" FindDialog::FindDialog(QWidget *parent)         : QDialog(parent) {         label = new QLabel(tr("Find &what:"));         lineEdit = new QLineEdit;         label->setBuddy(lineEdit);         caseCheckBox = new QCheckBox(tr("Match &case"));         backwardCheckBox = new QCheckBox(tr("Search &backford"));         findButton = new QPushButton(tr("&Find"));         findButton->setDefault(true);         findButton->setEnabled(false);         closeButton = new QPushButton(tr("Close"));               connect(lineEdit, SIGNAL(textChanged(const QString&)), this, SLOT(enableFindButton(const QString&))); 第 26 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理         connect(findButton, SIGNAL(clicked()), this, SLOT(findClicked()));          connect(closeButton, SIGNAL(clicked()), this, SLOT(close()));          QHBoxLayout *topLeftLayout = new QHBoxLayout;         topLeftLayout->addWidget(label);         topLeftLayout->addWidget(lineEdit);         QVBoxLayout *leftLayout = new QVBoxLayout;         leftLayout->addLayout(topLeftLayout);         leftLayout->addWidget(caseCheckBox);         leftLayout->addWidget(backwardCheckBox);         QVBoxLayout *rightLayout = new QVBoxLayout;         rightLayout->addWidget(findButton);         rightLayout->addWidget(closeButton);         rightLayout->addStretch();         QHBoxLayout *mainLayout = new QHBoxLayout;         mainLayout->addLayout(leftLayout);          mainLayout->addLayout(rightLayout);         setLayout(mainLayout);         setWindowTitle(tr("Find"));          setFixedHeight(sizeHint().height()); } FindDialog::~FindDialog() { 第 27 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 } void FindDialog::findClicked() {         QString text = lineEdit->text();               Qt::CaseSensitivity cs = caseCheckBox->isChecked() ? Qt::CaseInsensitive : Qt::CaseSensitive;          if(backwardCheckBox->isChecked()) {                 emit findPrevious(text, cs);         } else {                 emit findNext(text, cs);         } } void FindDialog::enableFindButton(const QString &text) {         findButton->setEnabled(!text.isEmpty()); }   CPP 文件要长一些哦——不过,它们的价钱也会更高,嘿嘿——嗯,来看代码,第一行 include 的是 QtGui。Qt 是分模块的,记得 我们建工程的时候就会问你,使用哪些模块? QtCore?QtGui?QtXml?等等。这里,我们引入 QtGui,它包括了 QtCore 和 QtGui 模块。不 过,这并不是最好的做法,因为 QtGui 文件很大,包括了 GUI 的所有组件,但是很多组件我 们根本是用不到的——就像 Swing 的 import,你可以 import 到类,也可以使用*,不过都 不会建议使用*,这里也是一样的。我们最好只引入需要的组件。不过,那样会把文件变长, 现在 就先用 QtGui 啦,只要记得正式开发时不能这么用就好啦!   构造函数有参数初始化列表,用来调用父类的构造函数,相当于 Java 里面的 super() 函数。这是 C++的相关知识,不是 Qt 发明的,这里不再赘述。   然后新建一个 QLabel。还记得前面的 Hello, world!里面也使用过 QLabel 吗?那时候只 第 28 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 是 简 单 的 传 入 一 个 字 符 串 啊 ! 这 里 怎 么 是 一 个 函 数 tr() ? 函 数 tr() 全 名 是 QObject::tr(),被它处理的字符串可以使用工具提取出来翻译成其他语言,也就是做国际 化使用。这以后还会仔细讲解,只要记住,Qt 的最佳实 践:如果你想让你的程序国际化的 话,那么,所有用户可见的字符串都要使用 QObject::tr()!但是,为什么我们没有写 QObject::tr(),而仅仅是 tr()呢?原来,tr()函数是定义在 Object 里面的,所有使用了 Q_OBJECT 宏的类都自动具有 tr() 函数。   字符串中的&代表快捷键。注意看下面的 findButton 的&Find,它会生成 Find 字符串, 当你按下 Alt+F 的时候,这个按钮就相当于被点击——这么说很难受,相信大家都明白什 么意思。同样,前面 label 里面也有一个&,因此它的快捷键就是 Alt+W。不过,这个 label 使用了 setBuddy 函数,它的意思是,当 label 获得焦点时,比如按下 Alt+W,它的焦点会自 动传给它的 buddy,也就是 lineEdit。看,这就是伙伴的含义(buddy 英文就是伙伴的意思)。   后面几行就比较简单了:创建了两个 QCheckBox,把默认的按钮设为 findButton,把 findButton 设为不可用——也 就是变成灰色的了。     再 下 面 是 三 个 connect 语 句 , 用 来 连 接 信 号 槽 。 可 以 看 到 , 当 lineEdit 发 出 textChanged(const QString&)信号时,FindDialog 的 enableFindButton(const QString&) 函数会被调用——这就是回调,是有系统自动调用,而不是你去调用——当 findButton 发 出 clicked()信号 时,FindDialog 的 findClicked()函数会被调用;当 closeButton 发出 clicked() 信 号 时 , FindDialog 的 close() 函 数 会 被 调 用 。 注 意 , connect() 函 数 也 是 QObject 的,因为我们继承了 QObject,所以能够直接使用。    后面的很多行语句都是 layout 的使用,虽然很复杂,但是很清晰——编写 layout 布 局最重要一点就是思路清楚,想清楚哪个套哪个,就会很好编写。这 里我们的对话框实际上 是这个样子的: 第 29 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理      注意那个 spacer 是由 rightLayout 的 addStretch()添加的,就像 弹簧一样,把上面的 组件“顶起来”。   最后的 setWindowTitle()就是设置对话框的标题,而 setFixedHeight()是设置成固定 的高度,其参数值 sizeHint()返回“最理想”的大小,这里我们使用的是 height()函数去 到 “最理想”的高度。   好了,下面该编写槽了——虽然说是 slot,但实际上它就是普通的函数,既可以和其 他函数一样使用,又可以被系统回 调。   先看 findClicked()函数。首先取出 lineEdit 的输入值;然后判断 caseCheckBox 是不是 选中,如果选中 就返回 Qt::CaseInsensitive,否则返回 Qt::CaseSensitive,用于判断 是不是大小写敏感的查找;最后,如果 backwardCheckBox 被选中,就 emit(发出)信号 findPrevious(),否则 emit 信号 findNext。    enableFindButton()则根据 lineEdit 的内容是不是变化——这是我们的 connect 连接 的——来设置 findButton 是不是 可以使用,这个很简单,不再说了。   这样,FindDialog.cpp 也就完成了。下面编写 main.cpp——其实 QtCreator 已经替我们 完成了—— #include #include "finddialog.h" 第 30 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 int main(int argc, char *argv[]) {         QApplication app(argc, argv);         FindDialog *dialog = new FindDialog;         dialog->show();         return app.exec(); }    运行一下看看我们的成果吧!   虽然很简单,也没有什么实质性的功能,但是我们已经能够制作对话框了——Qt 的组 件成百上千,不可能全部 介绍完,只能用到什么学什么,更重要的是,我们已经了解了其 编写思路,否则的话,即便是你拿着全世界所有的砖瓦,没有设计图纸,你也不知道怎么把 它们组合 成高楼大厦啊! Qt 学习之路(9):深入了解信号槽   槽函数和普通的 C++成员函数没有很大的区别。它们也可以使 virtual 的;可以被重写; 可以使 public、protected 或者 private 的;可以由其它的 C++函数调用;参数可以是任何 类型的。如果要说区别,那就是,槽函数可以和一个信号相连接,当这个信号发生时,它可 以被 自动调用。   connect()语句的原型类似于:   connect(sender, SIGNAL(signal), receiver, SLOT(slot));   这里,sender 和 receiver 都是 QObject 类型的,singal 和 slot 都是没有参数名称的 函数签名。SINGAL()和 SLOT()宏用于把参数转换成字符串。   深入的说,信号槽还有更多可能的用 法,如下所示。   一个信号可以和多个槽相连: connect(slider, SIGNAL(valueChanged(int)), 第 31 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理               spinBox, SLOT(setValue(int))); connect(slider, SIGNAL(valueChanged(int)),                this, SLOT(updateStatusBarIndicator(int)));    注意,如果是这种情况,这些槽会一个接一个的被调用,但是它们的调用顺序是不确 定的。   多个信号可以连接到一个槽: connect(lcd, SIGNAL(overflow()),               this, SLOT(handleMathError())); connect(calculator, SIGNAL(divisionByZero()),               this, SLOT(handleMathError()));   这是说,只要任意一个信号发出,这个槽就会被调用。    一个信号可以连接到另外的一个信号: connect(lineEdit, SIGNAL(textChanged(const QString &)),               this, SIGNAL(updateRecord(const QString &)));   这是说,当第一个信号发出 时,第二个信号被发出。除此之外,这种信号-信号的形式 和信号-槽的形式没有什么区别。   槽可以被取消链接: disconnect(lcd, SIGNAL(overflow()),                  this, SLOT(handleMathError()));    这种情况并不经常出现,因为当一个对象 delete 之后,Qt 自动取消所有连接到这个对 象上面的槽。   为了正确的连接信号槽,信号和槽 的参数个数、类型以及出现的顺序都必须相同,例如: connect(ftp, SIGNAL(rawCommandReply(int, const QString &)),               this, SLOT(processReply(int, const QString &)));   这里有一 种例外情况,如果信号的参数多于槽的参数,那么这个参数之后的那些参数 都会被忽略掉,例如: 第 32 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 connect(ftp, SIGNAL(rawCommandReply(int, const QString &)),             this, SLOT(checkErrorCode(int)));   这里,const QString &这个参数就会被槽忽略掉。   如果信号槽的参数不相容,或者是信号或槽有一个不存在,或者在信号槽的连接中出现 了参数名字,在 Debug 模式下编译的时候,Qt 都会很智能的给出警告。   在这之前,我们仅仅在 widgets 中使用到了信号槽,但是,注意到 connect()函数其实 是在 QObject 中实现的,并不局限于 GUI,因此,只要我们继承 QObject 类,就可以使用信 号槽机制了: class Employee : public QObject {         Q_OBJECT public:          Employee() { mySalary = 0; }         int salary() const { return mySalary; } public slots:         void setSalary(int newSalary); signals:         void salaryChanged(int newSalary); private:         int mySalary; };    在使用时,我们给出下面的代码: void Employee::setSalary(int newSalary) {          if (newSalary != mySalary) { 第 33 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理                 mySalary = newSalary;                 emit salaryChanged(mySalary);         } }    这样,当 setSalary()调用的时候,就会发出 salaryChanged()信号。注意这里的 if 判 断,这是避免递归的方式!还记得前面提到的 循环连接吗?如果没有 if,当出现了循环连 接的时候就会产生无限递归。 Qt 学习之路(10): Meta-Object 系统   前面说过,Qt 使用的是自己的预编译器,它提供了对 C++的一种扩展。利用 Qt 的信号槽 机制,就可以把彼此独立的模块相互连接起来,不需 要实现知道模块的任何细节。   为了达到这个目的,Qt 提出了一个 Meta-Object 系统。它提供了两个关键的作用:信号 槽和内省。    面向对象程序设计里面会讲到 Smalltalk 语言有一个元类系统。所谓元类,就是这里所 说的 Meta-Class。如果写过 HTML,会知道 HTML 标签里面也有一个,这是用于说明页 面的某些属性的。同样,Qt 的 Meta-Object 系统也是类似的作用。内省又称为 反射,允许程 序在运行时获得类的相关信息,也就是 meta-information。什么是 meta-information 呢?举 例来说,像这个类叫什 么名字?它有什么属性?有什么方法?它的信号列表?它的槽列表 等等这些信息,就是这个类的 meta-information,也就是“元信息”。这个机 制还提供了 对国际化的支持,是 QSA(Qt Script for Application)的基础。   标准 C++并没有 Qt 的 meta-information 所需要的动态 meta-information。所以,Qt 提 供了一个独立的工具,moc,通过定义 Q_OBJECT 宏 实现到标准 C++函数的转变。moc 使用纯 C++实现的,因此可以再任何编译器中使用。   这种机制工作过程是: 第 34 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理     首 先 , Q_OBJECT 宏 声 明 了 一 些 QObject 子 类 必 须 实 现 的 内 省 的 函 数 , 如 metaObject(),tr(),qt_metacall()等;    第二,Qt 的 moc 工具实现 Q_OBJECT 宏声明的函数和所有信号;   第三,QObject 成员函数 connect()和 disconnect()使用这些内省函数实现信号槽的连 接。   以上这些过程是 qmake,moc 和 QObject 自动处理的,你不需要 去考虑它们。如果实现 好奇的话,可以通过查看 QMetaObject 的文档和 moc 的源代码来一睹芳容 Qt 学习之路(11): MainWindow   尽管 Qt 提供了很方便的快速开发工具 QtDesigner 用来拖放界面元素,但是现在我并不 打算去介绍这个工具,原因之一在于我们的学习 大体上是依靠手工编写代码,过早的接触 设计工具并不能让我们对 Qt 的概念突飞猛进……     前 面 说 过 , 本 教 程 很 大 程 度 上 依 照 的 是 《 C++ GUI Programming with Qt4, 2nd Edition》这本书。但是,这本书中接下来的部分用了很大的篇幅完成了一个简单的类似 Excel 的程序。虽然最终效果看起来很不错,但我并不打算完全 依照这个程序来,因为这个 程序太大,以至于我们在开始之后会有很大的篇幅接触不到能够运行的东西,这无疑会严重 打击学习的积极性——至少我是如此,看不到 做的东西很难受——所以,我打算重新组织 一下这个程序,请大家按照我的思路试试看吧!   闲话少说,下面开始新的篇章!    就像 Swing 的顶层窗口一般都是 JFrame 一样,Qt 的 GUI 程序也有一个常用的顶层窗口, 叫做 MainWindow。好了,现在我们新建一个 Gui Application 项目 MyApp,注意在后面选择 的时候选择 Base Class 是 QMainWindow。 第 35 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理      然后确定即可。此时,QtCreator 已经为我们生成了必要的代码,我们只需点击一下 Run,看看运行出来的结果。   一个很简单的窗口,什么都没有,这就是我们的主窗口了。   MainWindow 继承自 QMainWindow。QMainWindow 窗口分成几个主要的区域: 第 36 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理   最上面是 Window Title,用于显示标题和控制按钮,比如最大化、最小化和关闭等;下 面一些是 Menu Bar,用于显示菜单;再下面一点事 Toolbar areas,用于显示工具条,注意, Qt 的主窗口支持多个工具条显示,因此这里是 ares,你可以把几个工具条并排显示在这里, 就像 Word2003 一 样;工具条下面是 Dock window areas,这是停靠窗口的显示区域,所谓 停靠窗口就是像 Photoshop 的工具箱一样,可以在主窗口的四周显示;再向下是 Status Bar,就是状态栏;中间最大的 Central widget 就是主要的工作区了。 好了,今天的内容不多,我们以后的工作就是要对这个 MainWindow 进行修改,以满足 我们的各种需要。 第 37 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 Qt 学习之路(12): 菜单和工具条   在前面的 QMainWindow 的基础之上,我们开始着手建造我们的应用程序。虽然现在已经 有一个框架,但是,确切地说我们还一行代码没 有写呢!下面的工作就不那么简单了!在 这一节里面,我们要为我们的框架添加菜单和工具条。   就像 Swing 里面的 Action 一样,Qt 里面也有一个类似的类,叫做 QAction。顾名思义, QAction 类保存有关于这个动作,也就是 action 的信息,比如它的文本描述、图标、快捷 键、 回调函数(也就是信号槽),等等。神奇的是,QAction 能够根据添加的位置来改变自己的样 子 ——如果添加到菜单中,就会显示成一个菜单项;如果添加到工具条,就会显示成一个 按钮。这也是为什么要把菜单和按钮放在一节里面。下面开始学习!    首先,我想添加一个打开命令。那么,就在头文件里面添加一个私有的 QAction 变量: class QAcion; //... private:          QAction *openAction; //...   注意,不要忘记 QAction 类的前向声明 哦!要不就会报错的!   然后我们要在 cpp 文件中添加 QAction 的定义。为了简单起见,我们直接把它定义在构 造函数里面:          openAction = new QAction(tr("&Open"), this);          openAction->setShortcut(QKeySequence::Open);          openAction->setStatusTip(tr("Open a file."));   第一行代码创建一 个 QAction 对象。QAction 有几个重载的构造函数,我们使用的是 QAction(const QString  &text, QObject* parent);   这一个。它有两个参数,第一个 text 是这个动作的文本描 述,用来显示文本信息,比 如在菜单中的文本;第二个是 parent,一般而言,我们通常传入 this 指针就可以了。我们不 需要去关心这个 parent 参数 具体是什么,它的作用是指明这个 QAction 的父组件,当这个 第 38 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 父组件被销毁时,比如 delete 或者由系统自动销毁,与其相关联的这个 QAction 也会自动 被销毁。   如果你还是不明白构造函数的参数是什么意思,或者说想要更加详细的了解 QAction 这 个类,那么就需要自己翻阅一下它的 API 文档。前 面说过有关 API 的使用方法,这里不再赘 述。这也是学习 Qt 的一种方法,因为 Qt 是一个很大的库,我们不可能面面俱到,因此只为 说道用到的东西,至于你自 己想要实现的功能,就需要自己去查文档了。     第 二 句 , 我 们 使 用 了 setShortcut 函 数 。 shortcut 是 这 个 动 作 的 快 捷 键 。 Qt 的 QKeySequence 已经为我们定义了很多内置的快捷键,比如我们使用的 Open。你可以通过查阅 API 文档获得所有的快捷键列表,或者是在 QtCreator 中输入::后会有系统的自动补全功能 显示出来。这个与我们自己定义的有什么区别呢?简单来说,我们完全可以自己定义一个 tr("Ctrl+O")来实现快捷键。原因在于,这是 Qt 跨平台性的体现。比如 PC 键盘和 Mac 键盘是 不一样的,一些键在 PC 键盘上有,而 Max 键盘上 可能并不存在,或者反之,所以,推荐使 用 QKeySequence 类来添加快捷键,这样,它会根据平台的不同来定义不同的快捷键。   第三 句是 setStatusTip 函数。这是添加状态栏的提示语句。状态栏就是主窗口最下面的 一条。现在我们的程序还没有添加状态栏,因此你是看不到有什么作 用的。   下面要做的是把这个 QAction 添加到菜单和工具条:         QMenu *file =  menuBar()->addMenu(tr("&File"));          file->addAction(openAction);         QToolBar *toolBar =  addToolBar(tr("&File"));          toolBar->addAction(openAction);   QMainWindow 有一个 menuBar()函数,会返回菜单栏,也就是最上面的那一条。如果不 存在会自动创建,如果已经存在就返回那个菜单栏的指针。直接使用返回值添加一个菜 单, 也就是 addMenu,参数是一个 QString,也就是显示的菜单名字。然后使用这个 QMenu 指针添 加这个 QAction。类似的,使用 addToolBar 函数的返回值添加了一个工具条,并且把这个 QAction 添加到了上面。   好了,主要的代码已经写完了。不过,如果你只修改这些的话,是编译不过的哦!因为 像 menuBar()函数返回一个 QMenuBar 指 针,但是你并没有 include 它的头文件哦!虽然没 有明着写出 QMenuBar 这个类,但是实际上你已经用到了它的 addMenu 函数了,所以还是要 第 39 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 注 意的!   下面给出来全部的代码:   1. mainwindow.h #ifndef  MAINWINDOW_H #define MAINWINDOW_H #include  class QAction; class MainWindow : public  QMainWindow {         Q_OBJECT public:          MainWindow(QWidget *parent = 0);         ~MainWindow(); private:          QAction *openAction; }; #endif // MAINWINDOW_H    2. mainwindow.cpp #include  #include   #include  QT5中可以直接前面的文件夹去掉 #clude #include #include #include include #include  #include  #include  "mainwindow.h" MainWindow::MainWindow(QWidget *parent)         :  QMainWindow(parent) {         openAction = new  QAction(tr("&Open"), this); 第 40 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理          openAction->setShortcut(QKeySequence::Open);          openAction->setStatusTip(tr("Open a file."));         QMenu *file  = menuBar()->addMenu(tr("&File"));          file->addAction(openAction);         QToolBar *toolBar =  addToolBar(tr("&File"));          toolBar->addAction(openAction); } MainWindow::~MainWindow() { }    main.cpp 没有修改,这里就不给出了。下面是运行结果: 很丑,是吧?不过我们已经添加上了菜单和工具条了哦!按一下键盘上的 Alt+F,因为 这是我们给它定义的快捷 键。虽然目前挺难看,不过以后就会变得漂亮的!想想看,Linux 的 KDE 桌面可是 Qt 实现的呢! 第 41 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 Qt 学习之路(13): 菜单和工具条(续)   前面一节我们已经把 QAction 添加到菜单和工具条上面。现在我们要添加一些图片美化 一下,然后把信号槽加上,这样,我们的 action 就可以相应啦!   首先来添加图标。QAction 的图标会显示在菜单项的前面以及工具条按钮上面显示。   为 了添加图标,我们首先要使用 Qt 的资源文件。在 QtCreator 的项目上右击,选择 New File...,然后选择 resource file。      然后点击 next,选择好位置,Finish 即可。为了使用方便,我就把这个文件建在根目 录下,建议应该在仔细规划好文件之后,建在专门的 rsources 文件夹下。完成之后,生成 的是一个.qrc 文件,qrc 其实是 Qt Recource Collection 的缩写。它只是一个普通的 XML 文 件,可以用记事本等打开。不过,这里我们不去深究它的结构,完全利用 QtCreator 操作这 个文 件, 第 42 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理      点击 Add 按钮,首先选择 Add prefix,然后把生成的/new/prefix 改成/。这是 prefix 就是以后使用图标时需要提供的前缀,以/开头。添加过 prefix 之后,然后 在工程文件中添 加一个图标,再选择 Add file,选择那个图标。这样完成之后保存 qrc 文件即可。   说明一下,QToolBar 的图标大小默认是 32*32,菜单默认是 16*16。如果提供的图标小于 要求的尺寸,则不做操作,Qt 不会为 你放大图片;反之,如果提供的图标文件大于相应的 尺寸要求,比如是 64*64,Qt 会自动缩小尺寸。      图片的路径怎么看呢?可以看出,Qt 的资源文件视图使用树状结构,根是/,叶子节点 就是图 片位置,连接在一起就是路径。比如这张图片的路径就是/Open.png。 第 43 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理   注意,为了简单起见,我们没有把图标放在专门的文件夹中。正 式的项目中应该单独有 一个 resources 文件夹放资源文件的。   然后回到前面的 mainwindow.cpp,在构造函数中修改代 码: openAction = new QAction(tr("&Open"), this); openAction->setShortcut(QKeySequence::Open); openAction->setStatusTip(tr("Open  a file.")); openAction->setIcon(QIcon(":/Open.png")); // Add  code.   我们使用 setIcon 添加图标。添加的类是 QIcon,构造函数需要一个参数,是一个字符串。 由于我们要使 用 qrc 中定义的图片,所以字符串以 : 开始,后面跟着 prefix,因为我们先 前定义的 prefix 是/,所以就需要一个/,然后后面是 file 的路径。这是在前面的 qrc 中定 义的,打开 qrc 看看那张图片的路径即可。   好了,图片添加完成,然后点击运行,看看效果吧!   瞧!我们只需要修改 QAction,菜单和工具条就已经为我们做好了相应的处理,还是很 方便的!    下一步,为 QAction 添加事件响应。还记得 Qt 的事件响应机制是基于信号槽吗?点击 QAction 会发出 triggered()信号,所以,我们要 做的是声名一个 slot,然后 connect 这个 信号。 mainwindow.h   class  MainWindow : public QMainWindow {         Q_OBJECT 第 44 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 public:          MainWindow(QWidget *parent = 0);         ~MainWindow(); private  slots:         void open();         private:         QAction  *openAction; };   因为我们的 open()目前只要在类的内部使用,因此定义成 private slots 即可。然后修 改 cpp 文件: MainWindow::MainWindow(QWidget *parent)          : QMainWindow(parent) {         openAction = new  QAction(tr("&Open"), this);          openAction->setShortcut(QKeySequence::Open);          openAction->setStatusTip(tr("Open a file."));          openAction->setIcon(QIcon(":/Open.png"));                 connect(openAction,   SIGNAL(triggered()),   this, SLOT(open()));          QMenu *file = menuBar()->addMenu(tr("&File"));          file->addAction(openAction);         QToolBar *toolBar =  addToolBar(tr("&File"));          toolBar->addAction(openAction); } void MainWindow::open() {                 QMessageBox::information(NULL,   tr("Open"),   tr("Open   a file")); } 第 45 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理    注意,我们在 open()函数中简单的弹出一个标准对话框,并没有其他的操作。编译后 运行,看看效果:   好了,关于 QAction 的动作也已经添加完毕了! 至此,QAction 有关的问题先告一 段落。最后说一下,如果你还不知道怎么添加子菜单 的话,看一下 QMenu 的 API,里面会有一个 addMenu 函数。也就是说,创建一个 QMenu 然后添 加就可以的啦! 第 46 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 Qt 学习之路(14): 状态栏   今天的内容主要还是继续完善前面的那个程序。我们要为我们的程序加上一个状态栏。   状态栏位于主窗口的最下方,提供一个 显示工具提示等信息的地方。一般地,当窗口不 是最大化的时候,状态栏的右下角会有一个可以调节大小的控制点;当窗口最大化的时候, 这个控制点会自动消失。 Qt 提供了一个 QStatusBar 类来实现状态栏。   Qt 具有一个相当成熟的 GUI 框架的实现——这一点感觉比 Swing 要强一些—— Qt 似乎 对 GUI 的开发做了很多设计,比如 QMainWindow 类里面就有一个 statusBar()函数,用于实 现状态栏的调用。类似 menuBar()函数,如果不存在状态栏,该函数会自动 创建一个,如果 已经创建则会返回这个状态栏的指针。如果你要替换掉已经存在的状态栏,需要使用 QMainWindow 的 setStatusBar()函 数。   在 Qt 里面,状态栏显示的信息有三种类型:临时信息、一般信息和永久信息。其中,临 时信息指临时显示的信息,比如 QAction 的 提示等,也可以设置自己的临时信息,比如程 序 启 动 之 后 显 示 Ready , 一 段 时 间 后 自 动 消 失 — — 这 个 功 能 可 以 使 用 QStatusBar 的 showMessage()函数来实现;一般信息可以用来显示页码之类的;永久信息是不会消失的信 息,比如可以在状态栏提示用户 Caps Lock 键被按下之类。   QStatusBar 继承自 QWidget,因此它可以添加其他的 QWidget。下面我们在 QStatusBar 上添加一个 QLabel。   首先在 class 的声明中添加一个私有的 QLabel 属性: private:          QAction *openAction;         QLabel *msgLabel;    然后在其构造函数中添加:         msgLabel = new QLabel;          msgLabel->setMinimumSize(msgLabel->sizeHint()); 第 47 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理          msgLabel->setAlignment(Qt::AlignHCenter);          statusBar()->addWidget(msgLabel);   这里,第一行创建一个 QLabel 的对象, 然后设置最小大小为其本身的建议大小——注 意,这样设置之后,这个最小大小可能是变化的——最后设置显示规则是水平居中 (HCenter)。最后一行使用 statusBar()函数将这个 label 添加到状态栏。编译运行,将鼠标 移动到工具条或者菜单的 QAction 上,状态栏就会有相应的提示:   看起来是不是很方便?只是,我们很快发现一个问题:当没有任何提示时,状态栏会有 一个短短的竖线:   这是什么呢?其实,这是 QLabel 的边框。当没有内容显示时,QLabel 只显示出自己的一 个边框。但是, 很多情况下我们并不希望有这条竖线,于是,我们对 statusBar()进行如下 设置: statusBar()->setStyleSheet(QString("QStatusBar::item{border:  0px}"));   这里先不去深究这句代码是什么意思,简单来说,就是把 QStatusBar 的子组件的 border 设 置为 0,也就是没有边框。现在再编译试试吧!那个短线消失了!   QStatusBar 右下角的大小控制点可以通过 setSizeGripEnabled()函数来设置是否存在, 详情参见 API 文档。   好了,现在,我们的状态栏已经初步完成了。由于 QStatusBar 可以添加多个 QWidget, 第 48 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 因此,我们可以构建出很复杂的状态栏。 Qt 学习之路(15): Qt 标准对话框之 QFileDialog   下面还是按照我们的进度,从 Qt 的标准对话框开始说起。所谓标准对话框,其实就是 Qt 内置的一些对话框,比如文件选择、颜色选择等等。今 天首先介绍一下 QFileDialog。     QFileDialog 是 Qt 中 用 于 文 件 打 开 和 保 存 的 对 话 框 , 相 当 于 Swing 里 面 的 JFileChooser。下面打开我们前面使用的工程。我们已经很有先见之明的写好了一个打开的 action,还记得前面的代码吗?当时,我们只是弹出 了一个消息对话框(这也是一种标准对 话框哦~)用于告知这个信号槽已经联通,现在我们要写真正的打开代码了!   修改 MainWindow 的 open 函数: void MainWindow::open() {         QString  path = QFileDialog::getOpenFileName(this, tr("Open Image"), ".",  tr("Image Files(*.jpg *.png)"));         if(path.length() == 0) {                                 QMessageBox::information(NULL,   tr("Path"), tr("You didn't  select any files."));         } else {                                 QMessageBox::information(NULL,   tr("Path"), tr("You selected ") + path);          } }   编译之前别忘记 include QFileDialog 哦!然后运行一下吧!点击打开按钮,就会弹出 打开对话框,然后选择文件或者直接点击取消,会有相应的消息提示。    QFileDialog 提供了很多静态函数,用于获取用户选择的文件。这里我们使用的是 getOpenFileName(), 也就是“获取打开文件名”,你也可以查看 API 找到更多的函数使用。 不过,这个函数的参数蛮长的,而且类型都是 QString,并不好记。考虑到这种情 况,Qt 提 第 49 页 共 243 页 整理:DZY 供了另外的写法: Qt 学习之路 DZY 整理 Qt 学习之路(16): Qt 标准对话框之 QColorDialog   继续来说 Qt 的标准对话框,这次说说 QColorDialog。这是 Qt 提供的颜色选择对话框。   使用 QColorDialog 也很简单, Qt 提供了 getColor()函数,类似于 QFileDialog 的 getOpenFileName(),可以直接 获得选择的颜色。我们还是使用前面的 QAction 来测试下这 个函数:         QColor color =  QColorDialog::getColor(Qt::white, this);                 QString   msg   =   QString("r:   %1,   g:   %2,   b: %3").arg(QString::number(color.red()),   QString::number(color.green()), QString::number(color.blue()));          QMessageBox::information(NULL, "Selected color", msg);    不要忘记 include QColorDialog 哦!这段代码虽然很少,但是内容并不少。   第一行 QColorDialog::getColor()调用了 QColorDialog 的 static 函数 getColor()。这 个函数有两个参数,第一个 是 QColor 类型,是对话框打开时默认选择的颜色,第二个是它 的 parent。   第二行比较长,涉及到 QString 的用法。如果我没 记错的话,这些用法还没有提到过, 本着“有用就说”的原则,尽管这些和 QColorDialog 毫不相干,这里还是解释一下 。 QString("r: %1, g: %2, b: %3")创建了一个 QString 对象。我们使用了参数化字符串,也 就是那些%1 之类。在 Java 的 properties 文件中,字符参数是用{0}, {1}之类实现的。其实这 都是一些占位符,也就是,后面会用别的字符串替换掉这些值。占位符的替换需要使用 QString 的 arg()函数。这个函数会返 回它的调用者,因此可以使用链式调用写法。它会按照 顺序替换掉占位符。然后是 QString::number()函数,这也是 QString 的一个 static 函数, 作用就是把 int、double 等值换成 QString 类型。这里是把 QColor 的 R、G、B 三个值输出了出来。 关于 QString 类,我们会在以后详细说明。   第三行就比较简单了,使用一个消息对话框把刚刚拼接的字符串输出。 第 50 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理   现在就可以运行这个 测试程序了。看上去很简单,不是吗?   QColorDialog 还有一些其他的函数可以使用。    QColorDialog::setCustomColor()可以设置用户自定义颜色。这个函数有两个值,第一 个是自定义颜色的索引,第二个是自定义颜 色的 RGB 值,类型是 QRgb,大家可以查阅 API 文档来看看这个类的使用,下面只给出一个简单的用发: QColorDialog::setCustomColor(0,  QRgb(0x0000FF));   getColor()还有一个重载的函数,签名如下: QColorDialog::(   const   QColor   &   initial,   QWidget   *   parent,   const QString &  title, ColorDialogOptions options = 0 )   第一个参数 initial 和前面一 样,是对话框打开时的默认选中的颜色;   第二个参数 parent,设置对话框的父组件;   第三个参数 title,设置对 话框的 title;   第四个参数 options,是 QColorDialog::ColorDialogOptions 类型的,可以设置 对话 框的一些属性,如是否显示 Alpha 值等,具体属性请查阅 API 文档。特别的,这些值是可以 使用 OR 操作的。    QColorDialog 相对简单一些,API 文档也很详细,大家遇到问题可以查阅文档的哦! 第 51 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 Qt 学习之路(17): Qt 标准对话框之 QMeageBox   现在还是继续来说说 Qt 的标准对话框吧!   这次来说一下 QMessageBox 以及类似的几种对话框。其实,我们已经用 过 QMessageBox 了,就在之前的几个程序中。不过,当时是大略的说了一下,现在专门来说说这几种对话框。   先来看一下最熟悉的 QMessageBox::information。我们在以前的代码中这样使用过: QMessageBox::information(NULL,   "Title",   "Content",   QMessageBox::Yes   | QMessageBox::No,  QMessageBox::Yes);   下面是一个简单的例子:   现在我们从 API 中看看它的函数签名: static  StandardButton QMessageBox::information ( QWidget * parent, const QString & title, const QString & text, StandardButtons buttons =  Ok, StandardButton defaultButton = NoButton );   首先,它是 static 的,所以我们能够使用类名直接访问到(怎么看都像废话…);然后 看它那一堆参数,第一个参数 parent,说明它的父组件;第二个参数 title,也就是对话 框的标题;第三个参数 text,是对话框显示的内容;第四个参数 buttons,声明对话框放置 的按钮,默认是只放置一个 OK 按 钮,这个参数可以使用或运算,例如我们希望有一个 Yes 和一个 No 的按钮,可以使用 QMessageBox::Yes | QMessageBox::No,所有的按钮类型可以 在 QMessageBox 声明的 StandarButton 枚举中找到;第五个参数 defaultButton 就是默认选 中的按钮,默认值是 NoButton,也就是哪个按钮都不选中。这么多参数,豆子也是记不住的 第 52 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 啊!所以,我们在用 QtCreator 写的时候,可以在输入 QMessageBox::information 之后输 入(QtCreator 就会帮我们把函数签名显示在右上方了,还是挺方便的一个功 能! Qt 学习之路(18): Qt 标准对话框之 QIutDialog   这是 Qt 标准对话框的最后一部分。正如同其名字显示的一样,QInputDialog 用于接收用 户的输入。QInputDialog 提供 了一些简单的 static 函数,用于快速的建立一个对话框,正 像 QColorDialog 提供了 getColor 函数一样。   首先来看看 getText 函数: bool isOK; QString text =  QInputDialog::getText(NULL, "Input Dialog",   "Please input your comment",   QLineEdit::Normal,   "your comment",   &isOK); if(isOK) {         QMessageBox::information(NULL, "Information",   "Your comment is: " + text + "",   QMessageBox::Yes |  QMessageBox::No,   QMessageBox::Yes); } 第 53 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理   代码比较简单,使用 getText 函数就可以弹出一个可供用户输入的对 话框:   下面来看一下这个函数的签名: static  QString QInputDialog::getText ( QWidget * parent,   const QString & title,   const QString &  label,   QLineEdit::EchoMode mode = QLineEdit::Normal,   const QString & text = QString(),   bool * ok = 0, Qt::WindowFlags  flags = 0 )   第一个参数 parent,也就是那个熟悉的父组件的指针;第二个参数 title 就是对话框的 标 题;第三个参数 label 是在输入框上面的提示语句;第四个参数 mode 用于指明这个 QLineEdit 的输入模式,取值范围是 QLineEdit::EchoMode,默认是 Normal,也就是正常显 示,你也可以声明为 password,这样就是密码的输入显示了,具体请查阅 API;第五个参 数 text 是 QLineEdit 的默认字符串;第六个参数 ok 是可选的,如果非 NLL,则当用户按下 对话框的 OK 按钮时,这个 bool 变量会被置为 true,可以由这个去判断用户是按下的 OK 还 是 Cancel, 从而获知这个 text 是不是有意义;第七个参数 flags 用于指定对话框的样式。   虽然参数很多,但是每个参数的含义都比较明显,大家只 要参照 API 就可以知道了。 第 54 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理   函数的返回值是 QString,也就是用户在 QLineEdit 里面输入的内容。至于这个内容有没 有意义, 那就要看那个 ok 参数是不是 true 了。 QInputDialog 不仅提供了获取字符串的函数,还有 getInteger,getDouble,getItem 三个类似的函数,这里就不一一介绍。 第 55 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 Qt 学习之路(19): 事件(event)   前面说了几个标准对话框,下面不打算继续说明一些组件的使用,因为这些使用很难讲 完,很多东西都是与实际应用相关的。实际应用的复杂性决 定了我们根本不可能把所有组件 的所有使用方法都说明白。这次来说说 Qt 相对高级一点的特性:事件。   事件(event)是有系统或者 Qt 本身在不同的时刻发出的。当用户按下鼠标,敲下键盘, 或者是窗口需要重新绘制的时候,都会发出一个相应的事件。一些事件是在对用户操作做出 响应的时候发 出,如键盘事件等;另一些事件则是由系统自动发出,如计时器事件。   一般来说,使用 Qt 编程时,我们并不会把主要精力放在事件上,因为在 Qt 中,需要我 们关心的事件总会发出一个信号。比如,我们关心的是 QPushButton 的鼠标点击,但我们不 需要关心这个鼠标点击事件,而是关心它的 clicked()信号。这与其他的一些框架不同:在 Swing 中, 你所要关心的是 JButton 的 ActionListener 这个点击事件。   Qt 的事件很容易和信号槽混淆。这里简单的说明一 下,signal 由具体对象发出,然后 会马上交给由 connect 函数连接的 slot 进行处理;而对于事件,Qt 使用一个事件队列对所 有发出的事件进行维 护,当新的事件产生时,会被追加到事件队列的尾部,前一个事件完 成后,取出后面的事件进行处理。但是,必要的时候,Qt 的事件也是可以不进入事件队列, 而 是直接处理的。并且,事件还可以使用“事件过滤器”进行过滤。总的来说,如果我们使 用组件,我们关心的是信号槽;如果我们自定义组件,我们关心的是事件。 因为我们可以通 过事件来改变组件的默认操作。比如,如果我们要自定义一个 QPushButton,那么我们就需 要重写它的鼠标点击事件和键盘处理事件,并 且在恰当的时候发出 clicked()信号。   还记得我们在 main 函数里面创建了一个 QApplication 对象,然后调用了它的 exec() 函数吗?其实,这个函数就是开始 Qt 的事件循环。在执行 exec()函数之后,程序将进入事件 循环来监听应用程序的事件。当事件发生时,Qt 将创建一个事件对象。Qt 的所有事件都继承 于 QEvent 类。在事件对象创建完毕后,Qt 将这个事件对象传递给 QObject 的 event()函数 。 event()函数并不直接处理事件,而是按照事 件对象的类型分派给特定的事件处理函数 第 56 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 (event handler)。关于这一点,我们会在以后的章节中详细说明。 Qt 学习之路(20): 事件接收与忽略   本章内容也是关于 Qt 事件。或许这一章不能有一个完整的例子,因为对于事件总是感觉 很抽象,还是从底层上理解一下比较好的吧!    前面说到了事件的作用,下面来看看我们如何来接收事件。回忆一下前面的代码,我们 在子类中重写了事件函数,以便让这些子类按照我们的需要完成某些功能, 就像下面的代 码: void MyLabel::mousePressEvent(QMouseEvent * event) {          if(event->button() == Qt::LeftButton) {                 //  do something         } else {                  QLabel::mousePressEvent(event);         } }   上面的代码和前 面类似,在鼠标按下的事件中检测,如果按下的是左键,做我们的处 理工作,如果不是左键,则调用父类的函数。这在某种程度上说,是把事件向上传递给父类 去响 应,也就是说,我们在子类中“忽略”了这个事件。   我们可以把 Qt 的事件传递看成链状:如果子类没有处理这个事件,就会继续向其他类 传 递。其实,Qt 的事件对象都有一个 accept()函数和 ignore()函数。正如它们的名字,前 者用来告诉 Qt,事件处理函数“接收”了这个事件,不要再传递;后者则告诉 Qt,事件处 理函数“忽略”了这个事 件,需要继续传递,寻找另外的接受者。在事件处理函数中,可以 使用 isAccepted()来查询这个事件是不是已经被接收了。 第 57 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 事实 上,我们很少使用 accept()和 ignore()函数,而是想上面的示例一样,如果希望 忽略事件,只要调用父类的响应函数即可。记得我们曾经说 过,Qt 中的事件大部分是 protected 的,因此,重写的函数必定存在着其父类中的响应函数,这个方法是可行的。为 什么要这么做呢?因为我们无法确认 父类中的这个处理函数没有操作,如果我们在子类中 直接忽略事件,Qt 不会再去寻找其他的接受者,那么父类的操作也就不能进行,这可能会 有潜在的危险。另外 我们查看一下 QWidget 的 mousePressEvent()函数的实现: 第 58 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 Qt 学习之路(21): event()   今天要说的是 event()函数。记得之前曾经提到过这个函数,说在事件对象创建完毕后, Qt 将这个事件对象传递给 QObject 的 event()函数。 event()函数并不直接处理事件,而是 将这些事件对象按照它们不同的类型,分发给不同的事件处理器(event handler)。   event() 函数主要用于事件的分发,所以,如果你希望在事件分发之前做一些操作,那 么,就需要注意这个 event()函数了。为了达到这种目的,我们可以重写 event()函数。例如, 如果你希望在窗口中的 tab 键按下时将焦点移动到下一组件,而不是让具有焦点的组件处理, 那么你就可以继承 QWidget,并 重写它的 event()函数,已达到这个目的: bool MyWidget::event(QEvent *event) {          if (event->type() == QEvent::KeyPress) {                  QKeyEvent *keyEvent = static_cast(event);                  if (keyEvent->key() == Qt::Key_Tab) {                          // 处理 Tab 鍵                         return true;                  }         }         return QWidget::event(event); }    event() 函数接受一个 QEvent 对象,也就是需要这个函数进行转发的对象。为了进行 转发,必定需要有一系列的类型判断,这就可以调用 QEvent 的 type() 函数,其返回值是 QEvent::Type 类型的枚举。我们处理过自己需要的事件后,可以直接 return 回去,对于其他 我们不关心的事件,需要调用父类 的 event()函数继续转发,否则这个组件就只能处理我 第 59 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 们定义的事件了。   event()函数返回值是 bool 类型,如果传入的事件已 被识别并且处理,返回 true,否 则返回 false。如果返回值是 true,QApplication 会认为这个事件已经处理完毕,会继续处 理事件队列中 的下一事件;如果返回值是 false,QApplication 会尝试寻找这个事件的下 一个处理函数。   event()函数的返回值和事 件的 accept()和 ignore()函数不同。accept()和 ignore() 函数用于不同的事件处理器之间的沟通,例如判断这一事件是否处 理;event()函数的返回 值主要是通知 QApplication 的 notify()函数是否处理下一事件。为了更加明晰这一点,我们 来看看 QWidget 的 event()函数是如何定义的: bool QWidget::event(QEvent *event)  {         switch (e->type()) {         case QEvent::KeyPress:                   keyPressEvent((QKeyEvent *)event);                 if  (!((QKeyEvent *)event)->isAccepted())                          return false;                 break;         case  QEvent::KeyRelease:                 keyReleaseEvent((QKeyEvent  *)event);                 if (!((QKeyEvent *)event)->isAccepted())                          return false;                 break;                  // more...         }         return true; }    QWidget 的 event()函数使用一个巨大的 switch 来判断 QEvent 的 type,并且分发给 不同的事件处理函数。在事件处理函数之后,使用这个事件的 isAccepted()方法,获知这个 事件是不是被接受,如果没有被接受则 event()函数立即返回 false,否则返回 true。 第 60 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理    另外一个必须重写 event()函数的情形是有自定义事件的时候。如果你的程序中有自定 义事件,则必须重写 event()函数以便将自定义事件进行分 发,否则你的自定义事件永远 也不会被调用。关于自定义事件,我们会在以后的章节中介绍。 Qt 学习之路(22): 事件过滤器   Qt 创建了 QEvent 事件对象之后,会调用 QObject 的 event()函数做事件的分发。有时候, 你可能需要在调用 event() 函数之前做一些另外的操作,比如,对话框上某些组件可能并 不需要响应回车按下的事件,此时,你就需要重新定义组件的 event()函数。如果组件很多, 就 需要重写很多次 event()函数,这显然没有效率。为此,你可以使用一个事件过滤器,来 判断是否需要调用 event()函数。    QOjbect 有一个 eventFilter()函数,用于建立事件过滤器。这个函数的签名如下:   virtual bool QObject::eventFilter ( QObject * watched, QEvent * event )   如果 watched 对象安装了事件过滤器,这个函数会被调用并进行事件过滤,然后才轮 到组件进行事件处理。在重写这个函数时,如果你需要过滤掉某个事件,例如 停止对这个事 件的响应,需要返回 true。 bool MainWindow::eventFilter(QObject  *obj, QEvent *event) {     if (obj == textEdit) {               if (event->type() == QEvent::KeyPress) {       QKeyEvent *keyEvent = static_cast(event);                           qDebug()   <<   "Ate   key   press"   <<   keyEvent>key();                  return true;                   } else {                          return false;                   } 第 61 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理          } else {          // pass the event on  to the parent class          return  QMainWindow::eventFilter(obj, event);          } }    上面的例子中为 MainWindow 建立了一个事件过滤器。为了过滤某个组件上的事件,首 先需要判断这个对象是哪个组件,然后判断这个事件的类型。例如, 我不想让 textEdit 组 件处理键盘事件,于是就首先找到这个组件,如果这个事件是键盘事件,则直接返回 true,也就是过滤掉了这个事件,其他事件还 是要继续处理,所以返回 false。对于其他组 件,我们并不保证是不是还有过滤器,于是最保险的办法是调用父类的函数。   在创建了过滤器 之后,下面要做的是安装这个过滤器。安装过滤器需要调用 installEventFilter()函数。这个函数的签名如下:   void QObject::installEventFilter ( QObject * filterObj )   这个函数是 QObject 的一个函数,因此可以安装到任何 QObject 的子类,并不仅仅是 UI 组件。这个函数接收一个 QObject 对象,调用了这个函数安装事件 过滤器的组件会调用 filterObj 定义的 eventFilter()函数。例 如,textField.installEventFilter(obj),则如 果有事件发送到 textField 组件是,会先调用 obj->eventFilter()函数,然后才会调用 textField.event()。   当然,你也可以把事件过滤器安装 到 QApplication 上面,这样就可以过滤所有的事件, 已获得更大的控制权。不过,这样做的后果就是会降低事件分发的效率。   如果 一个组件安装了多个过滤器,则最后一个安装的会最先调用,类似于堆栈的行为。   注意,如果你在事件过滤器中 delete 了某个接收组件, 务必将返回值设为 true。否则, Qt 还是会将事件分发给这个接收组件,从而导致程序崩溃。   事件过滤器和被安装的组件必须在同一线程, 否则,过滤器不起作用。另外,如果在 install 之后,这两个组件到了不同的线程,那么,只有等到二者重新回到同一线程的时候 过滤器才会有效。    事件的调用最终都会调用 QCoreApplication 的 notify()函数,因此,最大的控制权实 第 62 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 际上是重写 QCoreApplication 的 notify()函数。由此可以看出,Qt 的事件处理实际上是分 层五个层次:重定义事件处理函数,重定义 event()函数,为单个组件安装事件过滤器, 为 QApplication 安装事件过滤器,重定义 QCoreApplication 的 notify()函数。这几个层次 的控制权是逐层增大的。 Qt 学习之路(23): 自定义事件   Qt 允许你创建自己的事件类型,这在多线程的程序中尤其有用,当然,也可以用在单 线程的程序中,作为一种对象间通讯的机制。那么,为什么 我需要使用事件,而不是使用信 号槽呢?主要原因是,事件的分发既可以是同步的,又可以是异步的,而函数的调用或者说 是槽的回调总是同步的。事件的另外一个 好处是,它可以使用过滤器。   Qt 中的自定义事件很简单,同其他类似的库的使用很相似,都是要继承一个类进行扩 展 。 在 Qt 中 , 你 需 要 继 承 的 类 是 QEvent 。 注 意 , 在 Qt3 中 , 你 需 要 继 承 的 类 是 QCustomEvent,不过这个类在 Qt4 中已经被废除(这里的废除是不建议使用,并不是从 类库 中删除)。   继承 QEvent 类,你需要提供一个 QEvent::Type 类型的参数,作为自定义事件的类型值。 这里的 QEvent::Type 类型是 QEvent 里面定义的一个 enum,因此,你是可以传递一个 int 的。重要的是,你的事件类型不能和已经存在的 type 值重复,否则会有不可预料的错误发 生!因为系统会将你的事件当做系统事件进行派发和调用。在 Qt 中,系统将保留 0 - 999 的 值 , 也 就 是 说 , 你 的 事 件 type 要 大 于 999. 具 体 来 说 , 你 的 自 定 义 事 件 的 type 要 在 QEvent::User 和 QEvent::MaxUser 的 范 围 之 间 。 其 中 , QEvent::User 值 是 1000,QEvent::MaxUser 的值是 65535。从这里知道,你最多可以定义 64536 个事件,相信这 个数字已经足够大了!但是,即便如此, 也只能保证用户自定义事件不能覆盖系统事件, 并不能保 证自定义事件之间不会被覆盖。为了解决 这个 问题 , Qt 提供 了一个函 数: registerEventType(),用于自定义事件的注册。该函数签名如下: static int  QEvent::registerEventType ( int hint = -1 );   函数是 static 的,因 此可以使用 QEvent 类直接调用。函数接受一个 int 值,其默认值 为-1,返回值是创建的这个 Type 类型的值。如果 hint 是合法的,不会发生任何覆 盖,则会 第 63 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 返回这个值;如果 hint 不合法,系统会自动分配一个合法值并返回。因此,使用这个函数即 可完成 type 值的指定。这个函数是线程安全的,因此不 必另外添加同步。   你可以在 QEvent 子类中添加自己的事件所需要的数据,然后进行事件的发送。Qt 中提供 了两种发送方式:     * static bool QCoreApplication::sendEvent(QObjecy * receiver, QEvent * event):事件被 QCoreApplication 的 notify()函数直接发送给 receiver 对象,返回值是事 件处理函数的返回值。使用这个 函数必须要在栈上创建对象,例如:   QMouseEvent event(QEvent::MouseButtonPress, pos, 0, 0, 0);   QApplication::sendEvent(mainWindow, &event);     * static bool QCoreApplication::postEvent(QObject * receiver, QEvent * event):事件被 QCoreApplication 追加到事件列表的最后,并等待处理,该函数将事件追 加后会立即返回,并且注意,该函数是线程安全 的。另外一点是,使用这个函数必须要在堆 上创建对象,例如:     QApplication::postEvent(object, new MyEvent(QEvent::registerEventType(2048)));   这个对象不需要手动 delete,Qt 会自 动 delete 掉!因此,如果在 post 事件之后调用 delete,程序可能会崩溃。另外,postEvent()函数还有一个重载的版本,增加一个优先 级 参数,具体请参见 API。通过调用 sendPostedEvent()函数可以让已提交的事件立即得到处理。   如果要处理自定义事件, 可以重写 QObject 的 customEvent()函数,该函数接收一个 QEvent 对象作为参数。注意,在 Qt3 中这个参数是 QCustomEvent 类型的。你可以像前面介绍 的重写 event()函数的方法去重写这个函数: void  CustomWidget::customEvent(QEvent *event) {                 CustomEvent   *customEvent   =   static_cast(event);         //  .... }   另外,你也可以通过重写 event()函数来处理自定义事件: 第 64 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 bool  CustomWidget::event(QEvent *event) {         if (event->type() ==  MyCustomEventType) {                                 CustomEvent   *myEvent   = static_cast(event);                 //  processing...                 return true;         }          return QWidget::event(event); }   这两种办法都是可行的。 好 了,至此,我们已经概略的介绍了 Qt 的事件机制,包括事件的派发、自定义等一系 列的问题。下面的章节将继续我们的学习之路! 第 65 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 Qt 学习之路(24): QPainter   多些大家对我的支持啊!有朋友也提出,前面的几节有关 event 的教程缺少例子。因为 event 比较难做例子,也就没有去写,只是把大概 写了一下。今天带来的是新的部分,有关 Qt 的 2D 绘图。这部分不像前面的内容,还是比较好理解的啦!所以,例子也会增加出来。     有 人 问 豆 子 拿 Qt 做 什 么 , 其 实 , 豆 子 就 是 在 做 一 个 Qt 的 画 图 程 序 , 努 力 朝 着 Photoshop 和 GIMP 的方向发展。但这终究要经过很长的时间、很困难的路程的, 所以也放在 网上开源,有兴趣的朋友可以来试试的呀…   好了,闲话少说,来继续我们的学习吧!   Qt 的绘图系统允许使用相 同的 API 在屏幕和打印设备上进行绘制。整个绘图系统基于 QPainter,QPainterDevice 和 QPaintEngine 三个类。    QPainter 用来执行绘制的操作;QPaintDevice 是一个二维空间的抽象,这个二维空间 可以由 QPainter 在上面进行绘 制;QPaintEngine 提供了画笔 painter 在不同的设备上进行 绘制的统一的接口。QPaintEngine 类用在 QPainter 和 QPaintDevice 之间,并且通常对开发 人员是透明的,除非你需要自定义一个设备,这时候你就必须重新定义 QPaintEngine 了。    下图给出了这三个类之间的层次结构(出自 Qt API 文档): 这种实现的主要好处是,所有的绘制都遵循着同一种绘制流程,这样,添加可以很方便 的添加新的特性,也可以为不 支持的功能添加一个默认的实现方式。另外需要说明一点,Qt 提供了一个独立的 QtOpenGL 模块,可以让你在 Qt 的应用程序中使用 OpenGL 功能。该 模块 提供了一个 OpenGL 的模块,可以像其他的 Qt 组件一样的使用。它的不同之处在于,它是使 用 OpenGL 作为显示技术,使用 OpenGL 函数进行绘 制。对于这个组件,我们以后会再介绍。 第 66 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 Qt 学习之路(25): QPainter(续)   首先还是要先把上次的代码拿上来。 void  PaintedWidget::paintEvent(QPaintEvent *event) {         QPainter  painter(this);         painter.drawLine(80, 100, 650, 500);          painter.setPen(Qt::red);         painter.drawRect(10, 10, 100,  400);         painter.setPen(QPen(Qt::green, 5));          painter.setBrush(Qt::blue);         painter.drawEllipse(50, 150, 400,  200); }   上次我们说的是 Qt 绘图相关的架构,以及 QPainter 的建立和 drawXXXX 函数。可以看 到,基本上代码中已经设计到得函数还剩下两个:setPen()和 setBrush()。现在,我们就要 把这两个函数讲解一下。   Qt 绘 图系统提供了三个主要的参数设置,画笔(pen)、画刷(brush)和字体(font)。这里 我们要说明的是画笔和画刷。   所谓画笔, 是用于绘制线的,比如线段、轮廓线等,都需要使用画笔绘制。画笔类即 QPen,可以设置画笔的样式,例如虚线、实现之类,画笔的颜色,画笔的转折点样式 等。画 笔的样式可以在创建时指定,也可以由 setStyle()函数指定。画笔支持三种主要的样式:笔 帽 (cap) , 结 合 点 (join) 和 线 形 (line) 。 这 些 样 式 具 体 显 示 如 下 ( 图 片 来 自 C++ GUI Programming with Qt4, 2nd Edition): 第 67 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理   上图共分成三行:第一行是 Cap 样式,第二行是 Join 样式,第三行是 Line 样式。QPen 允许你使用 setCapStyle()、setJoinStyle()和 setStyle()分别进行设置。具体请参加 API 文 档。   所谓画刷,主 要用来填充封闭的几何图形。画刷主要有两个参数可供设置:颜色和样式。 当然,你也可以使用纹理或者渐变色来填充图形。请看下面的图片(图片出自 Qt API 文档): 第 68 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理   这里给出了不同 style 的画刷的表现。同画笔类似,这些样式也可用通过一个 enum 进行 设置。    明白了这些之后我们再来看看我们的代码。首先,我们直接使用 drawLine()函数,由 于没有设置任何样式,所以使用的是默认的 1px,,黑 色,solid 样式画了一条直线;然后 使用 setPen()函数,将画笔设置成 Qt::red,即红色,画了一个矩形;最后将画笔设置成绿 色,5px,画 刷设置成蓝色,画了一个椭圆。这样便显示出了我们最终的样式: 第 69 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理      另外要说明一点,请注意我们的绘制顺序,首先是直线,然后是矩形,最后是 椭圆。这 样,因为椭圆是最后画的,因此在最上方。 在我们学习 OpenGL 的时候,肯定听过这么一句话:OpenGL 是一个状态机。所谓 状态机, 就是说,OpenGL 保存的只是各种状态。怎么理解呢?比如,你把颜色设置成红色,那么,直 到你重新设置另外的颜色,它的颜色会一直是红色。 QPainter 也是这样,它的状态不会自 己恢复,除非你使用了各种 set 函数。因此,如果在上面的代码中,我们在椭圆绘制之后再 画一个椭圆,它的样式还 会是绿色 5px 的轮廓和蓝色的填充,除非你显式地调用了 set 进 行更新。这可能是绘图系统较多的实现方式,因为无论是 OpenGL、QPainter 还是 Java2D,都 是这样实现的(DirectX 不大清楚)。 第 70 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 Qt 学习之路(26): 反走样   今天继续前面的内容。既然已经进入 2D 绘图部分,那么就先继续研究一下有关 QPainter 的东西吧!   反走样是图形学 中的重要概念,用以防止“锯齿”现象的出现。很多系统的绘图 API 里 面都会内置了反走样的算法,不过默认一般都是关闭的,Qt 也不例外。下面我们来看看代 码。这段代码仅仅给出了 paintEvent 函数,相信你可以很轻松地替换掉前面章节中的相关代 码。 void  PaintedWidget::paintEvent(QPaintEvent *event) {         QPainter  painter(this);                 painter.setPen(QPen(Qt::black,   5,   Qt::DashDotLine, Qt::RoundCap));          painter.setBrush(Qt::yellow);         painter.drawEllipse(50, 150,  200, 150);         painter.setRenderHint(QPainter::Antialiasing,  true);                 painter.setPen(QPen(Qt::black,   5,   Qt::DashDotLine, Qt::RoundCap));         painter.setBrush(Qt::yellow);          painter.drawEllipse(300, 150, 200, 150); }   看看运行后的效果: 第 71 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理   查看原图(大图)   左边的是没有使用反走样技术的,右边是使用了反走样技术的。二者的差别可 以很容易 的看出来。   下面来看看相关的代码。为了尝试画笔的样式,这里故意使用了一个新的画笔: painter.setPen(QPen(Qt::black,  5, Qt::DashDotLine, Qt::RoundCap));   我们对照着 API 去看,第一个参数是画笔颜 色,这里设置为黑色;第二个参数是画笔 的粗细,这里是 5px;第三个是画笔样式,我们使用了 DashDotLine,正如同其名字所示, 是一个短线和一个点相间的类型;第四个是 RoundCap,也就是圆形笔帽。然后我们使用一个 黄色的画刷填 充,画了一个椭圆。   后面的一个和前面的十分相似,唯一的区别是多了一句 painter.setRenderHint(QPainter::Antialiasing,  true);   不过这句也很清楚,就是设置 Antialiasing 属性为 true。如果你学过图形学就会知道, 这个长 长的单词就是“反走样”。经过这句设置,我们就打开了 QPainter 的反走样功能。还 第 72 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 记得我们曾经说过,QPainter 是一个状态机,因此,只要这里 我们打开了它,之后所有的 代码都会是反走样绘制的了。   看到这里你会发现,反走样的效果其实比不走样要好得多,那么,为什么不默认打开反 走样呢?这是因为,反走样是一种比较复杂的算法,在一些对图像质量要求不高的应用中, 是不需要进行反走样的。为了提高效率,一般的图形绘制系统,如 Java2D、OpenGL 之类都是 默认不进行反走样的。   还有一个疑问,既然反走样比不反走样的图像质量高很多,不进行反走样的绘制还 有 什么作用呢?前面说的是一个方面,也就是,在一些对图像质量要求不高的环境下,或者说 性能受限的环境下,比如嵌入式和手机环境,是不必须要进行反走样 的。另外还有一点,在 一些必须精确操作像素的应用中,也是不能进行反走样的。请看下面的图片:   查看原图(大图) 上图是使用 Photoshop 的铅笔和画笔工具画的 1 像素的点在放大到 3200%视图下截下来 的。Photoshop 里面的铅笔工具是不进行反走样,而画笔是要进行反走样的。在放大的情况下 就会知道,有反走样的情况下是不能 进行精确到 1 像素的操作的。因为反走样很难让你控制 到 1 个像素。这不是 Photoshop 画笔工具的缺陷,而是反走样算法的问题。如果你想了解为什 么这样,请查阅计算机图形学里面关于反走样的原理部分。 第 73 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 Qt 学习之路(27): 渐变填充   前面说了有关反走样的相关知识,下面来说一下渐变。渐变是绘图中很常见的一种功能 简单来说就是可以把几种颜色混合在一起,让它们能够自 然地过渡,而不是一下子变成另 一种颜色。渐变的算法比较复杂,写得不好的话效率会很低,好在很多绘图系统都内置了渐 变的功能,Qt 也不例外。渐变一般是用 在填充里面的,所以,渐变的设置就是在 QBrush 里 面。     Qt 提 供 了 三 种 渐 变 画 刷 , 分 别 是 线 性 渐 变 (QLinearGradient) 、 辐 射 渐 变 (QRadialGradient) 、 角 度 渐 变 (QConicalGradient) 。 如 下 图 所 示 ( 图 片 出 自 C++ GUI Programming with Qt4, 2nd Edition):   下面我们来看一下线性渐变 QLinearGradient 的用法。 void  PaintedWidget::paintEvent(QPaintEvent *event) {         QPainter  painter(this);         painter.setRenderHint(QPainter::Antialiasing,  true);         QLinearGradient linearGradient(60, 50, 200, 200); 第 74 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理          linearGradient.setColorAt(0.2, Qt::white);          linearGradient.setColorAt(0.6, Qt::green);          linearGradient.setColorAt(1.0, Qt::black);          painter.setBrush(QBrush(linearGradient));          painter.drawEllipse(50, 50, 200, 150); }   同前面一样,这里也仅仅给出 了 paintEvent()函数里面的代码。   首先我们打开了反走样,然后创建一个 QLinearGradient 对象实例。 QLinearGradient 构造函数有四个参数,分别是 x1, y1, x2, y2,即渐变的起始点和终止点。在这里,我们从 (60, 50)开始渐变,到(200, 200)止。   渐变的颜色是在 setColorAt()函数中指定的。下面是这个函数的签名: void  QGradient::setColorAt ( qreal position, const QColor & color )    它的意思是把 position 位置的颜色设置成 color。其中,position 是一个 0 - 1 区间 的数字。也就是说,position 是相对于我们建立渐变对象时做的那个起始点和终止点区间的。 比如这个线性渐变,就是说,在从(60, 50)到(200, 200)的线段上,在 0.2,也就五分之一 处设置成白色,在 0.6 也就是五分之三处设置成绿色,在 1.0 也就是终点处设置成黑色。    在创建 QBrush 时,把这个渐变对象传递进去,就是我们的结果啦: 第 75 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理   那么,我们怎么让线段也是渐变的呢?要知道,直线是用画笔绘制的啊!这里,如果你 仔细查阅了 API 文档就会发 现,QPen 是接受 QBrush 作为参数的。也就是说,你可以利用一 个 QBrush 创建一个 QPen,这样,QBrush 所有的填充效果都可以用在画笔上 了! void PaintedWidget::paintEvent(QPaintEvent *event) {          QPainter painter(this);          painter.setRenderHint(QPainter::Antialiasing, true);          QLinearGradient linearGradient(60, 50, 200, 200);          linearGradient.setColorAt(0.2, Qt::white);          linearGradient.setColorAt(0.6, Qt::green);          linearGradient.setColorAt(1.0, Qt::black);          painter.setPen(QPen(QBrush(linearGradient), 5));          painter.drawLine(50, 50, 200, 200); }   看看我们的渐变线吧!    第 76 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 Qt 学习之路(28): 坐标变换   经过前面的章节,我们已经能够画出一些东西来,主要就是使用 QPainter 的相关函数。 今天,我们要看的是 QPainter 的坐标系 统。   同很多坐标系统一样,QPainter 的默认坐标的原点(0, 0)位于屏幕的左上角,X 轴正方 向是水平向右,Y 轴正方向是竖直向下。在这个坐标系统中,每个像素占据 1 x 1 的空间。你 可以把它想象成是一张坐标值,其中的每个小格都是 1 个像素。这么说来,一个像素的中心 实际上是一个“半像素坐标系”,也就是说,像素(x, y)的中心位置其实是在(x + 0.5, y + 0.5)的位置上。因此,如果我们使用 QPainter 在(100, 100)处绘制一个像素,那么,这个 像素的中心坐标是(100.5, 100.5)。   这种细微的差别在实际应用中,特别是对坐标要求精 确的系统中是很重要的。首先,只 有在禁止反走样,也就是默认状态下,才会有这 0.5 像素的偏移;如果使用了反走样,那么, 我们画(100, 100)位置的像素时,QPainter 会在(99.5, 99.5),(99.5, 100.5),(100.5, 99.5)和(100.5, 100.5)四个位置绘制一个亮色的像素,这么产生的效果就是在这四个像素 的焦点处(100, 100)产生了一个像素。如果不需要这个特性,就需要将 QPainter 的坐标系平 移(0.5, 0.5)。   这一特性在绘制直线、矩形 等图形的时候都会用到。下图给出了在没有反走样技术时, 使用 drawRect(2, 2, 6, 5)绘制一个矩形的示例。在 No Pen 的情况下,请注意矩形左上角的 像素是在(2, 2),其中心位置是在(2.5, 2.5)的位置。然后注意下有不同的 Pen 的值的绘制 样式,在 Pen 宽为 1 时,实际画出的矩形的面积是 7 x 6 的(图出自 C++ GUI Programming with Qt4, 2nd Edition):     在 具 有 反 走 样 时 , 使 用 drawRect(2, 2, 6, 5) 的 效 果 如 下 ( 图 出 自 C++ GUI Programming with Qt4, 2nd Edition): 第 77 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理   注意我们前面说过,通过平移 QPainter 的坐标系来消除着 0.5 像素的差异。下面给出了 使用 drawRect(2.5, 2.5, 6, 5)在反走样情况下绘制的矩形(图出自 C++ GUI Programming with Qt4, 2nd Edition):   请对比与上图的区别。   在上述的 QPainter 的默认坐标系下,QPainter 提供了视 口(viewport)窗口(window)机 制,用于绘制与绘制设备的大小和分辨率无关的图形。视口和窗口是紧密的联系在一起的, 它们一般都是矩形。视 口是由物理坐标确定其大小,而窗口则是由逻辑坐标决定。我们在使 用 QPainter 进行绘制时,传给 QPainter 的是逻辑坐标,然后,Qt 的绘图机制会使用坐标 变换将逻辑坐标转换成物理坐标后进行绘制。    通常,视口和窗口的坐标是一致的。比如一个 600 x 800 的 widget(这是一个 widget, 或许是一个对话框,或许是一个面板等等),默认情况下,视口和窗口都是一个 320 x 200 的矩形,原点都在(0, 0),此时,视口和窗口的坐标是相同的。   注意到 QPainter 提供了 setWindow()和 setViewport()函数,用来设置视口和窗口的矩 形大小。比如,在上面所述的 320 x 200 的 widget 中,我们要设置一个从(-50, -50)到 (+50, +50),原点在中心的矩形窗口,就可以使用 painter.setWindow(-50, -50, 100, 100);   其中, (-50, -50)指明了原点,100, 100 指明了窗口的长和宽。这里的“指明原点” 第 78 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 意思是,逻辑坐标的(-50, -50)对应着物理坐标的(0, 0);“长和宽”说明,逻辑坐标系下 的长 100,宽 100 实际上对应物理坐标系的长 320,宽 200。    或许你已经发现这么一个好处,我们可以随时改变 window 的范围,而不改变底层物理 坐标系。这就是前面所说的,视口与窗口的作用:“绘制与绘制设备的 大小和分辨率无关的 图形”,如下图所示(图出自 C++ GUI Programming with Qt4, 2nd Edition):   除了视口与窗口的变化,QPainter 还提供了一个“世界坐标系”,同样也可以变换图 形。所不同的是,视口 与窗口实际上是统一图形在两个坐标系下的表达,而世界坐标系的变 换是通过改变坐标系来平移、缩放、旋转、剪切图形。为了清楚起见,我们来看下面一个例子: void  PaintedWidget::paintEvent(QPaintEvent *event)  {           QPainter painter(this);          QFont font("Courier", 24);           painter.setFont(font);          painter.drawText(50, 50, "Hello,  world!");          QTransform transform;           transform.rotate(+45.0);           painter.setWorldTransform(transform);          painter.drawText(60,  60, "Hello, world!");  }   为了显示方便,我在这里使用了 QFont 改变了字体。 QPainter 的 drawText()函数提供 了绘制文本的功能。它有几种重载形式,我们使用了其中的一种,即制定文本的坐标然后绘 制。需要注意的是, 这里的坐标是文字   左下角 第 79 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理   的坐标(特别提醒这一点,因为很多绘图系统,比如 Java2D 都是把左上角作为坐标点 的)!下面是运行结果:   我们使用 QTransform 做了一个 rotate 变换。这个变换就是旋转,而且是顺时针旋转 45 度。然后我 们使用这个变换设置了 QPainter 的世界坐标系,注意到 QPainter 是一个状态机, 所以这种变换并不会改变之前的状态,因此只有第二个 Hello, world!被旋转了。确切的说, 被旋转的是坐标系而不是这个文字!请注意体会这两种说法的不同。 第 80 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 Qt 学习之路(29): 绘图设备   绘图设备是指继承 QPainterDevice 的子类。Qt 一共提供了四个这样的类,分别是 QPixmap、QBitmap、 QImage 和 QPicture。其中,QPixmap 专门为图像在屏幕上的显示做了优 化 , 而 QBitmap 是 QPixmap 的 一 个 子 类 , 它 的 色 深 限 定 为 1 , 你 可 以 使 用 QPixmap 的 isQBitmap()函数来确定这个 QPixmap 是不是一个 QBitmap。QImage 专门为图像的像素级访问 做了优化。QPicture 则可以记录和重现 QPainter 的各条命令。下面我们将分两部分介绍这四 种绘图设备。   QPixmap 继承了 QPaintDevice,因此,你可以使用 QPainter 直接在上面绘制图形 。 QPixmap 也可以接受一个字符串作为一个文件的路径来显示这个文 件,比如你想在程序之 中打开 png、jpeg 之类的文件,就可以使用 QPixmap。使用 QPainter 的 drawPixmap()函数可 以把这个文件绘制到一个 QLabel、QPushButton 或者其他的设备上 面。QPixmap 是针对屏幕进 行特殊优化的,因此,它与实际的底层显示设备息息相关。注意,这里说的显示设备并不是 硬件,而是操作系统提供的原生的绘图 引擎。所以,在不同的操作系统平台下,QPixmap 的 显示可能会有所差别。   QPixmap 提供了静态的 grabWidget()和 grabWindow()函数,用于将自身图像绘制到目 标上。同时,在使用 QPixmap 时,你可以直接使用传值也不需要传指针,因为 QPixmap 提供 了“隐式数据共享”。关于这一点,我们会在以后的章节中详细描述,这里只要知道传递 QPixmap 不必须使用指针就好了。    QBitmap 继承自 QPixmap,因此具有 QPixmap 的所有特性。QBitmap 的色深始终为 1. 色 深这个概念来自计算机图形学,是指用于表现颜色的二进制的位数。我们知道,计算机里面 的数据都是使用二进制表示的。为了表示一种颜色,我们也会使用二进 制。比如我们要表示 8 种颜色,需要用 3 个二进制位,这时我们就说色深是 3. 因此,所谓色深为 1,也就是使用 1 个二进制位表示颜色。1 个位只有两种状态:0 和 1,因此它所表示的颜色就有两种,黑和 白。所以说,QBitmap 实际上 是只有黑白两色的图像数据。   由于 QBitmap 色深小,因此只占用很少的存储空间,所以适合做光标文件和笔刷。   下面我们来看同一个图像文件在 QPixmap 和 QBitmap 下的不同表现: 第 81 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 void  PaintedWidget::paintEvent(QPaintEvent *event)  {          QPainter  painter(this);          QPixmap pixmap("Cat.png");           QBitmap bitmap("Cat.png");          painter.drawPixmap(10, 10, 128,  128, pixmap);          painter.drawPixmap(140, 10, 128, 128, bitmap);           QPixmap pixmap2("Cat2.png");          QBitmap  bitmap2("Cat2.png");          painter.drawPixmap(10, 140, 128, 128,  pixmap2);          painter.drawPixmap(140, 140, 128, 128, bitmap2);  }    先来看一下运行结果:   这里我们给出了两张 png 图片。Cat.png 是没有透明色的纯白背景,而 Cat2.png 是具有 透明色的背 景。我们分别使用 QPixmap 和 QBitmap 来加载它们。注意看它们的区别:白色的 背景在 Qbitmap 中消失了,而透明色在 QBitmap 中转换成 了黑色;其他颜色则是使用点的 疏密程度来体现的。   QPixmap 使用底层平台的绘制系统进行绘制,无法提供像素级别的操作,而 QImage 则 第 82 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 是使用独立于硬件的绘制系统,实际上是自己绘制自己,因此提供了像素级别的操作,并且 能够在不同系统之上提供一个一致的显示形式。   查看原图(大图)   如上图所示(出自 Qt API 文档),我们声明了一个 QImage 对象,大小是 3 x 3,颜色模 式是 RGB32,即使用 32 位数值表示一个颜色的 RGB 值,也就是说每种颜色使用 8 位。然后我 们对每个像素进行颜色赋值,从而构成了这个图像。 你可以把 QImage 想象成一个 RGB 颜色 的二维数组,记录了每一像素的颜色。   最后一个需要说明的是 QPicture。这是一个可以记 录和重现 QPainter 命令的绘图设备。 QPicture 将 QPainter 的命令序列化到一个 IO 设备,保存为一个平台独立的文件格式。这种 格式有时候会是“元文件(meta- files)”。Qt 的这种格式是二进制的,不同于某些本地的 元文件,Qt 的 pictures 文件没有内容上的限制,只要是能够被 QPainter 绘制的 元素,不 论是字体还是 pixmap,或者是变换,都可以保存进一个 picture 中。   QPicture 是平台无关的,因此它可以使用在 多种设备之上,比如 svg、pdf、ps、打印机 或者屏幕。回忆下我们这里所说的 QPaintDevice,实际上是说可以有 QPainter 绘制的对 象 。 QPicture 使用系统的分辨率,并且可以调整 QPainter 来消除不同设备之间的显示差异。     如 果 我 们 要 记 录 下 QPainter 的 命 令 , 首 先 要 使 用 QPainter::begin() 函 数 , 将 QPicture 实 例 作 为 参 数 传 递 进 去 , 以 便 告 诉 系 统 开 始 记 录 , 记 录 完 毕 后 使 用 QPainter::end()命令终止。代码示例如下: 第 83 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 QPicture picture;  QPainter  painter;  painter.begin(&picture);              // paint in  picture  painter.drawEllipse(10,20, 80,70); // draw an ellipse  painter.end();                                                       // painting done  picture.save("drawing.pic");          // save picture   如果我们要重现命令,首先要使用 QPicture::load()函 数进行装载: QPicture picture;  picture.load("drawing.pic");           // load picture  QPainter painter;  painter.begin(&myImage);             // paint in myImage  painter.drawPicture(0, 0, picture);  // draw the picture at (0,0)  painter.end();  第 84 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 Qt 学习之路(30): Graphics View Framework   现在基本上也已经到了 2D 绘图部分的尾声,所谓重头戏都是在最后压轴的,现在我们 就要来看看在绘图部分功能最强大的 Graphics View。我们经常说 KDE 桌面,新版本的 KDE 桌 面就是建立在 Graphics View 的基础之上,可见其强大之处。   Qt 的白皮书 里面这样写道:“Qt Graphics View 提供了用于管理和交互大量定制的 2D 图形对象的平面以及可视化显示对象的视图 widget,并支持缩放和旋转功能。Graphics View 使用 BSP(二进制空间划分)树形可非常快速地找到对象,因此即使是包含百万个对 象的大型场景,也能实时图形化显示。”   Graphics View 是一个基于 item 的 M-V 架构的框架。   基于 item 意思是,它的每一个组件都是一个 item。这是与 QPainter 的状态 机不同。回 忆一下,使用 QPainter 绘图多是采用一种面向过程的描述方式,首先使用 drawLine()画一 条直线,然后使用 drawPolygon()画一个多边形;而对于 Graphics View 来说,相同的过程 可以是,首先创建一个场景 scene,然后创建一个 line 对象和一个 polygon 对象,再使用 scene 的 add()函数将 line 和 polygon 添加到 scene,最后通过视口 view 就可以看到了。乍 看起来,后者似乎更加复杂,但是,如果你的图像中包含了成千上万的直 线、多边形之类, 管理这些对象要比管理 QPainter 的 draw 语句容易得多。并且,这些图形对象也更加符合面 向对象的设计要求:一个很复杂的图形可以 很方便的复用。   M-V 架构的意思是,Graphics View 提供一个 model 和一个 view。所谓 model 就是我们添 加的种种对象,所谓 view 就是我们观察这些对象的视口。同一个 model 可以由很 多 view 从 不同的角度进行观察,这是很常见的需求。使用 QPainter 就很难实现这一点,这需要很复杂 的计算,而 Qt 的 Graphics View 就可以很容易的实现。 编缉推荐阅读以下文章   Graphics View 提供了一个 QGraphicsScene 作为场景,即是我们添加图形的空间,相当 于整个世界;一个 QGraphicsView 作为视口,也就是我 们观察的窗口,相当于照相机的取 景框,这个取景框可以覆盖整个场景,也可以是场景的一部分;一些 QGraphicsItem 作为图 形元件,以便 scene 添加,Qt 内置了很多图形,比如 line、polygon 等,都是继承自 第 85 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 QGraphicsItem。   下面我们来看一下代码: #include   class DrawApp : public QWidget { public:          DrawApp(); protected:         void paintEvent(QPaintEvent  *event); }; DrawApp::DrawApp() { } void  DrawApp::paintEvent(QPaintEvent *event) {         QPainter  painter(this);         painter.drawLine(10, 10, 150, 300); } int  main(int argc, char *argv[]) {         QApplication a(argc,  argv);         QGraphicsScene *scene = new QGraphicsScene;          scene->addLine(10, 10, 150, 300);         QGraphicsView *view =  new QGraphicsView(scene);         view->resize(500, 500);          view->setWindowTitle("Graphics View");         view->show();          DrawApp *da = new DrawApp;         da->resize(500, 500);          da->setWindowTitle("QWidget"); 第 86 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理         da->show();          return a.exec(); }   为了突出重点,我们就直接 include 了 QtGui,不过在 实际应用中不建议这么做。这里 提供了直线的两种实现:一个是 DrawApp 使用我们前面介绍的技术,重写 paintEvent()函数, 这里就不在赘述, 重点来看 main()函数里面的实现。   首先,我们创建了一个 QGraphicsScene 作为场景,然后在 scene 中添加了一个直 线, 这样就把我们需要的图形元件放到了 scene 中。然后创建一个 QGraphicsView 对象进行观察。 就这样,我们就是用 Graphics View 搭建了一个最简单的应用。运行这个程序来看结果: 第 87 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理   第一张图是 Graphics View 的,第二个是 DrawApp 的。虽然这两个直线是同样的坐标, 但是,DrawApp 按照原始坐标绘制出了直线,而 Graphics View 则按照坐标绘制出直线之后, 自动将直线居中显示在 view 视口。你可以通过拖动 Graphics View 来看直线是一直居中显示 的。    这里仅仅是一个很简单的对比,不过你已经可以看到 Graphics View 功能的强大。仅这 一个居中的操作,如果你是用 QPainter,就需要很大的计算量了!当然,如果你不需要这 种居中,Graphics View 也是可以像 QPainter 绘制的一样进行显示的。 第 88 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 Qt 学习之路(31): 一个简易画板的实现(QWidget)   说实话,本来我是没有打算放一个很大的例子的,一则比较复杂,二来或许需要很多次 才能说得完。不过,现在已经说完了绘图部分,所以计划还 是上一个这样的例子。这里我会 只做出一个简单的画板程序,大体上就是能够画直线和矩形吧。这样,我计划分成两种实现 一是使用普通的 QWidget 作为画 板,第二则是使用 Graphcis View Framework 来实现。因为 前面有朋友说不大明白 Graphics View 的相关内容,所以计划如此。   好了,现在先来看看我们的主体框架。我们的框架还是使用 Qt Creator 创建一个 Gui Application 工程。   简单的 main()函数就不再赘述了,这里首先来看 MainWindow。顺便说一下,我一般不会 使用 ui 文件,所以这些内容都是手写的。首先先来看看最终的运行结果:   查看原图(大图)   或许很简单,但是至少我们能够把前面所说的各 种知识串连起来,这也就达到目的了。 第 89 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理   现在先来看看 MainWindow 的代码: mainwindow.h #ifndef  MAINWINDOW_H #define MAINWINDOW_H #include  #include  "shape.h" #include "paintwidget.h" class MainWindow : public  QMainWindow {         Q_OBJECT public:          MainWindow(QWidget *parent = 0); signals:         void  changeCurrentShape(Shape::Code newShape); private slots:          void drawLineActionTriggered();         void  drawRectActionTriggered(); }; #endif // MAINWINDOW_H   mainwindow.cpp #include  "mainwindow.h" MainWindow::MainWindow(QWidget *parent)         :  QMainWindow(parent) {         QToolBar *bar =  this->addToolBar("Tools");         QActionGroup *group = new  QActionGroup(bar);         QAction *drawLineAction = new  QAction("Line", bar);          drawLineAction->setIcon(QIcon(":/line.png")); 第 90 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理          drawLineAction->setToolTip(tr("Draw a line."));          drawLineAction->setStatusTip(tr("Draw a line."));          drawLineAction->setCheckable(true);          drawLineAction->setChecked(true);          group->addAction(drawLineAction);          bar->addAction(drawLineAction);         QAction *drawRectAction =  new QAction("Rectangle", bar);          drawRectAction->setIcon(QIcon(":/rect.png"));          drawRectAction->setToolTip(tr("Draw a rectangle."));          drawRectAction->setStatusTip(tr("Draw a rectangle."));          drawRectAction->setCheckable(true);          group->addAction(drawRectAction);          bar->addAction(drawRectAction);         QLabel *statusMsg = new  QLabel;         statusBar()->addWidget(statusMsg);          PaintWidget *paintWidget = new PaintWidget(this);          setCentralWidget(paintWidget);         connect(drawLineAction,  SIGNAL(triggered()),                  this,  SLOT(drawLineActionTriggered()));         connect(drawRectAction,  SIGNAL(triggered()),                                         this, SLOT(drawRectActionTriggered()));         connect(this,  SIGNAL(changeCurrentShape(Shape::Code)),                paintWidget, SLOT(setCurrentShape(Shape::Code))); } void  MainWindow::drawLineActionTriggered() {         emit  changeCurrentShape(Shape::Line); } 第 91 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 void  MainWindow::drawRectActionTriggered() {         emit  changeCurrentShape(Shape::Rect); }   应该说,从以往的学习中可以看出,这里的代码没有什么奇怪的了。我们在 MainWindow 类里面声明了一个信 号,changeCurrentShape(Shape::Code),用于按钮按下后通知画图板。 注意,QActio 的 triggered()信号是没 有参数的,因此,我们需要在 QAction 的槽函数中 重 新 emit 我 们 自 己 定 义 的 信 号 。 构 造 函 数 里 面 创 建 了 两 个 QAction , 一 个 是 drawLineAction,一个是 drawRectAction,分别用于绘制直线和矩形。MainWindow 的中心组 件是 PainWidget, 也就是我们的画图板。下面来看看 PaintWidget 类: paintwidget.h #ifndef  PAINTWIDGET_H #define PAINTWIDGET_H #include  #include   #include "shape.h" #include "line.h" #include  "rect.h" class PaintWidget : public QWidget {         Q_OBJECT public:          PaintWidget(QWidget *parent = 0); public slots:          void setCurrentShape(Shape::Code s)         {                 if(s  != currShapeCode) {                         currShapeCode = s;                  } 第 92 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理         } protected:         void  paintEvent(QPaintEvent *event);         void  mousePressEvent(QMouseEvent *event);         void  mouseMoveEvent(QMouseEvent *event);         void  mouseReleaseEvent(QMouseEvent *event); private:          Shape::Code currShapeCode;         Shape *shape;         bool  perm;         QList shapeList; }; #endif //  PAINTWIDGET_H   paintwidget.cpp #include "paintwidget.h" PaintWidget::PaintWidget(QWidget  *parent)         : QWidget(parent), currShapeCode(Shape::Line),  shape(NULL), perm(false) {          setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); } void  PaintWidget::paintEvent(QPaintEvent *event) {         QPainter  painter(this);         painter.setBrush(Qt::white);          painter.drawRect(0, 0, size().width(), size().height());          foreach(Shape * shape, shapeList) {                  shape->paint(painter); 第 93 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理         }         if(shape) {                  shape->paint(painter);         } } void  PaintWidget::mousePressEvent(QMouseEvent *event) {          switch(currShapeCode)         {         case Shape::Line:                  {                         shape = new Line;                          break;                 }         case Shape::Rect:                  {                         shape = new Rect;                          break;                 }         }          if(shape != NULL) {                 perm = false;                  shapeList<setStart(event->pos());                  shape->setEnd(event->pos());         } } void  PaintWidget::mouseMoveEvent(QMouseEvent *event) { 第 94 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理         if(shape  && !perm) {                  shape->setEnd(event->pos());                 update();          } } void PaintWidget::mouseReleaseEvent(QMouseEvent *event) {          perm = true; }   PaintWidget 类定义了一个 slot,用于接收改变后的新的 ShapeCode。最主要的是 PaintWidget 重 定 义 了 三 个 关 于 鼠 标 的 事 件 : mousePressEvent , mouseMoveEvent 和 mouseReleaseEvent。   我们来想象 一下如何绘制一个图形:图形的绘制与鼠标操作息息相关。以画直线为例, 首先我们需要按下鼠标,确定直线的第一个点,所以在 mousePressEvent 里面,我们让 shape 保存下 start 点。然后在鼠标按下的状态下移动鼠标,此时,直线就会发生变化,实际 上是直线的终 止点在随着鼠标移动,所以在 mouseMoveEvent 中我们让 shape 保存下 end 点, 然后调用 update()函数,这个函数会自动调用 paintEvent()函数,显示出我们绘制的内容。 最后,当鼠标松开时,图形绘制完毕,我们将一个标志位置为 true,此时说明这个图形绘 制完毕。    为了保存我们曾经画下的图形,我们使用了一个 List。每次按下鼠标时,都会把图形 存入这个 List。可以看到,我们在 paintEvent()函数中 使用了 foreach 遍历了这个 List, 绘制出历史图形。foreach 是 Qt 提供的一个宏,用于遍历集合中的元素。   最后我们来看看 Shape 类。 shape.h #ifndef SHAPE_H #define SHAPE_H #include   class Shape 第 95 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 { public:         enum Code {                  Line,                 Rect         };          Shape();         void setStart(QPoint s)         {                  start = s;         }         void setEnd(QPoint e)          {                 end = e;         }         QPoint  startPoint()         {                 return start;         }          QPoint endPoint()         {                 return end;          }         void virtual paint(QPainter & painter) = 0; protected:          QPoint start;         QPoint end; }; #endif // SHAPE_H 第 96 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理   shape.cpp #include  "shape.h" Shape::Shape() { }   Shape 类最重要的就是保存了 start 和 end 两个点。为什么只要这两个点呢?因为我们要 绘制的是直线和矩形。对于直线来说,有了两 个点就可以确定这条直线,对于矩形来说,有 了两个点作为左上角的点和右下角的点也可以确定这个矩形,因此我们只要保存两个点,就 足够保存这两种图形的位置 和大小的信息。paint()函数是 Shape 类的一个纯虚函数,子类 都必须实现这个函数。我们现在有两个子类:Line 和 Rect,分别定义如下: line.h #ifndef  LINE_H #define LINE_H #include "shape.h" class Line : public  Shape { public:         Line();         void paint(QPainter  &painter); }; #endif // LINE_H   line.cpp #include  "line.h" Line::Line() { } void Line::paint(QPainter  &painter) 第 97 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 {         painter.drawLine(start, end); }   rect.h #ifndef  RECT_H #define RECT_H #include "shape.h" class Rect : public  Shape { public:         Rect();         void paint(QPainter  &painter); }; #endif // RECT_H   rect.cpp #include  "rect.h" Rect::Rect() { } void Rect::paint(QPainter  &painter) {         painter.drawRect(start.x(), start.y(),   end.x() - start.x(), end.y() -  start.y()); }   使用 paint()函数,根据两个点的数据,Line 和 Rect 都可以绘制出它们自身 来。此时 就可以看出,我们之所以要建立一个 Shape 作为父类,因为这两个类有几乎完全相似的数据 第 98 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 对象,并且从语义上来说,Line、Rect 与 Shape 也完全是一个 is-a 的关系。如果你想要添加 颜色等的信息,完全可以在 Shape 类进行记录。这也就是类层次结构的好处。 代 码很多也会比较乱,附件里面是整个工程的文件,有兴趣的朋友不妨看看哦! 第 99 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 Qt 学习之路(32): 一个简易画板的实现(Graphics View)   这一次将介绍如何使用 Graphics View 来实现前面所说的画板。前面说了很多有关 Graphics View 的好话,但是没有具体的实例很难说究竟好在哪里。现在我们就把前面的内 容使用 Graphics View 重新实现一下,大家可以对比一下看有什么区别。   同前面相似的内容就不再叙述了,我们从上次代码的基础上进行修改,以便符合我们 的需要。首先来看 MainWindow 的代码:   mainwindow.cpp #include  "mainwindow.h" MainWindow::MainWindow(QWidget *parent)         :  QMainWindow(parent) {         QToolBar *bar =  this->addToolBar("Tools");         QActionGroup *group = new  QActionGroup(bar);         QAction *drawLineAction = new  QAction("Line", bar);          drawLineAction->setIcon(QIcon(":/line.png"));          drawLineAction->setToolTip(tr("Draw a line."));          drawLineAction->setStatusTip(tr("Draw a line."));          drawLineAction->setCheckable(true);          drawLineAction->setChecked(true);          group->addAction(drawLineAction);          bar->addAction(drawLineAction);         QAction *drawRectAction =  new QAction("Rectangle", bar);          drawRectAction->setIcon(QIcon(":/rect.png"));          drawRectAction->setToolTip(tr("Draw a rectangle."));          drawRectAction->setStatusTip(tr("Draw a rectangle."));          drawRectAction->setCheckable(true);          group->addAction(drawRectAction); 第 100 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理          bar->addAction(drawRectAction);         QLabel *statusMsg = new  QLabel;         statusBar()->addWidget(statusMsg);          PaintWidget *paintWidget = new PaintWidget(this);                 QGraphicsView   *view   =   new   QGraphicsView(paintWidget, this);          setCentralWidget(view);         connect(drawLineAction,  SIGNAL(triggered()),                                         this, SLOT(drawLineActionTriggered()));         connect(drawRectAction,  SIGNAL(triggered()),                                         this, SLOT(drawRectActionTriggered()));         connect(this,  SIGNAL(changeCurrentShape(Shape::Code)),                paintWidget, SLOT(setCurrentShape(Shape::Code))); } void  MainWindow::drawLineActionTriggered() {         emit  changeCurrentShape(Shape::Line); } void  MainWindow::drawRectActionTriggered() {         emit  changeCurrentShape(Shape::Rect); }   由于 mainwindow.h 的代码与前文相同,这里就不再贴出。而 cpp 文件里面只有少数几行 与前文不同。由于我们使用 Graphics View,所以,我们必须把 item 添加到 QGprahicsScene 里面。这里,我们创建了 scene 的对象,而 scene 对象需要通过 view 进行 观察,因此,我 们需要再使用一个 QGraphcisView 对象,并且把这个 view 添加到 MainWindow 里面。   我们把 PaintWidget 当做一个 scene,因此 PaintWidget 现在是继承 QGraphicsScene, 第 101 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 而不是前面的 QWidget。    paintwidget.h #ifndef PAINTWIDGET_H #define  PAINTWIDGET_H #include  #include  #include  "shape.h" #include "line.h" #include "rect.h" class  PaintWidget : public QGraphicsScene {         Q_OBJECT public:          PaintWidget(QWidget *parent = 0); public slots:          void setCurrentShape(Shape::Code s)         {                 if(s  != currShapeCode) {                         currShapeCode = s;                  }         } protected:         void  mousePressEvent(QGraphicsSceneMouseEvent *event);         void  mouseMoveEvent(QGraphicsSceneMouseEvent *event);         void  mouseReleaseEvent(QGraphicsSceneMouseEvent *event); private:          Shape::Code currShapeCode;         Shape *currItem;          bool perm; 第 102 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 }; #endif // PAINTWIDGET_H   paintwidget.cpp #include "paintwidget.h" PaintWidget::PaintWidget(QWidget  *parent)                 :   QGraphicsScene(parent),   currShapeCode(Shape::Line), currItem(NULL), perm(false) { } void  PaintWidget::mousePressEvent(QGraphicsSceneMouseEvent *event) {          switch(currShapeCode)         {         case Shape::Line:                  {                         Line *line = new Line;                          currItem = line;                          addItem(line);                         break;                 }          case Shape::Rect:                 {                          Rect *rect = new Rect;                         currItem = rect;                          addItem(rect);                         break;                  }         }         if(currItem) { 第 103 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理                  currItem->startDraw(event);                 perm = false;          }         QGraphicsScene::mousePressEvent(event); } void  PaintWidget::mouseMoveEvent(QGraphicsSceneMouseEvent *event) {          if(currItem && !perm) {                  currItem->drawing(event);         }          QGraphicsScene::mouseMoveEvent(event); } void  PaintWidget::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) {          perm = true;         QGraphicsScene::mouseReleaseEvent(event); }   我们把继承自 QWidget 改成继承自 QGraphicsScene,同样也会有鼠标事件,只不过在这 里我们把鼠标事件全部转发给具体的 item 进行处理。这个我们会在下面的代码中看到。另外 一点是,每一个鼠标处理函数都包含了调用其父类函数的语句。 shape.h #ifndef  SHAPE_H #define SHAPE_H #include  class Shape { public:          enum Code {                 Line, 第 104 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理                 Rect          };         Shape();         virtual void  startDraw(QGraphicsSceneMouseEvent * event) = 0;         virtual void  drawing(QGraphicsSceneMouseEvent * event) = 0; }; #endif //  SHAPE_H   shape.cpp #include "shape.h" Shape::Shape() { }    Shape 类也有了变化:还记得我们曾经说过,Qt 内置了很多 item,因此我们不必全部 重写这个 item。所以,我们要使用 Qt 提供的类,就不需要在 我们的类里面添加新的数据成 员了。这样,我们就有了不带有额外的数据成员的 Shape。那么,为什么还要提供 Shape 呢? 因为我们在 scene 的鼠标事件中需要修改这些数据成员,如果没有这个父类,我们就需要 按照 Code 写一个长长的 switch 来判断是那一个图形,这样是很麻烦的。所以我们依 然创建 了一个公共的父类,只要调用这个父类的 draw 函数即可。 line.h #ifndef LINE_H #define  LINE_H #include  #include "shape.h" class  Line : public Shape, public QGraphicsLineItem { public: 第 105 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理          Line();         void startDraw(QGraphicsSceneMouseEvent * event);          void drawing(QGraphicsSceneMouseEvent * event); }; #endif  // LINE_H   line.cpp #include "line.h" Line::Line() { } void  Line::startDraw(QGraphicsSceneMouseEvent * event) {          setLine(QLineF(event->scenePos(), event->scenePos())); } void  Line::drawing(QGraphicsSceneMouseEvent * event) {         QLineF  newLine(line().p1(), event->scenePos());         setLine(newLine); }     Line 类 已 经 和 前 面 有 了 变 化 , 我 们 不 仅 仅 继 承 了 Shape , 而 且 继 承 了 QGraphicsLineItem 类。这里我们使用了 C++的 多继承机制。这个机制是很危险的,很容易发 生错误,但是这里我们的 Shape 并没有继承其他的类,只要函数没有重名,一般而言是没有 问题的。如果不希望出 现不推荐的多继承(不管怎么说,多继承虽然危险,但它是符合面向 对象理论的),那就就想办法使用组合机制。我们之所以使用多继承,目的是让 Line 类同时 具有 Shape 和 QGraphicsLineItem 的性质,从而既可以直接添加到 QGraphicsScene 中,又 可以调用 startDraw()等函 数。   同样的还有 Rect 这个类: rect.h 第 106 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 #ifndef RECT_H #define  RECT_H #include  #include "shape.h" class  Rect : public Shape, public QGraphicsRectItem { public:          Rect();         void startDraw(QGraphicsSceneMouseEvent * event);          void drawing(QGraphicsSceneMouseEvent * event); }; #endif  // RECT_H   rect.cpp #include "rect.h" Rect::Rect() { } void  Rect::startDraw(QGraphicsSceneMouseEvent * event) {          setRect(QRectF(event->scenePos(), QSizeF(0, 0))); } void  Rect::drawing(QGraphicsSceneMouseEvent * event) {         QRectF  r(rect().topLeft(),                           QSizeF(event->scenePos().x() - rect().topLeft().x(),  event->scenePos().y() - rect().topLeft().y()));          setRect(r); } 第 107 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理   Line 和 Rect 类的逻辑都比较清楚,和前面的基本类似。所不同的是,Qt 并没有使 用我 们前面定义的两个 Qpoint 对象记录数据,而是在 QGraphicsLineItem 中使用 QLineF,在 QGraphicsRectItem 中使用 QRectF 记录数据。这显然比我们的两个点的数据 记录高级得多。 其实,我们也完全可以使用这样的数据结构去重定义前面那些 Line 之类。   这样,我们的程序就修改完毕了。运行一下你会发 现,几乎和前面的实现没有区别。这 里说“几乎”,是在第一个点画下的时候,scene 会移动一段距离。这是因为 scene 是自动居 中的,由于我们把 Line 的第一个点设置为(0, 0),因此当我们把鼠标移动后会有一个偏移。   看到这里或许并没有显示出 Graphics View 的优势。不过,建议在 Line 或者 Rect 的构 造函数里面加上下面的语句, setFlag(QGraphicsItem::ItemIsMovable,  true); setFlag(QGraphicsItem::ItemIsSelectable, true);    此时,你的 Line 和 Rect 就已经支持选中和拖放了!值得试一试哦!不过,需要注意 的是,我们重写了 scene 的鼠标控制函数,所以这里的拖动会很粗 糙,甚至说是不正确, 你需要动动脑筋重新设计我们的类啦! 第 108 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 Qt 学习之路(33): 国际化(上)   2D 绘图部分基本告一段落,还在想下面的部分要写什么,本来计划先说下 view-model 的相关问题,但是前面看到有朋友问关于国际化 的问题,所以现在先来说说 Qt 的国际化吧!   Qt 中的国际化的方法有很多,常用的有使用 QTextCodec 类和使用 tr()函数。前者 将编 码名称写到代码里面,除非你使用 Unicode 编码,否则国际化依然是一个问题;后者就不 会有这个问题,并且这也是 Qt 推荐的做法。因此,我们主要来说使用 tr()函数的方法进行应 用程序的国际化。    我们先来看一个很简单的 MainWindow。为了清楚起见,这里只给出了 cpp 文件的内容: #include  "mainwindow.h"    MainWindow::MainWindow(QWidget *parent)           : QMainWindow(parent)  {          QMenuBar *menuBar = new  QMenuBar(this);          QMenu *fileMenu = new QMenu(tr("&File"),  menuBar);          QAction *newFile = new QAction(tr("&New..."),  fileMenu);          fileMenu->addAction(newFile);           QAction *openFile = new QAction(tr("&Open..."), fileMenu);          fileMenu->addAction(openFile);           menuBar->addMenu(fileMenu);          setMenuBar(menuBar);                    connect(openFile,   SIGNAL(triggered()),   this, SLOT(fileOpen()));  }  第 109 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理   MainWindow::~MainWindow()  {    }    void  MainWindow::fileOpen()  {          QFileDialog *fileDialog = new  QFileDialog(this);          fileDialog->setWindowTitle(tr("Open  File"));          fileDialog->setDirectory(".");           if(fileDialog->exec() == QDialog::Accepted) {                   QString path = fileDialog->selectedFiles() [0];                                  QMessageBox::information(NULL,   tr("Path"), tr("You  selected\n%1").arg(path));          } else {                                  QMessageBox::information(NULL,   tr("Path"), tr("You didn't select any  files."));          }  }   这是一个很简单的类,运行结果想必大家也都非常清楚:就是一个主窗口,上面有一个 菜单栏,一个 File 菜单,里面有两个菜单项: 第 110 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理      之所以把运行图贴出来,是为了大家能够看清,在代码中的&符号实 际在界面中显示成 为一条下划线,标记出这个菜单或者菜单项的快捷键。按照代码,当我们点击了 Open 时,会 弹出一个打开文件的对话框: 第 111 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理      这里的 slot 里面的代码在前文中已经详细介绍过。也许你会问,为什么要 用这种麻烦 的写法呢?因为我们曾经说过,使用 static 函数实际上是直接调用系统的对话框,而这种 构造函数法是 Qt 自己绘制的。这对我们后面的国际化是 有一定的影响的。   好了,都已经准备好了,下面开始进行国际化。所谓国际化,实际上不仅仅是把界面中 的各种文字翻译成另外的语言,还有一 些工作是要进行书写方式、货币等的转换。比如,阿 拉伯书写时从右向左的,这些在国际化工作中必须完成。但是在这里,我们只进行最简单的 工作,就是把界面的 文字翻译成中文。   首先,我们需要在 pro 文件中增加一行: TRANSLATIONS +=  myapp.ts   myapp.ts 是我们需要创建的翻译文件。这个文件的名字是任意的,不过后缀名需要是 ts 。 然 后 我 们 打 开 命 令 提 示 符 , 进 入 到 工 程 所 在 目 录 , 比 如 我 的 是 E:\My Documents\Workspace\Qt\MyApp,也就是 pro 文件所在的文件夹,然后输入命令 第 112 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 lupdate  MyApp.pro   ,如果你出现的是命令不存在,请注意将 Qt 的 bin 目录添加到环境变量中。此时,如果 更新的数目, 说明 ts 文件创建成功:   最后一行是说,找到 7 个需要翻译的原文字,0 个已经存在。也就是说,这个文件是新建 的。这时你会在工程目录下 找到这个 myapp.ts 文件。也许你会奇怪,为什么这里还会说已存 在的数目呢?因为 Qt 这个工具很智能的能够识别出已经存在的文字和修改或新增的文字, 这样在以后的工作中就不需要一遍遍重复翻译以前的文字了。这也就是为什么这个工具的名 字是“lupdate”的原因,因为它是“update”,而不仅仅 是生成。   如果你有兴趣的话,可以用记事本打开这个 ts 文件,这个文件实际上是一个 XML 文件, 结构很清晰。不过,我们要使用专业的翻译 工具进行翻译。 Qt 提供了一个工具,Qt Linguist,你可以在开始菜单的 Qt 项下面的 Tools 中找到。用它可以打开我们的 ts 文件, 然后进行我们的翻译工作:   完全翻译完成后保存文件,然后在文件菜单下有个“发布”。点击这个按钮,工程目录 下会有一个 myapp.qm 文件,这就是我们翻译得到的文件。Qt 的 qm 文件实际上是二进制格式 第 113 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 的,因此它经过了高度的优化,体积很小。   下面我们 要修改 main()函数,使之加载这个 qm 文件: int main(int argc, char *argv[])  {          QApplication a(argc, argv);          QTranslator  qtTranslator;          qtTranslator.load("myapp.qm");           a.installTranslator(&qtTranslator);          MainWindow w;           w.resize(800, 600);          w.show();          return  a.exec();  }     注 意 , QTranslator 类 实 际 是 在 QtCore 下 面 的 。 代 码 还 是 很 清 晰 : 创 建 一 个 QTranslator 对象,然后加载 qm 文件,然后将这个对象安装到 QApplication 类。好了,现在 大功告成,重新编译后运行下程序吧!    咦?怎么还是英文的?哪里有错误了呢?这里往往令人疑惑,其实,这是由于我们使 用 load()函数加载 qm 文件时使用的是相对路径,这样直接 load(“myapp.qm”),其实会在 当前编译后的 exe 所在目录下寻找这个 qm 文件,所以,只要我们把 qm 文件同 exe 放在同一 目录下,再次运 行: 第 114 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 现在,这个界面已经是中文了吧!其实,这一小细节已经说明,qm 文件其实 是动态加 载到 exe 文件中的,而不是直接编译进去的。这一点为我们进行动态切换语言提供了基础。 第 115 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 Qt 学习之路(34): 国际化(下)   上次说了国际化的过程,现在来看一下具体的国际化的相关代码。   在代码中,我们使用 tr()将需要翻译的字符串标记出 来。lupdate 工具就是提取出 tr() 函数中的相关字符串。tr()函数是 QObject 类的一个 static 函数,其签名如下:    static QString tr(const char *sourceText, const char *comment = 0, int n  = -1);   虽然我们只传了一个参数,但是实际上 tr()函数是接受 3 个参数的。第一个参数是我们 需要翻译的文字,如果使用 qm 文件 有对应的字符串,则使用对应的字符串进行替换,否则 将 显 示 sourceText 参 数 指 定 的 字 符 串 。 第 二 个 参 数 是 一 个 注 释 , 用 于 解 释 前 面 的 sourceText 的含义,比如 table 一词既可以当做桌子翻译,又可以当成表格翻译,这时你 就需要提供这个注释。或许你会问,使用翻译工具的时候不是有源代码吗?问题是,有可能 人家 不使用这个翻译工具,而使用别的工具,这样就不能保证会有这个源代码的预览;并 且,你的程序不一定必须要发布源代码的;翻译人员往往只得到我们导出的 ts 文件,如果 你加上注释,就可以方便翻译人员进行翻译。最后一个参数 n 用于指定字符串是否为复数。我 们知道,很多语言,比如英语,很多名词的单复数形式是不 相同的,为了解决这个问题, Qt 在 tr()函数中提供了一个参数 n。请看如下代码: int n =  messages.count();  showMessage(tr("%n message(s) saved", "", n));    对于 n 的值的不同,Qt 会翻译成不同的文字,例如:  n  翻译 结果   0  0 message saved   1   1 message saved  2  2 messages saved   5  5 messages saved   tr()函数是 QObject 的函数,如果你的类不是继承自 QObject,就不能直接使用 tr()函 数。比如我们在 main()函数中希望增加一句设置 MainWindow 的 title 的代码:   w.setWindowTitle(tr("MyApp"));   直接这样写是无法通过编译的,因 为 main()函数是全局函数,所以这个 tr()是找不到 第 116 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 的。解决办法一是显式地调用 QObject 的函数:    w.setWindowTitle(QObject::tr("MyApp"));   或者,你可以使用 QCoreApplication 的 translate()函数。你一定还记得,我们的 main() 函 数 的 第 一 句 总 是 QApplication app; , 其 实 , QApplication 就 是 QCoreApplication 的子类。所以,我们也能这样去写:    w.setWindowTitle(app.translate("MyApp"));   由于在 Qt 程序 中,QCoreApplication 是一个单例类,因此,Qt 提供了一个宏 qApp, 用于很方便的访问 QCoreApplication 的这个单例。所以,在其他文件中,我们也可以直接 调用 qApp.translate()来替换 tr(),不过这并没有必 要。   如果你的翻译文本中包含了需要动态显示的数据,比如我们上次代码中的     QMessageBox::information(NULL,   tr("Path"),   tr("You   %1").arg(path)); selectedn   这句你当然可以写成    QMessageBox::information(NULL, tr("Path"), "You selectedn" + path);    但这种连接字符串的方式就 不能够   使用 tr()函数了!因此,如果你需要像 C 语言的 printf()函数这种能够格式化输出 并 且需要翻译时,你必须使用我们例子中的%1 加 arg()函数!     如 果 你 想 要 翻 译 函 数 外 部 的 字 符 串 , 你 需 要 使 用 两 个 宏 QT_TR_NOOP() 和 QT_TRANSLATE_NOOP()。前者是用来翻译一个字符串,后者可以翻译多个字符串。它们的使用 方法如下: QString  FriendlyConversation::greeting(int type)   {           static  const char *greeting_strings[] = {                    QT_TR_NOOP("Hello"),                   QT_TR_NOOP("Goodbye")            };  第 117 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理          return tr(greeting_strings[type]);   }   static  const char *greeting_strings[] = {            QT_TRANSLATE_NOOP("FriendlyConversation", "Hello"),            QT_TRANSLATE_NOOP("FriendlyConversation", "Goodbye")   };      QString FriendlyConversation::greeting(int type)   {            return tr(greeting_strings[type]);   }     QString  global_greeting(int type)   {           return  qApp->translate("FriendlyConversation",    greeting_strings[type]);   }    好了,以上就是我们用到的大部分函数和宏。除此之外,如果我们运行前面的例子就会 发现,实际上我们只是翻译了菜单等内容,打开文件对话框并没有被翻译。 原因是我们没有 给出国际化的信息。那么,怎么才能让 Qt 翻译这些内建的文字呢?我们要在 main()函数中添 加几句: int  main(int argc, char *argv[])  {          QApplication a(argc,  argv);          QTranslator qtTranslator;           qtTranslator.load("myapp.qm");  第 118 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理          a.installTranslator(&qtTranslator);          QTranslator  qtTranslator2;          qtTranslator2.load("qt_zh_CN.qm");           a.installTranslator(&qtTranslator2);          MainWindow w;           w.resize(800, 600);          w.show();          return  a.exec();  }   我们又增加了一个 QTranslator 对象。Qt 实际上是提供了内置字符串的翻译 qm 文件的。 我们需要在 Qt 安装目录下的 translations 文件夹下找到 qt_zh_CN.qm,然后同前面一样, 将它复制到 exe 所在目录。现在再运行一下程序:哈哈已经完全变成中文了 吧! 至此,我们的 Qt 程序的国际化翻译部分就结束啦! Qt 学习之路(35): Qt 容器类之顺序存储容器   容器 Containers,有时候也被称为集合 collections,指的是能够在内存中存储其他特 定类型的对象的对象,这种对象一般 是通用的模板类。C++提供了一套完整的解决方案,成 为标准模板库 Standard Template Library,也就是我们常说的 STL。 第 119 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理    Qt 提供了它自己的一套容器类,这就是说,在 Qt 的应用程序中,我们可以使用标准 C++的 STL,也可以使用 Qt 的容器类。Qt 容器类的好处在于,它提 供了平台无关的行为,以 及   隐式数据共享   技术。所谓平台无关,即 Qt 容器类不因编译器的不同而具有不同的实现;所谓“隐式数 据共享”,也可以称 作“写时复制 copy on write”,这种技术允许在容器类中使用传值 参数,而不会发生额外的性能损失。Qt 容器类提供了类似 Java 的遍历器语法,同样也提供了 类似 STL 的遍 历器语法,以方便用户选择自己习惯的编码方式。最后一点,在一些嵌入式平 台,STL 往往是不可用的,这时你就只能使用 Qt 提供的容器类,除非你想自己创 建。   今天我们要说的是“顺序储存容器”。所谓顺序存储,就是它存储数据的方式是一个接 一个的,线性的。   第一个顺序存 储容器是 QVector,即向量。QVector是一个类似数组的容器,它 将数据存储在连续内存区域。同 C++数组 不同之处在于,QVector知道它自己的长度,并 且可以改变大小。对于获取随机位置的数据,或者是在末尾处添加数 据,QVector的效率 都是很高的,但是,在中间位置插入数据或者删除数据,它的效率并不是很高。在内存中 QVector的存储类似下图(出自 C++ GUI Programming with Qt4, 2nd Edition):   同 STL 的 vector类类似,QVector也提供了[]的重载,我们可以使用[]赋值: QVector  v(2);  v[0] = 1.1;  v[1] = 1.2;   如果实现不知道 vector 的长度,可 以创建一个空参数的 vector,然后使用 append() 函数添加数据: QVector v;   v.append(1.1);  v.append(1.2); 第 120 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理   在 QVector类 中,<<也被重载,因此,我们也可以直接使用<<操作符: QVector  v;  v << 1.1 << 1.2;   注意,如果 QVector中的数据没有被显式地赋值,那么,数据项将使用加入类的默 认构造函数进行初始化,如果是基本数据类型和指针,则初始化为 0.   QLinekdList是另外一种顺序存储容器。在数据结构中,这是一个链表,使用指针连 接起所有数据。它的内 存分布如下(出自 C++ GUI Programming with Qt4, 2nd Edition):   正如数据结 构中所描述的那样,QLinkedList的优点是数据的插入和删除很快,但 是随机位置值的访问会很慢。与 QVector不同,QLinkedList并没有提供重载的[]操作 符,你只能使用 append()函数,或 者<<操作符进行数据的添加,或者你也可以使用遍历器, 这个我们将在后面内容中详细描述。   QList是一个同时拥有 QVector和 QLinkedList的大多数有点 的顺序存储容器 类。它像 QVector一样支持快速的随机访问,重载了[]操作符,提供了索引访问的方式; 它像 QLinkedList一样,支持快速的添加、删除操作。除非我们需要进行在很大的集合的 中间位置的添加、删除操作,或者是需要所有元 素在内存中必须连续存储,否则我们应该一 直使用 Qlist。   QList有几个特殊的情况。一个是 QStringList,这是 QList的子类,提供 针对 QString 的很多特殊操作。QStack和 QQueue分别实现了数据结构中的堆栈和队列, 前者具有 push(), pop(), top()函数,后者具有 enqueue(), dequeue(), head()函数。具体 情况请查阅 API 文档。   另外需要指出的 一点是,我们所说的模板类中的占位符 T,可以使基本数据类型,比 如 int,double 等,也可以指针类型,可以是类类型。如果是类类型的话,必须提供默 认构 造 函 数 , 拷 贝 构 造 函 数 和 赋 值 操 作 符 。 Qt 的 内 置 类 中 的 QByteArray,QDateTime,QRegExp,QString 和 QVariant 是满足这些条件的。但是,QObject 的子类并不符合这 些条件,因为它们通常缺少拷贝构造函数和赋值操作符。不过这并不是一 第 121 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 个问题,因为我们可以存储 QObject 的指针,而不是直接存储值。T 也可以是一个容 器,例 如: QList > list;    注意,在最后两个>之间有一个空格,这是为了防止编译器把它解析成>>操作符。这个 空格是必不可少的,切记切记!   下 面我们来看一个类(出自 C++ GUI Programming with Qt4, 2nd Edition): class  Movie  {  public:          Movie(const QString &title =  "", int duration = 0);            void setTitle(const QString  &title) { myTitle = title; }          QString title() const {  return myTitle; }          void setDuration(int duration) {  myDuration = duration; }          QString duration() const { return  myDuration; }    private:          QString myTitle;           int myDuration;  }; 我们能不能把这个类放进 Qt 容器类呢?答案是肯定的。下面我们来对照着 前面所说的 要求:第一,虽然这个类的构造函数有两个参数,但是这两个参数都有默认值,因此,像 Movie()这种写法是允许的,所以,它有默认构造函数; 第二,这个类表面上看上去没有拷 贝构造函数和赋值操作符,但是 C++编译器会为我们提供一个默认的实现,因此这个条件也 是满足的。对于这个类而言,默认拷 贝构造函数已经足够,无需我们自己定义。所以,我们 可以放心的把这个类放进 Qt 的容器类。 第 122 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 第 123 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 Qt 学习之路(36): Qt 容器类之遍历器和隐式数据共享   对于每一个容器类,都有与之相对应的遍历器:只读遍历器和读写遍历器。只读遍历器 有 QVectorIterator,QLinkedListIterator和 QListIterator三种;读写遍历器 同 样 也 有 三 种 , 只 不 过 名 字 中 具 有 一 个 Mutable , 即 QMutableVectorIterator , QMutableLinkedListIterator 和 QMutableListIterator。这里我们只讨论 QList 的遍历器,其余遍历器具有几乎相同的 API。    Java 风格的遍历器的位置如下图所示 (出自 C++ GUI Programming with Qt4, 2nd Edition):   可 以看出,Java 风格的遍历器,遍历器不指向任何元素,而是指向第一个元素之前、 两个元素之间或者是最后一个元素之后的位置。使用 Java 风格的遍历器进 行遍历的典型代 码是: QList list; // ... QListIterator  i(list); while (i.hasNext()) {         doSomethingWith(i.next()); }    这个遍历器默认指向第一个元素,使用 hasNext()和 next()函数从前向后遍历。你也可 以使用 toBack()函数让遍历器指向最后一个元素的 后面的位置,然后使用 hasPrevious() 和 previous()函数进行遍历。   这是只读遍历器,而读写遍历器则可以在遍历的时 候进行增删改的操作,例如: QMutableListIterator i(list); while  (i.hasNext()) { 第 124 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理         if (i.next() < 0.0)                  i.remove(); }   当然,读写遍历器也是可以从后向前遍历的,具体 API 和前面的几乎相同,这里就不再 赘述。   对应于 Java 风格的遍历 器,每一个顺序容器类 C都有两个 STL 风格的遍历器: C::iterator 和 C::const_iterator。正如名字所暗示的那样,const_iterator 不允 许我们对遍历的数据进行修改。 begin()函数返回指向第一个元素的 STL 风格的遍历器,例 如 list[0],而 end()函数则会返回指向最后一个之后的元素的 STL 风格的遍历 器,例如如 果一个 list 长度为 5,则这个遍历器指向 list[5]。下图所示 STL 风格遍历器的合法位置:   如 果容器是空的,begin()和 end()是相同的。这也是用于检测容器是否为空的方法之 一,不过调用 isEmpty()函数会更加方便。    STL 风格遍历器的语法类似于使用指针对数组的操作。我们可以使用++和--运算符使遍 历 器 移 动 到 下 一 位 置 , 遍 历 器 的 返 回 值 是 指 向 这 个 元 素 的 指 针 。 例 如 QVector 的 iterator 返回值是 T * 类型,而 const_iterator 返回值是 const T * 类型。    一个典型的使用 STL 风格遍历器的代码是: QList::iterator i =  list.begin(); while (i != list.end()) {         *i = qAbs(*i);          ++i; }   对于某些返回容器的函数而言,如果需要使用 STL 风格的遍历器,我们需要建立一个返 回 值的拷贝,然后再使用遍历器进行遍历。如下面的代码所示: QList list = splitter->sizes(); 第 125 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 QList::const_iterator  i = list.begin(); while (i != list.end()) {          doSomething(*i);         ++i; }   而如果你直接使用返回值,就像下面的代 码: // WRONG QList::const_iterator i =  splitter->sizes().begin(); while (i != splitter->sizes().end())  {         doSomething(*i);         ++i; }   这种写法一 般不是你所期望的。因为 sizes()函数会返回一个临时对象,当函数返回时, 这个临时对象就要被销毁,因此调用临时对象的 begin()函数是相当不明 智的做法。并且这 种写法也会有性能问题,因为 Qt 每次循环都要重建临时对象。因此请注意,如果要使用 STL 风格的遍历器,并且要遍历作为返回值的容器,就 要先创建返回值的拷贝,然后进行遍历。   在使用 Java 风格的只读遍历器时,我们不需要这么做,因此系统会自动为我们创建这 个拷贝,所 以,我们只需很简单的按下面的代码书写: QListIterator  i(splitter->sizes()); while (i.hasNext()) {          doSomething(i.next()); }   这里我们提出要建立容器的拷贝,似乎是一项很昂贵的操作。其实 并不然。还记得我们 上节说过一个隐式数据共享吗?Qt 就是使用这个技术,让拷贝一个 Qt 容器类和拷贝一个指 针那么快速。如果我们只进行读操作,数据是不会 被复制的,只有当这些需要复制的数据需 要进行写操作,这些数据才会被真正的复制,而这一切都是自动进行的,也正因为这个原因 隐式数据共享有时也被称为 “写时复制”。隐式数据共享不需要我们做任何额外的操作,它 是自动进行的。隐式数据共享让我们有一种可以很方便的进行值返回的编程风格: 第 126 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 QVector sineTable()     {                      QVector vect(360);                     for (int i  = 0; i < 360; ++i)                                      vect[i] = std::sin(i / (2 * M_PI));                     return vect;     } //  call QVector v = sineTable();   Java 中我们经常 这么写,这样子也很自然:在函数中创建一个对象,操作完毕后将其 返回。但是在 C++中,很多人都会说,要避免这么写,因为最后一个 return 语句会进行 临时 对象的拷贝工作。如果这个对象很大,这个操作会很昂贵。所以,资深的 C++高手们都会有一 个 STL 风格的写法: void  sineTable(std::vector &vect)     {                      vect.resize(360);                     for (int i = 0; i <  360; ++i)                        vect[i] = std::sin(i /  (2 * M_PI)); } // call QVector v; sineTable(v);    这种写法通过传入一个引用避免了拷贝工作。但是这种写法就不那么自然了。而隐式数 据共享的使用让我们能够放心的按照第一种写法书写,而不必担心性能问 题。   Qt 所有容器类以及其他一些类都使用了隐式数据共享技术,这些类包括 QByteArray, QBrush, QFont, QImage, QPixmap 和 QString。这使得这些类在参数和返回值中使用传值方 第 127 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 式相当高效。   不过,为了正确使用隐式数据共 享,我们需要建立一个良好的编程习惯。这其中之一就 是,对 list 或者 vector 使用 at()函数而不是[]操作符进行只读访问。原因是[]操作符既可 以是左值又可以是右值,这让 Qt 容器很难判断到底是左值还是右值,而 at()函数是不能作 为左值的,因此可以进行隐式数据共享。另外一点是,对于 begin(),end()以及其他一些非 const 容 器 , 在 数 据 改 变 时 Qt 会 进 行 深 复 制 。 为 了 避 免 这 一 点 , 要 尽 可 能 使 用 const_iterator, constBegin()和 constEnd().   最后,Qt 提供了一种不使用遍历器进行遍历的方法:foreach 循环。这实际上 是一个宏, 使用代码如下所示: QLinkedList list; Movie  movie; ... foreach (movie, list) {         if (movie.title() ==  "Citizen Kane") {                                 std::cout   <<   "Found   Citizen   Kane"   << std::endl;                 break;         } }    很多语言,特别是动态语言,以及 Java 1.5 之后,都有 foreach 的支持。Qt 中使用宏 实现了 foreach 循环,有两个参数,第一个是单个的对象,成为遍历对象,相当于指向容器 元素类型 的一个指针,第二个是一个容器类。它的意思很明确:每次取出容器中的一个元素, 赋值给前面的遍历元素进行操作。需要注意的是,在循环外面定义遍历元素,对 于定义中具 有逗号的类而言,如 QPair,是唯一的选择。 第 128 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 Qt 学习之路(37): Qt 容器类之关联存储容器   首先,我们看看数组的概念。数组可以看成是一种 形式的键-值对,它的 Key 只能是 int,而值的类 型是 Object,也就是任意类型(注意,这里我们只是说数组可以 是任意类型,这个 Object 并不必须是一个对象)。现在我们扩展数组的概念,把 Key 也做 成 任意类型的,而不仅仅是 int,这样就是一个关联容器了。如果学过数据结构,典型的关联 容器就是散列(Hash Map,哈希表)。Qt 提供两种关联容器类型:QMap和 QHash。    QMap是一种键-值对的数据结构,它实际上使用跳表 skip-list 实现,按照 K 进 行升序的方式进行存储。使用 QMap的 insert()函数可以向 QMap中插入数据,典 型的代码如下: QMap map; map.insert("eins", 1); map.insert("sieben", 7); map.insert("dreiundzwanzig",  23);   同样,QMap也重载了[]运算符,你可以按照数组的复制方式进行使用: map["eins"]  = 1; map["sieben"] = 7; map["dreiundzwanzig"] = 23;    []操作符同样也可以像数组一样取值。但是请注意,如果在一个非 const 的 map 中,使 用[]操作符取一个不存在的 Key 的值,则这个 Key 会被自动创建,并将其关联的 value 赋予 一个空值。如果要避免这种情况,请使用 QMap的 value()函数: int  val = map.value("dreiundzwanzig");   如果 key 不存在,基本类型和指针会返回 0, 对象类型则会调用默认构造函数,返回一 个对象,与[]操作符不同的是,value()函数不会创建一个新的键-值对。如果你希望让不存 在的键返回一个默认 值,可以传给 value()函数第二个参数: 第 129 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 int seconds = map.value("delay", 30);   这行代码等价 于: int seconds = 30; if (map.contains("delay"))          seconds = map.value("delay");   QMap中的 K 和 T 可以是基本数据类型,如 int,double,可以是指针,或者是拥 有默认构造函数、拷贝构造函数和赋值运算符的类。并且 K 必须要重 载<运算符,因为 QMap需要按 K 升序进行排序。   QMap提供了 keys()和 values()函数,可以获得键的集合和值的集合。这两个集合 都是使用 QList 作为返回值的。    Map 是单值类型的,也就是说,如果一个新的值分配给一个已存在的键,则旧值会被 覆盖。如果你需要让一个 key 可以索引多个值,可以使用 QMultiMap。这个类允许一个 key 索引多个 value,如: QMultiMap multiMap; multiMap.insert(1, "one"); multiMap.insert(1,  "eins"); multiMap.insert(1, "uno"); QList vals =  multiMap.values(1);   QHash是使用散列存储的键-值对。它的接口同 QMap几乎一样,但是它们两 个的实现需求不同。QHash的查找速度比 QMap快很多,并且它的存储是不排序 的。对于 QHash而言,K 的类型必须重载了==操作符,并且必须被全局函数 qHash()所 支持,这个函数用于返回 key 的散列值。Qt 已经为 int、指针、 QChar、QString 和 QByteArray 实现了 qHash()函数。   QHash会自动地为散列分配一个初始大小,并且在插入数据或者删除数据的时候 改变散列的大小。我们可以使用 reserve()函数扩大散列,使用 squeeze()函数将散列缩小 到最小大小(这个最小大小实际上是能够存储这些数据的最小空间)。在使用时,我们可以使 用 reserve()函数将数据 项扩大到我们所期望的最大值,然后插入数据,完成之后使用 第 130 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 squeeze()函数收缩空间。   QHash同样也是单值类型的,但是你可以使用 insertMulti()函数,或者是使用 QMultiHash 类 来 为 一 个 键 插 入 多 个 值 。 另 外 , 除 了 QHash , Qt 也 提 供 了 QCache来提供缓存,QSet用于仅存储 key 的情况。这两个类同 QHash一样具 有 K 的类型限制。    遍历关联存储容器的最简单的办法是使用 Java 风格的遍历器。因为 Java 风格的遍历器 的 next()和 previous()函数可以返回一个键-值 对,而不仅仅是值,例如: QMap map; ... int sum  = 0; QMapIterator i(map); while (i.hasNext())          sum += i.next().value();   如果我们并不需要访问键-值对,可以直接忽略 next()和 previous()函数的返回值, 而是调用 key()和 value()函数即可,如: QMapIterator i(map); while (i.hasNext()) {         i.next();          if (i.value() > largestValue) {                 largestKey =  i.key();                 largestValue = i.value();         } }    Mutable 遍历器则可以修改 key 对应的值: QMutableMapIterator i(map); while (i.hasNext()) {         i.next();          if (i.value() < 0.0) 第 131 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理                 i.setValue(-i.value()); }    如果是 STL 风格的遍历器,则可以使用它的 key()和 value()函数。而对于 foreach 循 环,我们就需要分别对 key 和 value 进行循环 了: QMultiMap map; ... foreach  (QString key, map.keys()) {         foreach (int value,  map.values(key)) {                 doSomething(key, value);          } } 第 132 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 Qt 学习之路(38): model-view 架构   我们的系统有很多数据显示的需求,比如从数据库中把数据取出,然后以自己的方式显 示在我们自己的应用程序的界面中。进行这一操作的典型方 式是使用 Qt 的 Item View 类。   在早期的 Qt 版本中,要实现这个功能,我们需要定义一个 widget,然后在这个 widget 中 保存一个数据对象,比如是个 list,然后我们对这个 list 进行查找、插入等的操作,或 者把修改的地方写回这个 list,然后刷新 widget 进行显 示。这个思路很简单,也很清晰, 但是对于大型程序,这种设计就显得苍白无力。比如,在一个大型系统中,你的数据可能很 大,如果全部存入一个 widget 的 数据对象中,效率会很低,并且这样的设计也很难在 widgets 之间共享变量,也就是说,如果你要几个组件共享一个数据对象,要么你就要用 getter 函 数公开这个数据对象,要么你就必须把这个数据对象放进不同的组件分别进行维 护。   Smalltalk 语言发明了一种崭新的实现,用来解决 这个问题,这就是著名的 MVC 模型。 对这个模型无需多言,简单来说,这是一个 model-view-controller 模型,即模型-视图-控 制器。在 MVC 中,模型负责获取需要显示的数据,并且能够存储这些数据的修改。每种数据 类型都有它自己对应的模型,但是这些模型提供一个相同的 API,用于隐藏内 部实现。视图 用于将模型数据显示给用户。对于很大的数据,或许只显示一小部分,这样就能很好的提高 性能。控制器是模型和视图之间的媒介,将用户的动作解析 成对数据的操作,比如查找数据 或者修改数据,然后转发给模型执行,最后再将模型中需要被显示的数据直接转发给视图进 行显示。   对于 Qt 而 言,它使用的是一个类似于 MVC 模型的 model-view 架构。其中,model 就相 当 于 MVC 架 构 中 的 model , 而 对 于 控 制 器 部 分 , Qt 使 用 的 是 另 外 的 一 种 抽 象 , 代 理 delegate。代理被用来提供对 item 渲染和编辑的控制。对于每种视 图,Qt 都提供了一个默认 的代理,对于大多数应用来说,我们只需要使用这个默认的代理即可。这其中的类关系如下 图所示(出自 C++ GUI Programming with Qt 4, 2nd Edition) 第 133 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理   使用 Qt 的 model-view 架构,我们可以让 model 是取回 view 所要展示的数据,这样就 可以在不降低性能的情形下处理大量数据。并且你可以把一个 model 注册给多个 view,让这 些 view 能够显示同样的数据,也就是为同一个数据提供不同的显示方式。Qt 会自动地对这些 view 保持同步,自动刷 新所有的 view 以显示最新的数据。这样,我们就可以只对 model 进 行修改,view 会自动更新。   在少量数据的情形下,我们不需要动 用 model 这样重量级的组件。Qt 为了方便起见也提 供了 item view 类,分别是 QListWidget,QTableWidget 和 QTreeWidget,使用这些类可以 直接对 item 进行操作。这种实现很像 Qt 早期版本,组件中包含了相应的 item,例如 QTableWidget 中包含有 QTableWidgetItem 等。但是对于很大的数据,我们则需要 使用 Qt 的 view 类,比如 QListView,QTabelView 和 QTreeView,同时需要提供一个 model,可以是自 定义 model,也可 以是 Qt 预置的 model。例如,如果数据来自数据库,那么你可以使用 QTabelView 和 QSqlTableModel 这两个类。    今天就说这些,下次我们将开始进入对 model-view 架构的具体介绍。 第 134 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 Qt 学习之路(39): QListWidget   第一个要说的是 QListWidget。这个类为我们展示一个 List 列表的视图。下面还是先看代 码:    listwidget.h #ifndef LISTWIDGET_H #define LISTWIDGET_H #include   class ListWidget : public QWidget { public:          ListWidget(); private:         QLabel *label;          QListWidget *list; }; #endif // LISTWIDGET_H    listwidget.cpp #include "listwidget.h" ListWidget::ListWidget() {          label = new QLabel;         label->setFixedWidth(70);          list = new QListWidget;         list->addItem(new  QListWidgetItem(QIcon(":/images/line.PNG"), 第 135 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 tr("Line")));          list->addItem(new QListWidgetItem(QIcon(":/images/rect.PNG"), tr("Rectangle")));         list->addItem(new  QListWidgetItem(QIcon(":/images/oval.PNG"), tr("Oval")));          list->addItem(new  QListWidgetItem(QIcon(":/images/tri.PNG"), tr("Triangle")));         QHBoxLayout *layout = new QHBoxLayout;          layout->addWidget(label);          layout->addWidget(list);         setLayout(layout);                 connect(list,   SIGNAL(currentTextChanged(QString)),   label, SLOT(setText(QString))); }   main.cpp #include   #include "listwidget.h" int main(int argc, char  *argv[]) {         QApplication a(argc, argv);          ListWidget lw;         lw.resize(400, 200);         lw.show();          return a.exec(); }   一共三个文件,但是都比较清晰。我们先建立了一个 ListWidget 类,然后在 main 函数 中将其显示出来。   ListWidget 类中包含一个 QLabel 对象和一个 QListWidget 对象。创建这个 QListWidget 第 136 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 对象很简单,只需要使用 new 运算符创建出来,然后调用 addItem()函数即可将 item 添加 到这个对象中。我们添加的对象是 QListWidgetItem 的指针,它有四 个重载的函数,我们使 用的是其中的一个,它接受两个参数,第一个是 QIcon 引用类型,作为 item 的图标,第二 个是 QString 类型,作为这个 item 后面的文字说明。当然,我们也可以使用 insertItem() 函 数 在 特 定的 位置 动态 的 增 加 item ,具 体使 用 请 查阅 API 文档 。最 后 , 我们 将 这 个 QListWidget 的 currentTextChanged()信号同 QLabel 的 setText()连接起来,这样,在我们 点击 item 的时 候,label 上面的文字就可以改变了。   我们还可以设 置 viewModel 这个参数,来确定使用不同的视图进行显示。比如,我们使 用下面的语句: list->setViewMode(QListView::IconMode);    再来看看程序界面吧! 第 137 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理   出 处:http://devbean.blog.51cto.com/448512/251391 Qt 学习之路(40): QTreeWidget   接着前面的内容,今天要说的是另外一个 item view class,QTreeWidget。顾名思义, 这个类用来展示树型结构。同前面说的 QListWidget 类似,这个类需要同另外一个辅助类 QTreeWidgetItem 一同使用。不过,既然是提供方面的封装类,即便是看上去很复杂的树, 在使用这个类的时候也是显得比较简单的。当不需要使用 复杂的 QTreeView 的特性的时候, 我们可以直接使用 QTreeWidget 代替。   下面来看代码。    treewidget.h #ifndef TREEWIDGET_H #define TREEWIDGET_H #include   class TreeWidget : public QWidget { public:          TreeWidget(); private:         QTreeWidget *tree; }; #endif  // TREEWIDGET_H   treewidget.cpp #include  "treewidget.h" TreeWidget::TreeWidget() {         tree = new  QTreeWidget(this);         tree->setColumnCount(1); 第 138 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理                 QTreeWidgetItem   *root   =   new   QTreeWidgetItem(tree, QStringList(QString("Root")));                 QTreeWidgetItem   *leaf   =   new   QTreeWidgetItem(root, QStringList(QString("Leaf 1")));          root->addChild(leaf);                 QTreeWidgetItem   *leaf2   =   new   QTreeWidgetItem(root, QStringList(QString("Leaf 1")));          leaf2->setCheckState(0, Qt::Checked);          root->addChild(leaf2);         QList  rootList;         rootList << root;          tree->insertTopLevelItems(0, rootList); }     首 先 , 我 们 在 构 造 函 数 里 面 创 建 了 一 个 QTreeWidget 的 实 例 。 然 后 我 们 调 用 setColumnCount() 函 数 设 定 栏 数 。 这 个 函 数 的 效 果 我 们 以 后 再 看 。 然 后 我 们 要 向 QTreeWidget 添加 QTreeWidgetItem。QTreeWidgetItem 有九个重载的构造函数。 我们在这里 只是来看看其中的一个,其余的请自行查阅 API 文档。这个构造函数的签名如下: QTreeWidgetItem::QTreeWidgetItem   (   QTreeWidget   *   parent,   const QStringList & strings, int type =  Type );   这里有 3 个参数,第一个参数用于指定这个 item 属于哪一个树;第二个参数是指定这 个 item 显示的文 字;第三个参数用于指定这个 item 的类型。Type 有两个可行的取值: QTreeWidgetItem::Type 和 QTreeWidgetItem::UserType,由于我们并没有定义用户类型, 所以只使用其默认值即可。这里你会奇怪,第二个参数为什么是一个 QStringList 类型的, 而不是 QString 类型的?我们先不去管它,继续下面的代码。   后面我们又创建了一个 QTreeWidgetItem,注意它的第一个参数不是 QTreeWidget 而是 QTreeWidgetItem 类型的,这就把它的父节点设置为前面 我们定义的 root 了。然后我们使用 了 setCheckState()函数,让它变得可以选择,最后使用 addChild()函数把它添加进来。    最后一步,我们创建了一个 QList 类型,前面的 root 添加进去,然后 insert 到 top 第 139 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 items。这里可以想象到,由于这个树组件可以由多个根组成(严格来说这已经不是树了,不 过姑且还是叫树吧),所以我们传进来的是一个 list。    好了,编译运行一下我们的代码吧!   样子同我们想象的基本一致,只是这个树的头上怎么会有一个 1?还记得我们跳过去的 那个函数吗?下面我们修改一下代码看看: #include  "listwidget.h" TreeWidget::TreeWidget() {         tree = new  QTreeWidget(this);         tree->setColumnCount(2);          QStringList headers;         headers << "Name" <<  "Number";         tree->setHeaderLabels(headers);          QStringList rootTextList;         rootTextList << "Root"  << "0";                 QTreeWidgetItem   *root   =   new   QTreeWidgetItem(tree, rootTextList);         QStringList  leafTextList;         leafTextList << "Leaf 1" << "1";                 QTreeWidgetItem   *leaf   =   new   QTreeWidgetItem(root, 第 140 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 leafTextList);          root->addChild(leaf);         QStringList leaf2TextList;          leaf2TextList << "Leaf 2" << "2";                 QTreeWidgetItem   *leaf2   =   new   QTreeWidgetItem(root, leaf2TextList);          leaf2->setCheckState(0, Qt::Checked);          root->addChild(leaf2);         QList  rootList;         rootList << root;          tree->insertTopLevelItems(0, rootList); }   我们把 columnCount 设为 2,然后传入的 QStringList 对应的有 2 个元素。这样再来运 行一下: 原来这个 columnCount 就是用于在列表中显示树的!这样,你就可以很容易的将树和 列表结合在一起,从而实现类似 Windows 资源管理器的界面。当然,如 果你不需要显示这个 header,可以调用 setHeaderHidden()函数将这个功能隐藏掉。 第 141 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 Qt 学习之路(41): QTableWidget   QTableWidget 用起来也很方便,并不比前面的两个复杂到哪里去。我们运行的结果是这 样子的:   下面是代 码:   tablewidget.h #ifndef TABLEWIDGET_H #define  TABLEWIDGET_H #include  class TableWidget : public  QWidget { public:         TableWidget(); private:          QTableWidget *table; }; #endif // TABLEWIDGET_H    tablewidget.cpp 第 142 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 #include "tablewidget.h" TableWidget::TableWidget() {          table = new QTableWidget(this);          table->setColumnCount(3);         table->setRowCount(5);          QStringList headers;         headers << "Line Number"  << "ID" << "Name" << "Age" << "Sex";          table->setHorizontalHeaderLabels(headers);          table->setItem(0, 0, new QTableWidgetItem(QString("1")));          table->setItem(1, 0, new QTableWidgetItem(QString("2")));          table->setItem(2, 0, new QTableWidgetItem(QString("3")));          table->setItem(3, 0, new QTableWidgetItem(QString("4")));          table->setItem(4, 0, new QTableWidgetItem(QString("5")));          table->setItem(0, 1, new QTableWidgetItem(tr("20100112"))); }    代码看起来很清楚。首先创建了 QTableWidget 对象,然后设置列数和行数。接下来使用 一个 QStringList,把每一列的标题设置了一下。 然后调用 addItem()函数。这个函数前两个 参数分别是行 row 和列 col,然后第三个参数构建一个 QTableWidgetItem 对象,这 样,Qt 就会把这个对象放在第 row 行第 col 列的单元格里面。注意,这里的行和列都是从 0 开始的。 第 143 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 Qt 学习之路(42): QStringListModel   今天开始我们要看看 Qt 的 model-view 类了。正如前面说的那样,之前三节的 item class 类只是 Qt 为了方便我们使用而封装了的一些操作。比起真正的 model-view 类来,那些 类更易于使用,但是功能也会更简单,并且缺少实时性 的支持,比如我们并不方便实现插 入、删除等一些常见操作。而现在我们要说的 model-view 类使用起来可能会复杂一些,但是 功能强大,并且在 model 更新时会自动更新 view,而 model 多是一些数据集合,因此比较 便于操作。   model-view 类中,view 大致有三 种:list、tree 和 table,但是 model 千奇百怪,不 同的业务,甚至同样的业务不同的建模都会有不同的 model。为了方便使用,Qt 提供了 一些 预定义好的 model 供我们使用。QStringListModel 是其中最简单的一种。   顾名思 义,QStringListModel 就是封装了 QStringList 的 model。QStringList 是一种 很常用的数据类型,它实际上是一个字 符串列表。我们可以想象,对于一个 list 来说,如 果提供一个字符串列表形式的数据,就应该能够把这个数据展示出来。因为二者是一致 的: QStringList 是线性的,而 list 也是线性的。所以,QStringListModel 很多时候都会作为 QListView 的 model。     下 面 我 们 来 看 怎 么 使 用 它 们 。 比 起 前 面 的 QListWidget , 这 里 要 使 用 两 个 类 : QStringListModel 和 QListView,并且还有一 些辅助类。不过你可以看到,即便这样复杂的 工作,我们的代码也不会很多的:   mylistview.h #ifndef  MYLISTVIEW_H #define MYLISTVIEW_H #include  class  MyListView : public QWidget {         Q_OBJECT public:          MyListView(); 第 144 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 private:         QStringListModel *model;          QListView *listView; private slots:         void  insertData();         void deleteData();         void showData(); }; #endif  // MYLISTVIEW_H   mylistview.cpp #include "mylistview.h" MyListView::MyListView() {          model = new QStringListModel(this);         QStringList data;          data << "Letter A" << "Letter B" << "Letter C";          model->setStringList(data);         listView = new  QListView(this);         listView->setModel(model);          QHBoxLayout *btnLayout = new QHBoxLayout;                 QPushButton   *insertBtn   =   new   QPushButton(tr("insert"), this);         QPushButton  *delBtn = new QPushButton(tr("Delete"), this);         QPushButton  *showBtn = new QPushButton(tr("Show"), this);          btnLayout->addWidget(insertBtn);          btnLayout->addWidget(delBtn);          btnLayout->addWidget(showBtn);         QVBoxLayout *mainLayout =  new QVBoxLayout(this); 第 145 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理         mainLayout->addWidget(listView);          mainLayout->addLayout(btnLayout);          this->setLayout(mainLayout);                 connect(insertBtn,   SIGNAL(clicked()),   this, SLOT(insertData()));                 connect(delBtn,   SIGNAL(clicked()),   this, SLOT(deleteData()));          connect(showBtn, SIGNAL(clicked()), this, SLOT(showData())); } void  MyListView::insertData() {         bool isOK;                 QString   text   =   QInputDialog::getText(NULL,   "Insert", "Please input new data:",   QLineEdit::Normal, "You are inserting new data.", &isOK);          if(isOK) {                 int row =  listView->currentIndex().row();                  model->insertRows(row, 1);                 QModelIndex index =  model->index(row);                 model->setData(index, text);                  listView->setCurrentIndex(index);                  listView->edit(index);         } } void  MyListView::deleteData() {         if(model->rowCount() > 1)  {                                 model->removeRows(listView- 第 146 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 >currentIndex().row(), 1);         } } void  MyListView::showData() {         QStringList data =  model->stringList();         QString str;          foreach(QString s, data) {                 str += s + "\n";          }         QMessageBox::information(this, "Data", str); }   来看看我们的代码吧。   首先我们创建一个 QStringListModel 的对象。然后创建一个 QStringList 对象,并且把 这个对象设置为 model 的数据。此时,这个 model 已经拥有数据了。然后,我们创建一个 QListView 的对象,并把 model 设置为 它的 model。后面是三个按钮的创建以及信号槽的连 接,这里就不再赘述。   先来运行一下看看结果吧! 第 147 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理   我们只是把 QStringListModel 设置为 QListView 的 model,QListView 就已经可以把 model 里面的数据展示出来了。下面我们看看增、删、改的操作。   先来看增加数据的操作。 这部分是在代码中的 insertData()函数实现的。先把那个函数 拿出来看看: void  MyListView::insertData() {         bool isOK;                 QString   text   =   QInputDialog::getText(NULL,   "Insert", "Please input new data:",   QLineEdit::Normal, "You are inserting new data.", &isOK);          if(isOK) {                 int row =  listView->currentIndex().row();                  model->insertRows(row, 1);                 QModelIndex index =  model->index(row);                 model->setData(index, text);                  listView->setCurrentIndex(index); 第 148 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理                  listView->edit(index);         } }   我们使用 QInputDialog::getText()函数要求用户输入数据。这部分在前面讲过,这里 也不再赘述。如果用户点击了 OK 按钮,首先,我们使用 listView()->currentIndex()函数, 获取 QListView 当前行。注意,这个函数的返回值是一个 QModelIndex 类型。这个类我们以后 再说,只要知道这个类保存了三个重要的数据:行、列以及属于哪一个 model。我们调用其 row() 函 数 获 得 行 , 这 个 返 回 值 是 一 个 int , 也 就 是 第 几 行 。 然 后 model 插 入 一 行 。 insertRows()函数签名如下:     bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex());   这个函数原本是 QAbstractListModel 类的函数,而 QStringListModel 把 它覆盖了。所 以我们会发现它还需要有一个列的参数。我们调用 insertRows(row, 1); ,所谓 1 就是指 1 列(因为我们使用 list 是一维的,列数永远是 1),而前面又把 row 保存成当前行,因此, 这行语句实际上是在当前行插入一行。然后我们 使用 model 的 index()函数获取当前行的 QModelIndex 对象,使用 setData()函数把我们用 QInputDialog 接受的数据插 入。这里其实 是一个冗余的操作,因为用 currentIndex()函数已经获取当前行了。这么写仅仅是为了展示 如何使用这个函数。不过,你知道了 insertRow()函数,就可以很容易的做出插入空白行的 效果了。然后我们把当前行设为新插入的一行,并调用 edit()函数,这个函数使得这一行可 以被编辑。就这样,我们向 model 插入了数据。   然后来看删除数据的操作: void  MyListView::deleteData() {         if(model->rowCount() > 1)  {                  >currentIndex().row(), 1);         } } model->removeRows(listView- 第 149 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理    使用 model 的 removeRows()函数可以轻松的完成这个功能。这个函数同前面所说的 insertRows()很类似,就不再多说了。需要注意 的是,我们用 rowCount()函数判断了一下, 要求最终始终保留 1 行。这是因为如果你把数据全部删除,你就不能再插入数据了,因为那 时侯按照我们所写 的插入逻辑就不对了。所以,前面所说的插入操作实际上还需要再详细考 虑。   最后那个 showData()仅仅为了查看 model 的数据, 没有什么要说的东西。你可以在 insert 或者 remove 完成后查看一下 model 里面的数据是不是真的被修改了。   关于 QStringListModel 就说这么多。你可以看到,我们的几乎所有操作都是针对 model 的,也就是说,我们直接针对的是数据,而 model 侦测到数据发生了变化,会立刻通知 view 刷新。这样,我们就可以把精力集中到对数据的操作上,而不用担心 view 的同步等操作。 这也是 model-view 模型的一个便捷之处。 Qt 学习之路(43): QDirModel   今天我们来看一个很有用的 model:QDirModel。这个 model 允许我们在 view 中显示操作 系统的目录结构。这次让我们先来 看看运行结果:   这个界面很熟悉吧?不过这可不是由 QFileDialog 打开的哦,这是我们自己实现的。而 第 150 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 提供这种实现支持 的,就是 QDirModel 和 QTreeView。我们来看一下代码。   mytreeview.h #ifndef  MYLISTVIEW_H #define MYLISTVIEW_H #include  class  MyTreeView : public QWidget {         Q_OBJECT public:          MyTreeView(); private:         QDirModel *model;          QTreeView *treeView; private slots:         void mkdir();          void rm(); }; #endif // MYLISTVIEW_H   mytreeview.cpp #include  "mylistview.h" MyTreeView::MyTreeView() {         model = new  QDirModel;         model->setReadOnly(false);                 model->setSorting(QDir::DirsFirst   |   QDir::IgnoreCase   | QDir::Name);          treeView = new QTreeView;          treeView->setModel(model); 第 151 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理          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);         QHBoxLayout  *btnLayout = new QHBoxLayout;                 QPushButton   *createBtn   =   new   QPushButton(tr("Create Directory..."));         QPushButton *delBtn =  new QPushButton(tr("Remove"));          btnLayout->addWidget(createBtn);          btnLayout->addWidget(delBtn);         QVBoxLayout *mainLayout =  new QVBoxLayout(this);         mainLayout->addWidget(treeView);          mainLayout->addLayout(btnLayout);          this->setLayout(mainLayout);         connect(createBtn,  SIGNAL(clicked()), this, SLOT(mkdir()));         connect(delBtn,  SIGNAL(clicked()), this, SLOT(rm())); } void MyTreeView::mkdir() {          QModelIndex index = treeView->currentIndex();         if  (!index.isValid()) {                 return;         }          QString dirName = QInputDialog::getText(this, 第 152 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理   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  MyTreeView::rm() {         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, 第 153 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理   tr("Remove"),   tr("Failed to remove  %1").arg(model->fileName(index)));         } }   构造函数中,首先我们创建了 QDirModel 的一个对象,并且设置 ReadOnly 为 false,也 就是说我们可以对其进行修改。而下 一个 setSorting()函数是对其进行排序,排序的依据 也很清楚:文件夹优先(QDir::DirsFirst),忽略大小写 (QDir::IgnoreCase),而且是根据 名字排序(QDir::Name)。更多的规则组合可以参见 API 文档了。   然后 我们创建一个 QTreeView 实例,并且把 model 设置为刚刚的 QDirModel 实例。然后 我们开始设置 QTreeView 的相关属性。首先把 stretchLastSection 设置为 true。如果把这个 属性设置为 true,就是说,当 QTreeView 的宽度大于所有列宽之和时,最后一列 的宽度自 动扩展以充满最后的边界;否则就让最后一列的宽度保持原始大小。第二个 setSortIndicator()函数是设置哪一列进行排序。由于我们 前面设置了 model 是按照名字排 序,所以我们这个传递的第一个参数是 0,也就是第 1 列。setSortIndicatorShown()函数设 置显示列 头上面的排序小箭头。setClickable(true)则允许鼠标点击列头。这样,我们的 QTreeView 就设置完毕了。最后,我们通过 QDir::currentPath()获取当前 exe 文件运行时路 径,并把这个路径当成程序启动时显示的路径。expand()函数即展开这一路 径;scrollTo() 函数是把视图的视口滚动到这个路径的位置;resizeColumnToContents()是要求把列头适应 内 容 的 宽 度 , 也 就 是 不 产 生 ... 符 号 。 这 样 , 我 们 就 通 过 一 系 列 的 参 数 设 置 好 了 QTreeView,让它能够为我们展示目录结构。   至于后面的两个 slot,其实并不能理解。第一个 mkdir()函数就是创建一个文件夹。 void MyTreeView::mkdir() {          QModelIndex index = treeView->currentIndex();         if  (!index.isValid()) {                 return; 第 154 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理         }          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"));                 }         } }   正如它的代码所示,首先获取选择的目录。后面这个 isValid()的判断很重要,因为默 认情况下是没有目录被选择的,此时这个路径是非 法的,为了避免程序出现异常,必须要 有这一步判断。然后会弹出对话框询问新的文件夹名字,如果创建失败会有提示,否则就是 创建成功。这时候你就可以到硬盘 上的实际位置看看啦!   删除目录的代码也很类似: void MyTreeView::rm() {          QModelIndex index = treeView->currentIndex();         if  (!index.isValid()) {                 return;         }          bool ok; 第 155 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理         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)));         } }    同样需要实现检测路径是否合法。另外需要注意的是,目录和文件的删除不是一个函数, 需要调用 isDir()函数检测。这一步在代码中有很清楚的描述,这里 就不再赘述了。 注 意 , QDirModel 在 最 新 版 Qt 中 已 经 不 建 议 使 用 了 。 文 档 中 说 使 用 QFileSystemModel 代替。由于这两者的函数几乎一样,所以就没有对代码进行修改。与 QDirModel 不同的是,QFileSystemModel 会启动自己的线程进行文件 夹的扫描,因此不会 发 生 因 扫 描 文 件 夹 而 导 致 的 主 线 程 阻 塞 的 现 象 。 另 外 , 无 论 QDirModel 还 是 QFileSystemModel 都 会 对 model 结 果 进 行 缓 存 , 如 果 你 要 立 即 刷 新 结 果 , 前 者 提 供 了 refresh()函数,而后 者会通知 QFileSystemWatcher 类 第 156 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 Qt 学习之路(44): QSortFilterProxyModel   t 为我们预定义了很多 model,前面已经说过了 QStringListModel、QDirModel(也算是 Qt 推荐使用的 QFileSystemModel 吧,这个在上一章最后重新加上了一段话,没有注意的朋 友去看看哦)。今天我们要说的这个 QSortFilterProxyModel 并不能单独使用,看它的名字 就会知道,它只是一个“代理”,真正的数据需要另外的一个 model 提供,并且它 是用来 排序和过滤的。所谓过滤,也就是说按照你输入的内容进行数据的筛选,很像 Excel 里面的 过滤器。不过 Qt 提供的过滤功能是基于正则表达式的,因而 功能强大。   我们从代码开始看起:   sortview.h #ifndef  SORTVIEW_H #define SORTVIEW_H #include  class  SortView : public QWidget {         Q_OBJECT 第 157 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 public:          SortView(); private:         QListView *view;          QStringListModel *model;         QSortFilterProxyModel *modelProxy;          QComboBox *syntaxBox; private slots:         void  filterChanged(QString text); }; #endif // SORTVIEW_H    sortview.cpp #include "sortview.h" SortView::SortView() {          model = new QStringListModel(QColor::colorNames(), this);          modelProxy = new QSortFilterProxyModel(this);          modelProxy->setSourceModel(model);          modelProxy->setFilterKeyColumn(0);         view = new  QListView(this);         view->setModel(modelProxy);          QLineEdit *filterInput = new QLineEdit;         QLabel *filterLabel =  new QLabel(tr("Filter"));         QHBoxLayout *filterLayout = new  QHBoxLayout;         filterLayout->addWidget(filterLabel);          filterLayout->addWidget(filterInput);         syntaxBox = new  QComboBox;                 syntaxBox->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); 第 158 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理                 syntaxBox->addItem(tr("Regular   expression"), QRegExp::RegExp);          syntaxBox->addItem(tr("Wildcard"), QRegExp::Wildcard);          syntaxBox->addItem(tr("Fixed string"), QRegExp::FixedString);          QLabel *syntaxLabel = new QLabel(tr("Syntax"));          QHBoxLayout *syntaxLayout = new QHBoxLayout;          syntaxLayout->addWidget(syntaxLabel);          syntaxLayout->addWidget(syntaxBox);         QVBoxLayout *layout =  new QVBoxLayout(this);         layout->addWidget(view);          layout->addLayout(filterLayout);          layout->addLayout(syntaxLayout);                 connect(filterInput,   SIGNAL(textChanged(QString)),   this, SLOT(filterChanged(QString))); } void  SortView::filterChanged(QString text) {          QRegExp::PatternSyntax syntax = QRegExp::PatternSyntax(                           syntaxBox->itemData(syntaxBox>currentIndex()).toInt());          QRegExp regExp(text, Qt::CaseInsensitive, syntax);          modelProxy->setFilterRegExp(regExp); }   至于 main()函数的内容,由于和前面的代码几乎是一样的,这里就不再贴出来了。我们 使用的是 QColor::colorNames()函数提供的数据。这个函数返回值是一个 QStringList 类型 的变量,可以给出预定义的颜色的名字。我们使 用一个 QStringListModel 包装这个数据, 这和前面的内容没有什么区别。然后创建一个 QSortFilterProxyModel 对象,使用 它的 setSourceModel()函数将前面定义的 QStringListModel 传进去,也就是我们需要对这个 model 进行代理。那么我们需要 过滤哪一列呢?虽然 QStringListModel 只有一列,但是我 第 159 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 们也需要使用 setFilterKeyColumn()函数设置一下,以便让这个 proxy 知道要过滤 的是第 0 列。最后重要的一点是,QListView 的 model 必须设置为 QSortFilterProxyModel,否则是 看不到效果的。    下面的 QLineEdit 提供过滤数据的输入,这个没什么好说的。后面的 QComboBox 列出了 三项: syntaxBox->addItem(tr("Regular  expression"), QRegExp::RegExp); syntaxBox->addItem(tr("Wildcard"),  QRegExp::Wildcard); syntaxBox->addItem(tr("Fixed string"),  QRegExp::FixedString);   这是正则表达式的类型。正则表达式有一套通用的语法,但是对于不同的环 境,正则表 达式的规则可能是不一样的。第一个 QregExp::RegExp 提供了最一般的正则表达式语法,不 过这个语法不支持贪婪限定符。这也是 Qt 默认的规则。如果你需要使用贪婪限定符,需要使 用 QRegExp::RegExp2,根据文档描述,这个将会是 Qt5 的默认规则。第二个是 Unix 下 shell 很常见的一种规则。第三个即固定表达式,也就是说基本上不使用正则表达式的。   我们使用 connect()函数,将 QLineEdit 的 textChanged()信号同 slot 连接起来。其中 我们的 slot 函数如下所示: void  SortView::filterChanged(QString text) {          QRegExp::PatternSyntax syntax = QRegExp::PatternSyntax(                           syntaxBox->itemData(syntaxBox>currentIndex()).toInt());          QRegExp regExp(text, Qt::CaseInsensitive, syntax);          modelProxy->setFilterRegExp(regExp); }   第一步,使用 QComboBox 的选择值创建一个 QRegExp::PatternSyntax 对象,然后利用 这个语法规则构造一个正则表达式,注意我们在 QLineEdit 里面输入的内容是通过参数传 递进来的,然后设置 proxy 的过滤器的表达式。好了,就这样运行一下看看效果吧! 第 160 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理    Qt 学习之路(45): 自定义 model 之一   前面我们说了 Qt 提供的几个预定义 model。但是,面对变化万千的需求,那几个 model 是远远不能满足我们的需要的。另外,对于 Qt 这种框架来说,model 的选择首先要能满足绝 大多数功能的需要,这就是说,可能这个 model 中的某些功能你永远也不会用到,但是还要 带着它,这样做的 后果就是效率不会很高。所以,我们还必须要能够自定义 model。   在我们真正的完成自定义 model 之前,先来看看在 Qt 的 model-view 架构中的几个关键 的概念。一个 model 中的每个数据元素都有一个 model 索引。这个索引指明这个数据位于 model 的位置,比如 行、列等。这就是前面我们曾经说到过的 QModelIndex。每个数据元素还 要有一组属性值,称为角色(roles)。这个属性值并不是数据的内容,而 是它的属性,比如 说,这个数据是用来展示数据的,还是用于显示列头的?因此,这组属性值实际上是 Qt 的 一 个 enum 定 义 的 , 比 较 常 见 的 有 Qt::DisplayRole 和 Qt::EditRole , 另 外 还 有 Qt::ToolTipRole, Qt::StatusTipRole, 和 Qt::WhatsThisRole 等。并且,还有一些属性是 第 161 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 用 来 描 述 基 本 的 展 现 属 性 的 , 比 如 Qt::FontRole, Qt::TextAlignmentRole, Qt::TextColorRole, Qt::BackgroundColorRole 等。    对于 list model 而言,要定位其中的一个数据只需要有一个行号就可以了,这个行号 可以通过 QModelIndex::row()函数进行访问;对于 table model 而言,这种定位需要有两个 值:行号和列号,这两个值可以通过 QModelIndex::row()和 QModelIndex::column()这两 个函数访问到。另外,对于 tree model 而言,用于定位的可以是这个元素的父节点。实际上, 不仅仅是 tree model,并且 list model 和 table model 的元素也都有自己的父节点,只不 过对于 list model 和 table model,它们元素的父节点都是相同的,并且指向一个非法的 QModelIndex。对于所有的 model,这个父节点都可以通过 QModelIndex::parent()函数访问 到。这就是说,每个 model 的项都有自己的角色数据,0 个、1 个或多个子节点。既然每个元素 都有自 己的子元素,那么它们就可以通过递归的算法进行遍历,就像数据结构中树的遍历 一样。关于父节点的描述,请看下面这张图 (出自 C++ GUI Programming with Qt4, 2nd Edition):   下面我们通过一个简单的例子来看看如何实现自定义 model。这个例子来自 C++ GUI Programming with Qt4, 2nd Edition。首先描述一下需求。这里我们要实现的是一个类似于 货币汇率表的 table。或许你会想,这是一个很简单的实现,直接用 QTableWidget 不就可以 了吗?的确,如果直接使用 QTableWidget 确实很方便。但是,试想一个包含了 100 种货币的 汇率表。显然,这是 一个二维表,并且,对于每一种货币,都需要给出相对于其他 100 种货 币的汇率(在这里,我们把自己对自己的汇率也包含在内,只不过这个汇率永远是 1.0000)。 那么,这张表要有 100 x 100 = 10000 个数据项。现在要求我们减少存储空间。于是我们想, 第 162 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 如果我们的数据不是显示的数据,而是这种货币相对于美元的汇率,那么,其他货币的汇率 都可以 根据这个汇率计算出来了。比如说,我存储的是人民币相对美元的汇率,日元相对美 元的汇率,那么人民币相对日元的汇率只要作一下比就可以得到了。我没有必要 存储 10000 个数据项,只要存储 100 个就够了。于是,我们要自己实现一个 model。   CurrencyModel 就是这样一个 model。它底层的数据使用一个 QMap类 型的数据,作为 key 的 QString 是货币名字,作为 value 的 double 是这种货币对美元的汇率。 然后我们来看代码:    .h class CurrencyModel : public QAbstractTableModel { public:          CurrencyModel(QObject *parent = 0);         void  setCurrencyMap(const QMap &map);          int rowCount(const QModelIndex &parent) const;         int  columnCount(const QModelIndex &parent) const;         QVariant  data(const QModelIndex &index, int role) const;                 QVariant   headerData(int   section,   Qt::Orientation orientation, int role) const; private:          QString currencyAt(int offset) const;         QMap currencyMap; };   .cpp CurrencyModel::CurrencyModel(QObject *parent)          : QAbstractTableModel(parent) { } int  CurrencyModel::rowCount(const QModelIndex & parent) const 第 163 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 {          return currencyMap.count(); } int  CurrencyModel::columnCount(const QModelIndex & parent) const {          return currencyMap.count(); } QVariant  CurrencyModel::data(const QModelIndex &index, int role) const {          if (!index.isValid())                 return QVariant();          if (role == Qt::TextAlignmentRole) {                                 return   int(Qt::AlignRight   | Qt::AlignVCenter);         } else if (role ==  Qt::DisplayRole) {                                 QString   rowCurrency   = currencyAt(index.row());                                 QString   columnCurrency   = currencyAt(index.column());                 if  (currencyMap.value(rowCurrency) == 0.0)                          return "####";                                 double   amount   = currencyMap.value(columnCurrency) / currencyMap.value(rowCurrency);                   return QString("%1").arg(amount, 0, 'f', 4);         }          return QVariant(); } QVariant   CurrencyModel::headerData(int   section,   Qt::Orientation 第 164 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 orientation, int role) const {          if (role != Qt::DisplayRole)                 return QVariant();          return currencyAt(section); } void  CurrencyModel::setCurrencyMap(const QMap  &map) {         currencyMap = map;         reset(); } QString  CurrencyModel::currencyAt(int offset) const {         return  (currencyMap.begin() + offset).key(); }   我们选择了继承 QAbstractTableModel。虽然是自定义 model,但各种 model 之间也会有 很多共性。Qt 提供了一系 列的抽象类供我们继承,以便让我们只需要覆盖掉几个函数就可 以轻松地定义出我们自己的 model。Qt 提供了 QAbstractListModel 和 QAbstractTableModel 两类,前者是一维数据 model,后者是二维数据 model。如果你的数据很复杂,那么可以直接 继 承 QAbstractItemModel 。 这 三 个 类 之 间 的 关 系 可 以 表 述 如 下 : ( 出 自 C++ GUI Programming with Qt4, 2nd Edition):   构造函数中没有添加任何代码,只要调用父类的构造函数就可以了。然后我们重写了 rowCount()和 columnCount()这两个函数,用于返回 model 的行数和列数。由于我们使用一 维的 map 记录数据,因此这里的行和列都是 map 的大小。然后我 们看最复杂的 data()函数。 QVariant CurrencyModel::data(const  QModelIndex &index, int role) const 第 165 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 {         if  (!index.isValid())                 return QVariant();            if (role == Qt::TextAlignmentRole) {                                 return   int(Qt::AlignRight   | Qt::AlignVCenter);         } else if (role ==  Qt::DisplayRole) {                                 QString   rowCurrency   = currencyAt(index.row());                                 QString   columnCurrency   = currencyAt(index.column());                 if  (currencyMap.value(rowCurrency) == 0.0)                          return "####";                                 double   amount   = currencyMap.value(columnCurrency) / currencyMap.value(rowCurrency);                   return QString("%1").arg(amount, 0, 'f', 4);         }          return QVariant(); }   data()函数返回单元格的数据。它有两个参数:第一个是 QModelIndex,也就是单元格 的位置;第二个是 role,也就是这个 数据的角色。这个函数的返回值是 QVariant。至此,我 们还是第一次见到这个类型。这个类型相当于是 Java 里面的 Object,它把绝大多数 Qt 提 供 的数据类型都封装起来,起到一个数据类型“擦除”的作用。比如我们的 table 单元格可以 是 string,也可以是 int,也可以是一个颜色值,那么这 么多类型怎么返回呢?于是,Qt 提供了这个 QVariant 类型,你可以把这很多类型都存放进去,到需要使用的时候使用一系 列 的 to 函 数 取 出 来 即 可 。 比 如 你 把 int 包 装 成 一 个 QVariant , 使 用 的 时 候 要 用 QVariant::toInt()重新取出来。这里需要注意的是,QVariant 类型的放入和取出 必须是相 第 166 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 对应的,你放入一个 int 就必须按 int 取出,不能用 toString(), Qt 不会帮你自动转换。或 许你会问,Qt 不是提供了一个 QObject 类型吗?为什么不像 Java 一样都用 Object 呢?关于 这一点豆子也没有官方文 档,不过可以猜测一下。和 Java 不同,C++的面向对象体系不是单 根的,C++对象并不是都继承于某一个类,因此,如果你要实现一个这种功能的类,做到 “类型擦除”,就必须用一个类包含所有的数据类型。就相当于设计一个能放进所有形状的 盒子,你才能把各种各样的形状放进去。这样的话这个类就会变得异常庞 大。对于 Qt,QObject 类是大多数类继承的类,理应越小越好,因此就把这个功能抽取出来,形成了 一个新类。这也只是豆子的猜测,大家不必往心里 去:-)   好了,下面看这个类的内容。首先判断传入的 index 是不是合法,如果不合法直接 return 一个空白的 QVariant。然 后如果 role 是 Qt::TextAlignmentRole,也就是文本的对 象 方 式 , 那 么 就 返 回 int(Qt::AlignRight | Qt::AlignVCenter) ; 否 则 , role 如 果 是 Qt::DisplayRole,就按照我们前面所说的逻辑进行计算,然后按照字符串返回。这 时候你 就会发现,其实我们在 if…else…里面返回的不是一种数据类型,if 里面是 int,而 else 里面是 QString,这就是 QVariant 的 作用了,也正是“类型擦除”的意思。   剩下的三个函数就很简单了:headerData()返回列名或者行 名;setCurrencyMap()用 于设置底层的数据源;currencyAt()返回偏移量为 offset 的键值。   至于调用就很 简单了: CurrencyTable::CurrencyTable() {          QMap data;         data["NOK"] = 1.0000;          data["NZD"] = 0.2254;         data["SEK"] = 1.1991;          data["SGD"] = 0.2592;         data["USD"] = 0.1534;          CurrencyModel *model = new CurrencyModel;          model->setCurrencyMap(data);         QTableView *view = new  QTableView(this); 第 167 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理         view->setModel(model);          view->resize(400, 300); }   好了,最后让我们来看一下最终结果吧! 注意,这一章中的代码不是完整的代码,缺少 view 的头文件,不过这只是一个空白的 文件。你也可以直接把 view 的代码放到 main()函数里面运行。 第 168 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 Qt 学习之路(46): 自定义 model 之二   前面的例子已经比较清楚的给出了自定义 model 的方法,就是要覆盖我们所需要的那几 个函数就可以了。但是,前面的例子仅仅是简单的展示 数据,也就是说数据时只读的。那么, 如何能做到读写数据呢?那就要来看进来的例子了。这个例子也是来自 C++GUI Programming with Qt 4, 2nd Edition 这本书的。   还是先来看代码吧:   citymodel.h class  CityModel : public QAbstractTableModel {         Q_OBJECT 第 169 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理    public:         CityModel(QObject *parent = 0);          void setCities(const QStringList &cityNames);         int  rowCount(const QModelIndex &parent) const;         int  columnCount(const QModelIndex &parent) const;         QVariant  data(const QModelIndex &index, int role) const;                 bool   setData(const   QModelIndex   &index,   const   QVariant &value, int  role);                 QVariant   headerData(int   section,   Qt::Orientation orientation, int role) const;         Qt::ItemFlags flags(const  QModelIndex &index) const;   private:          int offsetOf(int row, int column) const;         QStringList  cities;         QVector distances; };    citymodel.cpp CityModel::CityModel(QObject *parent)          : QAbstractTableModel(parent) { } int  CityModel::rowCount(const QModelIndex & parent) const {          return cities.count(); } int CityModel::columnCount(const  QModelIndex & parent) const {         return cities.count(); 第 170 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 } QVariant  CityModel::data(const QModelIndex &index, int role) const {          if (!index.isValid()) {                 return QVariant();          }         if (role == Qt::TextAlignmentRole) {                                 return   int(Qt::AlignRight   | Qt::AlignVCenter);         } else if  (role == Qt::DisplayRole) {                 if (index.row() ==  index.column()) {                         return 0;                  }                                 int   offset   =   offsetOf(index.row(), index.column());                 return distances[offset];          }         return QVariant(); } QVariant   CityModel::headerData(int   section,   Qt::Orientation   orientation, int  role) const {         if (role == Qt::DisplayRole) {                  return cities[section];         }         return QVariant(); } bool  CityModel::setData(const QModelIndex &index, const QVariant  &value, int role) 第 171 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 {         if (index.isValid() &&  index.row() != index.column() && role == Qt::EditRole) {                                 int   offset   =   offsetOf(index.row(), index.column());                  distances[offset] = value.toInt();                                 QModelIndex   transposedIndex   = createIndex(index.column(), index.row());                  emit dataChanged(index, index);                                 emit   dataChanged(transposedIndex, transposedIndex);                 return  true;         }         return false; } Qt::ItemFlags  CityModel::flags(const QModelIndex &index) const {          Qt::ItemFlags flags = QAbstractItemModel::flags(index);         if  (index.row() != index.column()) {                 flags |=  Qt::ItemIsEditable;         }         return flags; } void  CityModel::setCities(const QStringList &cityNames) {          cities = cityNames;         distances.resize(cities.count() *  (cities.count() - 1) / 2);         distances.fill(0); 第 172 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理          reset(); } int CityModel::offsetOf(int row, int column) const {          if (row < column) {                 qSwap(row, column);          }         return (row * (row - 1) / 2) + column; }   代码很长,但实际上和前面我们的那个例子非常相似。这个 model 也是用于 table 的, 因此还是继承了 QAbstractTableModel。CityModel 内部有两个数据源:一个 QStringList 类 型的对象,一个 QVector<int>类型的对象。前者用于保存城市的名字,需要用户显示的 给出;后者是 model 内部维护的一个存放 int 的向 量。这个 CityModel 就是要在 table 中显 示两个城市之间的距离。同前面的例子一样,如果我们要把所有的数据都保存下来,显然会 造成数据的冗余: 城市 A 到城市 B 的距离同城市 B 到城市 A 的距离是一样的!因此我们还是 自 定 义 一 个 model 。 同 样 这 个 CityModel 有 个 简 单 的 空 构 造 函 数 , rowCount() 和 columnCount()函数也是返回 list 的长度。data()函数根据 role 的不同返回不同的值。由于 在 table 中坐标是由 row 和 column 给出的,因此需要有一个二维坐标到一维坐标的转换, 这就是 offsetOf()函数的作用。我们把主要精力放在 setData()函数上面。 bool CityModel::setData(const QModelIndex  &index, const QVariant &value, int role) {         if  (index.isValid() && index.row() != index.column() && role == Qt::EditRole) {                                 int   offset   =   offsetOf(index.row(), index.column());                  distances[offset] = value.toInt();                                 QModelIndex   transposedIndex   = createIndex(index.column(), index.row()); 第 173 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理                  emit dataChanged(index, index);                                 emit   dataChanged(transposedIndex, transposedIndex);                 return  true;         }         return false; }   这个函数在用户编辑数据时会自动调用。也就是说,这时我们的数据已经不是只读的了。 函数开始是一个长长的判断:index 要是合法 的;index 的 row 和 column 不相等,也就是 说两个城市是不同的;数据想的 role 是 Qt::EditRole。如果满足了这三个条件,才会执行 下面的操作。首先,由 row 和 column 坐标定位到表中的数据项在 vector 中的位置。然后用户 新 修 改 的 数 据 被 作 为 参 数 value 传 入 , 所 以 我 们 要 把 这 个 参 数 赋 值 给 distances。createIndex()函数根据 column 和 row 值生成一个 QModelIndex 对象。请注意这 里的顺 序:row 和 column 是颠倒的!这就把坐标为(row, column)的点关于主对角线对称的 那个点(column, row)的 index 找到了。还记得我们的需求吗?当我们修改了一个数据时,对 应的数据也要被修改,这就是这个功能的实现。我们需要 emit dataChanged()信号,这个信 号接收两个参数:一个是被修改的数据的左上角的坐标,一个是被修改的数据的右下角的坐 标。为什么会有两个坐标呢? 因此我们修改的数据不一定只是一个。像这里,我们只修改了 一个数据,因此这两个值是相同的。数据更新了,我们用这个信号通知 view 刷新,这样就可 以显示 新的数据了。最后,如果函数数据修改成功就返回 true,否则返回 false。   最后,我们在 main()函数中显示出来这个 model: int main(int argc, char *argv[]) {          QApplication a(argc, argv);         QStringList cities;                 cities   <<   "Arvika"   <<   "Boden"   <<   "Eskilstuna"   << "Falun";         CityModel cityModel;          cityModel.setCities(cities); 第 174 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理         QTableView tableView;          tableView.setModel(&cityModel);          tableView.setAlternatingRowColors(true);          tableView.setWindowTitle(QObject::tr("Cities"));          tableView.show();         return a.exec(); }   这样,我们就 把这个 model 做完了。最后来看看效果吧!   出处:http://devbean.blog.51cto.com/448512/267972 Qt 学习之路(47): 自定义 Model 之三     今 天 来 说 的 是 自 定 义 model 中 最 复 杂 的 例 子 。 这 个 例 子 同 样 也 是 出 自 C++ GUI Programming with Qt 4, 2nd Edition 这本书。   这个例子是将布尔表达式分析成一棵树。这个分析过程在离散数学中经常遇到,特别是 复杂的布尔表达式,类 似的分析可以比较方便的进行表达式化简、求值等一系列的计算。同 样,这个技术也可以很方便的分析一个表达式是不是一个正确的布尔表达式。在这个例子中 一 共有四个类:   Node:组成树的节点;   BooleaModel:布尔表达式的 model,实际上是一个 tree model,用于将布尔表达式表 第 175 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 示成一棵树;   BooleanParser:将布尔表达式生成分析树的分析器;    BooleanWindow:输入布尔表达式并进行分析,展现成一棵树。   这个例子可能是目前为止最复杂的一个了,所以先来看看最终的结 果,以便让我们心 中有数:   先来看这张图片,我们输入的布尔表达式是!(a||b)&&c||d, 在下面的 Node 栏中,用树 的形式将这个表达式分析了出来。如果你熟悉编译原理,这个过程很像词法分析的过程:将 一个语句分析称一个一个独立的词素。    我们从最底层的 Node 类开始看起,一步步构造这个程序。   Node.h class Node { public:          enum Type         {                 Root,                  OrExpression,                 AndExpression,                  NotExpression,                 Atom,                 Identifier,                  Operator,                 Punctuator         };          Node(Type type, const QString &str = "");         ~Node(); 第 176 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理          Type type;         QString str;         Node *parent;          QList children; };   Node.cpp Node::Node(Type type, const QString  &str) {         this->type = type;         this->str  = str;         parent = 0; } Node::~Node() {          qDeleteAll(children); }   Node 很像一个典型的树的节点:一个 Node 指针类型的 parent 属性,保存父节点;一 个 QString 类型的 str,保存数据。另外,Node 里面还有一个 Type 属性,指明这个 Node 的类 型,是一个词 素,还是操作符,或者其他什么东西;children 是一个 QList类型 的列表,保存这个 node 的子节点。注意,在 Node 类的析构函数中,使用了 qDeleteAll()这 个全局函数。这个函数是将[start, end)范围内的所有元素进行 delete。因此,它的参数的 元素必须是指针类型的。并且,这个函数使用 delete 之后并不会将指针赋值为 0,所以,如 果要在析构函数之外调用这个函数,建议在调用之后显示的调用 clear()函数,将所有子元 素的指针清为 0.   在构造完子节点之后,我们 开始构造 model:   booleanmodel.h class BooleanModel :  public QAbstractItemModel { 第 177 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 public:          BooleanModel(QObject *parent = 0);         ~BooleanModel();          void setRootNode(Node *node);         QModelIndex index(int row,  int column,   const  QModelIndex &parent) const;         QModelIndex parent(const  QModelIndex &child) const;         int rowCount(const QModelIndex  &parent) const;         int columnCount(const QModelIndex  &parent) const;         QVariant data(const QModelIndex  &index, int role) const;                 QVariant   headerData(int   section,   Qt::Orientation orientation,   int role) const; private:         Node  *nodeFromIndex(const QModelIndex &index) const;         Node  *rootNode; };   booleanmodel.cpp BooleanModel::BooleanModel(QObject  *parent)         : QAbstractItemModel(parent) {          rootNode = 0; } BooleanModel::~BooleanModel() {          delete rootNode; 第 178 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 } void BooleanModel::setRootNode(Node *node) {          delete rootNode;         rootNode = node;         reset(); } QModelIndex  BooleanModel::index(int row, int column,   const QModelIndex &parent)  const {         if (!rootNode || row < 0 || column < 0)                  return QModelIndex();         Node *parentNode =  nodeFromIndex(parent);         Node *childNode =  parentNode->children.value(row);         if (!childNode)                  return QModelIndex();         return createIndex(row,  column, childNode); } Node *BooleanModel::nodeFromIndex(const  QModelIndex &index) const {         if (index.isValid()) {                                 return   static_cast(index.internalPointer());         } else {                  return rootNode;         } } int BooleanModel::rowCount(const  QModelIndex &parent) const 第 179 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 {         if (parent.column() >  0)                 return 0;         Node *parentNode =  nodeFromIndex(parent);         if (!parentNode)                  return 0;         return parentNode->children.count(); } int  BooleanModel::columnCount(const QModelIndex & /* parent */) const {          return 2; } QModelIndex BooleanModel::parent(const  QModelIndex &child) const {         Node *node =  nodeFromIndex(child);         if (!node)                 return  QModelIndex();         Node *parentNode = node->parent;          if (!parentNode)                 return QModelIndex();          Node *grandparentNode = parentNode->parent;         if  (!grandparentNode)                 return QModelIndex();          int row = grandparentNode->children.indexOf(parentNode);          return createIndex(row, 0, parentNode); } QVariant  BooleanModel::data(const QModelIndex &index, int role) const {          if (role != Qt::DisplayRole) 第 180 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理                 return  QVariant();         Node *node = nodeFromIndex(index);         if  (!node)                 return QVariant();         if  (index.column() == 0) {                 switch (node->type) {                  case Node::Root:                          return  tr("Root");                 case Node::OrExpression:                          return tr("OR Expression");                 case  Node::AndExpression:                         return tr("AND  Expression");                 case Node::NotExpression:                          return tr("NOT Expression");                 case  Node::Atom:                         return tr("Atom");                  case Node::Identifier:                         return  tr("Identifier");                 case Node::Operator:                          return tr("Operator");                 case  Node::Punctuator:                         return tr("Punctuator");                  default:                         return tr("Unknown");                  }         } else if (index.column() == 1) {                  return node->str;         }         return QVariant(); 第 181 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 } QVariant  BooleanModel::headerData(int section,   Qt::Orientation orientation,   int  role) const {                 if   (orientation   ==   Qt::Horizontal   &&   role   == Qt::DisplayRole) {                 if (section == 0) {                          return tr("Node");                 } else if  (section == 1) {                         return tr("Value");                  }         }         return QVariant(); }   现在,我们继承了 QAbstractItemModel。之所以不继承前面说的 QAbstractListModel 或 者 QAbstractTableModel,是因为我们要构造一个 tree model,而这个 model 是有层次结构 的。所以,我们直接继承了那两个类的基类。在构造函数中,我们把根节点的指针赋值为 0, 因此我们提供了另外的一 个函数 setRootNode(),将根节点进行有效地赋值。而在析构中, 我们直接使用 delete 操作符将这个根节点 delete 掉。在 setRootNode()函数中,首先我们 delete 掉原有的根节点,再将根节点赋值,然后调用 reset()函数。这个函数将通知所有的 view 对 界面进行重绘,以表现最新的数据。   使用 QAbstractItemModel,我们必须重写它的五个纯虚函数。首先是 index()函 数。这 个函数在 QAbstractTableModel 或者 QAbstractListModel 中不需要覆盖,因此那两个类已 经重写过了。但是,我们继承 QAbstractItemModel 时必须覆盖。这个函数的签名如下:     virtual   QModelIndex   index(int   row,   int   column,   const   QModelIndex &parent = QModelIndex()) const =  0; 第 182 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理   这是一个纯虚函数,用于返回第 row 行,第 column 列,父节点为 parent 的那个元素的 QModelIndex 对象。对于 tree model,我们关注的是 parent 参数。看一下我们的实现: QModelIndex  BooleanModel::index(int row, int column,   const QModelIndex &parent) const {          if (!rootNode || row < 0 || column < 0)                  return QModelIndex();         Node *parentNode =  nodeFromIndex(parent);         Node *childNode =  parentNode->children.value(row);         if (!childNode)                  return QModelIndex();         return createIndex(row,  column, childNode); }   如果 rootNode 或者 row 或者 column 非法,返回一个非法的 QModelIndex。然后使用 nodeFromIndex() 函数取得索引为 parent 的节点,然后我们使用 children 属性(这是我们 前面定义的 Node 里面的属性)获得子节点。如果子节点不存在,返回一个 非法值。最后,当 是一个有效值时,由 createIndex()函数返回有效地 QModelIndex 对象。   对于具有层次结构的 model 来说,只有 row 和 column 值是不能确定这个元素的位置的, 因此,QModelIndex 中除了 row 和 column 之外,还有一个 void*或者 int 的空白属性,可以 存储一个值。在这里我们就把父节点的指针存入,这样,就可以由这三个属性定位这个元素。 因 此 , createIndex() 中 第 三 个 参 数 就 是 这 个 内 部 的 指 针 。 所 以 我 们 自 己 定 义 一 个 nodeFromIndex()函数的时候要注意使用 QModelIndex 的 internalPointer()函数获得这个 内部指针,从而定位我们的 node。   后面的 rowCount()和 columnCount()这两个函数比较简单,就是要获得 model 的行和列 的值。由于我们的 model 定义成 2 列,所以在 columnCount()函数中始终返回 2.   parent()函数要返回子节点的父节点的索引,我们要从子节点开始寻找,直到找到父 节点的父节点,这样就能定位到父节点,从而得到子节点的位置。而 data()函数要返回每个 第 183 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 单元格的返回值,经过前面两个例子,我想这个函数已经不会有很 大的困难了的 。 headerData()函数返回列头的名字,同前面一样,这里就不再赘述了。   前面的代码很 长,BooleanWindow 部分就很简单了。就是把整个 view 和 model 组合起来。 另外的一个 BooleanParser 类没有什么 GUI 方面的代码,是纯粹的算法问题。如果我看得没 错的话,这里应该使用的是编译原理里面的递归下降词法分析,有兴趣的朋友可以到网上查 一下相 关的资料。我想在以后的《自己动手写编译器》中再详细介绍这个算法。 好了,今天的内容很多,为了方便大家查看和编译代码,我已经把这接 种出现的所有 代码打包传到附件中。 Qt 学习之路(48): 自定义委托   还是继续前面的内容。前面我们分三次把自定义 model 说完了,其实主要还是那三个实 例。在 model/view 架构中,与 model 同等重要的就是 view。   我们知道,在经典的 MVC 模型中,view 用于向用户展示 model 的数据。但是,Qt 提供 的不是 MVC 三层架构,而是一个 model/view 设计。这种设计并没有包含一个完整而独立的 组件用于管理用户的交互。一般来说,view 仅仅是用作对 model 数据的展示和对用户输入的 处理,而不应该去 做其他的工作。在这种结构中,为了获得对用户输入控制的灵活性,这种 交互工作交给了 delegate,也就是“委托”,去完成。简单来说,就像它们的名字 一样 , view 将用户输入委托给 delegate 处理,而自己不去处理这种输入。这些组件提供一种输入 能力,并且能够在某些 view 中提供这种交互情形下的渲染,比如在 table 中通过双击单 第 184 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 元格即可编辑内容等。对这种控制委托的标准接口被定义在 QAbstractItemDelegate 类中。   delegate 可以用于渲染内容,这是通过 paint() 和 sizeHint() 函数来完成的。但是, 对 于 一 些 简 单 的 基 于 组 件 的 delegate , 可 以 通 过 继 承 QItemDelegate 或 者 QStyledItemDelegate 来实现。这样就可以避免要完全重写 QAbstractItemDelegate 中所需 要的所有函数。对于一些相对比较通用的函数,在这两个类中已经有了一个默认的实现。   Qt 提供的标准组件使用 QItemDelegate 提供编辑功能的支持。这种默认的实现被用在 QListView , QTableView 和 QTreeView 之 中 。 view 实 用 的 delegate 可 以 通 过 itemDelegate() 函数获得。setItemDelegate() 函数则可以为一个标准组件设置自定义的 delegate。     Qt 4.4 版 本 之 后 提 供 了 两 个 可 以 被 继 承 的 delegate 类 : QItemDelegate 和 QStyledItemDelegate。默认的 delegate 是 QStyledItemDelegate。这两个类可以被相互替代, 用于给 view 组件提供绘制和编辑的功能。它们之间的主要区别在于,QStyledItemDelegate 使用当前的风格(style)去绘制组件。所以,在自定义 delegate 或者需要使用 Qt style sheets 时,建议使用 QStyledItemDelegate 作为父类。使用这两个类的代码通常是一样的, 除了需要使用 style 进行绘制的部份。如果你希望为 view item 自定义绘制函数,最好实现 一个自定义的 style。这个你可以通过 QStyle 类来实现。   如果 delegate 没有支持为你的数据类型进行绘制,或者你希望自己绘制 item,那么就 可以继承 QStyledItemDelegate 类,并且重写 paint() 或者还需要重写 sizeHint() 函数。 paint() 函数会被每一个 item 独立调用,而 sizeHint()函数则可以定义每一个 item 的大 小。在重写 paint() 函数的时候,通常需要用 if 语句找到你需要进行渲染的数据类型并进 行绘制,其他的数据类型需要调用父类的实现进行绘制。   一个自定义的 delegate 也可以直接 提供一个编辑器,而不是使用内置的编辑器工厂 (editor item factory)。如果你需要这种功能,那么需要实现一下几个函数:    * createEditor(): 返回修改数据的组件;   * setEditorData(): 为 editor 提供编辑的原始数据;   * updateEditorGeometry(): 保证 editor 显示在 item view 的合适位置以及大小;   * setModelData(): 根据 editor 的数据更新 model 的数据。 第 185 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理    好了,这就是一个自定义 delegate 的实现了。下面来看一个例子。   这是一个歌曲及其时间的例子。使用的是 QTableWidget,一共有两列,第一列是歌曲名 字,第二列是歌曲持续的时间。为了表示这个数据,我们建立一个 Track 类:    track.h #ifndef TRACK_H #define TRACK_H #include  class Track { public:         Track(const  QString &title = "", int duration = 0);         QString title;          int duration; }; #endif // TRACK_H   track.cpp #include  "track.h" Track::Track(const QString &title, int duration)          : title(title), duration(duration) { }   这个类的构造函数没有做任何操作,只是把 title 和 duration 这两个参数通过构造函 数初始化列表赋值给内部的成员变量。注意,现 在这两个成员变量都是 public 的,在正式 的程序中应该声明为 private 的才对。然后来看 TrackDelegate 类:    trackdelegate.h #ifndef TRACKDELEGATE_H #define  TRACKDELEGATE_H 第 186 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 #include  class TrackDelegate :  public QStyledItemDelegate {         Q_OBJECT public:          TrackDelegate(int durationColumn, QObject *parent = 0);          void paint(QPainter *painter, const QStyleOptionViewItem &option,  const QModelIndex &index) const;                 QWidget   *createEditor(QWidget   *parent,   const QStyleOptionViewItem &option,  const QModelIndex &index) const;                 void   setEditorData(QWidget   *editor,   const   QModelIndex &index) const;                 void   setModelData(QWidget   *editor,   QAbstractItemModel *model,  const QModelIndex &index) const; private slots:         void  commitAndCloseEditor(); private:         int durationColumn; }; #endif  // TRACKDELEGATE_H   trackdelegate.cpp #include  "trackdelegate.h" TrackDelegate::TrackDelegate(int durationColumn,  QObject *parent)         : QStyledItemDelegate(parent) {          this->durationColumn = durationColumn; } void   TrackDelegate::paint(QPainter   *painter,   const   QStyleOptionViewItem 第 187 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 &option, const QModelIndex &index) const {         if  (index.column() == durationColumn) {                                 int   secs   =   index.model()->data(index, Qt::DisplayRole).toInt();                  QString text = QString("%1:%2").arg(secs / 60, 2, 10,  QChar('0')).arg(secs % 60, 2, 10, QChar('0'));                                 QTextOption   o(Qt::AlignRight   | Qt::AlignVCenter);                  painter->drawText(option.rect, text, o);         } else {                   QStyledItemDelegate::paint(painter, option, index);          } } QWidget   *TrackDelegate::createEditor(QWidget   *parent,   const QStyleOptionViewItem &option, const QModelIndex &index) const {          if (index.column() == durationColumn) {                                 QTimeEdit   *timeEdit   =   new QTimeEdit(parent);                  timeEdit->setDisplayFormat("mm:ss");                  connect(timeEdit, SIGNAL(editingFinished()), this,  SLOT(commitAndCloseEditor()));                 return timeEdit;          } else {                                 return QStyledItemDelegate::createEditor(parent, option, index);         } 第 188 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 } void  TrackDelegate::commitAndCloseEditor() {         QTimeEdit *editor  = qobject_cast(sender());         emit  commitData(editor);         emit closeEditor(editor); } void   TrackDelegate::setEditorData(QWidget   *editor,   const   QModelIndex &index) const {         if (index.column() == durationColumn)  {                                 int   secs   =   index.model()->data(index, Qt::DisplayRole).toInt();                 QTimeEdit *timeEdit =  qobject_cast(editor);                  timeEdit->setTime(QTime(0, secs / 60, secs % 60));         } else  {                                 QStyledItemDelegate::setEditorData(editor, index);          } } void   TrackDelegate::setModelData(QWidget   *editor,   QAbstractItemModel *model, const QModelIndex &index) const {          if (index.column() == durationColumn) {                  QTimeEdit *timeEdit = qobject_cast(editor);                  QTime time = timeEdit->time(); 第 189 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理                                 int   secs   =   (time.minute()   *   60)   + time.second();                  model->setData(index, secs);         } else {                                 QStyledItemDelegate::setModelData(editor, model, index);         } }   正如前面所说的,这个类继承了 QStyledItemDelegate,覆盖了其中的四个函数。通过 前面的讲解,我们已经了解到这些函数 的作用。至于实现,我们前面也说过,需要通过 QModelIndex 选择我们需要进行渲染的列,然后剩下的数据类型仍然需要显式地调用父类的 相应函数。由 于我们在 Track 里面存储的是歌曲的秒数,所以在 paint()里面需要用除法计 算出分钟数,用%60 计算秒数。其他的函数都比较清楚,请注意代码。    最后写一个使用的类:   trackeditor.h #ifndef TRACKEDITOR_H #define  TRACKEDITOR_H #include  #include "track.h" class  TrackEditor : public QDialog {         Q_OBJECT public:          TrackEditor(QList *tracks, QWidget *parent); private:          QList *tracks;         QTableWidget *tableWidget; }; 第 190 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 #endif  // TRACKEDITOR_H   trackeditor.cpp #include  "trackeditor.h" #include "trackdelegate.h" TrackEditor::TrackEditor(QList  *tracks, QWidget *parent)         : QDialog(parent) {          this->tracks = tracks;         tableWidget = new  QTableWidget(tracks->count(), 2);          tableWidget->setItemDelegate(new TrackDelegate(1));                 tableWidget->setHorizontalHeaderLabels(QStringList()   << tr("Track") << tr("Duration"));         for (int row = 0; row  < tracks->count(); ++row) {                 Track track =  tracks->at(row);                                 QTableWidgetItem   *item0   =   new QTableWidgetItem(track.title);                  tableWidget->setItem(row, 0, item0);                                 QTableWidgetItem   *item1   =   new QTableWidgetItem(QString::number(track.duration));                  item1->setTextAlignment(Qt::AlignRight);                  tableWidget->setItem(row, 1, item1);         }          QVBoxLayout *mainLayout = new QVBoxLayout;          mainLayout->addWidget(tableWidget);          this->setLayout(mainLayout); }     其 实 也 并 没 有 很 大 的 不 同 , 只 是 我 们 使 用 setItemDelegate() 函 数 设 置 了 一 下 delegate。然后写 main()函数: 第 191 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 #include  #include "trackeditor.h" int main(int argc, char  *argv[]) {         QApplication a(argc, argv);          QList tracks;         Track t1("Song 1", 200);          Track t2("Song 2", 150);         Track t3("Song 3", 120);          Track t4("Song 4", 210);         tracks << t1 << t2  << t3 << t4;         TrackEditor te(&tracks, NULL);          te.show();         return a.exec(); }   好了,运行一 下看看效果吧!   第 192 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 Qt 学习之路(49): 通用算法   关于 Qt 的 model-view 部分就告一段落,今天我们开始新的部分。或许有些朋友觉得前 面的部分说得很简单。对此我也没有办法,毕 竟,Qt 是一个很庞大的库,一时半会根本不 可能穷尽所有内容,并且我也有很多东西不知道,有时候也必须去查找资料才能明白。   今天开始的 部分是关于 Qt 提供的一些通用算法。这部分内容来自 C++ GUI Programming with Qt 4, 2nd Edition。    提供了一系列通用的模板函数,用于实现容器上面的基本算法。这部分 算法很多依赖于 STL 风格的遍历器(还记得 前面曾经说过的 Java 风格的遍历器和 STL 风格 的遍历器吗?)。实际上,C++ STL 也提供了很多通用算法,包含在 头文件内。 这 部 分 算 法 对 于 Qt 容 器 同 样 也 是 适 用 的 。 因 此 , 如 果 你 想 使 用 的 算 法 在 Qt 的 头文件中没有包含,那么就 可以使用 STL 的算法代替,这并不会产生什么 冲突。这里我们来说几个 Qt 中的通用算法。虽然这些算法都是很简单的,但是,库函数往往 第 193 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 会比自己编写的更有效 率,因此还是推荐使用系统提供的函数的。   首先是 qFind()函数。qFind()函数会在容器中查找一个特定的值。它的参数中有一个 起 始位置和终止位置,如果被查找的元素存在,函数返回第一个匹配项的位置,否则则返回终 止位置。注意,我们这里说的“位置”,实际上是 STL 风格的遍历 器。我们知道,使用 STL 风格遍历器是可以反映一个位置的。例如下面的例子,i 的值将是 list.begin() + 1,而 j 会是 list.end(): QStringList list; list << "Emma"  << "Karl" << "James" << "Mariette"; QStringList::iterator  i = qFind(list.begin(), list.end(), "Karl"); QStringList::iterator j  = qFind(list.begin(), list.end(), "Petra");   qBinaryFind()的行为很像 qFind(),所不同的是,qBinaryFind()是二分查找算法,它 只适用于查找排序之后 的集合,而 qFind()则是标准的线性查找。通常,二分查找法使用条 件更为苛刻,但是效率也会更高。   qFill()会使用给定值对容 器进行填充。例如: QLinkedList list(10); qFill(list.begin(),  list.end(), 1009);   正如其他基于遍历器的算法一样,qFill()也可以针对容器的一部分进行操 作,例如下 面的代码将会把 vector 的前 5 位设置成 1009,而最后 5 位设置为 2013: QVector  vect(10); qFill(vect.begin(), vect.begin() + 5, 1009); qFill(vect.end()  - 5, vect.end(), 2013);   qCopy()算法可以实现将一个容器中的元素复制到另一个容器,例 如: QVector vect(list.count()); qCopy(list.begin(),  list.end(), vect.begin());   qCopy()也可以用于同一容器中的元素的复制。 qCopy()操作成功的关键是源容器和目的 容器的范围不会发生溢出。例如如下代码,我们将把一个列表的最后两个元素复制给前两个 元素: 第 194 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 qCopy(list.begin(),  list.begin() + 2, list.end() - 2);   qSort()实现了容器元素的递增排序,使 用起来也很简单: qSort(list.begin(), list.end());   默认情 况下,qSort()将使用 < 运算符进行元素的比较。这暗示如果需要的话,你必须 定义 < 运算符。如果需要按照递减排序,需要将 qGreater()当作第三个参数传给 qSort()函数。例如: qSort(list.begin(),  list.end(), qGreater());   注意,这里的 T 实际上是容器的泛型类型。实际上,我们可以利用第三个参数对排序进 行定义。例如,我们自定义的数据类型中有一个大小写不敏 感的 QString 的小于比较函数: bool insensitiveLessThan(const QString  &str1, const QString &str2) {         return  str1.toLower() < str2.toLower(); }   那么,我们可以这样使用 qSort()从而可以利用这个函数: QStringList list; // ... qSort(list.begin(),  list.end(), insensitiveLessThan);   qStableSort()函数类似与 qSort(),所不同之处在于它是稳定排序。稳定排序是算法设 计上的一个名词,意思是,在排序过程中,如果有两个元素相等,那么在排序结果中这两个 元素 的先后顺序同排序前的原始顺序是一致的。举个例子,对于一个序列:a1, a5, a32, a31, a4,它们的大小顺序是 a1 < a31 = a32 < a4 < a5,那么稳定排序之后的结果应该是 a1, a32, a31, a4, a5,也就是相等的元素在排序结果中出现的顺序和原始顺序是一致的。 稳定排序在某些场合是很有用的,比如,现在有一份按照学号排序的学生成绩单。你想按照 成绩高低重新进行排序,对于成绩一样的学生,还是遵循原来的学号顺序。这时候就要稳定 排序了。   qDeleteAll()函数将对容器中 存储的所有指针进行 delete 操作。这个函数仅在容器元 素是指针的情形下才适用。执行过这个函数之后,容器中的指针均被执行了 delete 运算,但 第 195 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 是这 些指针依然被存储在容器中,成为野指针,你需要调用容器的 clear()函数来避免这 些指针的误用: qDeleteAll(list); list.clear();    qSwap()函数可以交换两个元素的位置。例如: int x1 = line.x1(); int x2 =  line.x2(); if (x1 > x2)         qSwap(x1, x2);   最 后,在头文件中,也定义了几个有用的函数。这个头文件被其他所有的头 文件 include 了,因此你不需要显式的 include 这个头文件了。   在这个头文件中有这么几个函数:qAbs()返回参数的绝对值,qMin()和 qMax()则返回 两个值 的最大值和最小值。 Qt 学习之路(50): QString   这段时间回家,一直没有来得及写,今天才发现博客的编辑器有了新版。还是先来试试 新版编辑器的功能吧!   今天要说的是 QString。之所以把 QString 单独拿出来,是因为 string 是很常用的一个 数据结构,甚至在很多语言中,比如 JavaScript,都是把 string 作为一种同 int 等一样的 基本数据结构来实现的。   每一个 GUI 程序都需要 string,这些 string 可以用在界面上的 提示语,也可以用作一 般的数据结构。 C++语言提供了两种字符串的实现: C 风格的字符串,以 '\0‘结尾; std::string,即标准模版库中的类。Qt 则提供了自己的字符串实现:QString。QString 以 16 位 Uniode 进行编码。我们平常用的 ASCII 等一些编码集都作为 Unicode 编码的子集提供。关 于编码的问题,我们会到以后的时候再详细说明。    在使用 QString 的时候,我们不需要担心内存分配以及关于'\0'结尾的这些注意事项 。 第 196 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 QString 会把这些问题解决。通常,你可以把 QString 看作是一个 QChar 的向量。另外,与 C 风格的字符串不同,QString 中间是可以包含'\0'符号的,而 length()函数则会返回 整个 字符串的长度,而不仅仅是从开始到'\0'的长度。   同 Java 的 String 类类似,QString 也重载的+和+=运算符。这两 个运算符可以把两个字 符串连接到一起,正像 Java 里面的操作一样。QString 可以自动的对占用内存空间进行扩充, 这种连接操作是恨迅速的。下面是这 两个操作符的使用: QString str = "User: ";   str += userName +  "\n";    QString 的 append()函数则提供了类似的操作,例如: str = "User: ";   str.append(userName);   str.append("\n");     C 语言中有 printf()函数作为格式化输出,QString 则提供了一个 sprintf()函数实现 了相同的功 能: str.sprintf("%s %.1f%%", "perfect competition", 100.0);     这句代码将输出:perfect competition 100.0%,同 C 语言的 printf()一样。不过前面 我们也见到了 Qt 提供的另一种格式化字符串输出的函数 arg(): str  = QString("%1 %2 (%3s-%4s)")          .arg("permissive").arg("society").arg(1950).arg(1970);    这 段代码中,%1, %2, %3, %4 作为占位符,将被后面的 arg()函数中的内容依次替换, 比如%1 将被替换成 permissive,%2 将被替换成 society,%3 将被替换成 1950,%4 将被替 换曾 1970,最后,这句代码输出为:permissive society (1950s-1970s). arg()函数比起 sprintf()来是类型安全的,同时它也接受多种的数据类型作为参数,因此建议使用 arg() 函数而不是传统的 sprintf()。   使用 static 的函数 number()可以把数字转换成字符串。例如: QString  str = QString::number(54.3);    你也可以使用非 static 函数 setNum()来实现 相同的目的: 第 197 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 QString str;   str.setNum(54.3);    而 一系列的 to 函数则可以将字符串转换成其他基本类型,例如 toInt(), toDouble(), toLong()等。这些函数都接受一个 bool 指针作为参数,函数结束之后将根据是否转换成功设 置为 true 或者 false: bool  ok;   double d = str.toDouble(&ok);   if(ok)   {        // do something...   } else {       // do something...   }    对于 QString,Qt 提供了很多操作函数,例如,使用 mid()函数截取子串: QString x =  "Nine pineapples";   QString y = x.mid(5, 4);            // y ==  "pine"   QString z = x.mid(5);               // z == "pineapples"     mid()函数接受两个参数,第一个是起始位置,第二个是取串的长度。如果省略第二个 参数,则会从起始位置截取到末尾。正如上面的例子显示的那样。    函数 left()和 rigt()类似,都接受一个 int 类型的参数 n,都是对字符串进行截取。不 同之处在于,left()函数从左侧截取 n 个字符,而 right()从右侧开始截取。下面是 left() 的例子: QString x = "Pineapple";   QString  y = x.left(4);      // y == "Pine"    函数 indexOf()返回字符串的位置, 如: QString x = "sticky question";   QString y = "sti";   第 198 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 x.indexOf(y);                // returns 0   x.indexOf(y, 1);            // returns  10   x.indexOf(y, 10);           // returns 10   x.indexOf(y, 11);            // returns -1    函数 startsWith()和 endsWith()可以检测字符 串是不是以某个特定的串开始或结尾, 例如: if (url.startsWith("http:") &&  url.endsWith(".png"))   {   }    这段代码等价于 if  (url.left(5) == "http:" && url.right(4) == ".png")   {   }    不过,前者要比后者更加清楚简洁,并且性能也更快一些。   QString 还提供了 replace()函数供实现字符串的 替换功能;trimmed()函数去除字符 串两侧的 空白字符(注 意,空白字符包括空格、Tab 以及换 行符,而不仅仅是空格 ); toLower()和 toUpper()函数会将字符串转换成小写大写字符串;remove()和 insert()函数 提供了删除和插入字符串的能力;simplified()函数可以将串中的所有连续的空白字符替换 成一个,并且把两端的空白字符去 除,例如"   \t   ”会返回一个空格" "。   将 const char *类型的 C 风格字符串转换成 QString 也是很常见的需求,简单来说, QString 的+=即可完成这个功能: str +=  " (1870)";    这里,我们将 const char * 类型的字符串" (1870)"转换成为 QString 类型。如果需要 显式的转换,可以使用 QString 的强制转换操作,或者是使用函数 fromAscii()等。为了 将 QString 类型转成 const char *字符串,需要进行两步操作,一是使用 toAscii()获得一个 QByteArray 类型对象,然后调用它的 data()或者 constData()函 数,例如: printf("User: %s\n", str.toAscii().data());  第 199 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理     为 了 方 便 使 用 , Qt 提 供 了 一 个 宏 qPrintable() , 这 个 宏 等 价 于 toAscii().constData(),例如: printf("User:  %s\n", qPrintable(str));  我们调用 QByteArray 类上面的 data()或者 constData()函数,将获得 QByteArray 内 部的一个 const char*类型的字符串,因此,我们不需要担心内存泄漏等的问题, Qt 会替我 们管理好内存。不过这也暗示我们,注意不要使用这个指针太长时间,因为如果 QByteArray 被 delete,那么这个指针也就成为野指针了。如果这个 QByteArray 对象没有被放在一个变量 中,那么当语句结束 后,QbyteArray 对象就会被 delete,这个指针也就被 delete 了。 Qt 学习之路(51): QByteArray 和 QVariant   前面我们在介绍 QString 的最后部分曾经提到了 QByteArray 这个类。现在我们就首先对 这个类进行介绍。    QByteArray 具有类似与 QString 的 API。它也有相应的函数,比如 left(), right(), mid()等。这些函数不仅名字和 QString 一样,而且也具有几乎相同的功能。QByteArray 可以 存储原生的二进制数据和 8 位编码的文本数据。 这句话怎么理解呢?我们知道,计算机内部 所有的数据都是以 0 和 1 的形式存储的。这种形式就是二进制。比如一串 0、1 代码:1000,计 算机并不知道它代表 的是什么,这需要由上下文决定:它可以是整数 8,也可以是一个 ARGB 的颜色(准确的说,整数 8 的编码并不是这么简单,但我们姑且这个理解吧)。对于文 件, 即便是一个文本文件,读出时也可以按照二进制的形式读出,这就是二进制格式。如果把这 些二进制的 0、1 串按照编码解释成一个个字符,就是文本形式了。因此,QByteArray 实际上 是原生的二进制,但是也可以当作是文本,因此拥有文本的一些操作。但是,我们还是建议 使用 QString 表示文本,重要 的原因是,QString 支持 Unicode。 第 200 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理   为了方便期间,QByteArray 自动的保证“最后一个字节之后的那个位”是 '\0'。这就 使得 QByteArray 可以很容易的转换成 const char *,也就是上一章节中我们提到的那两个 函数。同样,作为原生二进制存储,QByteArray 中间也可以存储'\0',而不必须是'\0'在最 后一位。    在有些情况下,我们希望把数据存储在一个变量中。例如,我有一个数组,既希望存整 数,又希望存浮点数,还希望存 string。对于 Java 来说,很简单,只要把这个数组声明成 Object[]类型的。这是什么意思呢?实际上,这里用到的是继承。在 Java 中,int 和 float 虽然是原生数据类型,但是它们都有分别对应一个包装类 Integer 和 Float。所有这些 Integer、Float 和 String 都是继承于 Object,也就是说,Integer、Float 和 String 都是一 个(也就是 is-a 的关系)Object,这 样,Object 的数组就可以存储不同的类型。但是,C++中 没有这样一个 Object 类,原因在于,Java 是单根的,而 C++不是。在 Java 中,所 有类都可 以上溯到 Object 类,但是 C++中没有这么一个根。那么,怎么实现这么的操作呢?一种办法 是,我们都存成 string 类,比如 int i=10,我就存"10"字符串。简单的数据类型固然可以, 可复杂一些的呢?比如一个颜色?难道要把 ARGB 所有的值都转化成 string?这种做法很复 杂,而且失去了 C++的类型检查等好处。于是我们想另外的办法:创建一个 Object 类,这是 一个“很大很大的”类,里面存储了几乎所有的数据类型,比如 下面的代码: class Object   {   public:       int intValue;        float floatValue;       string stringValue;   };     这个类怎么样?它就足以存储 int、float 和 string 了。嗯,这就是我们的思路,也是 Qt 的思路。在 Qt 中,这样的类就是 QVariant。     QVariant 可 以 保 存 很 多 Qt 的 数 据 类 型 , 包 括 QBrush 、 QColor 、 QCursor 、 QDateTime、QFont、QKeySequence、QPalette、QPen、QPixmap、QPoint、QRect、QRegion、 QSize 和 QString,并且还有 C++基本类型,如 int、float 等。QVariant 还能保存很多集合类型,如 第 201 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 QMap, QStringList 和 QList。item view classes,数据库 模块和 QSettings 都大量使用了 QVariant 类,,以方便我们读写数据。   QVariant 也可以进行嵌套存储,例如 QMap pearMap;   pearMap["Standard"]  = 1.95;   pearMap["Organic"] = 2.25;     QMap fruitMap;   fruitMap["Orange"] = 2.10;   fruitMap["Pineapple"]  = 3.85;   fruitMap["Pear"] = pearMap;    QVariant 被用于构建 Qt Meta-Object,因此是 QtCore 的一部分。当然,我们也可以在 GUI 模块中使用,例如 QIcon  icon("open.png");   QVariant variant = icon;   // other function   QIcon  icon = variant.value();    我们使用了 value()模版函数,获取存储在 QVariant 中的数据。这种函数在非 GUI 数据中同样适用,但是,在非 GUI 模块中,我们通常 使用 toInt()这样的一系列 to...()函 数,如 toString()等。   如果你觉得 QVariant 提供的存储数据类型太少, 也可以自定义 QVariant 的存储类型。 被 QVariant 存储的数据类型需要有一个默认的构造函数和一个拷贝构造函数。为了实现这 个功能,首先必须使用 Q_DECLARE_METATYPE() 宏。通常会将这个宏放在类的声明所在头文 件的下面: Q_DECLARE_METATYPE(BusinessCard)     然后我们就可以使用: BusinessCard businessCard;   第 202 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 QVariant variant  = QVariant::fromValue(businessCard);   // ...   if  (variant.canConvert()) {       BusinessCard card  = variant.value();       // ...   }     由于 VC 6 的编译器限制,这些模板函数不能使用,如果你使用这个编译器,需要使用 qVariantFromValue(), qVariantValue()和 qVariantCanConvert()这三个宏。   如果自定 义数据类型重写了<<和>>运算符,那么就可以直接在 QDataStream 中使用。不 过 首 先 需 要 使 用 qRegisterMetaTypeStreamOperators(). 宏 进 行 注 册 。 这 就 能 够 让 QSettings 使用操作符对数据 进行操作,例如 qRegisterMetaTypeStreamOperators("BusinessCard"); Qt 学习之路(52): 拖放技术之一   拖放 Drag and Drop,有时又被称为 DnD,是现代软件开发中必不可少的一项技术。它 提供了一种能够在应用程序内部甚至是应用程序之间进行信息交换的机制,并且,操作系统 与应用程序之间进行 剪贴板的内容交换,也可以被认为是 DnD 的一部分。   DnD 其实是由两部分组成的:Drag 和 Drop。Drag 是将被拖放对象“拖动”,Drop 是 将被拖放对象“放下”,前者一般是一个按下鼠标的过程,而后者则是一个松开鼠标的过程 这两者之间鼠标一直是被按下的。当然,这只是一种通常的情况,其 他情况还是要看应用程 序的具体实现。对于 Qt 而言,widget 既可以作为 drag 对象,也可以作为 drop 对象,或 者二者都是。    下面的一个例子来自 C++ GUI Programming with Qt 4, 2nd Edition。在这个例子中, 我们创建一个程序,可以将系统中的文本文件拖放过来,然后在窗口中读取内容。   mainwindow.h #ifndef  MAINWINDOW_H   #define MAINWINDOW_H   第 203 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理   #include       class MainWindow : public QMainWindow   {        Q_OBJECT     public:       MainWindow(QWidget *parent = 0);        ~MainWindow();     protected:       void  dragEnterEvent(QDragEnterEvent *event);       void  dropEvent(QDropEvent *event);     private:       bool  readFile(const QString &fileName);       QTextEdit *textEdit;   };      #endif // MAINWINDOW_H    mainwindow.cpp #include "mainwindow.h"     MainWindow::MainWindow(QWidget  *parent)       : QMainWindow(parent)   {       textEdit = new  QTextEdit;       setCentralWidget(textEdit);   第 204 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理        textEdit->setAcceptDrops(false);       setAcceptDrops(true);          setWindowTitle(tr("Text Editor"));   }     MainWindow::~MainWindow()    {   }     void MainWindow::dragEnterEvent(QDragEnterEvent  *event)   {       if  (event->mimeData()->hasFormat("text/uri-list")) {            event->acceptProposedAction();       }   }     void  MainWindow::dropEvent(QDropEvent *event)   {        QList urls = event->mimeData()->urls();       if  (urls.isEmpty()) {           return;       }         QString  fileName = urls.first().toLocalFile();       if (fileName.isEmpty())  {           return;       }   第 205 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理       if (readFile(fileName))  {                   setWindowTitle(tr("%1   -   %2").arg(fileName,   tr("Drag File")));       }   }     bool MainWindow::readFile(const  QString &fileName)   {       bool r = false;       QFile  file(fileName);       QTextStream in(&file);       QString  content;       if(file.open(QIODevice::ReadOnly)) {           in  >> content;           r = true;       }        textEdit->setText(content);       return r;   }    main.cpp #include    #include  "mainwindow.h"     int main(int argc, char *argv[])   {        QApplication a(argc, argv);       MainWindow w;       w.show();    第 206 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理     return a.exec();   }    这里的代码并不是很复杂。在 MainWindow 中,一个 QTextEdit 作为窗口中间的 widget。 这个类中有两个 protected 的函 数:dragEnterEvent() 和 dropEvent(),这两个函数都是 继承自 QWidget,看它们的名字就知道这是两个事件,而不仅仅是 signal。   在构造函数中,我们创建了 QTextEdit 的对象。默认情况下,QTextEdit 可以接受从其 他的应用程序拖放过来的文本类型的信息。如果用户把一个文件拖到这里面,那么就会把文 件名插入到文本的当前位置。但是我们希望让 MainWindow 读取文件内容,而不仅仅是插入 文件 名 ,所以我们在 MainWindow 中对 drop 事件进 行了处理, 因此要把 QTextEdit 的 setAcceptDrops()函数置为 false,并且把 MainWindow 的 setAcceptDrops()置为 true,以 便让 MainWindow 对 drop 事件进行处理。   当用户将对象拖动到组件上面 时,dragEnterEvent()函数会被回调。如果我们在事件处 理代码中调用 acceptProposeAction() 函数,我们就可以向用户暗示,你可以将拖动的对 象放在这个组件上。默认情况下,组件是不会接受拖放的。如果我们调用了这样的函数,那么 Qt 会自动地以光标 来提示用户是否可以将对象放在组件上。在这里,我们希望告诉用户, 窗口可以接受拖放。因此,我们首先检查拖放的 MIME 类型。MIME 类型为 text/uri-list 通常 用来描述一个 URI 的列表。这些 URI 可以是文件名,可以是 URL 或者其他的资源描述符 。 MIME 类型由 Internet Assigned Numbers Authority (IANA) 定义,Qt 的拖放事件使用 MIME 类 型 来 判 断 拖 放 对 象 的 类 型 。 关 于 MIME 类 型 的 详 细 信 息 , 请 参 考 http://www.iana.org/assignments/media-types/.     当 用 户 将 对 象 释 放 到 组 件 上 面 时 , dropEvent() 函 数 会 被 回 调 。 我 们 使 用 QMimeData::urls()来或者 QUrl 的一个 list。通常,这种拖动应该只用一个文件,但是也 不排除多个文件一起拖动。因此我们需要检查这个 list 是否为空,如果不为空,则取出第一 个。如 果不成立,则立即返回。最后我们调用 readFile() 函数读取文件内容。关于读取操作 我们会在以后的章节中详细说明,这里不再赘述。    好了,至此我们的小程序就解释完毕了,运行一下看看效果吧! 对于拖动和脱离,Qt 也提供了类似的函数:dragMoveEvent() 和 dragLeaveEvent(), 不过对于大部分应用而言,这两个函数的使用率要小得多。 第 207 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 Qt 学习之路(53): 拖放技术之二   很长时间没有来写博客了,前段时间一直在帮同学弄一个 spring-mvc 的项目,今天终 于做完了,不过公司里面又要开始做 flex 4,估计还会忙一段时间吧!   接着上次的说,上次说到了拖放技术,今天依然是一个例子,同样是来自《C++ GUI Programming with Qt 4, 2nd Edition》的。   这次的 demo 还算是比较实用:实现的是两个 list 之间的数据互拖。在很多项目中, 这一需求还是比较常见的吧!下面也就算是抛砖引玉了啊!    projectlistwidget.h #ifndef PROJECTLISTWIDGET_H   #define  PROJECTLISTWIDGET_H     #include      class  ProjectListWidget : public QListWidget   第 208 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 {       Q_OBJECT     public:        ProjectListWidget(QWidget *parent = 0);     protected:        void mousePressEvent(QMouseEvent *event);       void  mouseMoveEvent(QMouseEvent *event);       void  dragEnterEvent(QDragEnterEvent *event);       void  dragMoveEvent(QDragMoveEvent *event);       void dropEvent(QDropEvent  *event);     private:       void performDrag();          QPoint startPos;   };     #endif // PROJECTLISTWIDGET_H     projectlistwidget.cpp #include "projectlistwidget.h"     ProjectListWidget::ProjectListWidget(QWidget  *parent)       : QListWidget(parent)   {        setAcceptDrops(true);   }   第 209 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理   void  ProjectListWidget::mousePressEvent(QMouseEvent *event)   {        if (event->button() == Qt::LeftButton)           startPos =  event->pos();       QListWidget::mousePressEvent(event);   }     void  ProjectListWidget::mouseMoveEvent(QMouseEvent *event)   {        if (event->buttons() & Qt::LeftButton) {                   int   distance   =   (event->pos()   startPos).manhattanLength();            if (distance >= QApplication::startDragDistance())                performDrag();       }       QListWidget::mouseMoveEvent(event);   }      void ProjectListWidget::performDrag()   {        QListWidgetItem *item = currentItem();       if (item) {            QMimeData *mimeData = new QMimeData;            mimeData->setText(item->text());             QDrag *drag =  new QDrag(this);           drag->setMimeData(mimeData);            drag->setPixmap(QPixmap(":/images/person.png"));   第 210 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理         if  (drag->exec(Qt::MoveAction) == Qt::MoveAction)                delete item;       }   }     void  ProjectListWidget::dragEnterEvent(QDragEnterEvent *event)   {        ProjectListWidget *source =                     qobject_cast(event->source());     if  (source && source != this) {            event->setDropAction(Qt::MoveAction);            event->accept();       }   }     void  ProjectListWidget::dragMoveEvent(QDragMoveEvent *event)   {        ProjectListWidget *source =                     qobject_cast(event->source());     if  (source && source != this) {            event->setDropAction(Qt::MoveAction);            event->accept();       }   }     void  ProjectListWidget::dropEvent(QDropEvent *event)   {   第 211 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理      ProjectListWidget *source =                     qobject_cast(event->source());     if  (source && source != this) {            addItem(event->mimeData()->text());            event->setDropAction(Qt::MoveAction);            event->accept();       }   }     我们从构造函数开始看起。Qt 中很多组件是可以接受拖放的,但是默认动作都是不允许 的,因此在构造函数中,我们调用 setAcceptDrops(true); 函数,让组件能够接受拖放事 件。   在 mousePressEvent() 函数中,我们检测鼠标左键点击,如果是的话就记录下当前位 置。需要注意的是,这个函数最后需要调用系统自带的处理函数,以便实现通常的那种操作。 这在一些 重写事件的函数中都是需要注意的!   然后我们重写了 mouseMoveEvent() 事件。下面还是先来看看代码: void  ProjectListWidget::mouseMoveEvent(QMouseEvent *event)   {        if (event->buttons() & Qt::LeftButton) {                   int   distance   =   (event->pos()   startPos).manhattanLength();            if (distance >= QApplication::startDragDistance())                performDrag();       }       QListWidget::mouseMoveEvent(event);   }    在这里判断了如果鼠标拖动的时候一直按住左键(也就是 if 里面的内容),那么就计算 一个 manhattanLength() 值。从字面上翻译,这是个“曼哈顿长度”。这是什么意思呢?我 第 212 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 们看一下 event.pos() - startPos 是什么。还记得在 mousePressEvent() 函数中,我们将 鼠标按下的坐标记录为 startPos,而 event.pos() 则是鼠标当前的坐标:一个点减去另外 一个点,没错,这就是向量!其实,所谓曼哈顿距离就是两点之间的距离(至于为什么叫这 个奇怪的名字,大家查查百科就知 道啦!),也就是这个向量的长度。下面又是一个判断, 如果大于 QApplication::startDragDistance(),我们才进行 drag 的操作。当然,最后还 是要调用系统默认的鼠标拖动函数。这一判断的意义在于,防止用户因为手的抖动等因素造 成的鼠标拖动。用户必须将鼠标拖动一段距离之 后,我们才认为他是希望进行拖动操作,而 这一距离就是 QApplication::startDragDistance() 提供的,这个值通常是 4px。   performDrag() 开始处理拖放过程。我们创建了一个 QDrag 对象,将 this 作为 parent。QDrag 使用 QMimeData 存储数据。例如我们使用 QMimeData::setText() 函数将一 个字符串存储为 text/plain 类型的数据。QMimeData 提供了很多函数,用于存储诸如 URL、 颜 色 等 类 型 的 数 据 。 使 用 QDrag::setPixmap() 则 可 以 设 置 拖 动 发 生 时 鼠 标 的 样 式 。 QDrag::exec() 会阻塞拖动的操作,直到用户完成操作或者取消操作。它接受不同类型的动 作作为参数,返回值是真正执行的动作。这些动作的类型为 Qt::CopyAction,Qt::MoveAction 和 Qt::LinkAction。返回值会有这三种动作,同时增加 一个 Qt::IgnoreAction 用于表示用户取消了拖放。这些动作取决于拖放源对象允许的类型, 目的对象接受的类型以及拖放时按下的键盘按键。在 exec() 调用之后,Qt 会在拖放对象不 需要的时候 delete 掉它。   ProjectListWidget 不仅能够发出拖动事件,而且能够接受同一应用程序中的不同 ProjectListWidget 对象的数据。在 dragEnterEvent() 中,我们使用 event->source() 获 取这样的对象:如果拖放数据来自同一类型的对象,并且来自同一应用程序则返回其指针, 否则返回 NULL。我们使用 qobject_cast 宏将指针转换成 ProjectListWidget* 类型,然后 设置接受 Qt::MoveAction 类型的拖动。dragMoveEvent() 则和这个函数具有相同的代码, 因为我们需要重写拖动移动的代码。     最 后 在 dropEvent() 函 数 中 , 我 们 取 出 QDrag 中 的 mimeData 数 据 , 调 用 addItem() 添加到当前的列表中。这样,一个相对完整的拖放的代码就完成了。 拖放技术是 Qt 中功能强大的一个技术,但是对于不涉及数据的同一组件中拖动,或许 仅仅简单的实现 mouse event 就足够了,具体还是要自己斟酌啦! 第 213 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 Qt 学习之路(54): 自定义拖放数据对象   前面的例子都是使用的系统提供的拖放对象 QMimeData 进行拖放数据的存储,比如使 用 QMimeData::setText() 创建文本,使用 QMimeData::urls() 创建 URL 对象。但是,如 果你希望使用一些自定义的对象作为拖放数据,比如自定义类等等,单纯使用 QMimeData 可能就没有那么容易了。为了实现这种操作,我们可以从下面三种实现方式中选择一个:   将自定义数据作为 QByteArray 对象,使用 QMimeData::setData() 函数作为二进制数 据存储到 QMimeData 中,然后使用 QMimeData::Data() 读取;   继承 QMimeData,重写其中的 formats() 和 retrieveData() 函数操作自定义数据;   如果拖放操作仅仅发生在同一个应用程序,可以直接继承 QMimeData,然后使用任意合 第 214 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 适的数据结构进行存储。   第一种方法不需要继承任何类,但是有一些局限:即是拖放不会发生,我们也必 须将 自定义的数据对象转换成 QByteArray 对象;如果你希望支持很多种拖放的数据,那么每种 类型的数据都必须使用一个 QMimeData 类,这可能会导致类爆炸;如果数据很大的话,这 种方式可能会降低系统的可维护性。然而,后两种实现方式就不会有这些问题,或者说是能 够减小这种问题,并且 能够让我们有完全控制权。     我 们 先 来 看 一 个 应 用 , 使 用 QTableWidget 来 进 行 拖 放 操 作 , 拖 放 的 类 型 包 括 plain/text,plain/html 和 plain/csv。如果使用第一种实现方法,我们的代码将会如下所 示: void  MyTableWidget::mouseMoveEvent(QMouseEvent *event)   {       if  (event->buttons() & Qt::LeftButton) {                   int   distance   =   (event->pos()   startPos).manhattanLength();           if  (distance >= QApplication::startDragDistance())                performDrag();       }       QTableWidget::mouseMoveEvent(event);    }     void MyTableWidget::performDrag()   {        QString plainText = selectionAsPlainText();       if  (plainText.isEmpty())           return;         QMimeData  *mimeData = new QMimeData;       mimeData->setText(plainText);        mimeData->setHtml(toHtml(plainText));   第 215 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理      mimeData->setData("text/csv", toCsv(plainText).toUtf8());          QDrag *drag = new QDrag(this);        drag->setMimeData(mimeData);       if  (drag->exec(Qt::CopyAction | Qt::MoveAction) == Qt::MoveAction)          deleteSelection();   }     对于这段代码,我们应该已经很容易的理解:在 performDrag() 函数中,我们调用 QMimeData 的 setText() 和 setHTML() 函数存储 plain/text 和 plain/html 数据,使用 setData() 将 text/csv 类型的数据作为二进制 QByteArray 类型存储。 QString  MyTableWidget::toCsv(const QString &plainText)   {        QString result = plainText;       result.replace("\\", "\\\\");        result.replace("\"", "\\\"");       result.replace("\t", "\",  \"");       result.replace("\n", "\"\n\"");        result.prepend("\"");       result.append("\"");       return  result;   }     QString MyTableWidget::toHtml(const QString  &plainText)   {       QString result = Qt::escape(plainText);        result.replace("\t", "");        result.replace("\n", "\n");        result.prepend("\n
    ");   第 216 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理      result.append("\n
    ");       return result;   }      toCsv() 和 toHtml() 函数将数据取出并转换成我们需要的 csv 和 html 类型的数据。 例如,下面的数据 Red   Green  Blue  Cyan Yellow Magenta   转换成 csv 格式为: "Red",  "Green", "Blue"  "Cyan", "Yellow", "Magenta"   转换成 html 格式为:      
    RedGreenBlue   
    CyanYellowMagenta   
      在放置的函数中我们像以前一样使用: void  MyTableWidget::dropEvent(QDropEvent *event)   {       if  (event->mimeData()->hasFormat("text/csv")) {               QByteArray   csvData  =  event->mimeData()->data("text/csv");          QString csvText = QString::fromUtf8(csvData);           // ...            event->acceptProposedAction();       } else if  (event->mimeData()->hasFormat("text/plain")) {            QString plainText = event->mimeData()->text();           // ...    第 217 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理         event->acceptProposedAction();       }   }      虽然我们接受三种数据类型,但是在这个函数中我们只接受两种类型。至于 html 类型, 我们希望如果用户将 QTableWidget 的数据拖到一个 HTML 编辑器,那么它就会自动转换成 html 代码,但是我们不计划支持将外部的 html 代码拖放到 QTableWidget 上。为了让这段 代 码 能 够 工 作 , 我 们 需 要 在 构 造 函 数 中 设 置 setAcceptDrops(true) 和 setSelectionMode(ContiguousSelection)。   好了,上面就是我们所说的第一种方式的实现。这里并没 有给出完整的实现代码,大家 可以根据需要自己实现一下试试。下面我们将按照第二种方法重新实现这个需求。 class TableMimeData : public QMimeData   {        Q_OBJECT     public:       TableMimeData(const QTableWidget  *tableWidget,                     const QTableWidgetSelectionRange  &range);       const QTableWidget *tableWidget() const {  return myTableWidget; }       QTableWidgetSelectionRange range()  const { return myRange; }       QStringList formats() const;     protected:        QVariant retrieveData(const QString &format,            QVariant::Type preferredType) const;     private:    第 218 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理     static QString toHtml(const QString &plainText);        static QString toCsv(const QString &plainText);          QString text(int row, int column) const;       QString  rangeAsPlainText() const;         const QTableWidget  *myTableWidget;       QTableWidgetSelectionRange myRange;        QStringList myFormats;   };     为了避免存储具体的数据,我们存储 table 和选择区域的坐标的指针。 TableMimeData::TableMimeData(const QTableWidget  *tableWidget,                                                             const QTableWidgetSelectionRange &range)   {       myTableWidget =  tableWidget;       myRange = range;       myFormats <<  "text/csv" << "text/html" << "text/plain";   }     QStringList  TableMimeData::formats() const  {       return myFormats;   }    构造函数中,我们对私有变量进行初始化。formats() 函数返回的是被 MIME 数据对象 支持的数据类型列表。这个列表是没有先后顺序的,但是最佳实践是将“最适合”的类型放 在第一位。对于支持多种类型的应用程序而言,有时候会直接选 用第一个符合的类型存储。 QVariant TableMimeData::retrieveData(const  QString &format,   第 219 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理                                       QVari ant::Type preferredType) const  {       if (format ==  "text/plain") {           return rangeAsPlainText();       } else  if (format == "text/csv") {           return  toCsv(rangeAsPlainText());       } else if (format == "text/html") {            return toHtml(rangeAsPlainText());       } else {            return QMimeData::retrieveData(format, preferredType);       }    }    函数 retrieveData() 将给出的 MIME 类型作为 QVariant 返回。参数 format 的值通 常是 formats() 函数返回值之一,但是我们并不能假定一定是这个值之一,因为并不是所 有的应用程序都会通过 formats() 函数检查 MIME 类型。一些返回函数,比如 text(), html(), urls(), imageData(), colorData() 和 data() 实 际 上 都 是 在 QMimeData 的 retrieveData() 函数中实现的。第二个参数 preferredType 给出我们应该在 QVariant 中 存储哪种类型的数据。在这里,我们简单的将其忽略了,并且在 else 语句中,我们假定 QMimeData 会自动将其转换成所需要的类型。 void MyTableWidget::dropEvent(QDropEvent *event)   {        const TableMimeData *tableData =                           qobject_cast(event>mimeData());          if (tableData) {           const QTableWidget *otherTable =  tableData->tableWidget(); 第 220 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理         QTableWidgetSelectionRange  otherRange = tableData->range();         // ...            event->acceptProposedAction();       } else if  (event->mimeData()->hasFormat("text/csv")) {               QByteArray   csvData  =  event->mimeData()->data("text/csv");          QString csvText = QString::fromUtf8(csvData);           // ...            event->acceptProposedAction();       } else if  (event->mimeData()->hasFormat("text/plain")) {            QString plainText = event->mimeData()->text();           // ...            event->acceptProposedAction();       }        QTableWidget::mouseMoveEvent(event);   }  在放置的函数中,我们需要 按照我们自己定义的数据类型进行选择。我们使用 qobject_cast 宏进行类型转换。如果成功,说明数据来自同一应用程序,因此我们直接设置 QTableWidget 相关 数据,如果转换失败,我们则使用一般的处理方式。 Qt 学习之路(55): 剪贴板操作   剪贴板的操作经常和前面所说的拖放技术在一起使用,因此我们现在先来说说剪贴板的 第 221 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 相关操作。   大家对剪贴板都很熟悉。我 们可以简单的把它理解成一个数据的存储池,可以把外面的 数据放置进去,也可以把里面的数据取出来。剪贴板是由操作系统维护的,所以这提供了跨 应用程序数据 交互的一种方式。Qt 已经为我们封装好很多关于剪贴板的操作,因此我们可 以在自己的应用中很容易的实现。下面还是从代码开始:    clipboarddemo.h #ifndef CLIPBOARDDEMO_H   #define  CLIPBOARDDEMO_H     #include      class  ClipboardDemo : public QWidget   {       Q_OBJECT     public:        ClipboardDemo(QWidget *parent = 0);     private slots:        void setClipboard();       void getClipboard();   };     #endif  // CLIPBOARDDEMO_H    clipboarddemo.cpp #include     #include "clipboarddemo.h"   第 222 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理   ClipboardDemo::ClipboardDemo(QWidget  *parent)       : QWidget(parent)   {       QVBoxLayout  *mainLayout = new QVBoxLayout(this);       QHBoxLayout *northLayout =  new QHBoxLayout;       QHBoxLayout *southLayout = new QHBoxLayout;          QTextEdit *editor = new QTextEdit;       QLabel *label = new  QLabel;       label->setText("Text Input: ");        label->setBuddy(editor);       QPushButton *copyButton = new  QPushButton;       copyButton->setText("Set Clipboard");        QPushButton *pasteButton = new QPushButton;        pasteButton->setText("Get Clipboard");          northLayout->addWidget(label);        northLayout->addWidget(editor);        southLayout->addWidget(copyButton);        southLayout->addWidget(pasteButton);        mainLayout->addLayout(northLayout);        mainLayout->addLayout(southLayout);         connect(copyButton,  SIGNAL(clicked()), this, SLOT(setClipboard()));      connect(pasteButton, SIGNAL(clicked()), this, SLOT(getClipboard())); }    第 223 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理   void ClipboardDemo::setClipboard()   {       QClipboard  *board = QApplication::clipboard();       board->setText("Text  from Qt Application");   }     void  ClipboardDemo::getClipboard()   {       QClipboard *board =  QApplication::clipboard();       QString str = board->text();        QMessageBox::information(NULL, "From clipboard", str);   }     main.cpp #include "clipboarddemo.h"     #include     #include      int main(int  argc, char *argv[])   {       QApplication a(argc, argv);        ClipboardDemo w;       w.show();       return a.exec();   }      main() 函数很简单,就是把我们的 ClipboardDemo 类显示了出来。我们重点来看 ClipboardDemo 中的代码。 第 224 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理    构造函数同样没什么复杂的内容,我们把一个 label。一个 textedit 和两个 button 摆放到窗口中。这些代码已经能够很轻易的写出来了;然后进行了信号槽的连接。 void  ClipboardDemo::setClipboard()   {       QClipboard *board =  QApplication::clipboard();       board->setText("Text from Qt  Application");   }     void ClipboardDemo::getClipboard()   {        QClipboard *board = QApplication::clipboard();        QString str = board->text();       QMessageBox::information(NULL,  "From clipboard", str);   }    在 slot 函数中,我们使用 QApplication::clipboard() 函数访问到系统剪贴板。这个 函 数 的 返 回 值 是 QClipboard 的 指 针 。 我 们 可 以 从 这 个 类 的 API 中 看 到 , 通 过 setText(),setImage() 或者 setPixmap() 函数可以将数据放置到剪贴板内,也就是通常 所说的剪贴或者复制的操作;使用 text(),image() 或者 pixmap() 函数则可以从剪贴板 获得数据,也就是粘贴。    另外值得 说的是,通过上面的 例子可以看 出, QTextEdit 默认就是 支持 Ctrl+C, Ctrl+V 等快捷键操作的。不仅如此,很多 Qt 的组件都提供了很方便的操作,因此我们需要 从文档中获取具体的信息,从而避免自己重新去发明轮子。   QClipboard 提供的数据类型很少,如果需要,我们可以继承 QMimeData 类,通过调用 setMimeData() 函数让剪贴板能够支持我们自己的数据类型。   在 X11 系统中,鼠标中键(一般就是滚轮)可以支持剪贴操作的。为了实现这一功能,我 们需要向 QClipboard::text() 函数传递 QClipboard::Selection 参数。例如,我们在鼠标 按键释放的事件中进行如下处理: void  MyTextEditor::mouseReleaseEvent(QMouseEvent *event)   第 225 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 {        QClipboard *clipboard = QApplication::clipboard();       if  (event->button() == Qt::MidButton               &&  clipboard->supportsSelection()) {           QString text =  clipboard->text(QClipboard::Selection);           pasteText(text);        }   }    这里的 supportsSelection() 在 X11 平台返回 true,其余平台都是返回 false 的。 另外,QClipboard 提供了 dataChanged() 信号,以便监听剪贴板数据变化 第 226 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 Qt 学习之路(56): 二进制文件读写   今天开始进入 Qt 的另一个部分:文件读写,也就是 IO。文件读写在很多应用程序中都 是需要的。Qt 通过 QIODevice 提供了 IO 的抽象,这种设备(device)具有读写字节块的能力。 常用的 IO 读写的类包括以下几个: QFlie 访 问本地文件系统或者嵌入资源 QTemporaryFile 创建和访问本地文件系统的临时文件 QBuffer 读 写 QByteArray QProcess 运行外部程序,处理进程间通讯 QTcpSocket TCP 协议网络数据传输 QUdpSocket 传输 UDP 报文 QSslSocket 使 用 SSL/TLS 传输数据   QProcess、QTcpSocket、 QUdpSoctet 和 QSslSocket 是顺序访问设备,它们的数据只能 访问一遍,也就是说,你只能从第一个字节开始访问,直到最后一个字节 QFile、QTemporaryFile 和 QBuffer 是随机访问设备,你可以从任何位置访问任意次数,还 可以使用 QIODevice::seek() 函数来重新定位文件指针。    在访问方式上,Qt 提供了两个更高级别的抽象:使用 QDataStream 进行二进制方式 的访问和使用 QTextStream 进行文本方式的访问。这些类可以帮助我们控制字节顺序和文本 编码,使程序员从这种问题中解脱出来。   QFile 对于访问独立的文件是非常方便的,无论是在文件系统中还是在应用程序的资源 文件中。Qt 同样也提供了 QDir 和 QFileInfo 两个类,用于处理文件夹相关事务以及查看 文件信息等。   这次我们先从二进制文件的读写说起。   以二进制格式访问数据的最 简单的方式是实例化一个 QFile 对象,打开文件,然后使 用 QDataStream 进行访问。QDataStream 提供了平台独立的访问数据格式的方法,这些数据 格 式 包 括 标 准 的 C++ 类 型 , 如 int 、 double 等 ; 多 种 Qt 类 型 , 如 QByteArray、QFont、QImage、QPixmap、QString 和 QVariant,以及 Qt 的容器类,如 QList 和 QMap。先看如下的代码: QImage  image("philip.png");     QMap map;   第 227 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 map.insert("red",  Qt::red);   map.insert("green", Qt::green);   map.insert("blue",  Qt::blue);     QFile file("facts.dat");   if  (!file.open(QIODevice::WriteOnly)) {       std::cerr << "Cannot  open file for writing: "                <<  qPrintable(file.errorString()) << std::endl;     return;   }      QDataStream out(&file);   out.setVersion(QDataStream::Qt_4_3);      out << quint32(0x12345678) << image << map;     这里,我们首先创建了一个 QImage 对象,一个 QMap,然后使用 QFile 创建了一个名为 "facts.dat" 的文件,然后以只写方式打开。如果打开失败,直接 return;否则我们使用 QFile 的指针创建一个 QDataStream 对象,然后设置 version,这 个我们以后再详细说明,最后就像 std 的 cout 一样,使用 << 运算符输出结果。   0x12345678 成为“魔术数字”,这是二进制文件输出中经常使用的一种技术。我们定义 的二进制格式通常具有一个这样的“魔术数字”,用于标志文件格式。例如,我们在文件 最 开始写入 0x12345678,在读取的时候首先检查这个数字是不是 0x12345678,如果不是的话, 这就不是可识别格式,因此根本不需要去读取。一般二进制格式都会有这么一个魔术数字, 例如 Java 的 class 文件的魔术数字就是 0xCAFE BABE(很 Java 的名字),使用二进制查 看器就可以查看。魔术数字是一个 32 位的无符号整数,因此我们使用 quint32 宏来得到一 个平台无关的 32 位无符号整数。   在这段代码中我们使用了一个 qPrintable() 宏,这个宏实际上是把 QString 对象转 换成 const char *。注意到我们使用的是 C++ 标准错误输出 cerr,因此必须使用这个转换。 第 228 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 当然,QString::toStdString() 函数也能够完成同样的操作。   读取的过程就很简 单了,需要注意的是读取必须同写入的过程一一对应,即第一个写 入 quint32 型的魔术数字,那么第一个读出的也必须是一个 quint32 格式的数据,如 quint32 n;   QImage image;   QMap map;     QFile file("facts.dat");   if  (!file.open(QIODevice::ReadOnly)) {       std::cerr << "Cannot  open file for reading: "                <<  qPrintable(file.errorString()) << std::endl;     return;   }      QDataStream in(&file);   in.setVersion(QDataStream::Qt_4_3);      in >> n >> image >> map;    好 了,数据读出了,拿着到处去用吧!   这个 version 是干什么用的呢?对于二进制的读写,随着 Qt 的版本升级,可能相同 的内容有了不同的读写方式,比如可能由大端写入变成了小端写入等,这样的话旧版本 Qt 写入的内容就不能正确的读出,因此需要设定一个版本号。比如这里我们使用 QDataStream::Qt_4_3,意思是,我们使用 Qt 4.3 的方式写入数据。实际上,现在的最高版 本号已经是 QDataStream::Qt_4_6。如果这么写,就是说,4.3 版本之前的 Qt 是不能保证 正确读写文件内容的。那么,问题就来了:我们以硬编码的方式写入这个 version,岂不是 不能使用最新版的 Qt 的读写了?    解决方法之一是,我们不仅仅写入一个魔术数字,同时写入这个文件的版本。例如: 第 229 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 QFile  file("file.xxx");   file.open(QIODevice::WriteOnly);   QDataStream  out(&file);     // Write a header with a "magic number" and a  version   out << (quint32)0xA0B0C0D0;   out <<  (qint32)123;     out.setVersion(QDataStream::Qt_4_0);     //  Write the data   out << lots_of_interesting_data;     这个 file.xxx 文件的版本号是 123。我们认为,如果版本号是 123 的话,则可以使 用 Qt_4_0 版本读取。所以我们的读取代码就需要判断一下: QFile file("file.xxx");     file.open(QIODevice::ReadOnly);    QDataStream in(&file);       // Read and check the header    quint32 magic;    in >>  magic;    if (magic != 0xA0B0C0D0)        return  XXX_BAD_FILE_FORMAT;      // Read the version    qint32  version;    in >> version;    if (version < 100)         return XXX_BAD_FILE_TOO_OLD;   第 230 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理  if (version > 123)         return XXX_BAD_FILE_TOO_NEW;      if (version <= 110)         in.setVersion(QDataStream::Qt_3_2);    else        in.setVersion(QDataStream::Qt_4_0);      // Read the data    in  >> lots_of_interesting_data;    if (version >= 120)         in >> data_new_in_XXX_version_1_2;    in >>  other_interesting_data;  这样,我们就可以比较完美的处理二进制格式的数据读写了 Qt 学习之路(57): 文本文件读写   二进制文件比较小巧,但是不是人可读的格式。文本文件是一种人可读的格式的文件, 为了操作这种文件,我们需要使用 QTextStream 类。 QTextStream 和 QDataStream 的使用类 第 231 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 似,只不过它是操作纯文本文件的。还有一些文本格式,比如 XML、HTML,虽然可以由 QTextStream 生成,但 Qt 提供了更方便的 XML 操作类,这里就不包括这部分内容了。   QTextStream 会自动将 Unicode 编码同操作系统的编码进行转换,这一操作对程序员 是透明的。它也会将换行符进行转换,同样不需要你自己去处理。QTextStream 使用 16 位的 QChar 作为基础的数据存储单位,同样,它也支持 C++标准类型,如 int 等。实际上,这是将 这种标准类型与字符串进行了相互转换。    QTextStream 同 QDataStream 使用基本一致,例如下面的代码将把“Thomas M. Disch: 334/n”写入到 tmp.txt 文件中: QFile file("sf-book.txt");   if  (!file.open(QIODevice::WriteOnly)) {       std::cerr << "Cannot  open file for writing: "                <<  qPrintable(file.errorString()) << std::endl;     return;   }      QTextStream out(&file);   out << "Thomas M.  Disch: " << 334 << endl;    可以看到,这段代码同前面的 QDataStream 相关代码基本雷同。文本文件的写入比较容 易,但是读出就不那么简单了。例如, out  << "Denmark" << "Norway";    是我们写入的代码。我们分别写入两个单 词,然后试图以与二进制文件读出的格式相同 的形式读出: in >> str1 >> str2;     上面两段代码的 out 和 in 都是 QTextStream 类型的。虽然我们可以正常写入,但读 出的时候,str1 里面将是 DenmarkNorway,str2 是空的。以文本形式写入数据,是不能区 分数据的截断位置的。因为使用 QDataStream 写入的时候,实际上是要在字符串前面写如长 第 232 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 度信息的。因此,对于文本文件,更多的是一种全局性质的操作,比如使用 QTextStream::readLine() 读取一行,使用 QTextStream::readAll() 读取所有文本,之后 再对获得的 QString 对象进行处理。    默认 情况下, QTextStream 使用操作系统的本地编码进行读写。不过你可以使用 setCodec() 函数进行设置,比如 stream.setCodec("UTF-8");     同 类似,QTextStream 也提供了一些用于格式化输出的描述符,称为 stream manipulators。这些描述符放置在输出内容之前,或者是使用相应的函数,用于对后 面的输出内容做格式化。具体的描述符如下 setIntegerBase(int) 0 读出时自动检测数字前缀 2 二 进制 8 八进制 10 十进制 16 十 六进制 setNumberFlags(NumberFlags) ShowBase 显 示前缀,二进制显示 0b,八进制显示 0,十六进制显示 0x ForceSign 在实数前面显示符号 ForcePoint 在 数字中显示点分隔符 UppercaseBase 使用大写的前缀,如 0B, 0X UppercaseDigits 使 用大写字母做十六进制数字 setRealNumberNotation(RealNumberNotation) FixedNotation 定 点计数表示,如 0.000123 ScientificNotation 科学计数法表示,如 1.23e-4 SmartNotation 定点或科学计数法表示,自动选择简洁的一种表示法 setRealNumberPrecision(int) 设 置生成的最大的小数位数,默认是 6 setFieldWidth(int) 设 置一个字段的最小值,默认是 0 setFieldAlignment(FieldAlignment) AlignLeft 左 对齐 AlignRight 右对齐 AlignCenter 中 间对齐 AlignAccountingStyle 符号和数字之间对齐 setPadChar(QChar) 设 置对齐时填充的字符,默认是空格   比如,下面的代码 out  << showbase << uppercasedigits << hex <<  12345678;  第 233 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理   将输出 0xBC614E。或者我们可以这样去写: out.setNumberFlags(QTextStream::ShowBase  | QTextStream::UppercaseDigits);   out.setIntegerBase(16);   out  << 12345678;    QTextStream 不仅仅可以输出到 QIODevice 上,也可以输出到 QString 上面,例如 QString str;   QTextStream(&str)  << oct << 31 << " " << dec << 25 <<  endl;  Qt 学习之路(58): 进程间交互   所谓 IO 其实不过是与其他设备之间的数据交互。在 Linux 上这个概念或许会更加清楚 一些。Linux 把所有设备都看作是一种文件,因此所有的 IO 都归结到对文件的数据交互。同 样,与其他进程之间也存在着数据交互,这就是进程间交互。    为什么需要进程间交互呢?Qt 虽然是一个很庞大的库,但是也不能面面俱到。每个需 求都提供一种解决方案是不现实的。比如操作系统提供了查看当前文件夹下所有文件的命令 (Windows 下是 dir, Linux 下是 ls),那么 Qt 就可以通过调用这个命令获取其中的信息。 当然这不是一个很恰当的例子,因为 Qt 同样提供了相同的操作。不过,如果你使用版本控 制系统,比如 SVN,然后你希望通过 SVN 的版本号生成自己系统的 build number,那么就 不得不调用 svn 命令获取当前仓库的版本号。这些操作都涉及到进程间交互。   Qt 使用 QProcess 类完成进程间交互。我们从例子开始看起。由于比较简单,所以没有 把 main() 函数贴上来,大家顺手写下就好的啦!    mainwindow.h #ifndef MAINWINDOW_H   #define MAINWINDOW_H    第 234 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理   #include      class MainWindow : public  QMainWindow   {       Q_OBJECT     public:        MainWindow(QWidget *parent = 0);       ~MainWindow();     private  slots:       void openProcess();     private:        QProcess *p;   };     #endif // MAINWINDOW_H    mainwindow.cpp #include "mainwindow.h"     MainWindow::MainWindow(QWidget  *parent)       : QMainWindow(parent)   {       p = new  QProcess(this);       QPushButton *bt = new QPushButton("execute  notepad", this);       connect(bt, SIGNAL(clicked()), this,  SLOT(openProcess()));   }   第 235 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理   MainWindow::~MainWindow()   {      }     void MainWindow::openProcess()   {        p->start("notepad.exe");   }    这个窗口很简单,只有一个按钮,当你点击按钮 之后,程序会调用 Windows 的记事本。 这里我们使用的是 p->start("notepad.exe");     语句。QProcess::start() 接受两个参数,第一个是要执行的命令或者程序,这里就是 notepad.exe;第二个是一个 QStringList 类型的数据,也就是需要传递给这个程序的运行 参数。注意,这个程序是需要能够由系统找到的,一般是完全路径。但是这里为什么只有 notepad.exe 呢?因为这个程序实际是放置在 Windows 系统文件夹下,是已经添加到了系 统路径之中,因此不需要再添加本身的路径。    下面我们再看一个更复杂的例子,调用一个系统命令,这里我使用的是 Windows,因 此需要调用 dir;如果你是在 Linux 进行编译,就需要改成 ls 了。   mainwindow.h #ifndef MAINWINDOW_H   #define  MAINWINDOW_H     #include      class  MainWindow : public QMainWindow   {   第 236 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理     Q_OBJECT     public:        MainWindow(QWidget *parent = 0);       ~MainWindow();     private  slots:       void openProcess();       void readResult(int  exitCode);     private:       QProcess *p;   };     #endif  // MAINWINDOW_H    mainwindow.cpp #include "mainwindow.h"     MainWindow::MainWindow(QWidget  *parent)       : QMainWindow(parent)   {       p = new  QProcess(this);       QPushButton *bt = new QPushButton("execute  notepad", this);       connect(bt, SIGNAL(clicked()), this,  SLOT(openProcess()));   }     MainWindow::~MainWindow()   {    第 237 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理   }     void MainWindow::openProcess()   {        p->start("cmd.exe", QStringList() << "/c" << "dir");        connect(p, SIGNAL(finished(int)), this, SLOT(readResult(int)));   }      void MainWindow::readResult(int exitCode)   {        if(exitCode == 0) {           QTextCodec* gbkCodec =  QTextCodec::codecForName("GBK");           QString result =  gbkCodec->toUnicode(p->readAll());            QMessageBox::information(this, "dir", result);       }   }      我们仅增加了一个 slot 函数。在按钮点击的 slot 中,我们通过 QProcess::start() 函数运行了指令    cmd.exe /c dir   这里是说,打开系统的 cmd 程序,然后运行 dir 指令。如果有对参数 /c 有疑问,只 好去查阅 Windows 的相关手册了哦,这已经不是 Qt 的问题了。然后我们 process 的 finished() 信号连接到新增加的 slot 上面。这个 signal 有一个参数 int。我们知道,对 于 C/C++ 程序而言,main() 函数总是返回一个 int,也就是退出代码,用于指示程序是否 正常退出。这里的 int 参数就是这个退出代码。在 slot 中,我们检查退出代码是否是 0, 一般而言,如果退出代码为 0,说明是正常退出。然后把结果显示在 QMessageBox 中。怎么 做到的呢?原来,QProcess::readAll() 函数可以读出程序输出内容。我们使用这个函数将 所有的输出获取之后,由于它的返回结果是 QByteArray 类型,所以再转换成 QString 显 示出来。另外注意一点,中文本 Windows 使用的是 GBK 编码,而 Qt 使用的是 Unicode 编 第 238 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 码,因此需要做一下转换,否则是会出现乱码的,大家可以尝试一下。 好了,进程间交互就说这么说,通过查看文档你可以找到如何用 QProcess 实现进程过 程的监听,或者是令 Qt 程序等待这个进程结束后再继续执行的函数。 Qt 学习之路(59): 编写跨平台的程序   看到评论里面有朋友抱怨以前文章里面的例子都是 for Windows 的,没有 for Linux 或者 for Mac 的。那么今天就来说说跨平台的问题吧!   所谓跨平台,其实有两种含义,一是跨硬件平台,一是跨软件平台。对于硬件平台,很 多时候我们都 会不自觉的忽略掉,因为硬件差异虽然很大,但是我们能够接触到的却很少。 目前 PC 系列基本都是兼容的,并且编译器可能会帮助我们完成这个问题,因此如果你的程 序没有用到汇编语言,基本很难考虑到这种跨平台的支持。但是,如果你的程序需 要接触到 硬件,不管是因为功能的需要还是因为性能的需要,就不得不考虑这个问题。比如 , Photoshop 的颜色处理直接使用汇编语言编写,以达到最好性能,这就不得不考虑硬件相关 的问题。通常我们说跨平台,更多的只软件的跨平台,也就是跨操作系统。现在主流 操作系 统 Windows,Linux/Unix,基本算作是两大阵营吧,它们之间的软件都是不兼容的,所以这 种跨平台是我们接触比较多的。    说起跨平台,就不得不提 Java。这是 Java 的卖点之一:“一次编写,到处运行” 。 Java 之所以能够实现跨平台,是因为 Java 源代码编译成一种中间代码,运行 Java 程序, 实际上是在 JVM 中。你编写出的 Java 程序是跨平台的,但是 JVM 不是跨平台的,必须根 据你的操作系统选择 JVM。这是适配器模式的典型应用 :-)   选择一个本身就是跨平台的库,你的工作量就会小很 多,比如 Qt。Qt 已经帮我们封装 第 239 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 好很多平台相关的代码。如果你打开一个 Qt 的源代码,就可能找到很多关于操作系统的判 断。简单来说,我们使用 QMainWindow::show() 就可以显示一个窗口。在 Windows 上,Qt 必须调用 Win32 API 完成;在 Linux 上,你就可能需要调用 GNOME 或者 KDE 的 API。但 是,无论如何,这部分代码都不是我们关心的,因为 Qt 已经替我们完成了。所以,如果你 的程序没有与平台相关的代码,那么只需要在 Windows 上编译成功,然后拿到 Linux 上重 新编译一下,通过一些简单测试,或者还需要调整一下 UI 比例等等,就可以拿去发行了。 编缉推荐阅读以下文章   但是,如果有部分代码不得不依赖操作系统,比如我们调用列出目录的命令, Windows 下是 dir,而 Linux 下是 ls,这就不得不根据平台进行编译了。这里我们就拿这个例子尝 试一下。   mainwindow.h #ifndef  MAINWINDOW_H   #define MAINWINDOW_H     #include       class QProcess;     class MainWindow  : public QMainWindow   {       Q_OBJECT     public:        MainWindow(QWidget *parent = 0);       ~MainWindow();     private  slots:       void openProcess();   第 240 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理     void readResult(int  exitCode);     private:       QProcess *p;   };     #endif  // MAINWINDOW_H    mainwindow.cpp #include     #include    #include     #include      #include  "mainwindow.h"     MainWindow::MainWindow(QWidget *parent)        : QMainWindow(parent)   {       p = new QProcess(this);        QPushButton *bt = new QPushButton("display", this);        connect(bt, SIGNAL(clicked()), this, SLOT(openProcess()));   }     MainWindow::~MainWindow()    {     }     void MainWindow::openProcess()   第 241 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 {   #if  defined(Q_OS_WIN32)       p->start("cmd.exe", QStringList()  << "/c" << "dir");   #elif defined(Q_OS_LINUX)        p->start("ls", QStringList() << "/home/usr_name");   #endif        connect(p, SIGNAL(finished(int)), this, SLOT(readResult(int)));    }     void MainWindow::readResult(int exitCode)   {        if(exitCode == 0) {   #if defined(Q_OS_WIN32)            QTextCodec* gbkCodec = QTextCodec::codecForName("GBK");            QString result = gbkCodec->toUnicode(p->readAll());   #elif  defined(Q_OS_LINUX)               QTextCodec*   utfCodec  =   QTextCodec::codecForName("UTF-8");         QString result =  utfCodec->toUnicode(p->readAll());   #endif            QMessageBox::information(this, "dir", result);       }   }      例子和前面是一样的,我们只是在 cpp 文件中添加了一些代码。我们使用 #if 这些预 处理指令通过判断 Q_OS_WIN32 这样的宏是否存在,来生成相应的平台相关的代码。这些宏 是在 Qt 中定义好的,我们只需根据它们是否存在进行判断。这样,我们的程序就可以在 Windows 和 Linux 下都可以编译运行。   进行跨平台编程,你不得不需要接触到一些平台相关的东西,比如文字符编码等,都是 需要好哈学习的。所以建议是尽可能使用 Qt 提供的标准函数进行编程,这样就不必写一大 第 242 页 共 243 页 整理:DZY Qt 学习之路 DZY 整理 堆 if 判断,代码也更加清晰 第 243 页 共 243 页 整理:DZY

    Top_arrow
    回到顶部
    EEWORLD下载中心所有资源均来自网友分享,如有侵权,请发送举报邮件到客服邮箱bbs_service@eeworld.com.cn 或通过站内短信息或QQ:273568022联系管理员 高进,我们会尽快处理。