1. Qt基本文件组成

1. .pro项目文件

Qt版本不同,内容略有差异。如5.14版本,记录了模块、编译器版本、编译文件、输出路径等信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#4.x版本的Qt的gui与widgets是合并的,在5.x中分开了,模块化编程要载入。
QT += core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

#编译器版本
CONFIG += c++11

# The following define makes your compiler emit warnings if you use
# any Qt feature that has been marked deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS

# You can also make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0

#编译的头文件、cpp文件
SOURCES += \
main.cpp \
widget.cpp

HEADERS += \
widget.h

# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target

2. 主程序文件main.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include "widget.h"

#include <QApplication>

int main(int argc, char *argv[])
{
//应用程序类实例化一个对象
//应用程序类QApplication:是Qt后台管理的命脉,是管理图形用户界面应用程序的控制流与主要设置
//包含主事件循环、来自窗口系统和其他资源的所有事情处理和调度
//处理应用程序的初始化和结束,并提供对话管理
//无论应用程序在同一时间是否有多个窗口,都正好存在一个QApplication对象。
QApplication a(argc, argv);

Widget w; //从窗口类继承的子类,在头文件声明了
w.show(); //显示窗口

//主事件循环:窗口不关闭,程序不结束;窗口关闭,程序结束。
return a.exec();
}

3. 头文件widget.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>

//QWidget是一个普通窗口类,Widget公有继承
class Widget : public QWidget
{
Q_OBJECT //支持信号和槽

public:
//有参构造函数,且设置了默认参数
Widget(QWidget *parent = nullptr);

//析构函数
~Widget();
};
#endif // WIDGET_H

4. cpp文件widget.cpp

之所以继承使用widget而不直接使用QWidget,是为了让用户在widget.h与widget.cpp中添加自己的组件和功能。

1
2
3
4
5
6
7
8
9
#include "widget.h"
//子类Widget类外实现有参构造,且调用了链表传参子类parent构造时同时向父类传入参数
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
}
Widget::~Widget()
{
}

2. Qt Assitant使用

得益于C++强大的可复用性,Qt编程中有大量的类不可能完全记忆,Assitant可以帮助查找有关类的信息。 例如需要查找某个窗口属性,索引搜索

QWidget

首先是目录索引: 目录介绍 查找思路:公有函数->公有的槽函数->父类的函数

其次是类的信息 类的信息

例如设置窗口标题,大小等:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//widget.cpp
//在构造函数中设置
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
//设置标题
this->setWindowTitle("Hello QT");
//获取窗口标题
QString s=this->windowTitle();
//输出标题
qDebug()<<s;
// //设置窗口尺寸
// this->resize(600,400);
//设置固定大小
this->setFixedSize(500,400);

}

3. 按钮控件

1. 按钮控件的类

1
2
3
4
5
6
7
8
9
QPushButton Class
Header:
#include <QPushButton>

Inherits:
QAbstractButton

Inherited By:
QCommandLinkButton

2. 添加一个按钮

1
QPushButton button;

然而这个代码没有输出按钮,这是因为按钮控件需要绑定一个父对象,决定了它在哪个窗口进行显示,这种嵌套的形式称对象树。标准写法是:

1
2
3
4
//创建一个按钮对象
QPushButton *button=new QPushButton;
//设置父对象
button->setParent(this);//在父类构造函数中

3. 对象树的空间释放问题:

  1. 父对象释放空间时,也会释放所有子对象列表的空间。
  2. Qt按钮控件一般选择在堆区开辟空间,原因是:如果在栈区开辟空间,如:
    1
    2
    3
    4
    5
    int main{
    Parent P0;
    QPushButton button;
    button.setparent(&P0);
    }
    此时父对象P0被释放空间时,按钮也会被释放,程序结束时,按钮空间再次被释放,引起多次释放。而如果定义在静态区,意味着该按钮在程序结束前始终未被释放,在多开的场合引起大量的资源浪费。

4. 设置按钮属性

打开Assistant,开始查找:设置文本属性函数在QPushButton的父类QAbstractButton中

1
void setText(const QString &text);
设置大小属性函数在QAbstractButton的父类 QWidget中,也就是我们刚刚测试的,大部分控件大小都使用该函数进行设置;
1
void setFixedSize(int w,int h);
同时还有设置位置函数:
1
void move(int x, int y);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//widget.cpp
#include "widget.h"
#include <QString>
#include <QDebug>
#include <QPushButton>
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
//设置标题
this->setWindowTitle("Hello QT");
//设置窗口尺寸
this->resize(600,400);

//创建一个按钮对象
QPushButton *button=new QPushButton;
//设置父对象
button->setParent(this);
//按钮大小
button->setFixedSize(100,100);
//按钮文本
button->setText("Poor Guy");
//x轴为300,y轴为200
button->move(200,100);
}
Widget::~Widget()
{
}

