QT入门看这一篇就够了——超详细讲解(40000多字详细讲解,涵盖qt大量知识)

02-29 阅读 0评论

目录

QT入门看这一篇就够了——超详细讲解(40000多字详细讲解,涵盖qt大量知识),QT入门看这一篇就够了——超详细讲解(40000多字详细讲解,涵盖qt大量知识),词库加载错误:未能找到文件“C:\Users\Administrator\Desktop\火车头9.8破解版\Configuration\Dict_Stopwords.txt”。,使用,我们,访问,第1张
(图片来源网络,侵删)

一、Qt概述

1.1 什么是Qt

1.2 Qt的发展史

1.3 Qt的优势

1.4 Qt版本

1.5 成功案例

QT入门看这一篇就够了——超详细讲解(40000多字详细讲解,涵盖qt大量知识),QT入门看这一篇就够了——超详细讲解(40000多字详细讲解,涵盖qt大量知识),词库加载错误:未能找到文件“C:\Users\Administrator\Desktop\火车头9.8破解版\Configuration\Dict_Stopwords.txt”。,使用,我们,访问,第2张
(图片来源网络,侵删)

二、创建Qt项目

2.1 使用向导创建

2.2 一个最简单的Qt应用程序

2.2.1 main函数中

2.2.2 类头文件

2.3 .pro文件

QT入门看这一篇就够了——超详细讲解(40000多字详细讲解,涵盖qt大量知识),QT入门看这一篇就够了——超详细讲解(40000多字详细讲解,涵盖qt大量知识),词库加载错误:未能找到文件“C:\Users\Administrator\Desktop\火车头9.8破解版\Configuration\Dict_Stopwords.txt”。,使用,我们,访问,第3张
(图片来源网络,侵删)

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 文本文件读写


 

