药品加工厂做网站,安装wordpress 此网页包含重定向循环,产品网络推广方案,如何建设盈利网站目录
一、Qt概述
1.1 什么是Qt
1.2 Qt的发展史
1.3 Qt的优势
1.4 Qt版本
1.5 成功案例
二、创建Qt项目
2.1 使用向导创建
2.2 一个最简单的Qt应用程序
2.2.1 main函数中
2.2.2 类头文件
2.3 .pro文件
2.4 命名规范
2.5 QtCreator常用快捷键
三、Qt按钮小程序
… 目录
一、Qt概述
1.1 什么是Qt
1.2 Qt的发展史
1.3 Qt的优势
1.4 Qt版本
1.5 成功案例
二、创建Qt项目
2.1 使用向导创建
2.2 一个最简单的Qt应用程序
2.2.1 main函数中
2.2.2 类头文件
2.3 .pro文件
2.4 命名规范
2.5 QtCreator常用快捷键
三、Qt按钮小程序
3.1按钮的创建和父子关系
3.2 Qt窗口坐标体系
3.3 对象树模型
四、信号与槽机制
4.1 系统自带的信号和槽
4.2 自定义信号和槽
4.2.1自定义信号使用条件
4.2.2 自定义槽函数使用条件
4.2.3 使用自定义信号和槽
4.3 信号和槽的扩展
4.4 Qt4版本的信号槽写法
4.5 Lambda 表达式
4.5.1 局部变量引入方式
4.5.2 函数参数
4.5.3 选项 Opt
4.5.4 函数返回值 -retType
4.5.5 是函数主体{}
4.5.6 槽函数使用 Lambda 表达式
五、QMainWindow
5.1 菜单栏
5.2 工具栏
5.3 状态栏
5.4 停靠部件也成为铆接部件、浮动窗口
5.5 核心部件中心部件
5.6 使用 UI 文件创建窗口
5.6.1 UI 设计窗口介绍
5.6.2 菜单栏
5.6.3 工具栏
5.6.4 状态栏
5.6.5 停靠部件
5.6.6 核心部件
5.7 UI 文件管理
5.8 UI 文件下使用信号和槽
5.8.1 转到槽
5.8.2 信号槽编辑器
5.9 资源文件
六、QDialog 对话框
6.1 基本概念
6.1.1 模态对话框
6.2 标准对话框
6.3 消息对话框
6.4 标准文件对话框
七、布局
7.1 系统提供的布局控件
7.2 利用 widget 做布局
八、常用控件
8.1 QLabel 控件使用
8.1.1 显示文字普通文本、html
8.1.2 显示图片
8.1.3 显示动画
8.2 QLineEdit
8.2.2 设置显示模式
8.3 其他控件
8.4 自定义控件
九、Qt消息事件机制
9.1 事件
9.2 事件处理函数
9.4 事件过滤器
9.5 消息事件机制和信号和槽机制的关系
9.6 总结
十、绘图事件和绘图设备
10.1QPainter
10.2 绘图设备
10.2.1 QPixmap 、QBitmap、QImage
10.2.2 QPicture
十一、文件操作
11.1 基本文件操作
11.2 二进制文件读写
11.3 文本文件读写 有关技术欢迎加入QQ群探讨
一个人可以走得很快一群人可以走得很远。
746491142
一、Qt概述
1.1 什么是Qt Qt是一个跨平台的C图形用户界面应用程序框架。它为应用程序开发者提供建立图形界面所需的所有功能。它是完全面向对象的很容易扩展并且允许真正的组件编程。 1.2 Qt的发展史 1991年 Qt最早由芬兰奇趣科技开发1996年 进入商业领域它也是目前流行的Linux桌面环境KDE的基础2008年 奇趣科技被诺基亚公司收购Qt称为诺基亚旗下的编程基础2012年 Qt又被Digia公司芬兰一家软件公司收购2014年4月 跨平台的集成开发环境Qt Creator3.1.0发布同年5月20日配发了Qt5.3正式版至此Qt实现了对iOS、Android、WP等各平台的全面支持。 1.3 Qt的优势 1)跨平台几乎支持所有的平台 2)接口简单容易上手学习QT框架对学习其他框架有参考意义。 3)一定程度上简化了内存回收机制 4)开发效率高能够快速的构建应用程序。 5)有很好的社区氛围市场份额在缓慢上升。 6)可以进行嵌入式开发。 1.4 Qt版本
Qt按照不同的版本发行分为商业版和开源版
商业版 为商业软件提供开发他们提供传统商业软件发行版并且提供在商业有效期内的免费升级和技术支持服务。 开源的LGPL协议版本 为了开发自有而设计的开放源码软件它提供了和商业版本同样的功能在GNU通用公共许可下它是免费的。 目前我们学习上使用的就是这个版本。可以到官网下载最新版本 Index of /archive/qthttp://download.qt.io/archive/qt/
1.5 成功案例 Linux桌面环境KDEK Desktop EnvironmentWPS Office 办公软件Skype 网络电话Google Earth 谷歌地球VLC多媒体播放器VirtualBox虚拟机软件等等 1.6近期就业行情 二、创建Qt项目
2.1 使用向导创建
打开Qt Creator 界面选择 New Project或者选择菜单栏 【文件】-【新建文件或项目】菜单项 弹出New Project对话框选择Qt Widgets Application 选择【Choose】按钮弹出如下对话框 设置项目名称和路径按照向导进行下一步 选择编译套件 向导会默认添加一个继承自QMainWindow的类可以在此修改类的名字和基类。默认的基类有QMainWindow、QWidget以及QDialog三个我们可以选择QWidget类似于空窗口这里我们可以先创建一个不带UI的界面继续下一步
系统会默认给我们添加main.cpp、mywidget.cpp、 mywidget.h和一个.pro项目文件点击完成即可创建出一个Qt桌面程序。 2.2 一个最简单的Qt应用程序
2.2.1 main函数中
#include widget.h
#include QApplicationint main(int argc, char *argv[])
{QApplication a(argc, argv);Widget w;w.show();return a.exec();
}
解释 1.Qt系统提供的类头文件没有.h后缀 2.Qt一个类对应一个头文件类名和头文件名一致 3.QApplication应用程序类 管理图形用户界面应用程序的控制流和主要设置。 是Qt生命一个程序要确保一直运行就肯定至少得有一个循环这就是Qt主消息循环在其中完成来自窗口系统和其它资源的所有事件消息处理和调度。它也处理应用程序的初始化和结束并且提供对话管理。 对于任何一个使用Qt的图形用户界面应用程序都正好存在一个QApplication 对象不论这个应用程序在同一时刻有多少个窗口。 4. a.exec() 程序进入消息循环等待对用户输入进行响应。这里main()把控制权转交给QtQt完成事件处理工作当应用程序退出的时候exec()的值就会返回。在exec()中Qt接受并处理用户和系统的事件并且把它们传递给适当的窗口部件。 2.2.2 类头文件
#include QWidgetclass MyWidget : public QWidget
{//引入Qt信号和槽机制的一个宏Q_OBJECTpublic://构造函数中parent是指父窗口//如果parent是0那么窗口就是一个顶层的窗口MyWidget (QWidget *parent 0);~ MyWidget ();
};
2.3 .pro文件 .pro就是工程文件(project)它是qmake自动生成的用于生产makefile的配置文件。类似于VS中的.sln 和vsproj文件。 以下是.pro文件的一个案例
#引入Qt的模块core gui
QT core gui
#如果qt版本大于4那么引入widgets模块
greaterThan(QT_MAJOR_VERSION, 4): QT widgets
#生成最终文件的文件名可执行文件exe
TARGET 01_MyWidget
#项目类型生成什么类型的文件可执行程序还是库文件
TEMPLATE app
#要编译的源文件列表
SOURCES \main.cpp \mywidget.cpp
#要编译的头文件列表
HEADERS \mywidget.h.pro文件的规则 1.注释 从“#”开始到这一行结束。 2.模块引入 QT 模块名表示当前项目引入Qt哪些模块。 引入模块的意思就简单理解为引入C/C头文件搜索路径如果没引入对应模块就使用该头文件的话会报错说找不到该头文件。当然不必要的模块还是别引入因为引入模块不仅仅是引入头文件搜索路径那么简单还包括引入连接的库等一系列操作会让程序变臃肿。 3. 模板变量告诉qmake为这个应用程序生成哪种makefile。下面是可供使用的选择TEMPLATE app app -建立一个应用程序的makefile。这是默认值所以如果模板没有被指定这个将被使用。 lib - 建立一个库的makefile。 vcapp - 建立一个应用程序的VisualStudio项目文件。 vclib - 建立一个库的VisualStudio项目文件。 subdirs -这是一个特殊的模板它可以创建一个能够进入特定目录并且为一个项目文件生成makefile并且为它调用make的makefile。 4.指定生成的应用程序名 TARGET QtDemo 5.工程中包含的头文件 HEADERS include/painter.h 6.工程中包含的.ui设计文件 FORMS forms/painter.ui 7.工程中包含的源文件 SOURCES sources/main.cpp sources 8.工程中包含的资源文件 RESOURCES qrc/painter.qrc 9.greaterThan(QT_MAJOR_VERSION, 4): QT widgets 这条语句的含义是如果QT_MAJOR_VERSION大于4也就是当前使用的Qt5及更高版本需要增加widgets模块。如果项目仅需支持Qt5也可以直接添加“QT widgets”一句。不过为了保持代码兼容最好还是按照QtCreator生成的语句编写。 10.配置信息 CONFIG用来告诉qmake关于应用程序的配置信息。 CONFIG c11 //使用c11的特性qt5.6以上版本默认使用C11 在这里使用“”是因为我们添加我们的配置选项到任何一个已经存在中。这样做比使用“”那样替换已经指定的所有选项更安全。 2.4 命名规范 类名单词首字母大写单词和单词之间直接连接无需连接字符 。 MyClassQPushButtonclass MainWindowQt中内置的类型头文件和类命名同名。
#include QStringQSring str;#include QWidgetQWidget w;函数名字变量名首字母小写之后每个单词首字母大写单词和单词之间直接连接无需连接字符 void connectTheSignal(); 类的成员变量设置函数用使用 set成员变量名获取成员变量的函数直接用成员变量名如果是bool类型有可能会用一些表示状态的术语如isVisilblehasFocus //普通成员变量设置和获取void setText(QString text);QString text()const;//bool的成员变量设置和获取void setEnabled(bool enabled);bool isEnabled()const;2.5 QtCreator常用快捷键 运行 ctrl R 编译 ctrl B 帮助文档 F1 点击F1两次跳到帮助界面 跳到符号定义 F2 或者ctrl 鼠标点击 注释 ctrl/ 字体缩放 ctrl 鼠标滚轮 整行移动代码 ctrl shift ↑或↓ 自动对齐 ctrl i 同名之间的.h和.cpp文件跳转 F4 三、Qt按钮小程序
3.1按钮的创建和父子关系 在Qt程序中最常用的控件之一就是按钮了首先我们来看下如何创建一个按钮
#include QPushButtonQPushButton * btn new QPushButton; //设置父亲btn-setParent(this);//设置文字btn-setText(德玛西亚);//移动位置btn-move(100,100);//第二种创建QPushButton * btn2 new QPushButton(孙悟空,this);//重新指定窗口大小this-resize(600,400);//设置窗口标题this-setWindowTitle(第一个项目);//限制窗口大小this-setFixedSize(600,400);上面代码中一个按钮其实就是一个QPushButton类的对象如果只是创建出对象是无法显示到窗口中的所以我们需要依赖一个父窗口也就是指定一个父亲利用setParent函数或者按钮创建的时候通过构造函数传参此时我们称两个窗口建立了父子关系。在有父窗口的情况下窗口调用show会显示在父窗口中如果没有父窗口那么窗口调用show显示的会是一个顶层的窗口顶层窗口是能够在任务栏中找到的不依赖于任何一个窗口而独立存在按钮也是继承于QWidget也属于窗口。 如果想设置按钮上显示的文字可以用setText移动按钮位置用move。 对于窗口而言我们可以修改左上角窗口的标题setWindowTitle重新指定窗口大小resize或者设置固定的窗口大小setFixedSize。 3.2 Qt窗口坐标体系 通过以上代码可以看出Qt的坐标体系。 以左上角为原点0,0以向右的方向为x轴的正方向以向下方向为y轴的正方向。 对于嵌套窗口其坐标是相对于父窗口来说的。顶层窗口的父窗口就是屏幕。 3.3 对象树模型 QObject是Qt里边绝大部分类的根类 1.QObject对象之间是以对象树的形式组织起来的。 当两个QObject或子类的对象建立了父子关系的时候。子对象就会加入到父对象的一个成员变量叫children孩子的list列表中。 当父对象析构的时候这个列表中的所有对象也会被析构。注意这里是说父对象和子对象不要理解成父类和子类 2.QWidget是能够在屏幕上显示的一切组件的父类 QWidget继承自QObject因此也继承了这种对象树关系。一个孩子自动地成为父组件的一个子组件。我们向某个窗口中添加了一个按钮或者其他控件建立父子关系当用户关闭这个窗口的时候该窗口就会被析构之前添加到他上边的按钮和其他控件也会被一同析构。这个结果也是我们开发人员所期望的。 当然我们也可以手动删除子对象。当子对象析构的时候会发出一个信号destroyed父对象收到这个信号之后就会从children列表中将它剔除。比如当我们删除了一个按钮时其所在的主窗口会自动将该按钮从其子对象列表children中删除并且自动调整屏幕显示按钮在屏幕上消失。当这个窗口析构的时候children列表里边已经没有这个按钮子对象所以我们手动删除也不会引起程序错误。 Qt 引入对象树的概念在一定程度上解决了内存问题。 3.对象树中对象的顺序是没有定义的。这意味着销毁这些对象的顺序也是未定义的。 4.任何对象树中的 QObject对象 delete 的时候如果这个对象有 parent则自动将其从 parent 的children()列表中删除如果有孩子则自动 delete 每一个孩子。Qt 保证没有QObject会被 delete 两次这是由析构顺序决定的。 如果QObject在栈上创建Qt 保持同样的行为。正常情况下这也不会发生什么问题。来看下下面的代码片段
{QWidget window;QPushButton quit(Quit, window);
}作为父组件的 window 和作为子组件的 quit 都是QObject的子类事实上它们都是QWidget的子类而QWidget是QObject的子类。这段代码是正确的quit 的析构函数不会被调用两次因为标准 C要求局部对象的析构顺序应该按照其创建顺序的相反过程。因此这段代码在超出作用域时会先调用 quit 的析构函数将其从父对象 window 的子对象列表中删除然后才会再调用 window 的析构函数。 但是如果我们使用下面的代码
{QPushButton quit(Quit);QWidget window;quit.setParent(window);
}情况又有所不同析构顺序就有了问题。我们看到在上面的代码中作为父对象的 window 会首先被析构因为它是最后一个创建的对象。在析构过程中它会调用子对象列表中每一个对象的析构函数也就是说 quit 此时就被析构了。然后代码继续执行在 window 析构之后quit 也会被析构因为 quit 也是一个局部变量在超出作用域的时候当然也需要析构。但是这时候已经是第二次调用 quit 的析构函数了C 不允许调用两次析构函数因此程序崩溃了。 由此我们看到Qt 的对象树机制虽然帮助我们在一定程度上解决了内存问题但是也引入了一些值得注意的事情。这些细节在今后的开发过程中很可能时不时跳出来烦扰一下所以我们最好从开始就养成良好习惯在 Qt 中尽量在构造的时候就指定 parent 对象并且大胆在堆上创建。 四、信号与槽机制 信号各种事件 槽 响应信号的动作 当某个事件发生后如某个按钮被点击了一下它就会发出一个被点击的信号signal。 某个对象接收到这个信号之后就会做一些相关的处理动作称为槽slot。 但是Qt对象不会无故收到某个信号要想让一个对象收到另一个对象发出的信号这时候需要建立连接connect 4.1 系统自带的信号和槽 下面我们完成一个小功能上面我们已经学习了按钮的创建但是还没有体现出按钮的功能按钮最大的功能也就是点击后触发一些事情比如我们点击按钮就把当前的窗口给关闭掉那么在Qt中这样的功能如何实现呢 其实两行代码就可以搞定了我们看下面的代码
QPushButton * quitBtn new QPushButton(关闭窗口,this);connect(quitBtn,QPushButton::clicked,this,MyWidget::close);第一行是创建一个关闭按钮这个之前已经学过第二行就是核心了也就是信号槽的使用方式 connect函数是建立信号发送者、信号、信号接收者、槽四者关系的函数 connect(sender, signal, receiver, slot);
参数解释 1)sender信号发送者 2)signal信号 3)receiver信号接收者 4)slot接收对象在接收到信号之后所需要调用的函数槽函数 这里要注意的是connect的四个参数都是指针信号和槽是函数指针。 系统自带的信号和槽如何查找呢这个就需要利用帮助文档了在帮助文档中比如我们上面的按钮的点击信号在帮助文档中输入QPushButton首先我们可以在Contents中寻找关键字 signals信号的意思但是我们发现并没有找到这时候我们应该想到也许这个信号的被父类继承下来的因此我们去他的父类QAbstractButton中就可以找到该关键字点击signals索引到系统自带的信号有如下几个 这里的clicked就是我们要找到槽函数的寻找方式和信号一样只不过他的关键字是slot。 4.2 自定义信号和槽 Qt框架默认提供的标准信号和槽不足以完成我们日常应用开发的需求比如说点击某个按钮让另一个按钮的文字改变这时候标准信号和槽就没有提供这样的函数。但是Qt信号和槽机制提供了允许我们自己设计自己的信号和槽。 4.2.1自定义信号使用条件 声明在类的signals域下没有返回值void类型的函数只有函数声明没有定义可以有参数可以重载通过emit关键字来触发信号形式emit object-sig(参数); 4.2.2 自定义槽函数使用条件 qt4 必须声明在 private/public/protected slots域下面qt5之后可以声明public下同时还可以是静态的成员函数全局函数lambda表达式没有返回值void类型的函数不仅有声明还得要有实现可以有参数可以重载 4.2.3 使用自定义信号和槽 定义场景下课了老师跟同学说肚子饿了信号学生请老师吃饭槽 首先定义一个学生类和老师类 老师类中声明信号 饿了 hungry
signals:void hungry();学生类中声明槽 请客treat
public slots:void treat();在窗口中声明一个公共方法下课这个方法的调用会触发老师饿了这个信号而响应槽函数学生请客 void MyWidget::ClassIsOver()
{//发送信号emit teacher-hungry();
}学生响应了槽函数并且打印信息 //自定义槽函数 实现
void Student::treat()
{qDebug() Student treat teacher;
}在窗口中连接信号槽 teacher new Teacher(this);student new Student(this);connect(teacher,Teacher::hungury,student,Student::treat);并且调用下课函数测试打印出相应log 自定义的信号 hungry带参数需要提供重载的自定义信号和 自定义槽
void hungry(QString name); 自定义信号
void treat(QString name ); 自定义槽
但是由于有两个重名的自定义信号和自定义的槽直接连接会报错所以需要利用函数指针来指向函数地址 然后在做连接
void (Teacher:: * teacherSingal)(QString) Teacher:: hangry;
void (Student:: * studentSlot)(QString) Student::treat;
connect(teacher,teacherSingal,student,studentSlot);
也可以使用static_cast静态转换挑选我们要的函数
connect(
teacher,
static_castvoid(Teacher:: *)(QString)(Teacher:: hangry),
student,
static_castvoid(Student:: *)(QString)( Student::treat));4.3 信号和槽的扩展 1.一个信号可以和多个槽相连 如果是这种情况这些槽会一个接一个的被调用但是槽函数调用顺序是不确定的。像上面的例子可以将一个按钮点击信号连接到关闭窗口的槽函数同时也连接到学生请吃饭的槽函数点击按钮的时候可以看到关闭窗口的同时也学生请吃饭的log也打印出来。 2. 多个信号可以连接到一个槽 只要任意一个信号发出这个槽就会被调用。如一个窗口多个按钮都可以关闭这个窗口。 3.一个信号可以连接到另外的一个信号 当第一个信号发出时第二个信号被发出。除此之外这种信号-信号的形式和信号-槽的形式没有什么区别。注意这里还是使用connect函数只是信号的接收者和槽函数换成另一个信号的发送者和信号函数。如上面老师饿了的例子可以新建一个按钮btn。 connect(btn,QPushButton::clicked,teacher,Teacher::hungry); 4.信号和槽可以断开连接 可以使用disconnect函数当初建立连接时connect参数怎么填的disconnect里边4个参数也就怎么填。这种情况并不经常出现因为当一个对象delete之后Qt自动取消所有连接到这个对象上面的槽。 5.信号和槽函数参数类型和个数必须同时满足两个条件 1信号函数的参数个数必须大于等于槽函数的参数个数 2信号函数的参数类型和槽函数的参数类型必须一一对应 4.4 Qt4版本的信号槽写法
connect(teacher,SIGNAL(hungry(QString)),student,SLOT(treat(QString))
); 这里使用了SIGNAL和SLOT这两个宏宏的参数是信号函数和槽函数的函数原型。 因为直接填入了函数原型所有这里边编译不会出现因为重载导致的函数指针二义性的问题。但问题是如果函数原型填错了或者不符合信号槽传参个数类型约定编译期间也不会报错只有运行期间才会看到错误log输出。 原因就是这两个宏将后边参数函数原型转化成了字符串。目前编译器还没有那么智能去判断字符串里边的内容符不符合运行条件。 4.5 Lambda 表达式 C11中的Lambda表达式用于定义匿名的函数对象以简化编程工作。首先看一下Lambda表达式的基本构成 分为四个部分[局部变量捕获列表]、函数参数、函数额外属性设置opt、函数返回值-retype、{函数主体} [capture](parameters) opt -retType
{……;
}4.5.1 局部变量引入方式 [ ]标识一个Lambda的开始。由于lambda表达式可以定义在某一个函数体A里边所以lambda表达式有可能会去访问A函数中的局部变量。中括号里边内容是描述了在lambda表达式里边可以使用的外部局部变量的列表 [] 表示lambda表达式不能访问外部函数体的任何局部变量 [a] 在函数体内部使用值传递的方式访问a变量 [b] 在函数体内部使用引用传递的方式访问b变量 [] 函数外的所有局部变量都通过值传递的方式使用, 函数体内使用的是副本 [] 引用的方式使用lambda表达式外部的所有变量 [, foo] foo使用引用方式, 其余是值传递的方式 [,foo] foo使用值传递方式, 其余是引用传递的方式 [this] 在函数内部可以使用类的成员函数和成员变量和形式也都会默认引入 由于引用方式捕获对象会有局部变量释放了而lambda函数还没有被调用的情况。如果执行lambda函数那么引用传递方式捕获进来的局部变量的值不可预知。 所以在无特殊情况下建议使用[](){}的形式
4.5.2 函数参数 (params)表示lambda函数对象接收的参数类似于函数定义中的小括号表示函数接收的参数类型和个数。参数可以通过按值如(int a,int b)和按引用如(int a,int b)两种方式进行传递。函数参数部分可以省略省略后相当于无参的函数。 4.5.3 选项 Opt Opt 部分是可选项最常用的是mutable声明这部分可以省略。外部函数局部变量通过值传递引进来时其默认是const所以不能修改这个局部变量的拷贝加上mutable就可以 int a 10 ;
[]()
{a20;//编译报错a引进来是const
}[]()mutable
{a20;//编译成功
};
4.5.4 函数返回值 -retType -retType标识lambda函数返回值的类型。这部分可以省略但是省略了并不代表函数没有返回值编译器会自动根据函数体内的return语句判断返回值类型但是如果有多条return语句而且返回的类型都不一样编译会报错如 []()mutable
{int b 20;float c 30.0;if(a0)return b;elsereturn c;//编译报错两条return语句返回类型不一致
};4.5.5 是函数主体{} {}标识函数的实现这部分不能省略但函数体可以为空。 4.5.6 槽函数使用 Lambda 表达式
以QPushButton点击事件为例
connect(btn,QPushButton::clicked,[](){qDebug()Clicked;
});这里可以看出使用Lambda表达式作为槽的时候不需要填入信号的接收者。当点击按钮的时候clicked信号被触发lambda表达式也会直接运行。当然lambda表达式还可以指定函数参数这样也就能够接收到信号函数传递过来的参数了。 由于lambda表达式比我们自己自定义槽函数要方便而且灵活得多所以在实现槽函数的时候优先考虑使用Lambda表达式。一般我们的使用习惯也是lambda表达式外部函数的局部变量全部通过值传递捕获进来也就是: [](){ }的形式 五、QMainWindow QMainWindow是一个为用户提供主窗口程序的类包含一个菜单栏menu bar、多个工具栏(tool bars)、多个停靠部件(dock widgets)、一个状态栏(status bar)及一个中心部件(central widget)是许多应用程序的基础如文本编辑器图片编辑器等。 5.1 菜单栏 一个主窗口最多只有一个菜单栏。位于主窗口顶部、主窗口标题栏下面。 1.通过QMainWindow类的menubar函数获取主窗口菜单栏指针如果当前窗口没有菜单栏该函数会自动创建一个。 QMenuBar * menuBar() const; 2.创建菜单调用QMenu的成员函数addMenu来添加菜单 QAction* addMenu(QMenu * menu);QMenu* addMenu(const QString title);QMenu* addMenu(const QIcon icon, const QString title);3.创建菜单项调用QMenu的成员函数addAction来添加菜单项 QAction* activeAction() const;QAction* addAction(const QString text);QAction* addAction(const QIcon icon, const QString text);QAction* addAction(const QString text, const QObject * receiver,const char * member, const QKeySequence shortcut 0);QAction* addAction(const QIcon icon, const QString text, const QObject * receiver, const char * member, const QKeySequence shortcut 0);Qt 并没有专门的菜单项类只是使用一个QAction类抽象出公共的动作。当我们把QAction对象添加到菜单就显示成一个菜单项添加到工具栏就显示成一个工具按钮。用户可以通过点击菜单项、点击工具栏按钮、点击快捷键来激活这个动作。 5.2 工具栏 主窗口的工具栏上可以有多个工具条通常采用一个菜单对应一个工具条的的方式也可根据需要进行工具条的划分。 1.调用QMainWindowd对象的成员函数addToolBar,该函数每次调用都会创建一个新的工具栏并且返回该工具栏的指针。 2.插入属于工具条的项这时工具条上添加项也是用QAction。通过QToolBar类的addAction函数添加。 3.工具条是一个可移动的窗口它的停靠区域由QToolBar的allowAreas决定包括以下值可以通过查帮助文档allowAreas来索引到 1Qt::LeftToolBarArea 停靠在左侧 2Qt::RightToolBarArea 停靠在右侧 3Qt::TopToolBarArea 停靠在顶部 4Qt::BottomToolBarArea 停靠在底部 5Qt::AllToolBarAreas 以上四个位置都可停靠 使用setAllowedAreas函数指定停靠区域 setAllowedAreasQt::LeftToolBarArea | Qt::RightToolBarArea 使用setFloatabletrueOrFalse函数来设定工具栏可否浮动 使用setMoveabletrueOrFalse函数设定工具栏的可移动性 setMoveablefalse//工具条不可移动, 只能停靠在初始化的位置上 5.3 状态栏 一个QMainWindow的程序最多只有一个状态栏。QMainWindow中可以有多个的部件都使用add…名字的函数而只有一个的部件就直接使用获取部件的函数如menuBar。同理状态栏也提供了一个获取状态栏的函数statusBar()没有就自动创建一个并返回状态栏的指针。 QMenuBar * menuBar() const;
1.添加小部件从状态栏左侧添加
void addWidget(QWidget * widget, int stretch 0);
//插入小部件
int insertWidget(int index, QWidget * widget, int stretch 0);
//删除小部件
void removeWidget(QWidget * widget);2.添加小部件从状态栏右侧添加
void addPermenentWidget (QWidget *widget, int stretch 0);
5.4 停靠部件也成为铆接部件、浮动窗口 停靠部件 QDockWidget也称浮动窗口可以有多个。
QDockWidget * dock new QDockWidget(标题,this);
//添加停靠部件到mainWindow中并且设定默认停靠在左边
addDockWidget(Qt::LeftDockWidgetArea,dock);
//设定停靠部件允许停靠的范围
dock-setAllowedAreas(Qt::LeftDockWidgetArea |
Qt::RightDockWidgetArea | Qt::TopDockWidgetArea); 5.5 核心部件中心部件 除了以上几个部件中心显示的部件都可以作为核心部件例如一个记事本程序中就是一个QTextEdit编辑框控件做核心部件
QTextEdit * edit new QTextEdit(this);
//设置mainWindow的核心部件
setCentralWidget(edit);5.6 使用 UI 文件创建窗口 创建工程的时候把UI文件留着
5.6.1 UI 设计窗口介绍 5.6.2 菜单栏
1.添加/删除菜单栏 默认情况下QMainWindow项目一创建就自带了菜单栏可以在对象树窗口中右键菜单栏对象移除菜单栏 删除后也可以创建菜单栏此时在对象树中右键MainWindow对象菜单里边会多了创建菜单栏的功能 2. 添加菜单栏 点击菜单栏的“在这里输入”可以输入一个菜单名字创建一个菜单。 3. 添加菜单项 在UI界面中添加菜单项只能用英文因为此时会创建一个QAction对象会用输入的值作为对象名所以不能用中文得添加后再属性窗口改中文。 4. 添加多级菜单 5.6.3 工具栏
1. 添加/删除工具栏 删除工具栏方法和删除菜单栏方法一样不过工具栏可以有多个所以每次右键MainWindow对象都可以看到添加工具栏的选项。 2. 工具栏添加动作 新添加的QAction对象会在动作编辑器里找到Action Editor可以直接拖拽上来添加到工具栏里。 可以对工具栏设定停靠区域、能否浮动、能否移动等 5.6.4 状态栏 添加和删除状态栏的方法和添加删除菜单栏方法一样。 状态栏添加左侧控件、右侧控件只能通过代码来添加。 5.6.5 停靠部件 从工具箱中拖出一个停靠部件就行。也可以像设定工具栏停靠范围一样在停靠部件的属性窗口中设定他可以停靠的范围。 5.6.6 核心部件 UI窗口中默认核心部件就是一个widget 5.7 UI 文件管理 使用UI文件创建界面很轻松很便捷他的原理就是每次我们保存UI文件的时候QtCreator就自动帮我们将UI文件翻译成C的图形界面创建代码。可以通过以下步骤查看代码 到工程编译目录一般就是工程同级目录下会生成另一个编译目录会找到一个带ui_前缀跟ui文件同名的.h文件这就是代码 代码内容 在项目MainWindow的构造函数中会调用这个函数来初始化窗口其实这里边就是初始化我们的各个控件。
MainWindow::MainWindow(QWidget *parent) :QMainWindow(parent),ui(new Ui::MainWindow)
{ui-setupUi(this);//如果想要使用ui里边的控件对象//代码必须写在setupUi之下//否则ui各个控件没有初始化时使用会出问题ui-pushButton-setText(Hello);
}5.8 UI 文件下使用信号和槽
5.8.1 转到槽 在UI编辑界面中使用信号和槽很方便比如拖出一个Button到窗口上右键这个button选择转到槽 此时会出现这个控件QPushButton可以连接的各个信号我们可以根据具体需求选中信号来创建一个连接这个信号的槽函数 以click(bool)信号为例创建了一个槽函数 这个槽函数是QtCreator自动帮我们创建的而且也使用生成C代码的方式帮我们做好了连接我们可以直接在这个函数体内实现功能就行。很方便比使用Lambda表达式还方便。 5.8.2 信号槽编辑器 可以使用动作编辑器旁边的信号槽编辑器里边也可以添加信号和槽的连接比如添加actionQuit的triggered信号和窗口close槽的连接 5.9 资源文件 Qt 资源系统是一个跨平台的资源机制用于将程序运行时所需要的资源以二进制的形式存储于可执行文件内部。如果你的程序需要加载特定的资源图标、文本翻译等那么将其放置在资源文件中就再也不需要担心这些文件的丢失。也就是说如果你将资源以资源文件形式存储它是会编译到可执行文件内部。 使用 Qt Creator 可以很方便地创建资源文件。我们可以在工程上点右键选择“添加新文件…”可以在 Qt 分类下找到“Qt 资源文件” 点击“选择…”按钮打开“新建 Qt 资源文件”对话框。在这里我们输入资源文件的名字和路径 点击下一步选择所需要的版本控制系统然后直接选择完成。我们可以在 Qt Creator 的左侧文件列表中看到“资源文件”一项也就是我们新创建的资源文件 右侧的编辑区有个“添加”我们首先需要添加前缀比如我们将前缀取名为 images。然后选中这个前缀继续点击添加文件可以找到我们所需添加的文件。这里我们选择 document-open.png 文件。当我们完成操作之后Qt Creator 应该是这样子的 接下来我们还可以添加另外的前缀或者另外的文件。这取决于你的需要。当我们添加完成之后我们可以像前面一章讲解的那样通过使用 : 开头的路径来找到这个文件。比如我们的前缀是 /images文件是 document-open.png那么就可以使用:/images/document-open.png找到这个文件。 这么做带来的一个问题是如果以后我们要更改文件名比如将 docuemnt-open.png 改成 docopen.png那么所有使用了这个名字的路径都需要修改。所以更好的办法是我们给这个文件去一个“别名”以后就以这个别名来引用这个文件。具体做法是选中这个文件添加别名信息 这样我们可以直接使用:/images/doc-open引用到这个资源无需关心图片的真实文件名。
Qrc文件只是记录了我们要用到的资源文件在项目路径哪个位置的一个信息如果我们使用文本编辑器打开 res.qrc 文件就会看到以下内容
RCCqresource prefix/imagesfile aliasdoc-opendocument-open.png/file/qresourceqresource prefix/images/fr langfrfile aliasdoc-opendocument-open-fr.png/file/qresource
/RCC当我们编译工程之后我们可以在构建目录中找到 qrc_res.cpp 文件。 这就是 Qt 将我们的资源编译成了 C 代码 可以看出Qt帮我们将资源文件内容一个字节一个字节的读出来最终放到了代码里使用字符数组的形式保存着所以程序启动的时候这些资源就会以数组的形式占用到程序内存里。当我们使用qt的qrc资源文件时要考虑内存占用的问题如果Qt程序资源很多而且并不是每次运行程序都会加载所有的资源比如制作一个游戏所需要的图片声音资源量很大可能超过了机器内存大小这时候使用qrc加载资源的方式很不合适。 可以考虑动态加载资源的方式当切入到某个游戏场景的时候才加载场景相关的图片声音资源。Qt有提供rcc的方式动态加载资源不过不常用所以一般游戏资源都放在可执行文件exe所在目录或者子目录下程序运行的时候就从游戏exe文件路径去搜寻资源。 六、QDialog 对话框
6.1 基本概念 对话框是 GUI 程序中不可或缺的组成部分。很多不能或者不适合放入主窗口的功能组件都必须放在对话框中比如用于完成一次性任务的功能如登录功能、选择某个文件打开、保存文件。对话框通常会是一个顶层窗口出现在程序最上层用于实现短期任务或者简洁的用户交互。 Qt 中使用QDialog类实现对话框但是声明一个QDilaog对象的时候不管这个对话框对象跟哪个窗口建立了父子关系当他显示出来的时候都还是一个顶层的窗口。 对话框分为模态对话框和非模态对话框。 1.模态对话框当对话框打开时不能操作同一个应用程序的其他窗口只有当对话框关闭的时候才可以操作。 模态对话框很常见比如“打开文件”功能。你可以尝试一下记事本的打开文件当打开文件对话框出现时我们是不能对除此对话框之外的窗口部分进行操作的。 2.此相反的是非模态对话框例如查找对话框我们可以在显示着查找对话框的同时继续对记事本的内容进行编辑。
6.1.1 模态对话框
Qt 有两种级别的模态对话框 1.应用程序级别的模态 当该种模态的对话框出现时用户必须首先对对话框进行交互直到关闭对话框然后才能访问程序中其他的窗口。 使用QDialog::exec()实现应用程序级别的模态对话框 2.窗口级别的模态 该模态仅仅阻塞与对话框关联的窗口但是依然允许用户与程序中其它窗口交互。窗口级别的模态尤其适用于多窗口模式。 使用QDialog::open()实现窗口级别的模态对话框 一般情况下我们只使用应用程序级别的模态对话框。 在下面的示例中我们调用了exec()将对话框显示出来因此这就是一个模态对话框。当对话框出现时我们不能与主窗口进行任何交互直到我们关闭了该对话框。 QDialog dialog;dialog.setWindowTitle(tr(Hello, dialog!));下面我们试着将exec()修改为show()看看非模态对话框 QDialog dialog(this);dialog.setWindowTitle(tr(Hello, dialog!));dialog.show();是不是事与愿违对话框竟然一闪而过这是因为show()函数不会阻塞当前线程对话框会显示出来然后函数立即返回代码继续执行。注意dialog 是建立在栈上的show()函数返回MainWindow::open()函数结束dialog 超出作用域被析构因此对话框消失了。知道了原因就好改了我们将 dialog 改成堆上建立当然就没有这个问题了
QDialog *dialog new QDialog;dialog-setWindowTitle(tr(Hello, dialog!));dialog-show();如果你足够细心应该发现上面的代码是有问题的dialog 存在内存泄露dialog 使用 new 在堆上分配空间却一直没有 delete。解决方案也很简单将 MainWindow 的指针赋给 dialog 即可。还记得我们前面说过的 Qt 的对象树吗 不过这样做有一个问题如果我们的对话框不是在一个界面类中出现呢由于QWidget的 parent 必须是QWidget指针那就限制了我们不能将一个普通的 C 类指针传给 Qt 对话框。另外如果对内存占用有严格限制的话当我们将主窗口作为 parent 时主窗口不关闭对话框就不会被销毁所以会一直占用内存。在这种情景下我们可以设置 dialog 的WindowAttribute
QDialog *dialog new QDialog;
dialog-setAttribute(Qt::WA_DeleteOnClose);
dialog-setWindowTitle(tr(Hello, dialog!));
dialog-show();setAttribute()函数设置对话框关闭时自动销毁对话框。 6.2 标准对话框 所谓标准对话框是 Qt 内置的一系列对话框用于简化开发。事实上有很多对话框都是通用的比如打开文件、设置颜色、打印设置等。这些对话框在所有程序中几乎相同因此没有必要在每一个程序中都自己实现这么一个对话框。 Qt 的内置对话框大致分为以下几类 1.QMessageBox 模态对话框用于显示信息、询问问题等 2.QColorDialog 选择颜色 3.QFontDialog 选择字体 4.QFileDialog 选择文件或者目录 5.QInputDialog 允许用户输入一个值并将其值返回 6.QPageSetupDialog 为打印机提供纸张相关的选项 7.QPrintDialog 打印机配置 8.QPrintPreviewDialog打印预览 9.QProgressDialog 显示操作过程。 6.3 消息对话框 QMessageBox用于显示消息提示。我们一般会使用其提供的几个 static 函数
About
显示关于对话框。
void about(QWidget * parent, const QString title, const QString text) 这是一个最简单的对话框其标题是 title内容是 text父窗口是 parent。对话框只有一个 OK 按钮。 AboutQt 显示关于 Qt 对话框。该对话框用于显示有关 Qt 的信息。
void aboutQt(QWidget * parent, const QString title QString()); Critical 显示严重错误对话框。
StandardButton critical(QWidget * parent,
const QString title,
const QString text,
StandardButtons buttons Ok,
StandardButton defaultButton NoButton);这个对话框将显示一个红色的错误符号。我们可以通过 buttons 参数指明其显示的按钮。默认情况下只有一个 Ok 按钮我们可以使用StandardButtons类型指定多种按钮。 Information 与critical类似不同之处在于这个对话框提供一个普通信息图标。
StandardButton information(QWidget * parent,
const QString title,
const QString text,
StandardButtons buttons Ok,
StandardButton defaultButton NoButton)Question 与QMessageBox::critical ()类似不同之处在于这个对话框提供一个问号图标并且其显示的按钮是“是”和“否”。
StandardButton question(QWidget * parent,
const QString title,
const QString text,
StandardButtons buttons StandardButtons( Yes | No ),
StandardButton defaultButton NoButton) 1.第一个参数是对话框的父窗口是 this。 QMessageBox是QDialog的子类这意味着它的初始显示位置将会是在 parent 窗口的中央。 2.第二个参数是对话框的标题。 3.第三个参数是我们想要显示的内容。 4.第四个参数是关联的按键类型我们可以使用或运算符|指定对话框应该出现的按钮。比如我们希望是一个 Yes 和一个 No。 5.最后一个参数指定默认选择的按钮。 这个函数有一个返回值用于确定用户点击的是哪一个按钮。按照我们的写法应该很容易的看出这是一个模态对话框因此我们可以直接获取其返回值。 我们可以通过以下代码来判断问题对话框的返回值
if (QMessageBox::Yes QMessageBox::question(this,tr(Question), tr(Are you OK?),QMessageBox::Yes | QMessageBox::No,QMessageBox::Yes)){QMessageBox::information(this, tr(Hmmm...),tr(Im glad to hear that!));}else{QMessageBox::information(this, tr(Hmmm...),tr(Im sorry!));}warning 与QMessageBox::critical()类似不同之处在于这个对话框提供一个黄色叹号图标。
StandardButton warning(QWidget * parent,
const QString title,
const QString text,
StandardButtons buttons Ok,
StandardButton defaultButton NoButton)QMessageBox类的 static 函数优点是方便使用缺点也很明显非常不灵活。我们只能使用简单的几种形式。为了能够定制QMessageBox细节我们必须使用QMessageBox的属性设置 API。如果我们希望制作一个询问是否保存的对话框我们可以使用如下的代码
QMessageBox msgBox;
msgBox.setText(tr(The document has been modified.));
msgBox.setInformativeText(tr(Do you want to save your changes?));
msgBox.setDetailedText(tr(Differences here...));
msgBox.setStandardButtons(QMessageBox::Save| QMessageBox::Discard| QMessageBox::Cancel);
msgBox.setDefaultButton(QMessageBox::Save);
int ret msgBox.exec();
switch (ret)
{
case QMessageBox::Save:qDebug() Save document!;break;
case QMessageBox::Discard:qDebug() Discard changes!;break;
case QMessageBox::Cancel:qDebug() Close document!;break;
}msgBox 是一个建立在栈上的QMessageBox实例。我们设置其主要文本信息为“The document has been modified.”informativeText 则是会在对话框中显示的简单说明文字。下面我们使用了一个detailedText也就是详细信息当我们点击了详细信息按钮时对话框可以自动显示更多信息。我们自己定义的对话框的按钮有三个保存、丢弃和取消。然后我们使用了exec()是其成为一个模态对话框根据其返回值进行相应的操作。 6.4 标准文件对话框 QFileDialog也就是文件对话框。在本节中我们将尝试编写一个简单的文本文件编辑器我们将使用QFileDialog来打开一个文本文件并将修改过的文件保存到硬盘。 首先我们需要创建一个带有文本编辑功能的窗口。借用我们前面的程序代码应该可以很方便地完成
openAction new QAction(QIcon(:/images/file-open),tr(Open...), this);
openAction-setStatusTip(tr(Open an existing file));
saveAction new QAction(QIcon(:/images/file-save), tr(Save...), this);
saveAction-setStatusTip(tr(Save a new file));
QMenu *file menuBar()-addMenu(tr(File));
file-addAction(openAction);
file-addAction(saveAction);QToolBar *toolBar addToolBar(tr(File));
toolBar-addAction(openAction);
toolBar-addAction(saveAction);textEdit new QTextEdit(this);
setCentralWidget(textEdit);我们在菜单和工具栏添加了两个动作打开和保存。接下来是一个QTextEdit类这个类用于显示富文本文件。也就是说它不仅仅用于显示文本还可以显示图片、表格等等。不过我们现在只用它显示纯文本文件。QMainWindow有一个setCentralWidget()函数可以将一个组件作为窗口的中心组件放在窗口中央显示区。显然在一个文本编辑器中文本编辑区就是这个中心组件因此我们将QTextEdit作为这种组件。 我们使用connect()函数为这两个QAction对象添加响应的动作 connect(openAction, QAction::triggered, this, MainWindow::openFile);connect(saveAction, QAction::triggered, this, MainWindow::saveFile);下面是最主要的openFile()和saveFile()这两个函数的代码
//打开文件
void MainWindow::openFile()
{QString path QFileDialog::getOpenFileName(this,tr(Open File), ., tr(Text Files(*.txt)));if(!path.isEmpty()) {QFile file(path);if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {QMessageBox::warning(this, tr(Read File),tr(Cannot open file:\n%1).arg(path));return;}QTextStream in(file);textEdit-setText(in.readAll());file.close();} else {QMessageBox::warning(this, tr(Path),tr(You did not select any file.));}
}//保存文件
void MainWindow::saveFile()
{QString path QFileDialog::getSaveFileName(this,tr(Open File), ., tr(Text Files(*.txt)));if(!path.isEmpty()) {QFile file(path);if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {QMessageBox::warning(this, tr(Write File),tr(Cannot open file:\n%1).arg(path));return;}QTextStream out(file);out textEdit-toPlainText();file.close();} else {QMessageBox::warning(this, tr(Path),tr(You did not select any file.));}
}在openFile()函数中我们使用QFileDialog::getOpenFileName()来获取需要打开的文件的路径。这个函数原型如下
QString getOpenFileName(QWidget * parent 0,const QString caption QString(),const QString dir QString(),const QString filter QString(),QString * selectedFilter 0,Options options 0);不过注意它的所有参数都是可选的因此在一定程度上说这个函数也是简单的。这六个参数分别是 1.parent父窗口 2.caption对话框标题 3.dir对话框打开时的默认目录 “.” 代表程序运行目录 “/” 代表当前盘符的根目录特指 Windows 平台Linux 平台当然就是根目录这个参数也可以是平台相关的比如“C:\\”等 4.filter过滤器 我们使用文件对话框可以浏览很多类型的文件但是很多时候我们仅希望打开特定类型的文件。比如文本编辑器希望打开文本文件图片浏览器希望打开图片文件。过滤器就是用于过滤特定的后缀名。如果我们使用“Image Files(*.jpg *.png)”则只能显示后缀名是 jpg 或者 png 的文件。如果需要多个过滤器使用“;;”分割比如“JPEG Files(*.jpg);;PNG Files(*.png)” 5.selectedFilter默认选择的过滤器 6.options对话框的一些参数设定 比如只显示文件夹等等它的取值是enum QFileDialog::Option每个选项可以使用 | 运算组合起来。 QFileDialog::getOpenFileName()返回值是选择的文件路径。我们将其赋值给 path。通过判断 path 是否为空可以确定用户是否选择了某一文件。只有当用户选择了一个文件时我们才执行下面的操作。 在saveFile()中使用的QFileDialog::getSaveFileName()也是类似的。使用这种静态函数在 Windows、Mac OS 上面都是直接调用本地对话框但是 Linux 上则是QFileDialog自己的模拟。这暗示了如果你不使用这些静态函数而是直接使用QFileDialog进行设置那么得到的对话框很可能与系统对话框的外观不一致。这一点是需要注意的。 七、布局 所谓 GUI 界面归根结底就是一堆组件的叠加。我们创建一个窗口把按钮放上面把图标放上面这样就成了一个界面。在放置时组件的位置尤其重要。我们必须要指定组件放在哪里以便窗口能够按照我们需要的方式进行渲染。这就涉及到组件布局定位的机制。 Qt 提供了两种组件布局定位机制静态布局和动态布局。 1.静态布局就是一种最原始的定位方法给出这个组件的坐标和长宽值 这样Qt 就知道该把组件放在哪里以及如何设置组件的大小。但是这样做带来的一个问题是如果用户改变了窗口大小比如点击最大化按钮或者使用鼠标拖动窗口边缘采用静态布局的组件是不会有任何响应的。这也很自然因为你并没有告诉 Qt在窗口变化时组件是否要更新自己以及如何更新。或者还有更简单的方法禁止用户改变窗口大小。但这总不是长远之计。
2.动态布局你只要把组件放入某一种布局(layout)布局由专门的布局管理器进行管理。当需要调整大小或者位置的时候Qt 使用对应的布局管理器进行调整。 动态布局解决了使用静态布局的缺陷。 Qt 提供的动态布局中以下三种是我们最常用的 1QHBoxLayout按照水平方向从左到右布局 2QVBoxLayout按照竖直方向从上到下布局 3QGridLayout在一个网格中进行布局类似于 HTML 的 table 7.1 系统提供的布局控件 这4个为系统给我们提供的布局的控件但是使用起来不是非常的灵活这里就不详细介绍了。
7.2 利用 widget 做布局 第二种布局方式是利用控件里的widget来做布局在Containers中 在widget中的控件可以进行水平、垂直、栅格布局等操作比较灵活。 再布局的同时我们需要灵活运用弹簧的特性让我们的布局更加的美观下面是一个登陆窗口利用widget可以搭建出如下登陆界面 八、常用控件 Qt为我们应用程序界面开发提供的一系列的控件下面我们介绍两种最常用一些控件所有控件的使用方法我们都可以通过帮助文档获取。 8.1 QLabel 控件使用 QLabel是我们最常用的控件之一其功能很强大我们可以用来显示文本图片和动画等。
8.1.1 显示文字普通文本、html
通过QLabel类的setText函数设置显示的内容:
void setText(const QString ) 1.可以显示普通文本字符串
QLable *label new QLable;
label-setText(“Hello, World!”);2.可以显示HTML格式的字符串 比如显示一个链接:
QLabel * label new QLabel(this);
label -setText(Hello, World);
label -setText(h1a href\https://www.baidu.com\百度一下/a/h1);
label -setOpenExternalLinks(true);其中setOpenExternalLinks()函数是用来设置用户点击链接之后是否自动打开链接如果参数指定为true则会自动打开。 8.1.2 显示图片 可以使用QLabel的成员函数setPixmap设置图片 void setPixmap(const QPixmap ) 首先定义QPixmap对象 QPixmap pixmap; 然后加载图片 pixmap.load(:/Image/boat.jpg); 最后将图片设置到QLabel中 QLabel *label new QLabel; label.setPixmap(pixmap); 8.1.3 显示动画 可以使用QLabel 的成员函数setMovie加载动画可以播放gif格式的文件 void setMovie(QMovie * movie) 首先定义QMovied对象并初始化: QMovie *movie new QMovie(:/Mario.gif); 播放加载的动画 movie-start(); 将动画设置到QLabel中 QLabel *label new QLabel label-setMovie(movie); 8.2 QLineEdit Qt提供的单行文本编辑框。 1.获取编辑框内容使用text函数声明如下 QString text() const 2.设置编辑框内容 void setText(const QString ) 8.2.2 设置显示模式
使用QLineEdit类的setEchoMode () 函数设置文本的显示模式,函数声明:
void setEchoMode(EchoMode mode)
EchoMode是一个枚举类型,一共定义了四种显示模式: 1.QLineEdit::Normal 模式显示方式按照输入的内容显示。 2.QLineEdit::NoEcho 不显示任何内容此模式下无法看到用户的输入。 3.QLineEdit::Password 密码模式输入的字符会根据平台转换为特殊字符。 4.QLineEdit::PasswordEchoOnEdit 编辑时显示字符否则显示字符作为密码。 另外我们再使用QLineEdit显示文本的时候希望在左侧留出一段空白的区域那么就可以使用QLineEdit给我们提供的setTextMargins函数
void setTextMargins(int left, int top, int right, int bottom)
用此函数可以指定显示的文本与输入框上下左右边界的间隔的像素数。 8.3 其他控件
Qt中控件的使用方法可参考Qt提供的帮助文档。 8.4 自定义控件 在搭建Qt窗口界面的时候在一个项目中很多窗口或者是窗口中的某个模块会被经常性的重复使用。一般遇到这种情况我们都会将这个窗口或者模块拿出来做成一个独立的窗口类以备以后重复使用。 在使用Qt的ui文件搭建界面的时候工具栏栏中只为我们提供了标准的窗口控件如果我们想使用自定义控件怎么办 例如我们从QWidget派生出一个类SmallWidget实现了一个自定窗口 // smallwidget.h
class SmallWidget : public QWidget
{Q_OBJECT
public:explicit SmallWidget(QWidget *parent 0);signals:public slots:
private:QSpinBox* spin;QSlider* slider;
};// smallwidget.cpp
SmallWidget::SmallWidget(QWidget *parent) : QWidget(parent)
{spin new QSpinBox(this);slider new QSlider(Qt::Horizontal, this);// 创建布局对象QHBoxLayout* layout new QHBoxLayout;// 将控件添加到布局中layout-addWidget(spin);layout-addWidget(slider);// 将布局设置到窗口中setLayout(layout);// 添加消息响应connect(spin, static_castvoid (QSpinBox::*)(int)(QSpinBox::valueChanged),slider, QSlider::setValue);connect(slider, QSlider::valueChanged, spin, QSpinBox::setValue);
}那么这个SmallWidget可以作为独立的窗口显示,也可以作为一个控件来使用 打开Qt的.ui文件,因为SmallWidget是派生自Qwidget类,所以需要在ui文件中先放入一个QWidget控件, 然后再上边鼠标右键 弹出提升窗口部件对话框 添加要提升的类的名字,然后选择 添加 添加之后,类名会显示到上边的列表框中,然后单击提升按钮,完成操作.
我们可以看到, 这个窗口对应的类从原来的QWidget变成了SmallWidget 再次运行程序,这个widget_3中就能显示出我们自定义的窗口了. 九、Qt消息事件机制
9.1 事件 事件event是由系统或者 Qt 应用程序本身在不同的时刻发出的。当用户按下鼠标、敲下键盘或者是窗口需要重新绘制的时候都会发出一个相应的事件。一些事件在对用户操作做出响应时发出如键盘事件等另一些事件则是由系统自动发出如计时器事件。 9.2 事件处理函数
在所有组件的父类QWidget中定义了很多事件处理的函数如 1.keyPressEvent()键盘按键按下事件 2.keyReleaseEvent()键盘按键松开事件 3.mouseDoubleClickEvent()鼠标双击事件 4.mouseMoveEvent()鼠标移动事件 5.mousePressEvent()鼠标按键按下事件 6.mouseReleaseEvent() 鼠标按键松开事件 7.等等 这些函数都是 protected virtual 的也就是说我们可以在子类中重新实现这些函数。下面来看一个例子
class EventLabel : public QLabel
{
protected:void mouseMoveEvent(QMouseEvent *event);void mousePressEvent(QMouseEvent *event);void mouseReleaseEvent(QMouseEvent *event);
};void EventLabel::mouseMoveEvent(QMouseEvent *event)
{this-setText(QString(centerh1Move: (%1, %2)/h1/center).arg(QString::number(event-x()),QString::number(event-y())));
}void EventLabel::mousePressEvent(QMouseEvent *event)
{this-setText(QString(centerh1Press:(%1, %2)/h1/center).arg(QString::number(event-x()),QString::number(event-y())));
}void EventLabel::mouseReleaseEvent(QMouseEvent *event)
{QString msg;msg.sprintf(centerh1Release: (%d, %d)/h1/center,event-x(), event-y());this-setText(msg);
}int main(int argc, char *argv[])
{QApplication a(argc, argv);EventLabel *label new EventLabel;label-setWindowTitle(MouseEvent Demo);label-resize(300, 200);label-show();return a.exec();
}1.EventLabel继承了QLabel覆盖了mousePressEvent()、mouseMoveEvent()和MouseReleaseEvent()三个函数。我们并没有添加什么功能只是在鼠标按下press、鼠标移动move和鼠标释放release的时候把当前鼠标的坐标值显示在这个Label上面。由于QLabel是支持 HTML 代码的因此我们直接使用了 HTML 代码来格式化文字。 2.QString的arg()函数可以自动替换掉QString中出现的占位符。其占位符以 % 开始后面是占位符的位置例如 %1%2 这种。 QString([%1, %2]).arg(x).arg(y); 语句将会使用x替换 %1y替换 %2因此生成的QString为[x, y]。 3.在mouseReleaseEvent()函数中我们使用了另外一种QString的构造方法。我们使用类似 C 风格的格式化函数sprintf()来构造QString。 运行上面的代码当我们点击了一下鼠标之后label 上将显示鼠标当前坐标值。 为什么要点击鼠标之后才能在mouseMoveEvent()函数中显示鼠标坐标值 这是因为QWidget中有一个mouseTracking属性该属性用于设置是否追踪鼠标。只有鼠标被追踪时mouseMoveEvent()才会发出。如果mouseTracking是 false默认即是组件在至少一次鼠标点击之后才能够被追踪也就是能够发出mouseMoveEvent()事件。如果mouseTracking为 true则mouseMoveEvent()直接可以被发出。
知道了这一点我们就可以在main()函数中添加如下代码
label-setMouseTracking(true);
在运行程序就没有这个问题了。 9.3 事件分发函数 event() 事件对象创建完毕后Qt 将这个事件对象传递给QObject的event()函数。event()函数并不直接处理事件而是将这些事件对象按照它们不同的类型分发给不同的事件处理器event handler。 如上所述event()函数主要用于事件的分发。所以如果你希望在事件分发之前做一些操作就可以重写这个event()函数了。例如我们希望在一个QWidget组件中监听 tab 键的按下那么就可以继承QWidget并重写它的event()函数来达到这个目的 bool CustomWidget::event(QEvent *e)
{if (e-type() QEvent::KeyPress) {QKeyEvent *keyEvent static_castQKeyEvent *(e);if (keyEvent-key() Qt::Key_Tab) {qDebug() You press tab.;return true;}}return QWidget::event(e);
}CustomWidget是一个普通的QWidget子类。我们重写了它的event()函数这个函数有一个QEvent对象作为参数也就是需要转发的事件对象。函数返回值是 bool 类型。 1.如果传入的事件已被识别并且处理则需要返回 true否则返回 false。如果返回值是 true那么 Qt 会认为这个事件已经处理完毕不会再将这个事件发送给其它对象而是会继续处理事件队列中的下一事件。 2.在event()函数中调用事件对象的accept()和ignore()函数是没有作用的不会影响到事件的传播。 我们可以通过使用QEvent::type()函数可以检查事件的实际类型其返回值是QEvent::Type类型的枚举。我们处理过自己感兴趣的事件之后可以直接返回 true表示我们已经对此事件进行了处理对于其它我们不关心的事件则需要调用父类的event()函数继续转发否则这个组件就只能处理我们定义的事件了。为了测试这一种情况我们可以尝试下面的代码
bool CustomTextEdit::event(QEvent *e)
{if (e-type() QEvent::KeyPress)
{QKeyEvent *keyEvent static_castQKeyEvent *(e);if (keyEvent-key() Qt::Key_Tab)
{qDebug() You press tab.;return true;}}return false;
}CustomTextEdit是QTextEdit的一个子类。我们重写了其event()函数却没有调用父类的同名函数。这样我们的组件就只能处理 Tab 键再也无法输入任何文本也不能响应其它事件比如鼠标点击之后也不会有光标出现。这是因为我们只处理的KeyPress类型的事件并且如果不是KeyPress事件则直接返回 false鼠标事件根本不会被转发也就没有了鼠标事件。 通过查看QObject::event()的实现我们可以理解event()函数同前面的章节中我们所说的事件处理器有什么联系
//!!! Qt5
bool QObject::event(QEvent *e)
{switch (e-type()) {case QEvent::Timer:timerEvent((QTimerEvent*)e);break;case QEvent::ChildAdded:case QEvent::ChildPolished:case QEvent::ChildRemoved:childEvent((QChildEvent*)e);break;// ...default:if (e-type() QEvent::User) {customEvent(e);break;}return false;}return true;
}这是 Qt 5 中QObject::event()函数的源代码Qt 4 的版本也是类似的。我们可以看到同前面我们所说的一样Qt 也是使用QEvent::type()判断事件类型然后调用了特定的事件处理器。比如如果event-type()返回值是QEvent::Timer则调用timerEvent()函数。可以想象QWidget::event()中一定会有如下的代码
switch (event-type()) {case QEvent::MouseMove:mouseMoveEvent((QMouseEvent*)event);break;// ...}事实也的确如此。timerEvent()和mouseMoveEvent()这样的函数就是我们前面章节所说的事件处理器 event handler。也就是说event()函数中实际是通过事件处理器来响应一个具体的事件。这相当于event()函数将具体事件的处理“委托”给具体的事件处理器。而这些事件处理器是 protected virtual 的因此我们重写了某一个事件处理器即可让 Qt 调用我们自己实现的版本。 由此可以见event()是一个集中处理不同类型的事件的地方。如果你不想重写一大堆事件处理器就可以重写这个event()函数通过QEvent::type()判断不同的事件。鉴于重写event()函数需要十分小心注意父类的同名函数的调用一不留神就可能出现问题所以一般还是建议只重写事件处理器当然也必须记得是不是应该调用父类的同名处理器。这其实暗示了event()函数的另外一个作用屏蔽掉某些不需要的事件处理器。正如我们前面的CustomTextEdit例子看到的那样我们创建了一个只能响应 tab 键的组件。这种作用是重写事件处理器所不能实现的。 9.4 事件过滤器 有时候对象需要查看、甚至要拦截发送到另外对象的事件。例如对话框可能想要拦截按键事件不让别的组件接收到或者要修改回车键的默认处理。 通过前面的章节我们已经知道Qt 创建了QEvent事件对象之后会调用QObject的event()函数处理事件的分发。显然我们可以在event()函数中实现拦截的操作。由于event()函数是 protected 的因此需要继承已有类。如果组件很多就需要重写很多个event()函数。这当然相当麻烦更不用说重写event()函数还得小心一堆问题。好在 Qt 提供了另外一种机制来达到这一目的事件过滤器。 QObject有一个eventFilter()函数用于建立事件过滤器。函数原型如下 virtual bool QObject::eventFilter ( QObject * watched, QEvent * event ); 这个函数正如其名字显示的那样是一个“事件过滤器”。所谓事件过滤器可以理解成一种过滤代码。事件过滤器会检查接收到的事件。如果这个事件是我们感兴趣的类型就进行我们自己的处理如果不是就继续转发。这个函数返回一个 bool 类型如果你想将参数 event 过滤出来比如不想让它继续转发就返回 true否则返回 false。事件过滤器的调用时间是目标对象也就是参数里面的watched对象接收到事件对象之前。也就是说如果你在事件过滤器中停止了某个事件那么watched对象以及以后所有的事件过滤器根本不会知道这么一个事件。 我们来看一段简单的代码
class MainWindow : public QMainWindow
{
public:MainWindow();
protected:bool eventFilter(QObject *obj, QEvent *event);
private:QTextEdit *textEdit;
};MainWindow::MainWindow()
{textEdit new QTextEdit;setCentralWidget(textEdit); textEdit-installEventFilter(this);
}bool MainWindow::eventFilter(QObject *obj, QEvent *event)
{if (obj textEdit) {if (event-type() QEvent::KeyPress) {QKeyEvent *keyEvent static_castQKeyEvent *(event);qDebug() Ate key press keyEvent-key();return true;} else {return false;}} else {// pass the event on to the parent classreturn QMainWindow::eventFilter(obj, event);}
}1.MainWindow是我们定义的一个类。我们重写了它的eventFilter()函数。为了过滤特定组件上的事件首先需要判断这个对象是不是我们感兴趣的组件然后判断这个事件的类型。在上面的代码中我们不想让textEdit组件处理键盘按下的事件。所以首先我们找到这个组件如果这个事件是键盘事件则直接返回 true也就是过滤掉了这个事件其他事件还是要继续处理所以返回 false。对于其它的组件我们并不保证是不是还有过滤器于是最保险的办法是调用父类的函数。 2.eventFilter()函数相当于创建了过滤器然后我们需要安装这个过滤器。安装过滤器需要调用QObject::installEventFilter()函数。函数的原型如下 void QObject::installEventFilter ( QObject * filterObj ); 这个函数接受一个QObject *类型的参数。记得刚刚我们说的eventFilter()函数是QObject的一个成员函数因此任意QObject都可以作为事件过滤器问题在于如果你没有重写eventFilter()函数这个事件过滤器是没有任何作用的因为默认什么都不会过滤。已经存在的过滤器则可以通过QObject::removeEventFilter()函数移除。 3.我们可以向一个对象上面安装多个事件处理器只要调用多次installEventFilter()函数。如果一个对象存在多个事件过滤器那么最后一个安装的会第一个执行也就是后进先执行的顺序。 还记得我们前面的那个例子吗我们使用event()函数处理了 Tab 键 bool CustomWidget::event(QEvent *e)
{if (e-type() QEvent::KeyPress) {QKeyEvent *keyEvent static_castQKeyEvent *(e);if (keyEvent-key() Qt::Key_Tab) {qDebug() You press tab.;return true;}}return QWidget::event(e);
}现在我们可以给出一个使用事件过滤器的版本
bool FilterObject::eventFilter(QObject *object, QEvent *event)
{if (object target event-type() QEvent::KeyPress) {QKeyEvent *keyEvent static_castQKeyEvent *(event);if (keyEvent-key() Qt::Key_Tab) {qDebug() You press tab.;return true;} else {return false;}}return false;
}事件过滤器的强大之处在于我们可以为整个应用程序添加一个事件过滤器。记得installEventFilter()函数是QObject的函数QApplication或者QCoreApplication对象都是QObject的子类因此我们可以向QApplication或者QCoreApplication添加事件过滤器。这种全局的事件过滤器将会在所有其它特性对象的事件过滤器之前调用。尽管很强大但这种行为会严重降低整个应用程序的事件分发效率。因此除非是不得不使用的情况否则的话我们不应该这么做。 注意 事件过滤器和被安装过滤器的组件必须在同一线程创建否则过滤器将不起作用。另外如果在安装过滤器之后这两个组件到了不同的线程那么只有等到二者重新回到同一线程的时候过滤器才会有效。 9.5 消息事件机制和信号和槽机制的关系
来看下官方文档对信号和槽以及事件机制的原文和翻译 In Qt, we have an alternative to the callback technique: We use signals and slots. A signal is emitted when a particular event occurs. Qts widgets have many predefined signals, but we can always subclass widgets to add our own signals to them. A slot is a function that is called in response to a particular signal. Qts widgets have many pre-defined slots, but it is common practice to subclass widgets and add your own slots so that you can handle the signals that you are interested in. 在Qt中我们有一个替代回调技术:我们使用信号和槽。当某个特定事件发生时发出一个信号。Qt的小部件有许多预定义的信号但我们总是可以将小部件添加到它们中。槽是响应特定信号的函数。Qt的小部件有许多预定义的槽但是子类化小部件并添加自己的槽是常见的做法这样您就可以处理您感兴趣的信号。 In Qt, events are objects, derived from the abstract QEvent class, that represent things that have happened either within an application or as a result of outside activity that the application needs to know about. Events can be received and handled by any instance of a QObject subclass, but they are especially relevant to widgets. 在Qt中事件是对象从抽象的QEvent类派生出来它表示应用程序中发生的事件或者应用程序需要知道的外部活动的结果。事件可以由QObject子类的任何实例接收和处理但它们与小部件特别相关。 简单总结可以有以下几点 1.两者都是Qt中的通讯机制 2.信号和槽被用于程序内对象之间的通信也是一种程序回调机制回调的意思是让框架调用某个函数叫回调函数意思就是这个函数不是我们手动调用的而是把函数指针告诉框架然后框架在适当时机调用。 3.事件不仅有程序内部产生的也有程序外部、系统产生的如鼠标、键盘、定时器事件 4.信号和槽都是函数当某个信号发生了就调用相关联的槽函数。 5.事件是对象代表着一条消息。其他对象接收到这个事件要对这个事件做相应的处理也可以不做任何处理。 6.消息事件比信号槽更底层一般Qt中的标准信号会是某些消息事件处理的结果比如鼠标移动到按钮上按钮一直收到鼠标移动的事件可是按钮没有做任何处理但是只要当鼠标点击按钮按钮就会收到一个鼠标点击的事件他会处理这个事件并且主动去发送一个被点击的信号clicked这时候相关联的槽函数就会被调用。 在前面我们也曾经简单提到Qt 程序需要在main()函数创建一个QApplication对象然后调用它的exec()函数。这个函数就是开始 Qt 的消息事件循环不断地处理收到的消息事件。当事件发生时Qt框架将创建一个事件对象。Qt中所有事件类都继承于QEvent。在事件对象创建完毕后Qt 将这个事件对象传递给QObject的event()函数。event()函数并不直接处理事件而是按照事件对象的类型分派给特定的事件处理函数event handler关于这一点会在后边详细说明。 9.6 总结 Qt 的事件是整个 Qt 框架的核心机制之一也比较复杂。说它复杂更多是因为它涉及到的函数众多而处理方法也很多有时候让人难以选择。现在我们简单总结一下 Qt 中的事件机制。 Qt 中有很多种事件鼠标事件、键盘事件、大小改变的事件、位置移动的事件等等。如何处理这些事件实际有两种选择 1.所有事件对应一个事件处理函数在这个事件处理函数中用一个很大的分支 2.每一种事件对应一个事件处理函数。Qt 就是使用的这么一种机制 1mouseEvent() 2keyPressEvent() 。。。 Qt 具有这么多种事件处理函数肯定有一个地方对其进行分发否则Qt 怎么知道哪一种事件调用哪一个事件处理函数呢这个分发的函数就是event()。显然当QMouseEvent产生之后event()函数将其分发给mouseEvent()事件处理器进行处理。
event()函数会有两个问题 1.event()函数是一个 protected 的函数这意味着我们要想重写event()必须继承一个已有的类。试想我的程序根本不想要鼠标事件程序中所有组件都不允许处理鼠标事件是不是我得继承所有组件一一重写其event()函数protected 函数带来的另外一个问题是如果我基于第三方库进行开发而对方没有提供源代码只有一个链接库其它都是封装好的。我怎么去继承这种库中的组件呢 2.event()函数的确有一定的控制不过有时候我的需求更严格一些我希望那些组件根本看不到这种事件。event()函数虽然可以拦截但其实也是接收到了QMouseEvent对象。我连让它收都收不到。这样做的好处是模拟一种系统根本没有那个事件的效果所以其它组件根本不会收到这个事件也就无需修改自己的事件处理函数。这种需求怎么办呢 这两个问题是event()函数无法处理的。于是Qt 提供了另外一种解决方案事件过滤器。事件过滤器给我们一种能力让我们能够完全移除某种事件。事件过滤器可以安装到任意QObject类型上面并且可以安装多个。如果要实现全局的事件过滤器则可以安装到QApplication或者QCoreApplication上面。这里需要注意的是如果使用installEventFilter()函数给一个对象安装事件过滤器那么该事件过滤器只对该对象有效只有这个对象的事件需要先传递给事件过滤器的eventFilter()函数进行过滤其它对象不受影响。如果给QApplication对象安装事件过滤器那么该过滤器对程序中的每一个对象都有效任何对象的事件都是先传给eventFilter()函数。 事件过滤器可以解决刚刚我们提出的event()函数的两点不足 1.首先事件过滤器不是 protected 的因此我们可以向任何QObject子类安装事件过滤器 2.其次事件过滤器在目标对象接收到事件之前进行处理如果我们将事件过滤掉目标对象根本不会见到这个事件。 事实上还有一种方法我们没有介绍。Qt 事件的调用最终都会追溯到QCoreApplication::notify()函数因此最大的控制权实际上是重写QCoreApplication::notify()。这个函数的声明是
virtual bool QCoreApplication::notify ( QObject * receiver,QEvent * event );该函数会将event发送给receiver也就是调用receiver-event(event)其返回值就是来自receiver的事件处理器。注意这个函数为任意线程的任意对象的任意事件调用因此它不存在事件过滤器的线程的问题。不过我们并不推荐这么做因为notify()函数只有一个而事件过滤器要灵活得多。 现在我们可以总结一下 Qt 的事件处理实际上是有五个层次 1.重写paintEvent()、mousePressEvent()等事件处理函数。这是最普通、最简单的形式同时功能也最简单。 2.重写event()函数。event()函数是所有对象的事件入口QObject和QWidget中的实现默认是把事件传递给特定的事件处理函数。 3.在特定对象上面安装事件过滤器。该过滤器仅过滤该对象接收到的事件。 4.在QCoreApplication::instance()上面安装事件过滤器。该过滤器将过滤所有对象的所有事件因此和notify()函数一样强大但是它更灵活因为可以安装多个过滤器。全局的事件过滤器可以看到 disabled 组件上面发出的鼠标事件。 5.重写QCoreApplication::notify()函数。这是最强大的和全局事件过滤器一样提供完全控制并且不受线程的限制。但是全局范围内只能有一个被使用因为QCoreApplication是单例的。 十、绘图事件和绘图设备
10.1QPainter Qt 的绘图系统允许使用相同的 API 在屏幕和其它打印设备上进行绘制。整个绘图系统基于QPainterQPainterDevice和QPaintEngine三个类。 QPainter用来执行绘制的操作QPaintDevice是一个二维空间的抽象这个二维空间允许QPainter在其上面进行绘制也就是QPainter工作的空间QPaintEngine提供了画家QPainter在不同的设备上进行绘制的统一的接口。QPaintEngine类应用于QPainter和QPaintDevice之间通常对开发人员是透明的。除非你需要自定义一个设备否则你是不需要关心QPaintEngine这个类的。我们可以把QPainter理解成画笔把QPaintDevice理解成使用画笔的地方比如纸张、屏幕等而对于纸张、屏幕而言肯定要使用不同的画笔绘制为了统一使用一种画笔我们设计了QPaintEngine类这个类让不同的纸张、屏幕都能使用一种画笔。 下图给出了这三个类之间的层次结构: 上面的示意图告诉我们Qt 的绘图系统实际上是使用QPainter在QPainterDevice上进行绘制它们之间使用QPaintEngine进行通讯也就是翻译QPainter的指令。
下面我们通过一个实例来介绍QPainter的使用
class PaintedWidget : public QWidget
{Q_OBJECT
public:PaintedWidget(QWidget *parent 0);
protected:void paintEvent(QPaintEvent *);
}注意我们重写了QWidget的paintEvent()函数。接下来就是PaintedWidget的源代码
PaintedWidget::PaintedWidget(QWidget *parent) :QWidget(parent)
{resize(800, 600);setWindowTitle(tr(Paint Demo));
}void PaintedWidget::paintEvent(QPaintEvent *)
{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);
}在构造函数中我们仅仅设置了窗口的大小和标题。而paintEvent()函数则是绘制的代码。首先我们在栈上创建了一个QPainter对象也就是说每次运行paintEvent()函数的时候都会重建这个QPainter对象。注意这一点可能会引发某些细节问题由于我们每次重建QPainter因此第一次运行时所设置的画笔颜色、状态等第二次再进入这个函数时就会全部丢失。有时候我们希望保存画笔状态就必须自己保存数据否则的话则需要将QPainter作为类的成员变量 QPainter接收一个QPaintDevice指针作为参数。QPaintDevice有很多子类比如QImage以及QWidget。注意回忆一下QPaintDevice可以理解成要在哪里去绘制而现在我们希望画在这个组件因此传入的是 this 指针。 QPainter有很多以 draw 开头的函数用于各种图形的绘制比如这里的drawLine()drawRect()以及drawEllipse()等。当绘制轮廓线时使用QPainter的pen()属性。比如我们调用了painter.setPen(Qt::red)将 pen 设置为红色则下面绘制的矩形具有红色的轮廓线。接下来我们将 pen 修改为绿色5 像素宽painter.setPen(QPen(Qt::green, 5))又设置了画刷为蓝色。这时候再调用 draw 函数则是具有绿色 5 像素宽轮廓线、蓝色填充的椭圆。 10.2 绘图设备 绘图设备是指继承QPainterDevice的子类。Qt一共提供了四个这样的类分别是QPixmap、QBitmap、QImage和 QPicture。其中
1.QPixmap专门为图像在屏幕上的显示做了优化
2.QBitmap是QPixmap的一个子类它的色深限定为1可以使用 QPixmap的isQBitmap()函数来确定这个QPixmap是不是一个QBitmap。
3.QImage专门为图像的像素级访问做了优化。
4.QPicture则可以记录和重现QPainter的各条命令。 10.2.1 QPixmap 、QBitmap、QImage QPixmap继承了QPaintDevice因此你可以使用QPainter直接在上面绘制图形。QPixmap也可以接受一个字符串作为一个文件的路径来显示这个文件比如你想在程序之中打开png、jpeg之类的文件就可以使用 QPixmap。使用QPainter的drawPixmap()函数可以把这个文件绘制到一个QLabel、QPushButton或者其他的设备上面。QPixmap是针对屏幕进行特殊优化的因此它与实际的底层显示设备息息相关。注意这里说的显示设备并不是硬件而是操作系统提供的原生的绘图引擎。所以在不同的操作系统平台下QPixmap的显示可能会有所差别。 QBitmap继承自QPixmap因此具有QPixmap的所有特性提供单色图像。QBitmap的色深始终为1. 色深这个概念来自计算机图形学是指用于表现颜色的二进制的位数。我们知道计算机里面的数据都是使用二进制表示的。为了表示一种颜色我们也会使用二进制。比如我们要表示8种颜色需要用3个二进制位这时我们就说色深是3. 因此所谓色深为1也就是使用1个二进制位表示颜色。1个位只有两种状态0和1因此它所表示的颜色就有两种黑和白。所以说QBitmap实际上是只有黑白两色的图像数据。
由于QBitmap色深小因此只占用很少的存储空间所以适合做光标文件和笔刷。
下面我们来看同一个图像文件在QPixmap和QBitmap下的不同表现
void PaintWidget::paintEvent(QPaintEvent *)
{QPixmap pixmap(:/Image/butterfly.png);QPixmap pixmap1(:/Image/butterfly1.png);QBitmap bitmap(:/Image/butterfly.png);QBitmap bitmap1(:/Image/butterfly1.png);QPainter painter(this);painter.drawPixmap(0, 0, pixmap);painter.drawPixmap(200, 0, pixmap1);painter.drawPixmap(0, 130, bitmap);painter.drawPixmap(200, 130, bitmap1);
}这里我们给出了两张png图片。butterfly1.png是没有透明色的纯白背景而butterfly.png是具有透明色的背景。我们分别使用QPixmap和QBitmap来加载它们。注意看它们的区别白色的背景在QBitmap中消失了而透明色在QBitmap中转换成了黑色其他颜色则是使用点的疏密程度来体现像以前黑白报纸图片打印原理一样的。 QPixmap使用底层平台的绘制系统进行绘制无法提供像素级别的操作而QImage则是使用独立于硬件的绘制系统实际上是自己绘制自己因此提供了像素级别的操作并且能够在不同系统之上提供一个一致的显示形式。 我们声明了一个QImage对象大小是300 x 300颜色模式是RGB32即使用32位数值表示一个颜色的RGB值也就是说每种颜色使用8位。然后我们对每个像素进行颜色赋值从而构成了这个图像。我们可以把QImage想象成一个RGB颜色的二维数组记录了每一像素的颜色。
void PaintWidget::paintEvent(QPaintEvent *)
{QPainter painter(this);QImage image(300, 300, QImage::Format_RGB32);QRgb value;//将图片背景填充为白色image.fill(Qt::white);//改变指定区域的像素点的值for(int i50; i100; i){for(int j50; j100; j){value qRgb(255, 0, 0); // 红色image.setPixel(i, j, value);}}//将图片绘制到窗口中painter.drawImage(QPoint(0, 0), image);
}QImage与QPixmap的区别 1.QPixmap主要是用于绘图针对屏幕显示而最佳化设计QImage主要是为图像I/O、图片访问和像素修改而设计的 2.QPixmap依赖于所在的平台的绘图引擎故例如反锯齿等一些效果在不同的平台上可能会有不同的显示效果QImage使用Qt自身的绘图引擎可在不同平台上具有相同的显示效果 3.由于QImage是独立于硬件的也是一种QPaintDevice因此我们可以在另一个线程中对其进行绘制而不需要在GUI线程中处理使用这一方式可以很大幅度提高UI响应速度。 4.QImage可通过setPixpel()和pixel()等方法直接存取指定的像素。 QImage与QPixmap之间的转换: 5.QImage转QPixmap 使用QPixmap的静态成员函数: fromImage() QPixmap fromImage(const QImage image,
Qt::ImageConversionFlags flags Qt::AutoColor) 6.QPixmap转QImage: 使用QPixmap类的成员函数: toImage() QImage toImage() const;
10.2.2 QPicture 最后一个需要说明的是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()命令终止。代码示例如下
void PaintWidget::paintEvent(QPaintEvent *)
{QPicture pic;QPainter painter;//将图像绘制到QPicture中,并保存到文件painter.begin(pic);painter.drawEllipse(20, 20, 100, 50);painter.fillRect(20, 100, 100, 100, Qt::red);painter.end();pic.save(D:\\drawing.pic);//将保存的绘图动作重新绘制到设备上pic.load(D:\\drawing.pic);painter.begin(this);painter.drawPicture(200, 200, pic);painter.end();
}十一、文件操作 文件操作是应用程序必不可少的部分。Qt 作为一个通用开发库提供了跨平台的文件操作能力。Qt 通过QIODevice提供了对 I/O 设备的抽象这些设备具有读写字节块的能力。下面是 I/O 设备的类图Qt5 1.QIODevice所有 I/O 设备类的父类提供了字节块读写的通用操作以及基本接口 2.QFileDeviceQt5新增加的类提供了有关文件操作的通用实现。 3.QFlie访问本地文件或者嵌入资源 4.QTemporaryFile创建和访问本地文件系统的临时文件 5.QBuffer读写QbyteArray, 内存文件 6.QProcess运行外部程序处理进程间通讯 7.QAbstractSocket所有套接字类的父类 8.QTcpSocketTCP协议网络数据传输 9.QUdpSocket传输 UDP 报文 10.QSslSocket使用 SSL/TLS 传输数据 文件系统分类: 1.顺序访问设备 是指它们的数据只能访问一遍从头走到尾从第一个字节开始访问直到最后一个字节中途不能返回去读取上一个字节这其中QProcess、QTcpSocket、QUdpSoctet和QSslSocket是顺序访问设备。 2.随机访问设备: 可以访问任意位置任意次数还可以使用QIODevice::seek()函数来重新定位文件访问位置指针QFile、QTemporaryFile和QBuffer是随机访问设备。 11.1 基本文件操作 文件操作是应用程序必不可少的部分。Qt 作为一个通用开发库提供了跨平台的文件操作能力。在所有的 I/O 设备中文件 I/O 是最重要的部分之一。因为我们大多数的程序依旧需要首先访问本地文件当然在云计算大行其道的将来这一观点可能改变。QFile提供了从文件中读取和写入数据的能力。 我们通常会将文件路径作为参数传给QFile的构造函数。不过也可以在创建好对象最后使用setFileName()来修改。QFile需要使用 / 作为文件分隔符不过它会自动将其转换成操作系统所需要的形式。例如 C:/windows 这样的路径在 Windows 平台下同样是可以的。 QFile主要提供了有关文件的各种操作比如打开文件、关闭文件、刷新文件等。我们可以使用QDataStream或QTextStream类来读写文件也可以使用QIODevice类提供的read()、readLine()、readAll()以及write()这样的函数。值得注意的是有关文件本身的信息比如文件名、文件所在目录的名字等则是通过QFileInfo获取而不是自己分析文件路径字符串。 下面我们使用一段代码来看看QFile的有关操作
int main(int argc, char *argv[])
{QApplication app(argc, argv);QFile file(in.txt);if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {qDebug() Open file failed.;return -1;} else {while (!file.atEnd()) {qDebug() file.readLine();}}QFileInfo info(file);qDebug() info.isDir();qDebug() info.isExecutable();qDebug() info.baseName();qDebug() info.completeBaseName();qDebug() info.suffix();qDebug() info.completeSuffix();return app.exec();
}1.我们首先使用 QFile 创建了一个文件对象 这个文件名字是 in.txt。如果你不知道应该把它放在哪里可以使用QDir::currentPath()来获得应用程序执行时的当前路径。只要将这个文件放在与当前路径一致的目录下即可。
2.使用open()函数打开这个文件打开形式是只读方式文本格式。 这个类似于fopen()的 r 这样的参数。open()函数返回一个 bool 类型如果打开失败我们在控制台输出一段提示然后程序退出。否则我们利用 while 循环将每一行读到的内容输出。
3.可以使用QFileInfo获取有关该文件的信息。
QFileInfo有很多类型的函数我们只举出一些例子。比如 1isDir() 检查该文件是否是目录在操作系统这种文件夹是一种特殊的文件我们可以通过QFile来打开一个文件夹或者路径这个函数返回值就是判断他是不是一个文件夹或路径 2isExecutable() 检查该文件是否可以被执行。 3baseName() 可以直接获得文件名 4completeBaseName() 获取完整的文件名 5suffix() 则直接获取文件后缀名。 6completeSuffix() 获取完整的文件后缀 我们可以由下面的示例看到baseName()和completeBaseName()以及suffix()和completeSuffix()的区别
QFileInfo fi(/tmp/archive.tar.gz);
QString base fi.baseName(); // base archive
QString base fi.completeBaseName(); // base archive.tar
QString ext fi.suffix(); // ext gz
QString ext fi.completeSuffix(); // ext tar.gz11.2 二进制文件读写 QDataStream提供了基于QIODevice的二进制数据的序列化。数据流是一种二进制流这种流完全不依赖于底层操作系统、CPU 或者字节顺序大端或小端。例如在安装了 Windows 平台的 PC 上面写入的一个数据流可以不经过任何处理直接拿到运行了 Solaris 的 SPARC 机器上读取。由于数据流就是二进制流因此我们也可以直接读写没有编码的二进制数据例如图像、视频、音频等。 QDataStream既能够存取 C 基本类型如 int、char、short 等也可以存取复杂的数据类型例如自定义的类。实际上QDataStream对于类的存储是将复杂的类分割为很多基本单元实现的。 结合QIODeviceQDataStream可以很方便地对文件、网络套接字等进行读写操作。我们从代码开始看起
QFile file(file.dat);
file.open(QIODevice::WriteOnly);
QDataStream out(file);
out QString(the answer is);
out (qint32)42;1.在这段代码中我们首先打开一个名为 file.dat 的文件注意我们为简单起见并没有检查文件打开是否成功这在正式程序中是不允许的。然后我们将刚刚创建的file对象的指针传递给一个QDataStream实例out。类似于std::cout标准输出流QDataStream也重载了输出重定向运算符。后面的代码就很简单了将“the answer is”和数字 42 输出到数据流。由于我们的 out 对象建立在file之上因此相当于将问题和答案写入file。
2.需要指出一点最好使用 Qt 整型来进行读写比如程序中的qint32。这保证了在任意平台和任意编译器都能够有相同的行为。 如果你直接运行这段代码你会得到一个空白的 file.dat并没有写入任何数据。这是因为我们的file没有正常关闭。为性能起见数据只有在文件关闭时才会真正写入。因此我们必须在最后添加一行代码
file.close(); // 如果不想关闭文件可以使用 file.flush();
接下来我们将存储到文件中的答案取出来
QFile file(file.dat);
file.open(QIODevice::ReadOnly);
QDataStream in(file);
QString str;
qint32 a;
in str a;唯一需要注意的是你必须按照写入的顺序将数据读取出来。顺序颠倒的话程序行为是不确定的严重时会直接造成程序崩溃。 那么既然QIODevice提供了read()、readLine()之类的函数为什么还要有QDataStream呢QDataStream同QIODevice有什么区别区别在于QDataStream提供流的形式性能上一般比直接调用原始 API 更好一些。我们通过下面一段代码看看什么是流的形式
QFile file(file.dat);
file.open(QIODevice::ReadWrite);QDataStream stream(file);
QString str the answer is 42;stream str;11.3 文本文件读写 上一节我们介绍了有关二进制文件的读写。二进制文件比较小巧却不是人可读的格式。而文本文件是一种人可读的文件。为了操作这种文件我们需要使用QTextStream类。QTextStream和QDataStream的使用类似只不过它是操作纯文本文件的。 QTextStream会自动将 Unicode 编码同操作系统的编码进行转换这一操作对开发人员是透明的。它也会将换行符进行转换同样不需要自己处理。QTextStream使用 16 位的QChar作为基础的数据存储单位同样它也支持 C 标准类型如 int 等。实际上这是将这种标准类型与字符串进行了相互转换。 QTextStream同QDataStream的使用基本一致例如下面的代码将把“The answer is 42”写入到 file.txt 文件中
QFile data(file.txt);if (data.open(QFile::WriteOnly | QIODevice::Truncate)) {QTextStream out(data);out The answer is 42;}这里我们在open()函数中增加了QIODevice::Truncate打开方式。我们可以从下表中看到这些打开方式的区别 枚举值 描述
QIODevice::NotOpen 未打开QIODevice::ReadOnly 以只读方式打开QIODevice::WriteOnly 以只写方式打开QIODevice::ReadWrite 以读写方式打开QIODevice::Append 以追加的方式打开新增加的内容将被追加到文件末尾 QIODevice::Truncate 以重写的方式打开在写入新的数据时会将原有 QIODevice::Truncate 以重写的方式打开在写入新的数据时会将原有 数据全部 清除游标设置在文件开头。QIODevice::Text 在读取时将行结束符转换成 \n在写入时 将行结束符转换成本地格式例如 Win32 平台上是 \r\n QIODevice::Unbuffered 忽略缓存 我们在这里使用了QFile::WriteOnly | QIODevice::Truncate也就是以只写并且覆盖已有内容的形式操作文件。注意QIODevice::Truncate会直接将文件内容清空。 虽然QTextStream的写入内容与QDataStream一致但是读取时却会有些困难 QFile data(file.txt);if (data.open(QFile::ReadOnly)) {QTextStream in(data);QString str;int ans 0;in str ans;}在使用QDataStream的时候这样的代码很方便但是使用了QTextStream时却有所不同读出的时候str 里面将是 The answer is 42ans 是 0。这是因为当使用QDataStream写入的时候实际上会在要写入的内容前面额外添加一个这段内容的长度值。而以文本形式写入数据是没有数据之间的分隔的。因此使用文本文件时很少会将其分割开来读取而是使用诸如使用
QTextStream::readLine();//读取一行
QTextStream::readAll();//读取所有文本这种函数之后再对获得的QString对象进行处理。 默认情况下QTextStream的编码格式是 Unicode如果我们需要使用另外的编码可以使用
stream.setCodec(UTF-8);
这样的函数进行设置。 12.附录图