按钮效果演示 显示一个简单的按钮及其空间分布。

4. 信号与槽机制

信号槽Qt框架重要的机制之一,当某个事件发生后(例如按钮被点击了一下),它就会发出某个信号(signal),这种信号是一种广播,没有目的性的,当某个对象对这个信号感兴趣,它就会使用连接(connect)函数,把这个信号和自身的一个函数(称为槽,也即slot)绑定;每次信号发出时,该槽函数会自动被回调。实际上类似一种观察者模式:当感兴趣的事件发生以后,某个操作会自动被触发。

connect函数,查找connect:

1
2
3
4
5
6
7
8
9
connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type = Qt::AutoConnection)

功能:接收者接收指定信号的处理操作
参数:
sender:信号的发起者(如按钮)
signal:指定信号(如点击),可以是系统的,也可以自定义
receiver:信号的接收者(如窗口)
method:槽函数,处理方法(对象的槽函数,如窗口关闭)
type:默认参数

1. 系统槽函数的调用

调用窗口退出槽函数,实现窗口关闭

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
//widget.cpp
#include "widget.h"
#include <QDebug>
#include <QPushButton>


Widget::Widget(QWidget *parent)
: QWidget(parent)
{
//设置标题
this->setWindowTitle("Hello QT");
//设置窗口尺寸
this->resize(600,400);

//创建登录、注册、退出按钮
QPushButton *buttonReg=new QPushButton;
QPushButton *buttonLog=new QPushButton;
QPushButton *buttonExit=new QPushButton;

buttonReg->setParent(this);
buttonLog->setParent(this);
buttonExit->setParent(this);

buttonReg->setFixedSize(100,50);
buttonLog->setFixedSize(100,50);
buttonExit->setFixedSize(100,50);

buttonReg->setText("Register");
buttonLog->setText("Login");
buttonExit->setText("Exit");

buttonReg->move(100,150);
buttonLog->move(250,150);
buttonExit->move(400,150);

//信号:点击 槽函数:窗口关闭 发起者:退出按钮 接收者:窗口
//4.x版本写法
//connect(buttonExit,SIGNAL(clicked()),this,SLOT(close()));
//Qt5.0版本以上,推荐
connect(buttonExit,&QPushButton::clicked,this, &QWidget::close);
}

Widget::~Widget()
{}

2.自定义槽函数

1. 全局函数定义槽函数

1
2
3
4
5
6
7
8
//widget.cpp中  
void buttonRegSlot()
{
qDebug()<<"Register Success!";
}
//构造函数添加绑定
//信号:点击 自定义槽函数:打印注册成功 发起者:注册按钮 接收者:窗口
connect(buttonReg,&QPushButton::clicked,this, &buttonRegSlot);

2. 成员函数定义槽函数

1
2
3
4
5
6
7
8
9
10
11
//widget.cpp
//类内实现
void Widget::buttonRegSlot(){
qDebug()<<"Register Success!";
}
//构造函数添加绑定
connect(buttonReg,&QPushButton::clicked,this, &Widget::buttonRegSlot);

//类外声明//widget.h
public slots: //slots起标识作用
void buttonRegSlot();

3. 使用lambda表达式实现槽函数

这是最简单简洁的方法,把槽函数和connect写在一起,只能在本窗口(类)中使用,甚至可以省略参数this指针。

1
2
//构造函数中
connect(buttonLog,&QPushButton::clicked,[=](){qDebug()<<"Login success!";});
信号与槽演示 每次点击对应按钮,打印对应字符,点击Exit即退出。

5.窗体切换

在cpp中选择Add new添加一个新窗口类,继承QWidget: 创建新类 这个操作相当于新建了一个窗口,我们可以在新窗口上添加按钮等元素,现在我们希望两个窗口页面通过按钮来实现来回切换。此前我们要先理解一个新的知识,定义自己的信号。

1. 自定义信号实现窗体切换

从本窗口点击按钮,切换到新的页面,这个只需要在本窗口包含新窗口头文件,创建对象,使用各自显示、隐藏槽函数即可。但是切换后如何回到原来窗口呢?关键是把新窗口的操作,以信号的方式广播出去,因此这里要了解自定义信号的定义和传递。首先在新窗口头文件定义一个新信号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//newwidget.h
#ifndef NEWWIDGET_H
#define NEWWIDGET_H
#include <QWidget>
class newwidget : public QWidget
{
Q_OBJECT
public:
explicit newwidget(QWidget *parent = nullptr);

signals:
void lspgsignal();//定义自己的信号
};
#endif // NEWWIDGET_H
然后在新窗口cpp中把信号发出,利用emit关键字
1
2
3
4
//newwidget.cpp构造函数中
connect(buttonlspage,&QPushButton::clicked,[=](){
emit this->lspgsignal(); //发出当前类的信号
});
在主窗口中捕获这个信号作为signal,发送者为新窗口,相当于主窗口获知新窗口想要回来的信号,让主窗口控制因此新窗口,显示主窗口。
1
2
3
4
5
//widget.cpp
connect(newwindow,&newwidget::lspgsignal,[=](){
newwindow->hide();
this->show();
});

