21. Qt界面开发与实战

1.前言

前面一章,我们简单学习了一下Qt库的使用,以及一个程序的开发流程:就是不停的调库。

这就是Qt之所以非常火的原因,因为其自带的库真的太多了,绝大多数时候都无需我们自己造轮子。

但除此之外,Qt另一个非常火的原因就是其界面的强大,前面我们使用MFC的时候可以看到,其实我们对界面的美化,能控制的范围相当有限,想要变成好看,非常难。

但Qt不一样,你可以非常容易的,将界面做的很好看,所以本章就将开始介绍Qt的界面开发流程。

2.创建项目

暂时我们还是使用qtcreator来开发Qt项目,虽然不好用,但好歹是官方自带的,得熟悉一下。

这次我们不再创建控制台项目了,而是要创建一个窗口项目:

image-20231011134752031

Qt Widgetswidget意为部件,因为在Qt里面,几乎所有东西都是部件,可以任意搭配缝合,窗口应用程序更是尤为突出。

然后随便取个项目名:

image-20231011134851881

依旧选择qmake

image-20231011134910604

这里注意一下,要选择QWidget,即创建最原始的部件窗口:

image-20231011134937224

它可以任意扩展为你想要的类型对话框,以后你熟悉了之后,当然也可以选择其它两种已经初始化好的对话框:

image-20231011134956378

  • QMainWIndows:一个所有部件都已经具备的窗口,比如状态栏,菜单栏等等,继承自QWidget
  • QDialog:对话框窗口,可以很方便弹出,继承自QWIdget

然后,这次我们就不选语言了:

image-20231011135017984

编译套件还是选择msvc

image-20231011135034150

汇总这里的版本控制系统也不需要,直接完成:

image-20231011135052571

然后我们就能看到自动生成的项目文件:

image-20231011135159525

这里你可能首先就会感到奇怪,明明是窗口程序,为啥是main函数?不应该是winMain函数吗?

这就是Qt的独到之处了,Qt将各种类型项目的差异性封装了起来,统一使用一个main函数!

当你的项目文件(.pro)中,没有添加CONFIG+=cmdline时,它就会链接对应的含有winMain函数的库,那个winMain才是真正的入口点,只不过在那个winMain函数里面,调用了我们这里的main函数。

所以不是没有使用winMain,而是它被隐藏了!

生成的文件之内的,我们也先不用管,直接点击运行一下试一试!

image-20231011135428497

与MFC一样,我们啥都没干,就可以生成一个窗口程序。

但相比较而言,你会发现Qt的代码非常简洁,文件很少,而MFC里面却是各种文件混杂,莫名其妙的代码一大堆。

这便是Qt的优势之处:简洁明了

3.文件与代码详解

首先来看,这个生成的项目中有哪些文件,都是干什么的:

image-20231011135527670

  1. main.cpp:程序源码,存放了入口函数代码。
  2. widget:主窗口类,包含widget.hwidget.cpp这两个文件,用于控制主窗口的行为。
  3. widget.ui:UI设计文件,和MFC类似,通过拖拽即可完成界面搭建。

然后我们来具体看一看文件中的代码,首先是main.cpp文件:

image-20231011135649108

代码相当简单,首先和前面控制台一样,也是声明了一个应用类,不过这个类名为QApplication,它是QCoreApplication的子类。

声明出对象后,然后调用exec函数进入消息循环:

    QApplication a(argc, argv);
    return a.exec();

只不过在中间,还实例化了一个窗口类,并调用show函数显示出来,这个操作学过win api界面编程的我们应该很清楚,其实就是在调用showwindow这个win API函数来显示窗口。

    Widget w;
    w.show();

这个类就是我们的主窗口类,在生成的文件widget.hwidget.cpp中。

然后我们就来看看这个主窗口类干了些什么:

image-20231011135746473

首先是最外层的宏定义,这是为了防止多次包含:

#ifndef WIDGET_H
#define WIDGET_H
 //省略中间代码
#endif // WIDGET_H

然后是头文件:

#include <QWidget>

这是Qt提供的标准窗口组件类,我们这个主窗口类,就是通过继承它,才能直接使用各种各样的窗口函数,这与MFC一样,都是通过继承实现的。

然后是一个看起来比较奇怪的东西:

QT_BEGIN_NAMESPACE

QT_END_NAMESPACE

这两个其实是空宏,给我们程序员看的,大致意思可能就是这块地方专门用于使用命名空间,没啥作用,有用的是里面的东西:

namespace Ui { class Widget; }

这代表着,将类Widget添加到命名空间Ui中。

然后来到主要部分,就是继承QWidget即可。


class Widget : public QWidget
{
    Q_OBJECT
public:
    Widget(QWidget *parent = nullptr);
    ~Widget();
private:
    Ui::Widget *ui;
};

