QT-第一节 QT简介、QT项目初识
Qt概述
- 什么是Qt
- Qt是一个跨平台的C++图形用户界面应用程序框架。
- 它为应用程序开发者提供建立艺术级图形界面所需的所有功能。
- 它是完全面向对象的,很容易扩展,并且允许真正的组件编程。
- Qt的发展史
- 1991年 Qt最早由奇趣科技开发
- 1996年 进入商业领域,它也是目前流行的Linux桌面环境KDE的基础
- 即在linux桌面上所有的图形工具都是Qt开发的。
- 2008年 奇趣科技被诺基亚公司收购,Qt称为诺基亚旗下的编程语言
- 2012年 Qt又被Digia公司收购
- 2014年4月 跨平台的集成开发环境Qt Creator3.1.0发布,同年5月20日配发了Qt5.3正式版,至此Qt实现了对iOS、Android、WP等各平台的全面支持。
- 支持的平台
- Windows – XP、Vista、Win7、Win8、Win2008、Win10
- Uinux/X11 – Linux、Sun Solaris、HP-UX、Compaq Tru64 UNIX、IBM AIX、SGI IRIX、FreeBSD、BSD/OS、和其他很多X11平台
- Macintosh – Mac OS X
- Embedded – 有帧缓冲支持的嵌入式Linux平台,Windows CE
- Qt版本
- Qt按照不同的版本发行,分为商业版和开源版
- 商业版:
- 为商业软件提供开发,他们提供传统商业软件发行版,并且提供在商业有效期内的免费升级和技术支持服务。
- 开源的LGPL版本:
- 为了开发自有而设计的开放源码软件,它提供了和商业版本同样的功能,在GNU通用公共许可下,它是免费的。
- 商业版:
- Qt按照不同的版本发行,分为商业版和开源版
- 下载与安装:
- 下载地址(我使用的是5.9.8):http://download.qt.io/archive/qt/
- 根据不同的平台下载不同的版本
- 安装:安装教程
- 双击安装
- 跳过账号设置
- 选择安装路径,最好不要选择C盘
- 安装组件选择(如果内存足够大建议全部勾选)
-
Qt组件
- 如果你使用MinGW那套编译器的话,则MinGW组件必选,同样的,如果你用VS的话则需要勾选相应VS版本的组件,想要进行安卓的开发也是同样的道理
- 注意,如果没有勾选MinGW,那么你用Qt创建的项目就必须用VS编译运行
- sources则是一些QT组件的C++或QML源代码,根据个人需求安装,不选对编程也无影响
- QTcharts则是帮助数据可视化的一个组件,提供一套易于使用的图表组件
- QT data Visualization就是数据可视化组件
- Keyboard是提供的可扩展的虚拟键盘
- Purchasing提供了一套商店购买API,可以将你的APP发布到这些应用商店
- Qt WebEngine,该模块集成了最新的谷歌浏览器引擎
- 下面几个是:技术预览版内容
- 最后一项则是废弃(deprecated)的或是过时的组件,不推荐安装
- 组建添加的越多,Qt的运行速度就越会受到影响,所以应该按需要添加组件。
- 如果你使用MinGW那套编译器的话,则MinGW组件必选,同样的,如果你用VS的话则需要勾选相应VS版本的组件,想要进行安卓的开发也是同样的道理
-
Tools组件
-
- VS的QT插件安装:地址
- 成功案例(下面都是一共Qt开发的)
- Linux桌面环境KDE
- WPS Office 办公软件
- Skype 网络电话
- Google Earth 谷歌地图
- VLC多媒体播放器
- VirtualBox虚拟机软件
- Qt核心知识
- 三大基类: QWidget、QMainWindow、QDialog
- 布局
- 常用控件
- 消息机制、事件
- 绘图
- 文件系统
创建Qt项目
- 使用向导创建
- 打开Qt Creator 界面选择 New Project或者选择菜单栏 【文件】-【新建文件或项目】菜单项
- 弹出New Project对话框,选择Application->Qt Widgets Application->选择【Choose】按钮
- 设置项目名称和路径,按照向导进行下一步(注意:项目名称、路径不支持中文、空格)
- build system:选择qmake->下一步
- Details :
- Class name : 类名
- base Class: 基类,这里有三个
- QMainWindow:继承自QWidget,新增了一些栏
- QWidget:基类,最简单的窗口,啥都没有
- QDialog:继承自QWidget,对话框功能
- 头文件:默认生成的.h文件
- 源文件:默认生成的.cpp文件
- 选择基类为:QWidget,设置类名为:MYWidget,取消勾选Generate form (不默认创建一个UI,相当于iOS的main.storyboard),点击下一步
- kit selection:选择编辑器
- 选择使用哪些编译器进行编辑
- 比如:
- Desktop Qt 5.9.8 MSVC2017 64bit :使用VS2017进行编译
- 创建完项目以后Qt自己不能编译,必须用VS打开编译运行
- Desktop Qt 5.9.8 MinGW 32bit :使用Qt自带的MinGW编译器编辑
- 创建完项目以后可以直接用Qt进行编译
- Desktop Qt 5.9.8 MSVC2017 64bit :使用VS2017进行编译
-
如果两个编译环境都选择了,打开项目后,编译会返现错误,因为此时的项目默认是使用VS编译的,那么我如何切换成MinGW编译呢?
- summary:
- 添加到版本控制系统->Configure
- 选择版本控制的类型,比如git
- 这里先不设置,点击完成
- 系统会默认给我们添加main.cpp、mywidget.cpp、 mywidget.h和一个.pro项目文件,点击完成,即可创建出一个Qt桌面程序。
- 手动创建
- 新建项目->选择“其他项目”-> empty qmake project->choose
- 设置名称、路径
- 选择编译器kit selection->下一步
- summary
- 完成,此时创建一个空项目,这个项目中只有一个pro文件
- 右击项目名称,选择“添加新文件(add new…)”
- 弹出新建文件对话框
- 在此对话框中选择要添加的类或者文件,根据向导完成文件的添加。
.pro
文件:为Qt工程文件- 在使用Qt向导生成的应用程序.pro文件,该文件为项目说明文件
- 该文件由项目自动生成
-
格式如下:
QT += core gui //Qt包含的模块 :core模块、gui模块,就相当于iOS有哪些框架framework,UIKit等,默认包含了core、gui模块,但是如果项目中使用了一些类不在在写模块时,还要在这里手动添加改模块 greaterThan(QT_MAJOR_VERSION, 4): QT += widgets //大于Qt4版本 才包含widget模块,因为QT4之前widget模块包含在GUI模块中 TARGET = QtFirst //应用程序名 生成的.exe程序名称 TEMPLATE = app //模板类型 应用程序模板 SOURCES += main.cpp\ //源文件 mywidget.cpp HEADERS += mywidget.h //头文件
.pro
就是工程文件(project),它是qmake自动生成的用于生产makefile的配置文件。.pro
文件的写法如下- 注释: 从“#”开始,到这一行结束。
- TEMPLATE:模板变量告诉qmake为这个应用程序生成哪种makefile。下面是可供使用的选择:
- app -建立一个应用程序的makefile。这是默认值,所以如果模板没有被指定,这个将被使用。
- lib - 建立一个库的makefile
- vcapp - 建立一个应用程序的VisualStudio项目文件。
- vclib - 建立一个库的VisualStudio项目文件。
- subdirs -这是一个特殊的模板,它可以创建一个能够进入特定目录并且为一个项目文件生成makefile并且为它调用make的makefile。
- TARGET: 指定生成的应用程序名,
TARGET = QtDemo
- HEADERS: 工程中包含的头文件,
HEADERS += include/painter.h
- FORMS: 工程中包含的.ui设计文件,
FORMS += forms/painter.ui
- SOURCES: 工程中包含的源文件,
SOURCES += sources/main.cpp sources
- RESOURCES: 工程中包含的资源文件,
RESOURCES += qrc/painter.qrc
- greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
- 这条语句的含义是,如果QT_MAJOR_VERSION大于4(也就是当前使用的Qt5及更高版本)需要增加widgets模块。如果项目仅需支持Qt5,也可以直接添加“QT += widgets”一句。不过为了保持代码兼容,最好还是按照QtCreator生成的语句编写
- CONFIG:配置信息
- CONFIG用来告诉qmake关于应用程序的配置信息
CONFIG += c++11 //使用c++11的特性
- 在这里使用“+=”,是因为我们添加我们的配置选项到任何一个已经存在中。这样做比使用“=”那样替换已经指定的所有选项更安全。
一个最简单的Qt程序
-
代码如下:
#include "mywidget.h" #include <QApplication> //程序入口: argc:命令行变量数量 grgv:命令行变量数组 int main(int argc, char *argv[]) { //应用程序对象,a在qt中有且仅有一个对象 QApplication a(argc, argv); //自定义的对象,继承自QWidget类 MyWidget w; //弹出窗口 w.show(); //a.exec:进入消息循环机制,防止引用程序退出 return a.exec(); } //mywidget.h #ifndef MYWIDGET_H #define MYWIDGET_H #include <QWidget> class MyWidget : public QWidget { Q_OBJECT //宏,写了这个宏,就支持了Qt中的信号和槽机制 public: MyWidget(QWidget *parent = nullptr); //构造 ~MyWidget(); //析构 }; #endif // MYWIDGET_H //mywidget.cpp文件 #include "mywidget.h" MyWidget::MyWidget(QWidget *parent) : QWidget(parent) //: QWidget(parent) 初始化列表,将参数传给他的父类,让父类初始化 { } MyWidget::~MyWidget() { }
- Qt系统提供的标准类名声明头文件没有.h后缀
- Qt一个类对应一个头文件,类名就是头文件名
- QApplication应用程序类,跟iOS的UIApplication一样
- 管理图形用户界面应用程序的控制流和主要设置。
- 是Qt的整个后台管理的命脉,它包含:
- 主事件循环,在其中来自窗口系统和其它资源的所有事件处理和调度。
- 它也处理应用程序的初始化和结束,并且提供对话管理。
- 对于任何一个使用Qt的图形用户界面应用程序,都正好存在一个QApplication 对象,而不论这个应用程序在同一时间内是不是有0、1、2或更多个窗口。即一个应用程序存在唯一的一个QApplication对象
- a.exec()
- 程序进入消息循环,等待对用户输入进行响应。这里main()把控制权转交给Qt,Qt完成事件处理工作,当应用程序退出的时候exec()的值就会返回。
- 在exec()中,Qt接受并处理用户和系统的事件并且把它们传递给适当的窗口部件。
-
Qt常用快捷键与编码规范
命名规范: 类名:首字母大写,采用驼峰标识 函数、变量:首字母小写,采用驼峰标识 快捷键: 运行:ctrl +R 编译:ctrl+B 查找:ctrl+F 帮助文档: F1 字体缩放:ctrl +滚动鼠标 自动对齐:ctrl +i 整行移动:ctrl +shift + ↑ 或者 ↓ 同名之间.h/.cpp 切换 :F4 注释: ctrl + / 跳转到函数定义:F2 跳过自动补全的括号和引号: ctrl + ];
按钮的创建
- 类名:QPushButton
- F1帮助中搜索查看可知
- QPushButton继承自QAbstractButton,抽象Button
- QAbstractButton继承自QWidget
-
代码示例:在mywidget窗口上添加一个按钮
#include "mywidget.h" #include<QPushButton> MyWidget::MyWidget(QWidget *parent) : QWidget(parent) //: QWidget(parent) 初始化列表,将参数传给他的父类,让父类初始化 { //1. 创建按钮 // QPushButton *btn = new QPushButton; //2. 设置按钮属性 //设置文字 //setText函数会将char*类型隐式转换为QString类型 // btn->setText("hello"); //show是基类QWidget的方法,在一个新的地方弹出窗口 //btn->show(); //在MyWidget窗口弹出 //3. 添加按钮 // btn->setParent(this); //第二种方式创建 QPushButton *btn2 = new QPushButton("第二个按钮",this); //设置按钮的位置point btn2->move(100,100); //指定窗口的大小 btn2->resize(100,50); //背景色 // btn2->setStyleSheet("background-color:yellow"); // //文字颜色 // btn2->setStyleSheet("color:red"); btn2->setStyleSheet("QPushButton{color:red;background:yellow}"); //设置MyWidget窗口标题 this->setWindowTitle("第二个标题"); //限制MyWidget窗口大小,就是拖动不能够缩小 // this->setFixedSize(600,400); //设置窗口的大小,拖动可以伸缩大小 this->resize(600,400); } MyWidget::~MyWidget() { }
对象模型(对象树)
- 主要是描述QT自动管理内存的机制
- 在Qt中创建对象的时候会提供一个Parent对象指针,下面来解释这个parent到底是干什么的。
- QObject是以对象树的形式组织起来的。QObject是组件的最基类,QWidget继承自QObject
- 当你创建一个QObject对象时,会看到QObject的构造函数接收一个QObject指针作为参数,这个参数就是 parent,也就是父对象指针。
-
比如QPushButton的构造函数:
QPushButton(QWidget *parent = Q_NULLPTR); QPushButton(const QString &text, QWidget *parent = Q_NULLPTR);
- 这相当于,在创建QObject对象时,可以提供一个其父对象,我们创建的这个QObject对象会自动添加到其父对象的children()列表。
-
相反,如果我们创建一个QObject对象没有使用这个构造函数,那么久需要专门设置它的父控件,否则无法显示
QPushButton *btn = new QPushButton; btn->setParent(this);
-
- 当父对象析构的时候,这个列表中的所有对象也会被析构。(注意,这里的父对象并不是继承意义上的父类!父控件的意思)
- 这种机制在 GUI 程序设计中相当有用。例如,一个按钮有一个QShortcut(快捷键)对象作为其子对象。当我们删除按钮的时候,这个快捷键理应被删除。这是合理的。
- 当你创建一个QObject对象时,会看到QObject的构造函数接收一个QObject指针作为参数,这个参数就是 parent,也就是父对象指针。
- QWidget是能够在屏幕上显示的一切组件的父类。
- QWidget继承自QObject,因此也继承了这种对象树关系。一个孩子自动地成为父组件的一个子组件。因此,它会显示在父组件的坐标系统中,被父组件的边界剪裁。例如,当用户关闭一个对话框的时候,应用程序将其删除,那么,我们希望属于这个对话框的按钮、图标等应该一起被删除。事实就是如此,因为这些都是对话框的子组件。
- 当然,我们也可以自己删除子对象,它们会自动从其父对象列表中删除。比如,当我们删除了一个工具栏时,其所在的主窗口会自动将该工具栏从其子对象列表中删除,并且自动调整屏幕显示。
- Qt 引入对象树的概念,在一定程度上解决了内存问题。
- 当一个QObject对象在堆上创建的时候
- 创建对象:Qt 会同时为其创建一个对象树。不过,对象树中对象的顺序是没有定义的。这意味着,销毁这些对象的顺序也是未定义的。
- 删除对象,即任何对象树中的 QObject对象 delete 的时候:
- 如果这个对象有 parent,则自动将其从 parent 的children()列表中删除;
- 如果有孩子,则自动 delete 每一个孩子。
- Qt 保证没有QObject会被 delete 两次,这是由析构顺序(2中的顺序)决定的。
- 如果QObject在栈上创建,Qt 保持同样的行为。
- 对象树中的对象是有顺序的!!!
- 栈上对象的特点:
- 标准 C++要求,局部对象的析构顺序应该按照其创建顺序的相反过程。
- 对象一旦离开作用域会自动销毁,内存回收
- 对象析构的过程同堆上的2一样
- 不能保证QObject对象会被delete 两次,因为同堆相比,栈多了一个超过作用域销毁delete。
- 举例:
-
正常情况下,这也不会发生什么问题。来看下下面的代码片段:
{ 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 对象,并且大胆在堆上创建。
- 当一个QObject对象在堆上创建的时候