2. 信号的参数传递

从上面的例子我们直观看到信号这种广播机制为不同窗口切换带来便利,此外,这种广播还可以用于不同窗口间值的传递。

1
2
3
4
5
6
7
8
9
10

signals:
//newwidget.h
void testpassval(int num);//定义信号

//newwidget.cpp
connect(buttonlspage,&QPushButton::clicked,[=](){
emit this->testpassval(100); //发出当前类的信号,参数是要发的值
});

应该注意的是,信号发出的值会直接装载到槽函数的参数表中,也即槽函数的参数是信号决定的。
1
2
3
4
5
6
//widget.cpp捕获信号和值,打印出来
connect(newwindow,&newwidget::testpassval,[=](int num){
newwindow->hide();
this->show();
qDebug()<<num;
});

6. 信号与槽函数的有关说明

  1. 发送者、接收者都必须是QObject的子类,槽函数是全局函数、Lambda表达式等无需接收者时除外。
  2. 信号和槽函数返回值都是void;
  3. 信号只需要声明,不需要实现;槽函数需要声明,也需要实现;
  4. 槽函数当使用成员函数实现时,会受到public、private、protected的影响。
  5. emit用于发送信号,connect()函数用于连接信号和槽,任何成员函数、全局函数、static函数、Lambda表达式都可以作为槽函数。
  6. 信号与槽一般参数一致,特殊情况是槽参数可以比信号的参数少,但是顺序要遵循(就像默认参数设置那块)。
  7. 一个信号可以连接到多个槽,但是槽函数被调用的顺序是不确定的;多个信号可以连接到一个槽,信号发出即可自动调用。
  8. 信号和信号可以连接,例如一个信号被发出,即发出第二个信号,和信号槽机制类似;
  9. 信号和槽可以被取消链接,关键字disconnect

7. 行编辑器QLineEdit

实现类似计算器输入框的功能,为下一节的计算器编程做铺垫。尝试利用按钮+行编辑器实现文本装载和清空功能。槽函数我们使用了QLineEdit的清空函数,为了更改字体,字体装载我们自定义了一个槽函数来定义字体样式、大小、粗细等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include<QLineEdit>
class Widget : public QWidget
{
Q_OBJECT //宏定义,不要去除,否则无法识别槽函数编译器报错

public:
Widget(QWidget *parent = nullptr);
~Widget();


QLineEdit *qledit;
public slots:
void setLineText();

};
#endif // WIDGET_H
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
//widget.cpp
#include "widget.h"
#include <QLineEdit>
#include <QPushButton>
#include <QFont>


Widget::Widget(QWidget *parent)
: QWidget(parent)
{
this->setFixedSize(600,400);

this->qledit=new QLineEdit(this);
this->qledit->setFixedSize(600,100);
this->qledit->move(0,50);

QPushButton *button_clear=new QPushButton(this);
QPushButton *button_equi=new QPushButton(this);

button_clear->setText("Clear");
button_equi->setText("equipment");
button_clear->setFixedSize(100,50);
button_equi->setFixedSize(100,50);
button_clear->move(100,200);
button_equi->move(300,200);

connect(button_clear,&QPushButton::clicked,this->qledit,&QLineEdit::clear);
//自定义槽函数
connect(button_equi,&QPushButton::clicked,this,&Widget::setLineText);
}
Widget::~Widget()
{
}
void Widget::setLineText(){
this->qledit->setText("Hello QT");
//QFont(const QString &family, int pointSize = -1, int weight = -1, bool italic = false)
this->qledit->setFont(QFont("Microsoft YaHei",28,QFont::Bold));//字体样式、大小、粗细、斜体(false不使用)
}

8. 代码实战:实现一个简单计算器

我们将新建一个工程,实现一个简单布局的计算器以及正整数加减乘除等操作。

9. QmainWindow

新建任务class选择QmainWindow,勿选择Qwidget

菜单栏、菜单、菜单项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include "mainwindow.h"
#include <QMenuBar>
#include <QMenu>
#include <QDebug>

MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent) //窗口类
{

//窗口标题、大小
this->setWindowTitle("组件窗口");
this->setFixedSize(1200,800);

//菜单栏绑定窗口:QMenuBar绑定到窗口
QMenuBar* menubar = new QMenuBar(this);
this->setMenuBar(menubar);

//菜单绑定菜单栏:QMenu
QMenu *menuFile = new QMenu("文件",this);
menubar->addMenu(menuFile);

QMenu *menuEdit = new QMenu("编辑",this);
menubar->addMenu(menuEdit);

QMenu *menuBuild = new QMenu("构建",this);
menubar->addMenu(menuBuild);

//菜单项绑定菜单:QAction、addQAction到QMenus
QAction *actnewproj = new QAction("新建文件或项目",this);
menuFile->addAction(actnewproj);
QAction *actnewfile = new QAction("新建文本",this);
menuFile->addAction(actnewfile);

//菜单项间设置分割线
menuFile->addSeparator();

//分割线后继续加菜单项
QAction *actopenfile = new QAction("打开文件",this);
menuFile->addAction(actopenfile);
QAction *actclosefile = new QAction("关闭文件",this);
menuFile->addAction(actclosefile);

//菜单项快捷键设计参考QKeySequence
actnewproj->setShortcut(QKeySequence("Ctrl+N")); //字符串写法
actopenfile->setShortcut(QKeySequence(Qt::CTRL+Qt::Key_O)); //Qt键写法

//简单响应函数
connect(actclosefile,&QAction::triggered,[=](){
qDebug()<<"Done";
});
}

MainWindow::~MainWindow()
{
}

实现效果: 菜单栏、菜单、菜单项

状态栏

状态栏和菜单栏属性有差异,状态栏可放入Qwidget类及其子类控件,例如前面所述的按钮、行编辑器等,QAction和Qwidget同继承自QObject,因此状态栏不能放入QAction对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
///////相应头文件///////
#include <QStatusBar>
#include <QPushButton>
#include <QLabel>
#include <QLineEdit>
///////////////////////

////MainWindow类追加以下:
//状态栏对象
QStatusBar *statusbar = new QStatusBar(this);
this->setStatusBar(statusbar);

//状态栏添加Widget类控件:按钮、QLabel、行编辑器等
QPushButton *button_search = new QPushButton("搜索",this);
statusbar->addWidget(button_search); //默认左对齐添加

QPushButton *button_output = new QPushButton("输出",this);
statusbar->addWidget(button_output);

QLineEdit *ledit = new QLineEdit("Please Input...");
statusbar->addPermanentWidget(ledit); //右对齐添加

QLabel *label_clarify = new QLabel("UTF-8");
statusbar->addPermanentWidget(label_clarify); //右对齐添加

实现效果: 菜单栏、菜单、菜单项

中心部件与铆接部件

铆接部件是《QMainWindow》定义的一种部件,指的是窗口上停靠的浮动框部件,可以使用函数进行停靠位置、窗体属性等设置,往往配合中心部件一起使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//设置停靠窗体可停靠的区域
setAllowedAreas()
//Qt::LeftDockWidgetArea //可在主窗口的左侧停靠
//Qt::RightDockWidgetArea //可在主窗口的右侧停靠
//Qt::TopDockWidgetArea //可在主窗口的上侧停靠
//Qt::BottomDockWidgetArea //可在停靠主窗口的底部停靠
//Qt::AllDockWidgetAreas //可在主窗口的任意部分停靠
//Qt::NoDockWidgetArea //只停靠在插入处

//设置停靠窗体的特性
setFeatures(DockWidgetFeatures features)
//QDockWidget::DockWidgetClosable //停靠窗体可以关闭
//QDockWidget::DockWidgetFloatable //停靠窗体可浮动
//QDockWidget::DockWidgetMovable //停靠窗体可移动
//QDockWidget::AllDockWidgetFeatures //此参数表示拥有停靠窗体的全部属性
//QDockWidget::NoDockWidgetFeatures //不可移动,不可关闭,不可浮动

中心部件不是一种独立的类,任何QWidget类控件都可以作为中心部件,例如按钮、行编辑器等,比较常使用的是文本编辑器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
////////////头文件///////////////////////
//中心部件
#include <QTextEdit> //文本编辑器作中心部件

//铆接部件
#include <QDockWidget>
#include <QMainWindow>
////////////////////////////////////

//以下添加在MainWindow

//文本编辑器作中心部件
QTextEdit * mainwindow = new QTextEdit("Edit Here...",this);
setCentralWidget(mainwindow); //设置为中心部件


//铆接部件
QDockWidget *dockwindows = new QDockWidget("选项卡",this);
this->addDockWidget(Qt::RightDockWidgetArea,dockwindows); //默认停靠位置
dockwindows->setFixedSize(500,100);
dockwindows->setAllowedAreas(Qt::LeftDockWidgetArea); //允许停靠位置
//属性配置
dockwindows->setFeatures(QDockWidget::DockWidgetFloatable|QDockWidget::DockWidgetClosable|QDockWidget::DockWidgetMovable);

实现效果: 菜单栏、菜单、菜单项