很久以前写的笔记,综合了很多内容,主要是来源于传智教育的Qt教学视频。时间久远,排版可能有点问题。
Qt相关内容解释
.pro文件解释
QT += core gui #Qt包含的模块
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets #大于4版本以上 包含 widget 模块
CONFIG += c++11
DEFINES += QT_DEPRECATED_WARNINGS
SOURCES += \
main.cpp \
mywidget.cpp
HEADERS += \
mywidget.h
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
Qt5常用模块
Qt Core:
提供核心的非GUI功能,所有的模块都需要这个模块。这个模块的类包括了动画框架、定时器、各个容器类、时间日期类、事件、IO、JSON、插件机制、智能指针、图形(矩形、路径等)、线程、XML等。所有这些类都可以通过头文件引入。
Qt GUI:
提供GUI程序的基本功能,包括与窗口系统的集成、事件处理、OpenGL和OpenGL ES集成、2D图像、字体、拖放等。这些类一般由Qt用户界面类内部使用,当然也可以用于访问底层的OpenGL ES图像API。Qt Gui模块提供的是所有图形用户界面程序都需要的通用功能。
Qt Multimedia:
提供视频、音频、收音机以及摄像头等功能。这些类可以通过引入,而且需要在pro文件中添加QT += multimedia。
Qt Network:
提供跨平台的网络功能。这些类可以通过引入,而且需要在pro文件中添加QT += network。
Qt Qml:
提供QML(一种脚本语言,也提供JavaScript的交互机制)使用的C++API。这些类可以通过引入,而且需要在pro文件中添加QT += qml。
Qt Quick:
允许在Qt/C++程序中嵌入Qt Quick(一种基于Qt的高度动画的用户界面,适合于移动平台开发)。这些类可以通过引入,而且需要在pro文件中添加QT += sql。
Qt SQL:
允许使用SQL访问数据库。这些类可以通过引入,而且需要再pro文件中添加QT += sql。
Qt Test:
提供Qt程序的淡云测试功能。这些类可以通过引入,而且需要在pro文件中添加QT += testlib。
Qt Webkit:
基于Webkit2的实现以及一套全新的QML API。
命名规范
类名:首字母大写,单词和单词之间首字母大写
函数名:变量名称 首字母小写,单词和单词之间首字母大写
快捷键
编译工程 Ctrl+B
运行工程 Ctrl+R
自动对齐 Ctrl+I
开始调试 F5
停止调试 Shift+F5
设置和取消断点 F9
单步跳过 F10
单步进入 F11
单步跳出 shift + F11
执行到行 ctrl + F10
注释行,取消注释行 Ctrl + /
跳转到函数定义 F2
声明和定义之间切换 Shift+F2
头文件和源文件之间切换 F4
Ctrl+F
查找替换当前选中的内容,按下Ctrl+F,会列出所有和你选的内容一样的语句
Ctrl+Shift+F
查找内容
Ctrl+1
欢迎模式
Ctrl+2
编辑模式
Ctrl+3
调试模式
Ctrl+4
项目设置模式
Ctrl+5
帮助模式
Ctrl+6
输出模式
Alt+0
显示或者隐藏侧边条,编辑模式下起作用(有时写的函数太长,屏幕不够大,就用这个)
Ctrl+Space
自动补全(貌似会和输入法的切换冲突)
ESC
切换到编辑模式
Alt+1
创建细节窗口
Alt+2
搜索结果窗口
Alt+3
程序输出窗口
Alt+4
编译输出窗口
Ctrl+Shift+<
折叠代码块
Ctrl+Shift+>
展开代码块
Ctrl+[
跳到代码块的头部
Ctrl+]
跳到代码块的尾部
Ctrl+L
跳到某一行
基础引入
QPushButton创建
头文件:#inlcude<QPushButton>
可以在Qt助手里查到。
show()
接口是顶层新窗口弹出空间。所以在这里不使用这个接口。
若要QPushButton
依附在一个窗口中。需要传入指针(this指针)
//头文件 #include <QPushButton>
QPushButton * btn = new QPushButton;
//设置父亲
btn->setParent(this);
//设置文字
btn->setText("AAA");
//移动位置
btn->move(100,100);
//第二种创建
QPushButton * btn2 = new QPushButton("BBB",this);
//重新指定窗口大小
this->resize(600,400);
//设置窗口标题
this->setWindowTitle("第一个项目");
//限制窗口大小
this->setFixedSize(600,400);
对象模型(对象树)
在Qt中创建对象的时候会提供一个Parent对象指针,下面来解释这个parent到底是干什么的。
- QObject是以对象树的形式组织起来的。
- 当你创建一个QObject对象时,会看到QObject的构造函数接收一个QObject指针作为参数,这个参数就是 parent,也就是父对象指针。
这相当于,在创建QObject对象时,可以提供一个其父对象,我们创建的这个QObject对象会自动添加到其父对象的children()列表。 - 当父对象析构的时候,这个列表中的所有对象也会被析构。(注意,这里的父对象并不是继承意义上的父类!)
这种机制在 GUI 程序设计中相当有用。例如,一个按钮有一个QShortcut(快捷键)对象作为其子对象。当我们删除按钮的时候,这个快捷键理应被删除。这是合理的。
- 当你创建一个QObject对象时,会看到QObject的构造函数接收一个QObject指针作为参数,这个参数就是 parent,也就是父对象指针。
- QWidget是能够在屏幕上显示的一切组件的父类。
- QWidget继承自QObject,因此也继承了这种对象树关系。一个孩子自动地成为父组件的一个子组件。因此,它会显示在父组件的坐标系统中,被父组件的边界剪裁。例如,当用户关闭一个对话框的时候,应用程序将其删除,那么,我们希望属于这个对话框的按钮、图标等应该一起被删除。事实就是如此,因为这些都是对话框的子组件。
- 当然,我们也可以自己删除子对象,它们会自动从其父对象列表中删除。比如,当我们删除了一个工具栏时,其所在的主窗口会自动将该工具栏从其子对象列表中删除,并且自动调整屏幕显示。
Qt 引入对象树的概念,在一定程度上解决了内存问题。 - 当一个QObject对象在堆上创建的时候,Qt 会同时为其创建一个对象树。不过,对象树中对象的顺序是没有定义的。这意味着,销毁这些对象的顺序也是未定义的。
- 任何对象树中的 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 对象,并且大胆在堆上创建。
窗口坐标系
坐标体系:
以左上角为原点(0,0),X向右增加,Y向下增加。
对于嵌套窗口,其坐标是相对于父窗口来说的。
信号和槽机制
QPushButton * quitBtn = new QPushButton("关闭窗口",this);
connect(quitBtn,&QPushButton::clicked,this,&MyWidget::close);
connect()函数最常用的一般形式:
connect(sender, signal, receiver, slot);
参数解释:
- sender:发出信号的对象
- signal:发送对象发出的信号
- receiver:接收信号的对象
- slot:接收对象在接收到信号之后所需要调用的函数(槽函数)
在帮助文档中比如我们上面的按钮的点击信号,在帮助文档中输入QPushButton,首先我们可以在Contents中寻找关键字 signals,信号的意思,但是我们发现并没有找到,这时候我们应该想到也许这个信号的被父类继承下来的,因此我们去他的父类QAbstractButton中就可以找到该关键字,点击signals索引到系统自带的信号.
自定义信号和槽
使用connect()可以让我们连接系统提供的信号和槽。但是,Qt 的信号槽机制并不仅仅是使用系统提供的那部分,还会允许我们自己设计自己的信号和槽。
首先定义一个学生类和老师类:
老师类中声明信号 饿了 hungry
信号没有返回值,只需要声明不需要实现,可以有参数,可以重载
signals:
void hungury();
学生类中声明槽 请客 treat
返回值void,需要声明,需要实现,可以有参数,可以发生重载
public slots:
void treat();
在窗口中声明一个公共方法下课,这个方法的调用会触发老师饿了这个信号,而响应槽函数学生请客
void MyWidget::ClassIsOver()
{
//发送信号
emit teacher->hungury();
}
学生响应了槽函数,并且打印信息
//自定义槽函数 实现
void Student::treat()
{
qDebug() << "该吃饭了!";
}
在窗口中连接信号槽
teacher = new Teacher(this);
student = new Student(this);
connect(teacher,&Teacher::hungury,student,&Student::treat);
并且调用下课函数,测试打印出 “该吃饭了”
自定义的信号 hungry带参数,需要提供重载的自定义信号和 自定义槽
void hungury(QString name); 自定义信号
void treat(QString name ); 自定义槽
但是由于有两个重名的自定义信号和自定义的槽,直接连接会报错,所以需要利用函数指针来指向函数地址, 然后在做连接
void (Teacher:: * teacherSingal)(QString) = &Teacher::hungury;
void (Student:: * studentSlot)(QString) = &Student::treat;
connect(teacher,teacherSingal,student,studentSlot);
lambda表达式
C++11中的Lambda表达式用于定义并创建匿名的函数对象,以简化编程工作。首先看一下Lambda表达式的基本构成:
[capture](parameters) mutable ->return-type
{
statement
}
[函数对象参数](操作符重载函数参数)mutable ->返回值{函数体}
函数对象参数;
[],标识一个Lambda的开始,这部分必须存在,不能省略。函数对象参数是传递给编译器自动生成的函数对象类的构造函数的。函数对象参数只能使用那些到定义Lambda为止时Lambda所在作用范围内可见的局部变量(包括Lambda所在类的this)。函数对象参数有以下形式:
空
。没有使用任何函数对象参数。=
。函数体内可以使用Lambda所在作用范围内所有可见的局部变量(包括Lambda所在类的this),并且是值传递方式(相当于编译器自动为我们按值传递了所有局部变量)。&
。函数体内可以使用Lambda所在作用范围内所有可见的局部变量(包括Lambda所在类的this),并且是引用传递方式(相当于编译器自动为我们按引用传递了所有局部变量)。this
。函数体内可以使用Lambda所在类中的成员变量。a
。将a按值进行传递。按值进行传递时,函数体内不能修改传递进来的a的拷贝,因为默认情况下函数是const的。要修改传递进来的a的拷贝,可以添加mutable修饰符。&a
。将a按引用进行传递。a, &b
。将a按值进行传递,b按引用进行传递。=,&a, &b
。除a和b按引用进行传递外,其他参数都按值进行传递。&, a, b
。除a和b按值进行传递外,其他参数都按引用进行传递。
操作符重载函数参数;
标识重载的()操作符的参数,没有参数时,这部分可以省略。参数可以通过按值(如:(a,b))和按引用(如:(&a,&b))两种方式进行传递。
可修改标示符;
mutable声明,这部分可以省略。按值传递函数对象参数时,加上mutable修饰符后,可以修改按值传递进来的拷贝(注意是能修改拷贝,而不是值本身)。
QPushButton * myBtn = new QPushButton (this);
QPushButton * myBtn2 = new QPushButton (this);
myBtn2->move(100,100);
int m = 10;
connect(myBtn,&QPushButton::clicked,this,[m] ()mutable { m = 100 + 10; qDebug() << m; });
connect(myBtn2,&QPushButton::clicked,this,[=] () { qDebug() << m; });
qDebug() << m;
函数返回值;
->
返回值类型,标识函数返回值的类型,当返回值为void,或者函数体中只有一处return的地方(此时编译器可以自动推断出返回值类型)时,这部分可以省略。
是函数体;
{}
,标识函数的实现,这部分不能省略,但函数体可以为空。
Qt4版本的信号槽写法
connect(zt,SIGNAL(hungry(QString)),st,SLOT(treat(QString)));
这里使用了SIGNAL和SLOT这两个宏,将两个函数名转换成了字符串。注意到 connect()
函数的 signal
和 slot
都是接受字符串,一旦出现连接不成功的情况,Qt4是没有编译错误的(因为一切都是字符串,编译期是不检查字符串是否匹配),而是在运行时给出错误。这无疑会增加程序的不稳定性。
Qt5在语法上完全兼容Qt4,而反之是不可以的。
QMainWindow
QMainWindow是一个为用户提供主窗口程序的类,包含一个菜单栏(menu bar)、多个工具栏(tool bars)、多个锚接部件(dock widgets)、一个状态栏(status bar)及一个中心部件(central widget),是许多应用程序的基础,如文本编辑器,图片编辑器等。
菜单栏
一个主窗口最多只有一个菜单栏。位于主窗口顶部、主窗口标题栏下面。
创建菜单栏,通过QMainWindow类的menubar()函数获取主窗口菜单栏指针
QMenuBar * menuBar() const
创建菜单,调用QMenu的成员函数addMenu来添加菜单
QAction* addMenu(QMenu * menu)
QMenu* addMenu(const QString & title)
QMenu* addMenu(const QIcon & icon, const QString & title)
创建菜单项,调用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对象添加到菜单,就显示成一个菜单项,添加到工具栏,就显示成一个工具按钮。用户可以通过点击菜单项、点击工具栏按钮、点击快捷键来激活这个动作。
工具栏
主窗口的工具栏上可以有多个工具条,通常采用一个菜单对应一个工具条的的方式,也可根据需要进行工具条的划分。
直接调用QMainWindow类的addToolBar()
函数获取主窗口的工具条对象,每增加一个工具条都需要调用一次该函数。
插入属于工具条的动作,即在工具条上添加操作。
通过QToolBar类的addAction函数添加。
工具条是一个可移动的窗口,它的停靠区域由QToolBar的allowAreas决定,包括:
Qt::LeftToolBarArea 停靠在左侧
Qt::RightToolBarArea 停靠在右侧
Qt::TopToolBarArea 停靠在顶部
Qt::BottomToolBarArea 停靠在底部
Qt::AllToolBarAreas 以上四个位置都可停靠
使用setAllowedAreas()
函数指定停靠区域:
setAllowedAreas(Qt::LeftToolBarArea | Qt::RightToolBarArea)
使用setMoveable()
函数设定工具栏的可移动性:
setMoveable(false)
//工具条不可移动, 只能停靠在初始化的位置上
状态栏
派生自QWidget类,使用方法与QWidget类似,QStatusBar类常用成员函数:
状态栏也只能最多有一个
//添加小部件
void addWidget(QWidget * widget, int stretch = 0)
//插入小部件
int insertWidget(int index, QWidget * widget, int stretch = 0)
//删除小部件
void removeWidget(QWidget * widget)
铆接部件
铆接部件 QDockWidget,也称浮动窗口,可以有多个。
QDockWidget * dock = new QDockWidget("标题",this);
addDockWidget(Qt::LeftDockWidgetArea,dock);
dock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea | Qt::TopDockWidgetArea); 设置区域范围
核心部件(中心部件)
除了以上几个部件,中心显示的部件都可以作为核心部件,例如一个记事本文件,可以利用QTextEdit做核心部件
QTextEdit * edit = new QTextEdit(this);
setCentralWidget(edit);
资源文件
Qt 资源系统是一个跨平台的资源机制,用于将程序运行时所需要的资源以二进制的形式存储于可执行文件内部。如果你的程序需要加载特定的资源(图标、文本翻译等),那么,将其放置在资源文件中,就再也不需要担心这些文件的丢失。也就是说,如果你将资源以资源文件形式存储,它是会编译到可执行文件内部。
使用 Qt Creator 可以很方便地创建资源文件。
按钮组(Buttons)
按钮组(Buttons)中各个按钮的名称依次解释如下。
Push Button: 按钮。
Tool Button: 工具按钮。
Radio Button: 单选按钮。
Check Box: 复选框。
Command Link Button:命令链接按钮。
Button Box: 按钮盒。
输入部件组(Input Widgets)
Combo Box: 组合框。
FontCombo Box: 字体组合框。
LineEdit: 行编辑。
TextEdit: 文本编辑。
Plain Text Edit: 纯文本编辑。
SpinBox: 数字显示框(自旋盒)。
Double Spin Box: 双自旋盒。
TimeEdit: 时间编辑。
DateEdit: 日期编辑。
Date/Time Edit: 日期/时间编辑。
Dial: 拨号。
Horizontal Scroll Bar: 横向滚动条。
Vertical Scroll Bar: 垂直滚动条。
Horizontal Slider: 横向滑块。
Vertical Slider: 垂直滑块。
Keysequence Edit: 按键序列编辑。
设计里的各种控件
显示控件组(Display Widgets)
显示控件组(Display Widgets)中各个控件的名称依次解释如下。
Label: 标签。
TextBrowser: 文本浏览器。
Graphics View: 图形视图。
Calendar: 日历。
LCDNumber: 液晶数字。
Progress Bar: 进度条。
Horizontal Line: 水平线。
Vertical Line: 垂直线。
OpenGL Widget: 开放式图形库工具。
QQuickWidget: 嵌入QML工具。
QWebView: Web视图。
空间间隔组(Spacers)
空间间隔组(Spacers)中各个控件的名称依次解释如下。
Horizontal Spacer: 水平间隔。
Vertical Spacer: 垂直间隔。
容器组(Containers)
容器组(Containers)中各个控件的名称依次解释如下。
Group Box: 组框。
Scroll Area: 滚动区域。
ToolBox: 工具箱。
TabWidget: 标签小部件。
Stacked Widget:堆叠部件。
Frame: 帧。
Widget: 小部件。
MdiArea: MDI区域。
DockWidget: 停靠窗体部件。
QAxWidget: 封装Flash的ActiveX控件。
项目视图组(Item Views)
List View: 清单视图。
Tree View: 树视图。
Table View: 表视图。
Column View: 列视图。
项目控件组(Item Widgets)
项目控件组(Item Widgets)中各个控件的名称依次解释如下。
List Widget: 清单控件。
Tree Widget: 树形控件。
Table Widget: 表控件。
对话框QDialog
基本概念
对话框是 GUI 程序中不可或缺的组成部分。很多不能或者不适合放入主窗口的功能组件都必须放在对话框中设置。对话框通常会是一个顶层窗口,出现在程序最上层,用于实现短期任务或者简洁的用户交互。
Qt 中使用QDialog类实现对话框。就像主窗口一样,我们通常会设计一个类继承QDialog。QDialog(及其子类,以及所有Qt::Dialog类型的类)的对于其 parent 指针都有额外的解释:如果 parent 为 NULL,则该对话框会作为一个顶层窗口,否则则作为其父组件的子对话框(此时,其默认出现的位置是 parent 的中心)。顶层窗口与非顶层窗口的区别在于,顶层窗口在任务栏会有自己的位置,而非顶层窗口则会共享其父组件的位置。
对话框分为模态对话框和非模态对话框。
- 模态对话框,就是会阻塞同一应用程序中其它窗口的输入。
模态对话框很常见,比如“打开文件”功能。你可以尝试一下记事本的打开文件,当打开文件对话框出现时,我们是不能对除此对话框之外的窗口部分进行操作的。 - 与此相反的是非模态对话框,例如查找对话框,我们可以在显示着查找对话框的同时,继续对记事本的内容进行编辑。
标准对话框
所谓标准对话框,是 Qt 内置的一系列对话框,用于简化开发。事实上,有很多对话框都是通用的,比如打开文件、设置颜色、打印设置等。这些对话框在所有程序中几乎相同,因此没有必要在每一个程序中都自己实现这么一个对话框。
Qt 的内置对话框大致分为以下几类:
QColorDialog: 选择颜色;
QFileDialog: 选择文件或者目录;
QFontDialog: 选择字体;
QInputDialog: 允许用户输入一个值,并将其值返回;
QMessageBox: 模态对话框,用于显示信息、询问问题等;
QPageSetupDialog: 为打印机提供纸张相关的选项;
QPrintDialog: 打印机配置;
QPrintPreviewDialog:打印预览;
QProgressDialog: 显示操作过程。
自定义消息框
Qt 支持模态对话框和非模态对话框。
模态与非模态的实现:
- 使用QDialog::exec()实现应用程序级别的模态对话框
- 使用QDialog::open()实现窗口级别的模态对话框
- 使用QDialog::show()实现非模态对话框。
模态对话框
当对话框出现时,我们不能与主窗口进行任何交互,直到我们关闭了该对话框。
QDialog dialog;
dialog.setWindowTitle(tr("Hello, dialog!"));
dialog.exec();
非模态对话框
下面我们试着将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()
函数设置对话框关闭时,自动销毁对话框。
消息对话框
QMessageBox
用于显示消息提示。我们一般会使用其提供的几个 static 函数:
显示关于对话框。
void about(QWidget * parent, const QString & title, const QString & text)
这是一个最简单的对话框,其标题是 title,内容是 text,父窗口是 parent。对话框只有一个 OK 按钮。
显示关于 Qt 对话框。该对话框用于显示有关 Qt 的信息。
void aboutQt(QWidget * parent, const QString & title = QString()):
显示严重错误对话框。
StandardButton critical(QWidget * parent,
const QString & title,
const QString & text,
StandardButtons buttons = Ok,
StandardButton defaultButton = NoButton):
这个对话框将显示一个红色的错误符号。我们可以通过 buttons 参数指明其显示的按钮。默认情况下只有一个 Ok 按钮,我们可以使用StandardButtons类型指定多种按钮。
与QMessageBox::critical()类似,不同之处在于这个对话框提供一个普通信息图标。
StandardButton information(QWidget * parent,
const QString & title,
const QString & text,
StandardButtons buttons = Ok,
StandardButton defaultButton = NoButton)
与QMessageBox::critical ()类似,不同之处在于这个对话框提供一个问号图标,并且其显示的按钮是“是”和“否”。
StandardButton question(QWidget * parent,
const QString & title,
const QString & text,
StandardButtons buttons = StandardButtons( Yes | No ),
StandardButton defaultButton = NoButton)
与QMessageBox::critical()类似,不同之处在于这个对话框提供一个黄色叹号图标。
StandardButton warning(QWidget * parent,
const QString & title,
const QString & text,
StandardButtons buttons = Ok,
StandardButton defaultButton = NoButton)
我们可以通过下面的代码来演示下如何使用QMessageBox。
if (QMessageBox::Yes == QMessageBox::question(this,
tr("Question"), tr("Are you OK?"),
QMessageBox::Yes | QMessageBox::No,
QMessageBox::Yes))
{
QMessageBox::information(this, tr("Hmmm..."),
tr("I'm glad to hear that!"));
}
else
{
QMessageBox::information(this, tr("Hmmm..."),
tr("I'm sorry!"));
}
我们使用QMessageBox::question()来询问一个问题。
- 这个对话框的父窗口是 this。
QMessageBox是QDialog的子类,这意味着它的初始显示位置将会是在 parent 窗口的中央。 - 第二个参数是对话框的标题。
- 第三个参数是我们想要显示的内容。
- 第四个参数是关联的按键类型,我们可以使用或运算符(|)指定对话框应该出现的按钮。比如我们希望是一个 Yes 和一个 No。
- 最后一个参数指定默认选择的按钮。
这个函数有一个返回值,用于确定用户点击的是哪一个按钮。按照我们的写法,应该很容易的看出,这是一个模态对话框,因此我们可以直接获取其返回值。
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()是其成为一个模态对话框,根据其返回值进行相应的操作。
标准文件对话框
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)
不过注意,它的所有参数都是可选的,因此在一定程度上说,这个函数也是简单的。这六个参数分别是:
- parent:父窗口。
Qt 的标准对话框提供静态函数,用于返回一个模态对话框; - caption:对话框标题;
- dir:对话框打开时的默认目录8 “.” 代表程序运行目录
- “/” 代表当前盘符的根目录(特指 Windows 平台;Linux 平台当然就是根目录),这个参数也可以是平台相关的,比如“C:\”等;
- filter:过滤器。
我们使用文件对话框可以浏览很多类型的文件,但是,很多时候我们仅希望打开特定类型的文件。比如,文本编辑器希望打开文本文件,图片浏览器希望打开图片文件。过滤器就是用于过滤特定的后缀名。如果我们使用Image Files(*.jpg *.png)
,则只能显示后缀名是 jpg 或者 png 的文件。如果需要多个过滤器,使用;;
分割,比如JPEG Files(*.jpg);;PNG Files(*.png)
; - selectedFilter:默认选择的过滤器;
- options:对话框的一些参数设定
比如只显示文件夹等等,它的取值是enum QFileDialog::Option
,每个选项可以使用 | 运算组合起来。
QFileDialog::getOpenFileName()
返回值是选择的文件路径。我们将其赋值给 path。通过判断 path 是否为空,可以确定用户是否选择了某一文件。只有当用户选择了一个文件时,我们才执行下面的操作。
在saveFile()
中使用的QFileDialog::getSaveFileName()
也是类似的。使用这种静态函数,在 Windows、Mac OS 上面都是直接调用本地对话框,但是 Linux 上则是QFileDialog自己的模拟。这暗示了,如果你不使用这些静态函数,而是直接使用QFileDialog进行设置,那么得到的对话框很可能与系统对话框的外观不一致。这一点是需要注意的。
常用控件
QLabel控件使用
QLabel是我们最常用的控件之一,其功能很强大,我们可以用来显示文本,图片和动画等。
显示文字 (普通文本、html)
通过QLabel类的setText函数设置显示的内容:
void setText(const QString &)
- 可以显示普通文本字符串
QLable *label = new QLable;
label->setText(“Hello, World!”);
- 可以显示HTML格式的字符串
比如显示一个链接:
QLabel * label = new QLabel(this);
label ->setText("Hello, World");
label ->setText("<h1><a href=\"https://www.baidu.com\">百度一下</a></h1>");
label ->setOpenExternalLinks(true);
其中setOpenExternalLinks()
函数是用来设置用户点击链接之后是否自动打开链接,如果参数指定为true则会自动打开。
显示图片
可以使用QLabel的成员函数setPixmap设置图片
void setPixmap(const QPixmap &)
首先定义QPixmap对象
QPixmap pixmap;
然后加载图片
pixmap.load(":/Image/boat.jpg");
最后将图片设置到QLabel中
QLabel *label = new QLabel;
label.setPixmap(pixmap);
显示动画
可以使用QLabel 的成员函数setMovie加载动画,可以播放gif格式的文件
void setMovie(QMovie * movie)
首先定义QMovied对象,并初始化:
QMovie *movie = new QMovie(":/Mario.gif");
播放加载的动画:
movie->start();
将动画设置到QLabel中:
QLabel *label = new QLabel;
label->setMovie(movie);
QLineEdit
Qt提供的单行文本编辑框。
设置/获取内容
- 获取编辑框内容使用text(),函数声明如下:
QString text() const
- 设置编辑框内容
void setText(const QString &)
设置显示模式
使用QLineEdit类的setEchoMode () 函数设置文本的显示模式,函数声明:
void setEchoMode(EchoMode mode)
EchoMode是一个枚举类型,一共定义了四种显示模式: QLineEdit::Normal
模式显示方式,按照输入的内容显示。QLineEdit::NoEcho
不显示任何内容,此模式下无法看到用户的输入。QLineEdit::Password
密码模式,输入的字符会根据平台转换为特殊字符。QLineEdit::PasswordEchoOnEdit
编辑时显示字符否则显示字符作为密码。
另外,我们再使用QLineEdit显示文本的时候,希望在左侧留出一段空白的区域,那么,就可以使用QLineEdit给我们提供的setTextMargins函数:
void setTextMargins(int left, int top, int right, int bottom)
用此函数可以指定显示的文本与输入框上下左右边界的间隔的像素数。
消息机制和事件
事件
Qt 中所有事件类都继承于QEvent。在事件对象创建完毕后,Qt 将这个事件对象传递给QObject的event()函数。event()函数并不直接处理事件,而是按照事件对象的类型分派给特定的事件处理函数(event handler).
在所有组件的父类QWidget中,定义了很多事件处理的回调函数,如
- keyPressEvent()
- keyReleaseEvent()
- mouseDoubleClickEvent()
- mouseMoveEvent()
- mousePressEvent()
- mouseReleaseEvent() 等。
例子:
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("<center><h1>Move: (%1, %2)
</h1></center>").arg(QString::number(event->x()),
QString::number(event->y())));
}
void EventLabel::mousePressEvent(QMouseEvent *event)
{
this->setText(QString("<center><h1>Press:(%1, %2)
</h1></center>").arg(QString::number(event->x()),
QString::number(event->y())));
}
void EventLabel::mouseReleaseEvent(QMouseEvent *event)
{
QString msg;
msg.sprintf("<center><h1>Release: (%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();
}
event()
event()函数中实际是通过事件处理器来响应一个具体的事件。这相当于event()函数将具体事件的处理“委托”给具体的事件处理器。而这些事件处理器是 protected virtual 的,因此,我们重写了某一个事件处理器,即可让 Qt 调用我们自己实现的版本。
事件过滤器
事件过滤器的强大之处在于,我们可以为整个应用程序添加一个事件过滤器。记得,installEventFilter()函数是QObject的函数,QApplication或者QCoreApplication对象都是QObject的子类,因此,我们可以向QApplication或者QCoreApplication添加事件过滤器。这种全局的事件过滤器将会在所有其它特性对象的事件过滤器之前调用。尽管很强大,但这种行为会严重降低整个应用程序的事件分发效率。因此,除非是不得不使用的情况,否则的话我们不应该这么做。
注意,
事件过滤器和被安装过滤器的组件必须在同一线程,否则,过滤器将不起作用。另外,如果在安装过滤器之后,这两个组件到了不同的线程,那么,只有等到二者重新回到同一线程的时候过滤器才会有效。
总结
- 重写paintEvent()、mousePressEvent()等事件处理函数。这是最普通、最简单的形式,同时功能也最简单。
- 重写event()函数。event()函数是所有对象的事件入口,QObject和QWidget中的实现,默认是把事件传递给特定的事件处理函数。
- 在特定对象上面安装事件过滤器。该过滤器仅过滤该对象接收到的事件。
- 在QCoreApplication::instance()上面安装事件过滤器。该过滤器将过滤所有对象的所有事件,因此和notify()函数一样强大,但是它更灵活,因为可以安装多个过滤器。全局的事件过滤器可以看到 disabled 组件上面发出的鼠标事件。全局过滤器有一个问题:只能用在主线程。
- 重写QCoreApplication::notify()函数。这是最强大的,和全局事件过滤器一样提供完全控制,并且不受线程的限制。但是全局范围内只能有一个被使用(因为QCoreApplication是单例的)。
绘图和绘图设备
QPainter
Qt 的绘图系统允许使用相同的 API 在屏幕和其它打印设备上进行绘制。整个绘图系统基于QPainter,QPainterDevice和QPaintEngine三个类。
QPainter用来执行绘制的操作;QPaintDevice是一个二维空间的抽象,这个二维空间允许QPainter在其上面进行绘制,也就是QPainter工作的空间;QPaintEngine提供了画笔(QPainter)在不同的设备上进行绘制的统一的接口。QPaintEngine类应用于QPainter和QPaintDevice之间,通常对开发人员是透明的。除非你需要自定义一个设备,否则你是不需要关心QPaintEngine这个类的。我们可以把QPainter理解成画笔;把QPaintDevice理解成使用画笔的地方,比如纸张、屏幕等;而对于纸张、屏幕而言,肯定要使用不同的画笔绘制,为了统一使用一种画笔,我们设计了QPaintEngine类,这个类让不同的纸张、屏幕都能使用一种画笔。
三个类之间的层次结构:
QPainter ---> QPaintEngine ----> QPaintDevice
Qt 的绘图系统实际上是,使用QPainter在QPainterDevice上进行绘制,它们之间使用QPaintEngine进行通讯(也就是翻译QPainter的指令)。
绘图设备
绘图设备是指继承QPainterDevice的子类。Qt一共提供了四个这样的类,分别是QPixmap、QBitmap、QImage和 QPicture。其中,
- QPixmap专门为图像在屏幕上的显示做了优化
- QBitmap是QPixmap的一个子类,它的色深限定为1,可以使用 QPixmap的isQBitmap()函数来确定这个QPixmap是不是一个QBitmap。
- QImage专门为图像的像素级访问做了优化。
- QPicture则可以记录和重现QPainter的各条命令。
文件系统
- QIODevice:所有 I/O 设备类的父类,提供了字节块读写的通用操作以及基本接口;
- QFileDevice:Qt5新增加的类,提供了有关文件操作的通用实现。
- QFlie:访问本地文件或者嵌入资源;
- QTemporaryFile:创建和访问本地文件系统的临时文件;
- QBuffer:读写QbyteArray, 内存文件;
- QProcess:运行外部程序,处理进程间通讯;
- QAbstractSocket:所有套接字类的父类;
- QTcpSocket:TCP协议网络数据传输;
- QUdpSocket:传输 UDP 报文;
- QSslSocket:使用 SSL/TLS 传输数据;
文件系统分类: - 顺序访问设备:
是指它们的数据只能访问一遍:从头走到尾,从第一个字节开始访问,直到最后一个字节,中途不能返回去读取上一个字节,这其中,QProcess、QTcpSocket、QUdpSoctet和QSslSocket是顺序访问设备。 - 随机访问设备:
可以访问任意位置任意次数,还可以使用QIODevice::seek()函数来重新定位文件访问位置指针,QFile、QTemporaryFile和QBuffer是随机访问设备