一、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按照不同的版本发行,分为商业版和开源版

    1. 商业版

            为商业软件提供开发,他们提供传统商业软件发行版,并且提供在商业有效期内的免费升级和技术支持服务。

    1. 开源的LGPL协议版本:

            为了开发自有而设计的开放源码软件,它提供了和商业版本同样的功能,在GNU通用公共许可下,它是免费的。

            目前我们学习上使用的就是这个版本。可以到官网下载最新版本:

    Index of /archive/qtQT入门看这一篇就够了——超详细讲解(40000多字详细讲解,涵盖qt大量知识),icon-default.png?t=N4HB,词库加载错误:未能找到文件“C:\Users\Administrator\Desktop\火车头9.8破解版\Configuration\Dict_Stopwords.txt”。,使用,我们,访问,第4张http://download.qt.io/archive/qt/

    1.5 成功案例

    1. Linux桌面环境KDE(K Desktop Environment)
    2. WPS Office 办公软件
    3. Skype 网络电话
    4. Google Earth 谷歌地球
    5. VLC多媒体播放器
    6. VirtualBox虚拟机软件
    7. 等等

     

    1.6近期就业行情

     

     

    二、创建Qt项目

    2.1 使用向导创建

    打开Qt Creator 界面选择 New Project或者选择菜单栏 【文件】-【新建文件或项目】菜单项

    QT入门看这一篇就够了——超详细讲解(40000多字详细讲解,涵盖qt大量知识)

     弹出New Project对话框,选择Qt Widgets Application

    QT入门看这一篇就够了——超详细讲解(40000多字详细讲解,涵盖qt大量知识)

    选择【Choose】按钮,弹出如下对话框

    QT入门看这一篇就够了——超详细讲解(40000多字详细讲解,涵盖qt大量知识)

    设置项目名称和路径,按照向导进行下一步

    QT入门看这一篇就够了——超详细讲解(40000多字详细讲解,涵盖qt大量知识)

    选择编译套件

    QT入门看这一篇就够了——超详细讲解(40000多字详细讲解,涵盖qt大量知识)

    向导会默认添加一个继承自QMainWindow的类,可以在此修改类的名字和基类。默认的基类有QMainWindow、QWidget以及QDialog三个,我们可以选择QWidget(类似于空窗口),这里我们可以先创建一个不带UI的界面,继续下一步

    QT入门看这一篇就够了——超详细讲解(40000多字详细讲解,涵盖qt大量知识)系统会默认给我们添加main.cpp、mywidget.cpp、 mywidget.h和一个.pro项目文件,点击完成,即可创建出一个Qt桌面程序。

     

    2.2 一个最简单的Qt应用程序

    2.2.1 main函数中

    #include "widget.h"
    #include 
    int 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()把控制权转交给Qt,Qt完成事件处理工作,当应用程序退出的时候exec()的值就会返回。在exec()中,Qt接受并处理用户和系统的事件并且把它们传递给适当的窗口部件。

     

    2.2.2 类头文件

    #include 
    class MyWidget : public QWidget
    {
        //引入Qt信号和槽机制的一个宏
        Q_OBJECT
        public:
        //构造函数中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 += c++11 //使用c++11的特性(qt5.6以上版本默认使用C++11)

                   在这里使用“+=”,是因为我们添加我们的配置选项到任何一个已经存在中。这样做比使用“=”那样替换已经指定的所有选项更安全。

     

    2.4 命名规范 

            类名:单词首字母大写,单词和单词之间直接连接,无需连接字符 。

    MyClass,QPushButton
    		class MainWindow
    

            Qt中内置的类型,头文件和类命名同名。

    #include 
    		QSring str;
    		
    		#include 
    		QWidget w;
    

            函数名字,变量名:首字母小写,之后每个单词首字母大写,单词和单词之间直接连接,无需连接字符

    void connectTheSignal();

            类的成员变量设置函数用使用 set+成员变量名,获取成员变量的函数直接用成员变量名(如果是bool类型,有可能会用一些表示状态的术语,如isVisilble,hasFocus):

    //普通成员变量设置和获取
    		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 
    QPushButton * 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轴的正方向。

    QT入门看这一篇就够了——超详细讲解(40000多字详细讲解,涵盖qt大量知识)

     

            对于嵌套窗口,其坐标是相对于父窗口来说的。顶层窗口的父窗口就是屏幕。

     

    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索引到系统自带的信号有如下几个

    QT入门看这一篇就够了——超详细讲解(40000多字详细讲解,涵盖qt大量知识)

             这里的clicked就是我们要找到,槽函数的寻找方式和信号一样,只不过他的关键字是slot。

     

    4.2 自定义信号和槽

            Qt框架默认提供的标准信号和槽不足以完成我们日常应用开发的需求,比如说点击某个按钮让另一个按钮的文字改变,这时候标准信号和槽就没有提供这样的函数。但是Qt信号和槽机制提供了允许我们自己设计自己的信号和槽。

    4.2.1自定义信号使用条件

    1. 声明在类的signals域下
    2. 没有返回值,void类型的函数
    3. 只有函数声明,没有定义
    4. 可以有参数,可以重载
    5. 通过emit关键字来触发信号,形式:emit object->sig(参数);

    4.2.2 自定义槽函数使用条件

    1. qt4 必须声明在 private/public/protected slots域下面,qt5之后可以声明public下,同时还可以是静态的成员函数,全局函数,lambda表达式
    2. 没有返回值,void类型的函数
    3. 不仅有声明,还得要有实现
    4. 可以有参数,可以重载

    4.2.3 使用自定义信号和槽

            定义场景:下课了,老师跟同学说肚子饿了(信号),学生请老师吃饭(槽)

            首先定义一个学生类和老师类:

            老师类中声明信号 饿了 hungry

    signals:
           void hungry();
    

            学生类中声明槽 请客treat

    public slots:
           void treat();
    

            在窗口中声明一个公共方法下课,这个方法的调用会触发老师饿了这个信号,而响应槽函数学生请客

    	void MyWidget::ClassIsOver()
    {
        //发送信号
        emit teacher->hungry();
    }
    

            学生响应了槽函数,并且打印信息

            //自定义槽函数 实现

    void Student::treat()
    {
           qDebug() 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 ;
    [=]()
    {
        a=20;//编译报错,a引进来是const
    }
    [=]()mutable
    {
        a=20;//编译成功
    };

    4.5.4 函数返回值 ->retType

            ->retType,标识lambda函数返回值的类型。这部分可以省略,但是省略了并不代表函数没有返回值,编译器会自动根据函数体内的return语句判断返回值类型,但是如果有多条return语句,而且返回的类型都不一样,编译会报错,如:

    [=]()mutable
    {
        int b = 20;
        float c = 30.0;
        if(a>0)
            return b;
        else
            return c;//编译报错,两条return语句返回类型不一致
    };
    

    4.5.5 是函数主体{}

            {},标识函数的实现,这部分不能省略,但函数体可以为空。

    4.5.6 槽函数使用 Lambda 表达式

    以QPushButton点击事件为例:

    connect(btn,&QPushButton::clicked,[=](){
            qDebug()setupUi(this);
        //如果想要使用ui里边的控件对象
        //代码必须写在setupUi之下
        //否则ui各个控件没有初始化时使用会出问题
        ui->pushButton->setText("Hello");
    }
    

    5.8 UI 文件下使用信号和槽

    5.8.1 转到槽

            在UI编辑界面中使用信号和槽很方便,比如,拖出一个Button到窗口上,右键这个button,选择转到槽:

    QT入门看这一篇就够了——超详细讲解(40000多字详细讲解,涵盖qt大量知识)

     

            此时会出现这个控件(QPushButton)可以连接的各个信号,我们可以根据具体需求选中信号来创建一个连接这个信号的槽函数:

    QT入门看这一篇就够了——超详细讲解(40000多字详细讲解,涵盖qt大量知识)

             以click(bool)信号为例,创建了一个槽函数

    QT入门看这一篇就够了——超详细讲解(40000多字详细讲解,涵盖qt大量知识)

     这个槽函数是QtCreator自动帮我们创建的,而且也使用生成C++代码的方式帮我们做好了连接,我们可以直接在这个函数体内实现功能就行。很方便,比使用Lambda表达式还方便。

     

    5.8.2 信号槽编辑器

            可以使用动作编辑器旁边的信号槽编辑器,里边也可以添加信号和槽的连接,比如添加actionQuit的triggered信号和窗口close槽的连接:

    QT入门看这一篇就够了——超详细讲解(40000多字详细讲解,涵盖qt大量知识)

     

    5.9 资源文件

            Qt 资源系统是一个跨平台的资源机制,用于将程序运行时所需要的资源以二进制的形式存储于可执行文件内部。如果你的程序需要加载特定的资源(图标、文本翻译等),那么,将其放置在资源文件中,就再也不需要担心这些文件的丢失。也就是说,如果你将资源以资源文件形式存储,它是会编译到可执行文件内部。

            使用 Qt Creator 可以很方便地创建资源文件。我们可以在工程上点右键,选择“添加新文件…”,可以在 Qt 分类下找到“Qt 资源文件”:

    QT入门看这一篇就够了——超详细讲解(40000多字详细讲解,涵盖qt大量知识)

             点击“选择…”按钮,打开“新建 Qt 资源文件”对话框。在这里我们输入资源文件的名字和路径:

    QT入门看这一篇就够了——超详细讲解(40000多字详细讲解,涵盖qt大量知识)

     

            点击下一步,选择所需要的版本控制系统,然后直接选择完成。我们可以在 Qt Creator 的左侧文件列表中看到“资源文件”一项,也就是我们新创建的资源文件:

    QT入门看这一篇就够了——超详细讲解(40000多字详细讲解,涵盖qt大量知识)

     

            右侧的编辑区有个“添加”,我们首先需要添加前缀,比如我们将前缀取名为 images。然后选中这个前缀,继续点击添加文件,可以找到我们所需添加的文件。这里,我们选择 document-open.png 文件。当我们完成操作之后,Qt Creator 应该是这样子的:

    QT入门看这一篇就够了——超详细讲解(40000多字详细讲解,涵盖qt大量知识)

            接下来,我们还可以添加另外的前缀或者另外的文件。这取决于你的需要。当我们添加完成之后,我们可以像前面一章讲解的那样,通过使用 : 开头的路径来找到这个文件。比如,我们的前缀是 /images,文件是 document-open.png,那么就可以使用:/images/document-open.png找到这个文件。

            这么做带来的一个问题是,如果以后我们要更改文件名,比如将 docuemnt-open.png 改成 docopen.png,那么,所有使用了这个名字的路径都需要修改。所以,更好的办法是,我们给这个文件去一个“别名”,以后就以这个别名来引用这个文件。具体做法是,选中这个文件,添加别名信息:

    QT入门看这一篇就够了——超详细讲解(40000多字详细讲解,涵盖qt大量知识)

     

    这样,我们可以直接使用:/images/doc-open引用到这个资源,无需关心图片的真实文件名。

    Qrc文件只是记录了我们要用到的资源文件在项目路径哪个位置的一个信息,如果我们使用文本编辑器打开 res.qrc 文件,就会看到以下内容:

        	
            	document-open.png
        	
        	
            	document-open-fr.png
        	
    
    

            当我们编译工程之后,我们可以在构建目录中找到 qrc_res.cpp 文件。

    QT入门看这一篇就够了——超详细讲解(40000多字详细讲解,涵盖qt大量知识)

             这就是 Qt 将我们的资源编译成了 C++ 代码:

    QT入门看这一篇就够了——超详细讲解(40000多字详细讲解,涵盖qt大量知识)

     

            可以看出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 按钮。

    QT入门看这一篇就够了——超详细讲解(40000多字详细讲解,涵盖qt大量知识)

     

            AboutQt

                    显示关于 Qt 对话框。该对话框用于显示有关 Qt 的信息。

    void aboutQt(QWidget * parent, const QString & title = QString());

    QT入门看这一篇就够了——超详细讲解(40000多字详细讲解,涵盖qt大量知识)

     

            Critical

                    显示严重错误对话框。

    StandardButton critical(QWidget * parent, 
    const QString & title, 
    const QString & text, 
    StandardButtons buttons = Ok, 
    StandardButton defaultButton = NoButton);
    

            这个对话框将显示一个红色的错误符号。我们可以通过 buttons 参数指明其显示的按钮。默认情况下只有一个 Ok 按钮,我们可以使用StandardButtons类型指定多种按钮。

    QT入门看这一篇就够了——超详细讲解(40000多字详细讲解,涵盖qt大量知识)

     

            Information

                与critical类似,不同之处在于这个对话框提供一个普通信息图标。    

    StandardButton information(QWidget * parent, 
    const QString & title, 
    const QString & text, 
    StandardButtons buttons = Ok, 
    StandardButton defaultButton = NoButton)
    

    QT入门看这一篇就够了——超详细讲解(40000多字详细讲解,涵盖qt大量知识)

     

            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("I'm glad to hear that!"));
            }
            else
            {
                QMessageBox::information(this, tr("Hmmm..."),
            tr("I'm sorry!"));
            }
    

     QT入门看这一篇就够了——超详细讲解(40000多字详细讲解,涵盖qt大量知识)

            

             warning

            与QMessageBox::critical()类似,不同之处在于这个对话框提供一个黄色叹号图标。

    StandardButton warning(QWidget * parent, 
    const QString & title, 
    const QString & text, 
    StandardButtons buttons = Ok, 
    StandardButton defaultButton = NoButton)
    

    QT入门看这一篇就够了——超详细讲解(40000多字详细讲解,涵盖qt大量知识)

             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() 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 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 提供的动态布局中以下三种是我们最常用的:

                    1)QHBoxLayout:按照水平方向从左到右布局;

                    2)QVBoxLayout:按照竖直方向从上到下布局;

                    3)QGridLayout:在一个网格中进行布局,类似于 HTML 的 table;

     

    7.1 系统提供的布局控件

    QT入门看这一篇就够了——超详细讲解(40000多字详细讲解,涵盖qt大量知识)

             这4个为系统给我们提供的布局的控件,但是使用起来不是非常的灵活,这里就不详细介绍了。

    7.2 利用 widget 做布局

            第二种布局方式是利用控件里的widget来做布局,在Containers中

    QT入门看这一篇就够了——超详细讲解(40000多字详细讲解,涵盖qt大量知识)

     

            在widget中的控件可以进行水平、垂直、栅格布局等操作,比较灵活。

            再布局的同时我们需要灵活运用弹簧的特性让我们的布局更加的美观,下面是一个登陆窗口,利用widget可以搭建出如下登陆界面:

    QT入门看这一篇就够了——超详细讲解(40000多字详细讲解,涵盖qt大量知识)

     

    八、常用控件

            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("

    百度一下

    "); 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_cast(&QSpinBox::valueChanged),
                slider, &QSlider::setValue);
        connect(slider, &QSlider::valueChanged, 
                spin, &QSpinBox::setValue);
    }
    

    QT入门看这一篇就够了——超详细讲解(40000多字详细讲解,涵盖qt大量知识)

            那么这个SmallWidget可以作为独立的窗口显示,也可以作为一个控件来使用:

            打开Qt的.ui文件,因为SmallWidget是派生自Qwidget类,所以需要在ui文件中先放入一个QWidget控件, 然后再上边鼠标右键

    QT入门看这一篇就够了——超详细讲解(40000多字详细讲解,涵盖qt大量知识)

     弹出提升窗口部件对话框

    QT入门看这一篇就够了——超详细讲解(40000多字详细讲解,涵盖qt大量知识)

     

    添加要提升的类的名字,然后选择 添加

    QT入门看这一篇就够了——超详细讲解(40000多字详细讲解,涵盖qt大量知识)

     

    添加之后,类名会显示到上边的列表框中,然后单击提升按钮,完成操作.

    我们可以看到, 这个窗口对应的类从原来的QWidget变成了SmallWidget

    QT入门看这一篇就够了——超详细讲解(40000多字详细讲解,涵盖qt大量知识)

     再次运行程序,这个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("

    Move: (%1, %2)

    ").arg(QString::number(event->x()), QString::number(event->y()))); } void EventLabel::mousePressEvent(QMouseEvent *event) { this->setText(QString("

    Press:(%1, %2)

    ").arg(QString::number(event->x()), QString::number(event->y()))); } void EventLabel::mouseReleaseEvent(QMouseEvent *event) { QString msg; msg.sprintf("

    Release: (%d, %d)

    ", 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替换 %1,y替换 %2,因此,生成的QString为[x, y]。       

            3.在mouseReleaseEvent()函数中,我们使用了另外一种QString的构造方法。我们使用类似 C 风格的格式化函数sprintf()来构造QString。

    运行上面的代码,当我们点击了一下鼠标之后,label 上将显示鼠标当前坐标值。

    QT入门看这一篇就够了——超详细讲解(40000多字详细讲解,涵盖qt大量知识)

     

            为什么要点击鼠标之后才能在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_cast(e);
            if (keyEvent->key() == Qt::Key_Tab) {
                qDebug() type() == QEvent::KeyPress) 
    {
            QKeyEvent *keyEvent = static_cast(e);
           if (keyEvent->key() == Qt::Key_Tab) 
    {
                qDebug() 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_cast(event);
                qDebug() type() == QEvent::KeyPress) {
            QKeyEvent *keyEvent = static_cast(e);
            if (keyEvent->key() == Qt::Key_Tab) {
                qDebug() type() == QEvent::KeyPress) 
        {
            QKeyEvent *keyEvent = static_cast(event);
            if (keyEvent->key() == Qt::Key_Tab) {
                qDebug() 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 在屏幕和其它打印设备上进行绘制。整个绘图系统基于QPainter,QPainterDevice和QPaintEngine三个类。

            QPainter用来执行绘制的操作;QPaintDevice是一个二维空间的抽象,这个二维空间允许QPainter在其上面进行绘制,也就是QPainter工作的空间;QPaintEngine提供了画家(QPainter)在不同的设备上进行绘制的统一的接口。QPaintEngine类应用于QPainter和QPaintDevice之间,通常对开发人员是透明的。除非你需要自定义一个设备,否则你是不需要关心QPaintEngine这个类的。我们可以把QPainter理解成画笔;把QPaintDevice理解成使用画笔的地方,比如纸张、屏幕等;而对于纸张、屏幕而言,肯定要使用不同的画笔绘制,为了统一使用一种画笔,我们设计了QPaintEngine类,这个类让不同的纸张、屏幕都能使用一种画笔。

    下图给出了这三个类之间的层次结构:

    QT入门看这一篇就够了——超详细讲解(40000多字详细讲解,涵盖qt大量知识)

            上面的示意图告诉我们,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);
    }
    

    QT入门看这一篇就够了——超详细讲解(40000多字详细讲解,涵盖qt大量知识)

     

            这里我们给出了两张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 i=50; i

免责声明
本网站所收集的部分公开资料来源于AI生成和互联网,转载的目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。
文章版权声明:除非注明,否则均为主机测评原创文章,转载或复制请以超链接形式并注明出处。

发表评论

快捷回复: 表情:
评论列表 (暂无评论,人围观)

还没有评论,来说两句吧...

目录[+]