QT-第一节 QT简介、QT项目初识

Qt概述

  1. 什么是Qt
    1. Qt是一个跨平台的C++图形用户界面应用程序框架
    2. 它为应用程序开发者提供建立艺术级图形界面所需的所有功能。
    3. 它是完全面向对象的,很容易扩展,并且允许真正的组件编程。
  2. Qt的发展史
    1. 1991年 Qt最早由奇趣科技开发
    2. 1996年 进入商业领域,它也是目前流行的Linux桌面环境KDE的基础
      1. 即在linux桌面上所有的图形工具都是Qt开发的。
    3. 2008年 奇趣科技被诺基亚公司收购,Qt称为诺基亚旗下的编程语言
    4. 2012年 Qt又被Digia公司收购
    5. 2014年4月 跨平台的集成开发环境Qt Creator3.1.0发布,同年5月20日配发了Qt5.3正式版,至此Qt实现了对iOS、Android、WP等各平台的全面支持。
  3. 支持的平台
    1. Windows – XP、Vista、Win7、Win8、Win2008、Win10
    2. Uinux/X11 – Linux、Sun Solaris、HP-UX、Compaq Tru64 UNIX、IBM AIX、SGI IRIX、FreeBSD、BSD/OS、和其他很多X11平台
    3. Macintosh – Mac OS X
    4. Embedded – 有帧缓冲支持的嵌入式Linux平台,Windows CE
  4. Qt版本
    1. Qt按照不同的版本发行,分为商业版和开源版
      1. 商业版:
        1. 为商业软件提供开发,他们提供传统商业软件发行版,并且提供在商业有效期内的免费升级和技术支持服务。
      2. 开源的LGPL版本:
        1. 为了开发自有而设计的开放源码软件,它提供了和商业版本同样的功能,在GNU通用公共许可下,它是免费的。
  5. 下载与安装:
    1. 下载地址(我使用的是5.9.8):http://download.qt.io/archive/qt/
    2. 根据不同的平台下载不同的版本
    3. 安装:安装教程
      1. 双击安装
      2. 跳过账号设置
      3. 选择安装路径,最好不要选择C盘
      4. 安装组件选择(如果内存足够大建议全部勾选)
        1. Qt组件 图4

          1. 如果你使用MinGW那套编译器的话,则MinGW组件必选,同样的,如果你用VS的话则需要勾选相应VS版本的组件,想要进行安卓的开发也是同样的道理
            1. 注意,如果没有勾选MinGW,那么你用Qt创建的项目就必须用VS编译运行
          2. sources则是一些QT组件的C++或QML源代码,根据个人需求安装,不选对编程也无影响
          3. QTcharts则是帮助数据可视化的一个组件,提供一套易于使用的图表组件
          4. QT data Visualization就是数据可视化组件
          5. Keyboard是提供的可扩展的虚拟键盘
          6. Purchasing提供了一套商店购买API,可以将你的APP发布到这些应用商店
          7. Qt WebEngine,该模块集成了最新的谷歌浏览器引擎
          8. 下面几个是:技术预览版内容
          9. 最后一项则是废弃(deprecated)的或是过时的组件,不推荐安装
          10. 组建添加的越多,Qt的运行速度就越会受到影响,所以应该按需要添加组件。
        2. Tools组件 图4

    4. VS的QT插件安装:地址
  6. 成功案例(下面都是一共Qt开发的)
    1. Linux桌面环境KDE
    2. WPS Office 办公软件
    3. Skype 网络电话
    4. Google Earth 谷歌地图
    5. VLC多媒体播放器
    6. VirtualBox虚拟机软件
  7. Qt核心知识
    1. 三大基类: QWidget、QMainWindow、QDialog
    2. 布局
    3. 常用控件
    4. 消息机制、事件
    5. 绘图
    6. 文件系统

