Qt学习笔记整理,内容主要包含:
- QT的概述
- 创建QT的项目(hello qt)
- 第一个窗口及窗口属性
- 第一个按钮
- 信号与槽机制
- 带菜单栏的窗口
- 对话框
- 布局
- 常见的控件
- QT消息机制以及事件
- 绘图与绘图设备
笔记整理时间:2023年3月24日~2023年3月29日
代码仓库:https://gitee.com/wwyybtt/qt
Qt是一个跨平台的C++图形用户界面应用程序框架。它为应用程序开发者提供建立艺术级图形界面所需的所有功能。它是完全面向对象的,很容易扩展,并且允许真正的组件编程。
常见GUI:
- Qt:支持多平台开发;支持CSS;面向对象特性体现突出;发展趋势良好。
- MFC:仅在Windows开发;运行效率高;库安全性好。
- 1991年Qt最早由奇趣科技开发
- 1996年进入商业领域,它也是目前流行的linux桌面环境KDE的基础
- 2008年奇趣科技被诺基亚公司收购,Qt称为诺基亚旗下的编程语言
- 2012年Qt又被 Digia公司收购
- 2014年4月跨平台的集成开发环境Qt Creator3.1.0发布,同年5月20日配发了Qt5.3正式版,至此Qt实现了对i0S、Android、WP等各平台的全面支持。当前Qt最新版本为5.13.2(2019.12之前)
- 跨平台,几乎支持所有的平台
- 接口简单,容易上手,学习QT框架对学习其他框架有参考意义。
- 一定程度上简化了内存回收机制
- 开发效率高,能够快速的构建应用程序。
- 有很好的社区氛围,市场份额在缓慢上升。可以进行嵌入式开发。
- Linux桌面环境KDE
- Skype 网络电话
- Google Earth谷歌地图
- VLC多媒体播放器
- virtualBox虚拟机软件
- 咪咕音乐
- WPS Office
- 极品飞车
下载地址:https://download.qt.io/archive/qt/
安装流程参考:http://t.csdn.cn/sb407
这里以5.13版本为例:
Qt和QtCreator的区别:
- Qt:通俗来说,开发工具包
- QtCreator:集成的编译器,Qt的桌面环境
QtCreator主页面:
创建项目的方式:
- 方式1:欢迎->Projects->New Project
- 方式2:菜单栏->文件->新建文件或项目
打开项目:
- 打开之前创建的项目
- 方式1:欢迎->Projects->Open Project
- 方式2:菜单栏->文件->打开文件或项目
创建工程时需要注意:
- 项目名称
- 一般不要有特殊符号,不要有中文
- 选择Application->Qt Widgets Application
- 例:01_demo
- 创建路径(项目保存路径)
- 路径不要带中文
- 更改为:D:\2021code\Qt,设置为默认的项目路径
- 创建类的基类:
- 编译和运行
Qt项目框架及文件介绍:
QT += core gui //包含的模块 greaterThan(QT_MAJOR_VERSION, 4): QT += widgets //大于Qt4版本才包含此模块 CONFIG += c++11 DEFINES += QT_DEPRECATED_WARNINGS //定义编译选项,表示有些功能被标记为过时了,编译器就会发出警告 SOURCES += \ main.cpp \ //源文件 widget.cpp HEADERS += \ widget.h //头文件
- main.cpp
#include "widget.h" //Qt中一个类对应一个头文件,类名就是头文件名 #include
//Qt系统提供的标准类名声明头文件 int main(int argc, char *argv[]) { QApplication a(argc, argv); //应用程序类(整个后台管理的命脉,处理应用程序的初始化和结束,事件处理调度。注意不管有多少窗口,一个QApplication类就可) Widget w; //实例化对象,调用构造函数 w.show(); //显示图形界面 return a.exec(); //主事件循环,在exec函数中,Qt接受并处理用户和系统的事件并且将他们传递给适当的窗口控件 }
- widget.cpp
#include "widget.h" Widget::Widget(QWidget *parent) : QWidget(parent) { } Widget::~Widget() { }
- widget.h
#ifndef WIDGET_H #define WIDGET_H #include
class Widget : public QWidget { Q_OBJECT public: Widget(QWidget *parent = nullptr); ~Widget(); }; #endif // WIDGET_H
代码自动对齐:Ctrl + i
快速添加/取消注释:Ctrl + /
.pro文件用于生成可执行文件,.main.cpp主函数,widget.cpp类的函数,widget.h类和头文件。
一般情况下,窗口的属性和添加控件、对控件的操作都会在类的构造函数中书写
- 优点:可以让主程序中无多余代码
帮助手册如何查看,以QWidget类为例:
中文乱码解决:
- 在创建项目之前,Qt主界面->工具->选项->文本编辑器->行为->默认编码,选择UTF-8,Apply->OK
设置窗口属性:在widget.cpp中
Widget::Widget(QWidget *parent) : QWidget(parent) { //修改窗口的标题(第一个窗口) this->setWindowTitle("第一个窗口"); //设置窗口的大小,设置完成可以拉伸 //this->resize(800, 600); //设置窗口固定大小,设置完成不可以拉伸 this->setFixedSize(500, 500); }
新建工程:02_demo
使用帮助手册查找QPushButton类
- #include
创建按钮的步骤:
- 包含头文件(.cpo)及模块(.pro)
#include
//.cpp QT += widgets //.pro
- 调用类的构造函数创建并显示按钮
//创建按钮方式1 QPushButton* button = new QPushButton; //button->show();//会新开一个窗口显示按钮 //设置按钮的父对象为窗口,使按钮在窗口上显示 button->setParent(this);
- 设置按钮的属性
//----------设置按钮的属性 //设置按钮的文字、内容 button->setText("第一个按钮"); //设置按钮的显示位置 button->move(100, 100); //设置按钮的大小 button->setFixedSize(400, 400);
- 创建按钮的第二种方法
//创建按钮方式2 QPushButton* button2 = new QPushButton("第二个按钮", this); this->resize(600, 400);
创建按钮两种方式的区别:
- 方式1:窗口默认大小,按钮显示在左上角
- 方式2:窗口根据按钮的大小来创建,使用方法2,一般还需要调用resize函数重置窗口大小
在Qt中创建对象的时候会提供一个Parent对象指针,下面来解释这个parent到底是干什么的。
概念:Qt对象间父子关系。
- QObject是以对象树的形式组织起来的。
- 当你创建一个QObject对象时,会看到QObject_的构造函数接收一个QObject指针作为参数,这个参数就是parent,也就是父对象指针。这相当于,在创建QObject对象时,可以提供一个其父对象,我们创建的这个QObject对象会自动添加到其父对象的children()列表。
- 当父对象析构的时候,这个列表中的所有对象也会被析构。(注意,这里的父对象并不是继承意义上的父类! )
- QWidget是能够在屏幕上显示的一切组件的父类。
- QWidget继承自QObject,因此也继承了这种对象树关系。一个孩子自动地成为父组件的一个子组件。因此,它会显示在父组件的坐标系统中,被父组件的边界剪裁。例如,当用户关闭一个对话框的时候,应用程序将其删除,那么,我们希望属于这个对话框的按钮、图标等应该一起被删除。事实就是如此,因为这些都是对话框的子组件。
- 当然,我们也可以自己删除子对象,它们会自动从其父对象列表中删除。比如,当我们删除了一个工具栏时,其所在的主窗口会自动将该工具栏从其子对象列表中删除,并且自动调整屏幕显示。
解决问题:Qt引对象树的概念,在一定程度上解决了内存问题。
- 当一个QObject对象在堆上创建的时候,Qt会同时为其创建一个对象树。不过,对象树中对象的顺序是没有定义的。这意味着,销毁这些对象的顺序也是未定义的。
- 任何对象树中的QObject对象 delete 的时候,如果这个对象有parent,则自动将其从parent 的children()列表中删除;如果有孩子,则自动delete每一个孩子。Qt保证没有QObject会被delete 两次,这是由析构顺序决定的。
- 如果QObject在栈上创建,Qt保持同样的行为。正常情况下,这也不会发生什么问题。
C++在栈上创建对象时,是后创建的先析构,对于Qt的对象树,有以下需要注意的地方:
// 代码1 QWidget window; QPushButton button = QPushButton("退出", &window); // 代码2 QPushButton quit("Quit"); QWidget window; quit.setParent(&window);
- 代码1无问题:
- 因为栈一般先构造的后析构,代码1中执行结束后会先析构button,同时将button从window的子对象列表删除,然后析构window。因为window中已经无button子对象,仅析构window。
- 代码2有问题:
- 代码执行结束后会先析构window,而析构window时,会先析构它的子对象quit,然后析构window。接下来会再次析构quit,会导致二次析构,程序崩溃!
- 如何解决:在Qt中,尽量在构造的时候就指定parent对象,并且大胆地在堆上创建!
窗口坐标体系:
信号槽是Qt 框架引以为豪的机制之一。所谓信号槽,实际就是观察者模式。当某个事件发生之后,比如,按钮检测到自己被点击了一下,它就会发出一个信号(signal)。这种发出是没有目的的,类似广播。如果有对象对这个信号感兴趣,它就会使用连接(connect)函数,意思是,将想要处理的信号和自己的一个**函数(称为槽(slot))**绑定来处理这个信号。也就是说,当信号发出时,被连接的槽函数会自动被回调。
connect()函数是QObject类中的公有函数,其声明如下:
QMetaObject::Connection connect(const QObject *sender, const char *signal, const char *method, Qt::ConnectionType type = Qt::AutoConnection) const
connect()函数最常用的一般形式:
connect(sender, signal, receiver, slot);
- sender:发出信号的对象
- signal:发送对象发出的信号
- receiver:接收信号的对象
- slot:接收对象在接收到信号之后所需要调用的函数(槽函数)
大部分的类都存在槽函数和信号,打开帮助手册,以QWidget为例:
要求:创建一个按钮,点击按钮能实现关闭窗口的功能。
新建项目:03_demo
//创建按钮 QPushButton* button = new QPushButton("点击关闭窗口", this); //重置窗口大小 this->resize(600, 400); //信号与槽函数 connect(button, &QPushButton::clicked, this, &Widget::close);
所在项目:03_demo
步骤1:确定场景
- 老师饿了,学生请客(以这个为例)
- 小哥敲门,家人开门(自己练习实现)
步骤2:添加老师类和学生类
步骤3:
- 在老师类中声明信号(声明即可)
- teacher.h
- 信号一般在类声明中的signals下写
- 信号返回值为void,参数可以添加或为空
- 仅声明,不实现
- 一般情况下可以重载
- 并且在学生类中声明槽函数(声明并实现)
- 在student.h声明
- 槽函数声明一般在类声明中的public slots下写(对于高版本的Qt,也可以写到public或者全局)
- 槽函数返回值为void,参数可以添加或者为空
- 槽函数声明且实现
- 槽函数定义实现在student.cpp中
//teacher.h signals: void hungury(); //student.h public slots: void treat(); //student.cpp void Student::treat() { qDebug() << "请吃饭"; //包含头文件:#include
} 步骤4:创建老师对象和学生对象,并使用connect连接
- 在widget.cpp中创建并连接
this->tea = new Teacher(this); this->stu = new Student(this); connect(tea, &Teacher::hungury, stu, &Student::treat);
步骤5:触发信号
- widget.h中声明触发信号的成员函数
- widget.cpp定义触发信号的成员函数
- 调用该函数
//widget.h public: void ClassOver(); //widget.cpp void Widget::ClassOver() { emit tea->hungury(); } //调用 ClassOver();
补充:
- 点击按钮,请老师吃饭
connect(button, &QPushButton::clicked, this, &Widget::ClassOver); this->tea = new Teacher(this); this->stu = new Student(this); connect(tea, &Teacher::hungury, stu, &Student::treat);
- 信号连接信号
this->tea = new Teacher(this); this->stu = new Student(this); connect(tea, &Teacher::hungury, stu, &Student::treat); connect(button, &QPushButton::clicked, tea, &Teacher::hungury);
步骤1:重新写信号(带参数)
void hungury(QString food);
步骤2:重新写槽函数声明及定义(带参数)
void treat(QString food); void Student::treat(QString food) { qDebug() << "请老师吃饭:" << food; }
步骤3:由于函数重载了,所以需要利用函数指针指向函数地址,然后再作连接
//定义函数指针 void (Teacher::*teachersignal)(QString) = &Teacher::hungury; void (Student::*studentslot)(QString) = &Student::treat; connect(tea, teachersignal, stu, studentslot); ClassOver();
自定义信号与槽注意事项:
- 发送者与接受者需要是QObject的子类(槽函数全局,lambda除外)。
- 信号与槽函数返回值都是void。
- 信号需要声明,不需要定义实现。槽函数需要声明也需要定义实现。
- 槽函数是普通的成员函数,作为成员函数,会受到public、private、protected的影响。
- 使用emit在恰当的位置发送信号。
- 使用connect()函数连接信号和槽。
- 任何成员函数、static函数、全局函数和Lambda 表达式都可以作为槽函数。
- 信号槽要求信号和槽的参数一致,所谓一致,是参数类型一致。
- 如果信号和槽的参数不一致,允许的情况是,槽函数的参数可以比信号的少,即便如此,槽函数存在的那些参数的顺序也必须和信号的前面几个一致起来。这是因为,你可以在槽函数中选择忽略信号传来的数据(也就是槽函数的参数比信号的少)。
- 一个信号可以和多个槽相连
- 如果是这种情况,这些槽会一个接一个的被调用,但是它们的调用顺序是不确定的。
- 多个信号可以连接到一个槽
- 只要任意一个信号发出,这个槽就会被调用。
- 一个信号可以连接到另外的一个信号
- 当第一个信号发出时,第二个信号被发出。除此之外,这种信号-信号的形式和信号-槽的形式没有什么区别。
- 槽可以被断开连接,使用disconnect。
- 槽也可以被取消连接(当一个对象delete了,就会取消这个对象上的槽)
- 使用C++11中的lambda表达式
概念:C++11中的Lambda表达式用于定义并创建匿名的函数对象,以简化编程工作。
语法:
[capture](parameters) mutable ->return-type{statement}; 1、[capture]捕获列表,捕获的是那些到定义Lambda为止时Lambda所在作用范围内可见的局部变量 2、(parameters)参数列表,与普通函数的参数列表一致的。 3、mutable可修改标示符,按值传递捕获列表参数时(默认仅读权限),加上mutable修饰符后,可以修改按值传递进来的拷贝 4、->return-type返回值类型 5、{statement}函数体,内容跟普通函数一致 6、分号不能省略
注意:
- [] 标识一个Lambda的开始,这部分必须存在,不能省略。
- () 参数列表,如果不需要传递参数的话,()可以一同省略。
- 如果使用mutable,参数列表 () 不能省略的即使参数为空;如果使用mutable,修改拷贝,而不是值本身。
- 返回值类型,如果不需要,->return-type都可省略。
- {函数体},可以使用参数列表,也可以使用捕获列表。
补充:对于捕获列表[]的参数形式,有如下情况
- 空。没有使用任何函数对象参数。
- =。函数体内可以使用Lambda所在作用范围内所有可见的局部变量(包括Lambda 所在类的this),并且是值传递方式(相当于编译器自动为我们按值传递了所有局部变量)。
- &。函数体内可以使用Lambda所在作用范围内所有可见的局部变量(包括Lambda所在类的 this),并且是引用传递方式(相当于编译器自动为我们按引用传递了所有局部变量)。
- this。函数体内可以使用Lambda所在类中的成员变量。
- a。将a按值进行传递。按值进行传递时,函数体内不能修改传递进来的的拷贝,因为默认情况下函数是const_的。要修改传递进来的a的拷贝,可以添加mutable修饰符。
- &a。将a 按引用进行传递。
- a,&b。将a按值进行传递,b按引用进行传递。
- =,&a,&b。除a和b 按引用进行传递外,其他参数都按值进行传递。
- &,a, b。除 a和b按值进行传递外,其他参数都按引用进行传递。
一个简单的lambda表达式:
auto fun = [](){ qDebug() << "Lambda is running!"; }; fun(); // []{ qDebug() << "Lambda is running!"; }();
一个带参数和返回值的lambda:
auto fun = [](int a, int b){ qDebug() << "Lambda is running!"; return a + b; }; int sum = fun(100, 200); qDebug() << sum;
对于mutable:如果缺少mutable,下列代码将报错;省略()也会报错
int m = 10; auto fun = [m]()mutable{ qDebug() << "Lambda is running!"; m = 300; };
结合信号与槽:槽函数可以使用lambda,但后面的分号要省略
QPushButton* myBtn = new QPushButton("点击", this); this->resize(600, 400); connect(myBtn, &QPushButton::clicked, this, [=](){qDebug() << "按钮被按下";}); //这里的lambda省略了;
QMainWindow是一个为用户提供主窗口程序的类,包含一个菜单栏(menu bar)、多个工具栏(tool bars)、多个铆接部件(dock widgets)、一个状态栏(status bary及一个中心部件(central widget)。
以Qt界面为例,说明各个部件:
创建新的项目:04_demo
菜单栏类:QMenuBar
菜单类:QMenu
QAction类:充当子菜单(菜单项)
菜单栏创建方法:
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { resize(800, 600); //创建菜单栏 QMenuBar* menubar = new QMenuBar(this);//使用new创建 //QMenuBar* menubar = menuBar();//使用成员函数创建 this->setMenuBar(menubar); //创建菜单 QMenu* menu1 = new QMenu("文件"); QMenu* menu2 = new QMenu("编辑"); QMenu* menu3 = new QMenu("构建"); //添加菜单到菜单栏 menubar->addMenu(menu1); menubar->addMenu(menu2); menubar->addMenu(menu3); //创建菜单项/子菜单 QAction* act1 = new QAction("打开文件"); QAction* act2 = new QAction("另存为"); QAction* act3 = new QAction("关闭"); //添加菜单项到菜单 menu1->addAction(act1); menu1->addAction(act2); menu1->addAction(act3); //使用信号实现关闭功能(点击菜单项实现对应功能) connect(act3, &QAction::triggered, this, &QMainWindow::close); }
工具栏类:QToolBar
QAction类:充当子工具/工具项
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { resize(800, 600); //创建菜单栏 //QMenuBar* menubar = new QMenuBar(this); QMenuBar* menubar = menuBar(); this->setMenuBar(menubar); //创建菜单 QMenu* menu1 = new QMenu("文件"); QMenu* menu2 = new QMenu("编辑"); QMenu* menu3 = new QMenu("构建"); //添加菜单到菜单栏 menubar->addMenu(menu1); menubar->addMenu(menu2); menubar->addMenu(menu3); //创建菜单项/子菜单 QAction* act1 = new QAction("打开文件"); QAction* act2 = new QAction("另存为"); QAction* act3 = new QAction("关闭"); //添加菜单项到菜单 menu1->addAction(act1); menu1->addAction(act2); menu1->addAction(act3); //使用信号实现关闭功能 connect(act3, &QAction::triggered, this, &QMainWindow::close); //创建工具栏 QToolBar* toolbar = new QToolBar(this); //添加工具栏到窗口 this->addToolBar(toolbar); //创建工具项 QAction* act4 = new QAction("开始执行不调试"); QAction* act5 = new QAction("开始调试"); QAction* act6 = new QAction("编译"); //添加工具项到工具栏 toolbar->addAction(act4); toolbar->addAction(act5); toolbar->addAction(act6); //设置工具栏的停靠区域(左或右) //this->addToolBar(Qt::LeftToolBarArea, toolbar); //toolbar->setAllowedAreas(Qt::LeftToolBarArea | Qt::RightToolBarArea); //修改工具栏不可移动(默认可上下左右移动) //toolbar->setMovable(false); //设置工具栏的浮动状态(默认可悬浮窗口) //toolbar->setFloatable(false); }
状态栏有且只能有一个。
状态栏类:QStatusBar
标签类:QLabel
状态栏信息分类:
- 临时信息:使用方法showMessage
- 正式信息:使用方法addWidget
- 永久信息:使用方法addPermanentWidget
- 注意:临时信息和正式信息都显示在左侧,添加时避免同时添加,不然会有一个显示不出。
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { resize(800, 600); //创建菜单栏 //QMenuBar* menubar = new QMenuBar(this); QMenuBar* menubar = menuBar(); this->setMenuBar(menubar); //创建菜单 QMenu* menu1 = new QMenu("文件"); QMenu* menu2 = new QMenu("编辑"); QMenu* menu3 = new QMenu("构建"); //添加菜单到菜单栏 menubar->addMenu(menu1); menubar->addMenu(menu2); menubar->addMenu(menu3); //创建菜单项/子菜单 QAction* act1 = new QAction("打开文件"); QAction* act2 = new QAction("另存为"); QAction* act3 = new QAction("关闭"); //添加菜单项到菜单 menu1->addAction(act1); menu1->addAction(act2); menu1->addAction(act3); //使用信号实现关闭功能 connect(act3, &QAction::triggered, this, &QMainWindow::close); //创建工具栏 QToolBar* toolbar = new QToolBar(this); //添加工具栏到窗口 //this->addToolBar(toolbar); this->addToolBar(Qt::LeftToolBarArea, toolbar); //创建工具项 QAction* act4 = new QAction("开始执行不调试"); QAction* act5 = new QAction("开始调试"); QAction* act6 = new QAction("编译"); //添加工具项到工具栏 toolbar->addAction(act4); toolbar->addAction(act5); toolbar->addAction(act6); //设置工具栏的停靠区域(左或右) //this->addToolBar(Qt::LeftToolBarArea, toolbar); //toolbar->setAllowedAreas(Qt::LeftToolBarArea | Qt::RightToolBarArea); //修改工具栏不可移动(默认可上下左右移动) //toolbar->setMovable(false); //设置工具栏的浮动状态(默认可悬浮窗口) //toolbar->setFloatable(false); //创建状态栏 //QStatusBar* statusbar = new QStatusBar(this); QStatusBar* statusbar = statusBar(); //添加状态栏到窗口 this->setStatusBar(statusbar); //给状态栏添加信息 //临时信息 //statusbar->showMessage("页面显示成功"); //statusbar->showMessage("页面显示成功", 3000);//3000毫秒后消失 //正式信息(一般位于状态栏的左侧) QLabel* label1 = new QLabel("左边信息", this); statusbar->addWidget(label1); //永久信息(一般位于状态栏的右侧) QLabel* label2 = new QLabel("wwyybtt", this); statusbar->addPermanentWidget(label2); }
铆接部件类:QDockWidget
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { resize(800, 600); //创建菜单栏 //QMenuBar* menubar = new QMenuBar(this); QMenuBar* menubar = menuBar(); this->setMenuBar(menubar); //创建菜单 QMenu* menu1 = new QMenu("文件"); QMenu* menu2 = new QMenu("编辑"); QMenu* menu3 = new QMenu("构建"); //添加菜单到菜单栏 menubar->addMenu(menu1); menubar->addMenu(menu2); menubar->addMenu(menu3); //创建菜单项/子菜单 QAction* act1 = new QAction("打开文件"); QAction* act2 = new QAction("另存为"); QAction* act3 = new QAction("关闭"); //添加菜单项到菜单 menu1->addAction(act1); menu1->addAction(act2); menu1->addAction(act3); //使用信号实现关闭功能 connect(act3, &QAction::triggered, this, &QMainWindow::close); //创建工具栏 QToolBar* toolbar = new QToolBar(this); //添加工具栏到窗口 //this->addToolBar(toolbar); this->addToolBar(Qt::LeftToolBarArea, toolbar); //创建工具项 QAction* act4 = new QAction("开始执行不调试"); QAction* act5 = new QAction("开始调试"); QAction* act6 = new QAction("编译"); //添加工具项到工具栏 toolbar->addAction(act4); toolbar->addAction(act5); toolbar->addAction(act6); //设置工具栏的停靠区域(左或右) //this->addToolBar(Qt::LeftToolBarArea, toolbar); //toolbar->setAllowedAreas(Qt::LeftToolBarArea | Qt::RightToolBarArea); //修改工具栏不可移动(默认可上下左右移动) //toolbar->setMovable(false); //设置工具栏的浮动状态(默认可悬浮窗口) //toolbar->setFloatable(false); //创建状态栏 //QStatusBar* statusbar = new QStatusBar(this); QStatusBar* statusbar = statusBar(); //添加状态栏到窗口 this->setStatusBar(statusbar); //给状态栏添加信息 //临时信息 //statusbar->showMessage("页面显示成功"); //statusbar->showMessage("页面显示成功", 3000);//3000毫秒后消失 //正式信息(一般位于状态栏的左侧) QLabel* label1 = new QLabel("左边信息", this); statusbar->addWidget(label1); //永久信息(一般位于状态栏的右侧) QLabel* label2 = new QLabel("wwyybtt", this); statusbar->addPermanentWidget(label2); //创建铆接部件 //QDockWidget* dockwidget = new QDockWidget(this); QDockWidget* dockwidget = new QDockWidget("first", this); //添加铆接部件到窗口 //this->addDockWidget(dockwidget); this->addDockWidget(Qt::TopDockWidgetArea, dockwidget);//添加到上边,下边可使用Qt::BottomDockWidgetArea }
核心部件也叫中心部件。
//创建记事本作为核心部件 QTextEdit* edit = new QTextEdit("文本编辑器", this); //添加核心部件到窗口 this->setCentralWidget(edit);
Qt资源系统是一个跨平台的资源机制,用于将程序运行时所需要的资源以二进制的形式存储于可执行文件内部。**如果你的程序需要加载特定的资源(图标、文本翻译等),那么,将其放置在资源文件中,就再也不需要担心这些文件的丢失。也就是说,如果你将资源以资源文件形式存储,它是会编译到可执行文件内部。
使用Qt Creator可以很方便地创建资源文件。我们可以在工程上点右键,选择“添加新文件…”,可以在Qt分类下找到“Qt资源文件”:
右侧的编辑区有个“添加”,我们首先需要添加前缀,比如我们将前缀取名为images。然后选中这个前缀,继续点击添加文件,可以找到我们所需添加的文件。这里,我们选择dog.jpg文件。当我们完成操作之后,Qt Creator应该是这样子的:
接下来,我们还可以添加另外的前缀或者另外的文件。这取决于你的需要。当我们添加完成之后,我们可以像前面那样,通过使用:开头的路径来找到这个文件。比如,若我们的前缀是/images,文件是 document-open.png,那么就可以使用: / images/document-open.png_找到这个文件。
这么做带来的一个问题是,如果以后我们要更改文件名,比如将docuemnt-open. png度成docopen. png,那么,所有使用了这个名字的路径都需要修改。所以,更好的办法是,我们**给这个文件去一个“别名”,以后就以这个别名来引用这个文件。**具体做法是,选中这个文件,添加别名信息:
添加资源的步骤:
- 在工程中新增资源文件(右键工程)
- 给资源文件增加前缀(方便在代码中寻找)
- 给资源文件添加资源(图片等)
- 起别名(选用)
//给窗口设置背景图 MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { this->setFixedSize(800, 600); this->setAutoFillBackground(true);//允许背景图填满窗口/允许绘制 //创建图片控件 // QPixmap pix; // pix.load(":/open"); QPixmap pix = QPixmap(":/open").scaled(this->size());//控制大小 QPalette palette; palette.setBrush(QPalette::Background, QBrush(pix)); this->setPalette(palette); }
ui的功能:绘制界面(通过拖拽控件)
新建项目:05_demo,并且勾选创建ui文件
此外,ui也支持代码修改
对话框是GUI程序中不可或缺的组成部分。很多不能或者不适合放入主窗口的功能组件都必须放在对话框中设置。对话框通常会是一个顶层窗口,出现在程序最上层,用于实现短期任务或者简洁的用户交互。
Qt中使用QDialog类实现对话框。就像主窗口一样,我们通常会设计一个类继承QDialog。QDialog(及其子类,以及所有Qt::Dialog 类型的类)的对于其parent指针都有额外的解释:如果parent为 NULL,则该对话框会作为一个顶层窗口,否则则作为其父组件的子对话框(此时,其默认出现的位置是 parent的中心)。顶层窗口与非顶层窗口的区别在于,顶层窗口在任务栏会有自己的位置,而非顶层窗口则会共享其父组件的位置。
对话框分为模态对话框和非模态对话框。
- 模态对话框,就是会阻塞同一应用程序中其它窗口的输入。模态对话框很常见,比如“打开文件”功能。你可以尝试一下记事本的打开文件,当打开文件对话框出现时,我们是不能对除此对话框之外的窗口部分进行操作的。
- 与此相反的是非模态对话框,例如查找对话框,我们可以在显示着查找对话框的同时,继续对记事本的内容进行编辑。
标准对话框:所谓标准对话框,是Qt内置的一系列对话框,用于简化开发。事实上,有很多对话框都是通用的,比如打开文件、设置颜色、打印设置等。这些对话框在所有程序中几乎相同,因此没有必要在每一个程序中都自己实现这么一个对话框。Qt的内置对话框大致分为以下几类:
- QColorDialog:选择颜色;
- QFileDialog:选择文件或者目录;
- QFontDialog:选择字体;
- QInputDialog:允许用户输入一个值,并将其值返回;
- QMessageBox:模态对话框,用于显示信息、询问问题等;
- QPageSetupDialog:为打印机提供纸张相关的选项;
- QPrintDialog:打印机配置;
- QPrintPreviewDialog:打印预览;
- QProgressDialog:显示操作过程。
自定义对话框可以有模态与非模态对话框
- 使用QDialog::exec()实现应用程序级别的模态对话框
- 使用QDialog::open()实现窗口级别的模态对话框
- 使用QDialog::show()实现非模态对话框。
//自定义模态对话框 QDialog dialog; dialog.setWindowTitle("Hello, dailog"); dialog.exec();
下面将exec()修改为show()
//自定义非模态对话框 QDialog dialog; dialog.setWindowTitle("Hello, dailog"); dialog.show();//
上述代码执行现象:
- 事与愿违﹖对话框一闪而过!这是因为,show()函数不会阻塞当前线程,对话框会显示出来,然后函数立即返回,代码继续执行。注意,dialog是建立在栈上的,show()函数返回,MainWindow: : open()函数结束,dialog 超出作用域被析构,因此对话框消失了。知道了原因就好改了,我们将 dialog改成堆上建立,当然就没有这个问题了
//自定义非模态对话框 QDialog* dialog = new QDialog(this); //设置自动销毁 dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->setWindowTitle("Hello, dailog"); dialog->show();
关于非模态对话框,还需注意:
- 对话框要创建在堆上
- 由于对话框的特性(没有parent,没有指定this作为parent),可以设置对话框关闭,自动销毁对话框。
新建项目:06_demo
文件对话框需要的类:QFileDialog
可以查看静态成员函数:
#include "mainwindow.h" #include "ui_mainwindow.h" #include
#include #include #include MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) { setFixedSize(800, 600); ui->setupUi(this); ui->pushButton->setFixedSize(200, 30); ui->pushButton_2->setFixedSize(200, 30); } MainWindow::~MainWindow() { delete ui; } void MainWindow::on_pushButton_clicked() { //打开一个文件 QString fileName = QFileDialog::getOpenFileName(this, tr("打开文件"), "./", tr("Images (*.png *.xpm *.jpg);;Text (*.txt)")); if(!fileName.isEmpty()) { ui->plainTextEdit->appendPlainText(fileName); } } void MainWindow::on_pushButton_2_clicked() { //打开多个文件 QStringList fileNames = QFileDialog::getOpenFileNames(this, tr("打开文件"), "./", tr("Images (*.png *.xpm *.jpg);;Text (*.txt)")); for(int i = 0; i < fileNames.count(); i++) { qDebug()<<fileNames.at(i); } } getOpenFileName函数的参数列表:
- 父类对象指针
- 文件对话框的标题
- 打开文件路径
- 文件过滤器
- 文件过滤器写法:
"Images (*.png *.xpm *.jpg)"
"Images (*.png *.xpm *.jpg);;Text (*.txt *.docx *.pdf)"
需要的类:QColorDialog
静态成员函数:getColor,可以生成颜色选择对话框,返回值是一个颜色变量,如果在颜色对话框选择了颜色则有效,若取消则无效。
#include
void MainWindow::on_pushButton_3_clicked() { QPalette pal = ui->plainTextEdit->palette();//获取现有palette QColor initColor = pal.color(QPalette::Text);//获取现有文字颜色 QColor color = QColorDialog::getColor(initColor, this, "选择颜色"); //判断选择的颜色是否有效 if(color.isValid()) { pal.setColor(QPalette::Text, color); ui->plainTextEdit->setPalette(pal); } }
需要的类:QFontDialog
静态成员函数:getFont,生成选择字体对话框,返回值不能判断有效,一般是根据getFont的第一个参数逻辑变量是否为true进行判断。
#include
void MainWindow::on_pushButton_4_clicked() { bool ok; QFont initFont = ui->plainTextEdit->font();//获取原有文本框字体 //QFont font = QFontDialog::getFont(&ok, QFont("Times", 12), this); QFont font = QFontDialog::getFont(&ok, initFont, this); if(ok)//如果ok为true,选择字体有效 { ui->plainTextEdit->setFont(font); } }
需要的类:QMessageBox
静态成员函数:
#include
void MainWindow::on_pushButton_5_clicked() { //错误弹窗 QMessageBox::critical(this, "错误消息对话框", "程序出现错误"); //警告弹窗 QMessageBox::warning(this, "警告消息对话框", "程序出现了警告,可能存在越界"); //消息弹窗 //QMessageBox::information(this, "消息对话框", "程序执行结束"); QMessageBox::information(this, "消息对话框", "程序执行结束", QMessageBox::Ok, QMessageBox::NoButton); //消息选择弹窗 QMessageBox::StandardButton result; result = QMessageBox::question(this, "选择消息框", "文件已 修改,是否保存", QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::NoButton); if(result == QMessageBox::Yes) { qDebug()<<"正在保存"; } else if(result == QMessageBox::No) { qDebug()<<"不保存"; } else { qDebug()<<"取消操作"; } }
需要的类:QInputDialog,可以输入文本、整型数据、浮点型数据等
#include
void MainWindow::on_pushButton_6_clicked() { //生成输入文字对话框 bool ok; QString text = QInputDialog::getText(this, tr("输入文字对话框"), tr("请输入文字"), QLineEdit::Normal, "demo", &ok); if(ok && !text.isEmpty()) { ui->plainTextEdit->appendPlainText(text); } }
所谓GUI 界面,归根结底,就是一堆组件的叠加。我们创建一个窗口,把按钮放上面,把图标放上面,这样就成了一个界面。在放置时,组件的位置尤其重要。我们必须要指定组件放在哪里,以便窗口能够按照我们需要的方式进行渲染。这就涉及到组件定位的机制。
Qt提供了两种组件定位机制:绝对定位和布局定位。
- 绝对定位:需要提供组件的长、高,坐标值
- 绝对定位就是一种最原始的定位方法:给出这个组件的坐标和长宽值。这样,Qt 就知道该把组件放在哪里以及如何设置组件的大小。但是这样做带来的一个问题是,如果用户改变了窗口大小,比如点击最大化按钮或者使用鼠标拖动窗口边缘,采用绝对定位的组件是不会有任何响应的。这也很自然,因为你并没有告诉Qt,在窗口变化时,组件是否要更新自己以及如何更新。或者,还有更简单的方法:禁止用户改变窗口大小。但这总不是长远之计。
- 布局定位:需要指定使用哪种布局(垂直、水平、网格、窗体等)
通过ui直接拖拽使用。
创建新项目:07_demo,设计一个登录界面的布局
在07_demo创建一个设计师类,新增一个页面
QLabel是我们最常用的控件之一,其功能很强大,我们可以用来显示文本,图片和动画等。
创建新项目:08_demo
创建QLabel:
- 方式1:使用代码(头文件QLabel)
#include
QLabel* label = new QLabel(this); label->setText("这是代码创建的标签");
- 方式2:使用ui绘制(直接拖拽)
单行文本编辑框的使用。
新建项目:09_demo
命令创建:
#include
QLineEdit* edit = new QLineEdit(this); edit->setText("这是代码创建的编辑框"); ui创建:直接拖拽
ui->lineEdit->setEchoMode(QLineEdit::Password);//密码模式
设置输出模式:
- 显示(默认)
- 不显示
- 密码
在搭建Qt窗口界面的时候,在一个项目中很多窗口,或者是窗口中的某个模块会被经常性的重复使用。一|般遇到这种情况我们都会将这个窗口或者模块拿出来做成一个独立的窗口类,以备以后重复使用。
在使用Qt的ui_文件搭建界面的时候,工具栏栏中只为我们提供了标准的窗口控件,如果我们想使用自定义控件怎么办?
新建项目:10_demo
- 自定义控件一般是重复性使用的窗口或者窗口中的模块。
- 解决问题:将许多重复性使用的窗口或者窗口中的模块封装成自定义控件,可以减少创建次数,仅创建一次,使用时提升。
事件(event)是由系统或者Qt本身在不同的时刻发出的。当用户按下鼠标、敲下键盘,或者是窗口需要重新绘制的时候,都会发出一个相应的事件。一些事件在对用户操作做出响应时发出,如键盘事件等;另一些事件则是由系统自动发出,如计时器事件。
事件要经过以下四个阶段:
- 事件派发
- 事件过滤器
- 事件分发
- 事件处理阶段
事件循环的开始:exec()函数
Qt程序需要在main()函数创建一个QApplication对象,然后调用它的exec()函数。这个函数就是开始Qt 的事件循环。在执行exec()函数之后,程序将进入事件循环来监听应用程序的事件。当事件发生时,Qt将创建一个事件对象。Qt中所有事件类都继承于QEvent。在事件对象创建完毕后,Qt 将这个事件对象传递给QObject_的event()函数。event()函数并不直接处理事件,而是按照事件对象的类型分派给特定的事件处理函数(eventhandler),关于这一点,会在后边详细说明。
在所有组件的父类QWidget中,定义了很多事件处理的回调函数,如
- keyPressEvent()
- keyReleaseEvent()
- mouseDoubleClickEvent()
- mouseMoveEvent()
- mousePressEvent()
- mouseReleaseEvent()等。
详细的信息可查看帮助手册。
这些函数都是protected virtual的(虚函数,可在子类重写),也就是说,我们可以在子类中重新实现这些函数。
新建项目:11_demo
步骤:
- 新增一个类继承于QWidget(新建工程)
- 子类.h中声明重写的事件函数
- 子类.cpp中去实现事件函数
//Widget.h protected: void closeEvent(QCloseEvent* event);//光标定位到当前行,按下Alt + Enter可以快速在.cpp添加定义 void resizeEvent(QResizeEvent *event); //widget.cpp void Widget::closeEvent(QCloseEvent *event) { int ret = QMessageBox::question(this, "提示", "您确定要关闭窗口?"); if(ret == QMessageBox::Yes) { event->accept(); } else { event->ignore(); } } void Widget::resizeEvent(QResizeEvent *event) { qDebug()<<"oldsize = "<<event->oldSize(); qDebug()<<"newsize = "<<event->size(); }
新建项目12_demo(widget.h和widge.cpp从11_demo拷贝),并新增一个类MyLabel,继承于QLabel
鼠标按下、释放、移动事件步骤:
- 新建label类继承QLabel(新建工程,ui绘制label,新增c++类继承QWiget,代码修改继承QLabel,ui绘制label提升新创建label类)
- 查看帮助文档QLabel
- 在mylabel.h声明事件函数,在mylabel.cpp实现事件函数(定位到.h的声明:Alt + Enter)
//mylabel.h #ifndef MYLABEL_H #define MYLABEL_H #include
class MyLabel : public QLabel { Q_OBJECT public: explicit MyLabel(QWidget *parent = nullptr); signals: public slots: protected: void mousePressEvent(QMouseEvent *ev); void mouseReleaseEvent(QMouseEvent *ev); void mouseMoveEvent(QMouseEvent *ev); }; #endif // MYLABEL_H //mylabel.cpp #include "mylabel.h" #include #include MyLabel::MyLabel(QWidget *parent) : QLabel(parent) { this->setMouseTracking(true);//默认是false,只有鼠标按下后移动才会追踪,修改为true,鼠标移动就会立即追踪 } void MyLabel::mousePressEvent(QMouseEvent *ev) { qDebug()<<"标签被鼠标按下了"; if(ev->button() == Qt::LeftButton) { qDebug()<<"左键按下了"; } else if(ev->button() == Qt::RightButton) { qDebug()<<"右键按下了"; } else { qDebug()<<"其他按下了"; } } void MyLabel::mouseReleaseEvent(QMouseEvent *ev) { qDebug()<<"在标签上的鼠标释放了"; } void MyLabel::mouseMoveEvent(QMouseEvent *ev) { qDebug()<<"在标签上的鼠标移动了" << " " << ev->x() << " " << ev->y(); }
在项目12_demo中,重写一个按下Tab的键盘事件分发函数。
//.h bool event(QEvent* event); //.cpp bool Widget::event(QEvent *event) { //判断事件类型(键盘事件) if(event->type() == QEvent::KeyPress) { // 将 QEvent类型的event转换成QKeyEvent QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event); if(keyEvent->key() == Qt::Key_Tab) { qDebug()<<"Tab被按下"; return true; } } return QWidget::event(event); //如果传入的事件已被识别并且处理,则需要返回 true,否则返回false。如果返回值是 true,那么Qt会认为这个事件已经处理完毕,不会再将这个事件发送给其它对象,而是会继续处理事件队列中的下一事件。 //在event()函数中,调用事件对象的accept)和ignore()函数是没有作用的,不会影响到事件的传播。 }
注意:
- 事件分发函数的返回值bool。
- 我们处理过自己感兴趣的事件之后,可以直接返回true,表示我们已经对此事件进行了处理;对于其它我们不关心的事件,则需要调用父类的event()函数继续转发,否则这个组件就只能处理我们定义的事件了。
有时候,对象需要查看、甚至要拦截发送到另外对象的事件。例如,对话框可能想要拦截按键事件,不让别的组件接收到;或者要修改回车键的默认处理。
使用:重写(但实际上,我们很少重写)
步骤:
- 声明
- 实现(重写):实现(先判断是否是要过滤事件的组件,如果是是要过滤事件的组件,再去判断事件是否过滤,如果过滤返回true,如果不过滤返回false;如果不是要过滤事件的组件,返回父类的事件过滤函数)
- 安装过滤器(使用前要进行安装):
void QObject::installEventFilter ( Q0bject * filterobj )
- 移除过滤器:
void Qobject::removeEventFilter( Qobject * filterobj )
//.h virtual bool Q0bject::eventFilter ( Q0bject * watched,QEvent * event ); //.cpp bool Mainwindow::eventFilter(Qobject *obj,QEvent *event){ if (obj == textEdit) { if ( event->type() == QEvent : :KeyPress) { QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);qDebug() <<"Ate key press" << keyEvent->key(); return true; } else { return false; } } else { ll pass the event on to the parent classreturn QMainwindow : : eventFilter(obj, event) ; } }
补充:我们可以向一个对象上面安装多个事件处理器,只要调用多次instailEventFilter()函数。如果一个对象存在多个事件过滤器,那么,最后一个安装的会第一个执行,也就是后进先执行的顺序。
- Qt的绘图系统允许使用相同的API在屏幕和其它打印设备上进行绘制。整个绘图系统基于QPainter,QPainterDevice和 QPaintEngine_三个类.
- QPainter用来执行绘制的操作;QPaintDevice是一个二维空间的抽象,这个二维空间允许QPainter_在其上面进行绘制,也就是QPainter_工作的空间;QPaintEngine提供了画笔(QPainter)在不同的设备上进行绘制的统一的接口。QPaintEngine_类应用于QPainter和 QPaintDevice之间,通常对开发人员是透明的。除非你需要自定义一个设备,否则你是不需要关心QPaintEngine这个类的。
- 我们可以把QPainter理解成画笔;把QPaintDevice理解成使用画笔的地方,比如纸张、屏幕等;而对于纸张、屏幕而言,肯定要使用不同的画笔绘制,为了统一使用一种画笔,我们设计了QPaintEngine类,这个类让不同的纸张、屏幕都能使用一种画笔。
三个类之间的层次关系:
新建项目:13_demo
类:QPainter、QPaintEvent
画笔使用:帮助手册
void Widget::paintEvent(QPaintEvent *p)//页面加载,update { //qDebug()<<"------------"; QPainter painter(this); painter.drawLine(80, 100, 500, 500);//单条线 painter.setPen(Qt::red);//更换画笔颜色 painter.drawRect(0, 0, 100, 400);//矩形 painter.setPen(QPen(Qt::blue, 5));//更换画笔颜色和线条大小 painter.drawEllipse(0, 0, 400, 200); }
绘图设备是指继承QPainterDevice的子类。Qt 一共提供了四个这样的类,分别是QPixmap、QBitmap、QImage和QPicture。其中:
- QPixmap专门为图像在屏幕上的显示做了优化。
- QBitmap是QPixmap的一个子类,它的色深限定为1,可以使用QPixmap的isQBitmap(函数来确定这个QPixmap是不是一个QBitmap。
- Qlmage专门为图像的像素级访问做了优化。
- QPicture则可以记录和重现QPainter的各条命令。