这里有个宏Q_OBJECT很重要,因为Qt里面最重要的机制就是信号与槽,虽然暂时还没讲怎么用,但你需要知道的是,如果类想要使用这个机制,就必须写上这个宏

然后构造函数与析构函数就不多说了,我们可以看到它还有一个很奇怪的操作,就是定义了一个自己的成员指针变量,但名字却写成ui

 Ui::Widget *ui;

这句话的意思应该都能看懂吧!就是命名空间Ui中的Widget声明一个指针变量。

它是在前面添加到这个命名空间中的,这不就相当于自己里面声明了一个自己类型的指针变量嘛!这有啥用?

但先不用急,我们再来看看实现文件:

image-20231011140050441

这里只有构造函数析构函数,构造函数很简单,这种赋值方式以前应该提到过,这种叫做初始化列表,理论上比在函数体内赋值更快,更详细的内容可以参照这篇文章:初始化列表

比如这里:

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
}

QWidget(parent)就是调用的父类构造函数,将该参数交给父类处理,然后成员函数指针ui则是new了一个对象:

ui(new Ui::Widget) //等价于 ui=new Ui::Widget;

最离奇的事情是,下面这句:

 ui->setupUi(this);

是不是感觉有几个问号???

自己这个类里面声明了一个自己的对象指针,然后这个对象指针调用一个函数将自己的指针传进去。

简直魔幻!这有啥用?意义何在?

setupUi这个函数,看名字应该是绑定这个类的UI的,但这里却传进去了自己!UI用什么表示的呢?UI与类是如何绑定在一起的呢?

析构函数就不用说了,只是delete这个指针。

之所以感到魔幻,只是因为我们还没有看完代码,最后我们再来看一看这个ui_widget.h做了些什么事情。

在这里插入图片描述

ui_widget.h:这是一个UI类,用于控制UI的,相当于上面的窗口类与我们看到的界面的桥梁连接。

此时你可以按住键盘上的Ctrl键,然后鼠标左键点击这个头文件,就可以跳转过去:

在这里插入图片描述

这里是一个类,并且我们看到了一个函数,setupUi!,这不就是刚才调用的那个函数嘛!

然后再看到下面的:

namespace Ui {
    class Widget: public Ui_Widget {};
} // namespace Ui

这下就一下子明悟了,原来是使用的多继承!

所谓多继承,就是Widget原本就已经继承了QWidget,而现在又继承了Ui_Widget ,相当于它一个类里面拥有了两个父类的东西!

至于这个类里面具体干了些什么,可以看到里面调用了一些我们常见的函数。

比如resize即改变窗口大小,我们就大概能猜到,这个类才是真正用来控制UI的。

但这个类又是如何绑定我们界面的呢?

这就不用管了!Qt内部机制既然已经帮我们封装好了,我们又何必自讨没趣。

我们只需要知道,这个类是Qt自动用来控制控件的,我们并不用管,并且Qt还使用了多继承,仿佛是特意想要将这个类给隐藏了(实际上是通过多继承的方式在使用这个类)

至此,我们就把Qt的代码过了一遍,看上去是不是比MFC简单多了!短短几行代码就能轻易生成一个窗口!而且还相当简洁!

4.初步学习使用UI

在大致了解了这些文件与代码的用途以及逻辑之后,我们就可以正式写代码了!

首先是设计界面,这个很简单,只需要双击下面这个文件:

image-20231011140519348

然后就会自动弹出与MFC类似的界面

image-20231011140703821

然后左边便是Qt里面的控件,操作和MFC相同,比如我们可以拖一个按钮到窗口上:

image-20231011140807693

Qt里面的按钮名称为Push Button,然后根据我们使用MFC的逻辑,下一步肯定就是更改这个控件的属性了!

界面的右下方就显示的是当前控件的属性:

image-20231011140923607

将这些内容折叠后,我们就可以很清楚的看到继承关系,从上到下,我们这个按钮类QPushButton已经是第四层了。

也正因为是继承自这些类,所以这些类中的某些属性,只要继承了它,就可以在这里找到,并更改。

首先是QObject类,这是Qt里面最基层的类,Qt里面所有的类都继承自它。

它有一个属性,就是这个对象的名字!这和MFC里面给控件绑定一个控件变量很类似,不过很显然,这比MFC要方便且简单的多:

image-20231011141115898

在代码中,我们也是通过这个更改后的变量名来控制这个控件的,比如这里我将其更改为了pb_test

然后是QWidget类,它是一个最基层的窗口类,几乎所有控件都继承自它:

image-20231011141319321

可以看到,它的属性就相当多了,虽然都是英文,但都不难,基本能够见名知意,这里不再深入,可以自己稍微研究研究,有个印象即可。

我要提的是上图箭头所指的一个非常重要的属性,当我们想要美化控件时,就可以使用它,非常的方便,就是styleSheet这个属性。