创建Qt项目

  1. 使用向导创建
    1. 打开Qt Creator 界面选择 New Project或者选择菜单栏 【文件】-【新建文件或项目】菜单项
    2. 弹出New Project对话框,选择Application->Qt Widgets Application->选择【Choose】按钮
    3. 设置项目名称和路径,按照向导进行下一步(注意:项目名称、路径不支持中文、空格)
    4. build system:选择qmake->下一步
    5. Details :
      1. Class name : 类名
      2. base Class: 基类,这里有三个
        1. QMainWindow:继承自QWidget,新增了一些栏
        2. QWidget:基类,最简单的窗口,啥都没有
        3. QDialog:继承自QWidget,对话框功能
      3. 头文件:默认生成的.h文件
      4. 源文件:默认生成的.cpp文件
      5. 选择基类为:QWidget,设置类名为:MYWidget,取消勾选Generate form (不默认创建一个UI,相当于iOS的main.storyboard),点击下一步
    6. kit selection:选择编辑器
      1. 选择使用哪些编译器进行编辑
      2. 比如:
        1. Desktop Qt 5.9.8 MSVC2017 64bit :使用VS2017进行编译
          1. 创建完项目以后Qt自己不能编译,必须用VS打开编译运行
        2. Desktop Qt 5.9.8 MinGW 32bit :使用Qt自带的MinGW编译器编辑
          1. 创建完项目以后可以直接用Qt进行编译
      3. 如果两个编译环境都选择了,打开项目后,编译会返现错误,因为此时的项目默认是使用VS编译的,那么我如何切换成MinGW编译呢?

        图4

    7. summary:
      1. 添加到版本控制系统->Configure
      2. 选择版本控制的类型,比如git
      3. 这里先不设置,点击完成
    8. 系统会默认给我们添加main.cpp、mywidget.cpp、 mywidget.h和一个.pro项目文件,点击完成,即可创建出一个Qt桌面程序。
  2. 手动创建
    1. 新建项目->选择“其他项目”-> empty qmake project->choose
    2. 设置名称、路径
    3. 选择编译器kit selection->下一步
    4. summary
    5. 完成,此时创建一个空项目,这个项目中只有一个pro文件
    6. 右击项目名称,选择“添加新文件(add new…)”
    7. 弹出新建文件对话框
    8. 在此对话框中选择要添加的类或者文件,根据向导完成文件的添加。
  3. .pro文件:为Qt工程文件
    1. 在使用Qt向导生成的应用程序.pro文件,该文件为项目说明文件
    2. 该文件由项目自动生成
    3. 格式如下:

       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   //头文件
      
      1. .pro就是工程文件(project),它是qmake自动生成的用于生产makefile的配置文件。.pro文件的写法如下
        1. 注释: 从“#”开始,到这一行结束。
        2. TEMPLATE:模板变量告诉qmake为这个应用程序生成哪种makefile。下面是可供使用的选择:
          1. app -建立一个应用程序的makefile。这是默认值,所以如果模板没有被指定,这个将被使用。
          2. lib - 建立一个库的makefile
          3. vcapp - 建立一个应用程序的VisualStudio项目文件。
          4. vclib - 建立一个库的VisualStudio项目文件。
          5. subdirs -这是一个特殊的模板,它可以创建一个能够进入特定目录并且为一个项目文件生成makefile并且为它调用make的makefile。
        3. TARGET: 指定生成的应用程序名,TARGET = QtDemo
        4. HEADERS: 工程中包含的头文件,HEADERS += include/painter.h
        5. FORMS: 工程中包含的.ui设计文件,FORMS += forms/painter.ui
        6. SOURCES: 工程中包含的源文件,SOURCES += sources/main.cpp sources
        7. RESOURCES: 工程中包含的资源文件,RESOURCES += qrc/painter.qrc
        8. greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
          1. 这条语句的含义是,如果QT_MAJOR_VERSION大于4(也就是当前使用的Qt5及更高版本)需要增加widgets模块。如果项目仅需支持Qt5,也可以直接添加“QT += widgets”一句。不过为了保持代码兼容,最好还是按照QtCreator生成的语句编写
        9. CONFIG:配置信息
          1. CONFIG用来告诉qmake关于应用程序的配置信息
          2. CONFIG += c++11 //使用c++11的特性
          3. 在这里使用“+=”,是因为我们添加我们的配置选项到任何一个已经存在中。这样做比使用“=”那样替换已经指定的所有选项更安全。

一个最简单的Qt程序

  1. 代码如下:

     #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()
     {
        
     }
    
    1. Qt系统提供的标准类名声明头文件没有.h后缀
    2. Qt一个类对应一个头文件,类名就是头文件名
    3. QApplication应用程序类,跟iOS的UIApplication一样
      1. 管理图形用户界面应用程序的控制流和主要设置。
      2. 是Qt的整个后台管理的命脉,它包含:
        1. 主事件循环,在其中来自窗口系统和其它资源的所有事件处理和调度。
        2. 它也处理应用程序的初始化和结束,并且提供对话管理
      3. 对于任何一个使用Qt的图形用户界面应用程序,都正好存在一个QApplication 对象,而不论这个应用程序在同一时间内是不是有0、1、2或更多个窗口。即一个应用程序存在唯一的一个QApplication对象
    4. a.exec()
      1. 程序进入消息循环,等待对用户输入进行响应。这里main()把控制权转交给Qt,Qt完成事件处理工作,当应用程序退出的时候exec()的值就会返回。
      2. 在exec()中,Qt接受并处理用户和系统的事件并且把它们传递给适当的窗口部件。
  2. Qt常用快捷键与编码规范

     命名规范:
     类名:首字母大写,采用驼峰标识
     函数、变量:首字母小写,采用驼峰标识
     快捷键:
     运行:ctrl +R
     编译:ctrl+B
     查找:ctrl+F
     帮助文档: F1
     字体缩放:ctrl +滚动鼠标
     自动对齐:ctrl +i
     整行移动:ctrl +shift + ↑ 或者 ↓
     同名之间.h/.cpp 切换 :F4
     注释: ctrl + /
     跳转到函数定义:F2
     跳过自动补全的括号和引号: ctrl + ];
    

按钮的创建

  1. 类名:QPushButton
  2. F1帮助中搜索查看可知
    1. QPushButton继承自QAbstractButton,抽象Button
    2. QAbstractButton继承自QWidget
  3. 代码示例:在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()
     {
     }
    

对象模型(对象树)

  1. 主要是描述QT自动管理内存的机制
  2. 在Qt中创建对象的时候会提供一个Parent对象指针,下面来解释这个parent到底是干什么的。
  3. QObject是以对象树的形式组织起来的。QObject是组件的最基类,QWidget继承自QObject
    1. 当你创建一个QObject对象时,会看到QObject的构造函数接收一个QObject指针作为参数,这个参数就是 parent,也就是父对象指针。
      1. 比如QPushButton的构造函数:

         QPushButton(QWidget *parent = Q_NULLPTR);
         QPushButton(const QString &text, QWidget *parent = Q_NULLPTR);
        
      2. 这相当于,在创建QObject对象时,可以提供一个其父对象,我们创建的这个QObject对象会自动添加到其父对象的children()列表。
      3. 相反,如果我们创建一个QObject对象没有使用这个构造函数,那么久需要专门设置它的父控件,否则无法显示

         QPushButton *btn = new QPushButton;
         btn->setParent(this);
        
    2. 当父对象析构的时候,这个列表中的所有对象也会被析构。(注意,这里的父对象并不是继承意义上的父类!父控件的意思
      1. 这种机制在 GUI 程序设计中相当有用。例如,一个按钮有一个QShortcut(快捷键)对象作为其子对象。当我们删除按钮的时候,这个快捷键理应被删除。这是合理的。
  4. QWidget是能够在屏幕上显示的一切组件的父类。
    1. QWidget继承自QObject,因此也继承了这种对象树关系。一个孩子自动地成为父组件的一个子组件。因此,它会显示在父组件的坐标系统中,被父组件的边界剪裁。例如,当用户关闭一个对话框的时候,应用程序将其删除,那么,我们希望属于这个对话框的按钮、图标等应该一起被删除。事实就是如此,因为这些都是对话框的子组件。
    2. 当然,我们也可以自己删除子对象,它们会自动从其父对象列表中删除。比如,当我们删除了一个工具栏时,其所在的主窗口会自动将该工具栏从其子对象列表中删除,并且自动调整屏幕显示。
  5. Qt 引入对象树的概念,在一定程度上解决了内存问题。
    1. 当一个QObject对象在堆上创建的时候
      1. 创建对象:Qt 会同时为其创建一个对象树。不过,对象树中对象的顺序是没有定义的。这意味着,销毁这些对象的顺序也是未定义的。
      2. 删除对象,即任何对象树中的 QObject对象 delete 的时候:
        1. 如果这个对象有 parent,则自动将其从 parent 的children()列表中删除;
        2. 如果有孩子,则自动 delete 每一个孩子。
      3. Qt 保证没有QObject会被 delete 两次,这是由析构顺序(2中的顺序)决定的。
    2. 如果QObject在栈上创建,Qt 保持同样的行为。
      1. 对象树中的对象是有顺序的!!!
      2. 栈上对象的特点:
        1. 标准 C++要求,局部对象的析构顺序应该按照其创建顺序的相反过程。
        2. 对象一旦离开作用域会自动销毁,内存回收
      3. 对象析构的过程同堆上的2一样
      4. 不能保证QObject对象会被delete 两次,因为同堆相比,栈多了一个超过作用域销毁delete。
      5. 举例:
        1. 正常情况下,这也不会发生什么问题。来看下下面的代码片段:

           {
               QWidget window;
               QPushButton quit("Quit", &window);
           }
          
          1. 作为父组件的 window 和作为子组件的 quit 都是QObject的子类(事实上,它们都是QWidget的子类,而QWidget是QObject的子类)。
          2. 这段代码是正确的,quit 的析构函数不会被调用两次,因为标准 C++要求,局部对象的析构顺序应该按照其创建顺序的相反过程。因此,这段代码在超出作用域时,会先调用 quit 的析构函数,将其从父对象 window 的子对象列表中删除,然后才会再调用 window 的析构函数。
        2. 但是,如果我们使用下面的代码:

           {
               QPushButton quit("Quit");
               QWidget window;
               quit.setParent(&window);
           }
          
          1. 情况又有所不同,析构顺序就有了问题。
          2. 我们看到,在上面的代码中,作为父对象的 window 会首先被析构,因为它是最后一个创建的对象。
          3. 在析构过程中,它会调用子对象列表中每一个对象的析构函数,也就是说, quit 此时就被析构了。
          4. 然后,代码继续执行,在 window 析构之后,quit 也会被析构,因为 quit 也是一个局部变量,在超出作用域的时候当然也需要析构。但是,这时候已经是第二次调用 quit 的析构函数了,C++ 不允许调用两次析构函数,因此,程序崩溃了。
    3. 由此我们看到,Qt 的对象树机制虽然帮助我们在一定程度上解决了内存问题,但是也引入了一些值得注意的事情。所以,我们最好从开始就养成良好习惯,在 Qt 中,尽量在构造的时候就指定 parent 对象,并且大胆在堆上创建。
Table of Contents