『 QT 』Hello World控件实现指南
文章目录
- 输入框实现 "Hello world"
- 用 Designer 实现
- 用代码实现
- 按钮实现 "Hello world"
- 用 Designer 实现
- 按钮添加点击处理
- 用代码实现
输入框实现 “Hello world”
用 Designer 实现
-
创建一个
Widget
项目 -
双击
.ui
文件进入Designer
设计模式 -
将左侧工具栏找到
Input Widget
分类并将需要的控件拖动至程序框内 -
在控件中文本写入编辑内容
也可通过右侧的属性编辑区进行修改;
-
构建并运行
在使用Designer
中添加/设计的控件一般是一XML
的形式写进.ui
(FromFile
)文件中, 通过元编程的方式将XML
转化为代码, 该项目所使用的框架是Widget
框架, 因此在编译后项目中会存在一个ui_widget.h
头文件, 在这个文件中可以看到使用Designer
中添加的控件;
因此使用Designer
添加的控件无需手动去设置链接对象树;
用代码实现
代码实现通常在使用框架的源文件中的构造函数中对控件进行编辑;
例如此处使用的是widget
框架, 对应的需要再widget.cpp
中的class Widget
的构造函数中对控件进行编辑, 或者如果有一个子控件类继承了这个类, 则在这个派生类的构造函数中去设置控件;
#include "widget.h"
#include "ui_widget.h"
#include <QLineEdit> // 包含头文件 <QLineEdit>Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);QLineEdit* _edit = new QLineEdit(this); // 创建一个 QLineEdit 实例并指名父对象为 this_edit->setText("Hello World"); // 在 QLineEdit 中设置文本 "Hello World"
}Widget::~Widget()
{delete ui;
}
在这里的控件位于左上角, 本质上是没有设置控件在窗口的具体位置;
按钮实现 “Hello world”
用 Designer 实现
使用Designer
实现与文本框的实现方式一致;
即拖动Buttons
中的控件至UI
界面完成实现;
构建后运行结果为如下:
但在这个按钮中点击是无反应的, 本质上是没有设置信号和槽;
-
信号和槽
信号和槽本质上是通过按钮对应的一些行为绑定对应的处理函数, 如当点击按钮后, 可能触发的是一个
click
事件, 这个事件会调用所绑定的处理函数从而进行一些变化;通常情况下载
QT
中使用connect()
来绑定一个控件具体某个行为的处理函数;该
connect()
与socket
中的connect()
用途不同, 属于同名但不同作用和意义;需要注意的是, 槽并不是一个容器, 与可能抽象理解的凹槽不太一样;
并不能抽象理解为这种槽;
槽本身是一种函数, 全程为槽函数, 本质上是与信号通过
connect()
函数的绑定关系;硬要说的话可以这么说:
槽函数就是普通的成员函数, 没有"空槽"的概念, 一个信号可以连接槽函数, 也可以不连接任何槽函数, 当连接槽函数时收到对应信号将会调用槽函数对信号进行处理, 若是不连接任何槽函数时将会忽略发送的信号;
以该例子来说, 所采用的控件为QPushButton
, 该控件的信号通常有如下:
-
pressed()
信号鼠标按下时触发;
对应的事件处理函数是
mousePressEvent()
; -
clicked()
信号鼠标松开时触发, 如果鼠标拖拽到按钮区域外再释放则不会触发;
对应的事件处理函数是
mouseReleaseEvent()
; -
released()
信号鼠标松开时触发, 按下后鼠标拖拽到按钮区域外再释放仍然会触发;
对应的事件处理函数是
mouseReleaseEvent()
; -
toggled()
信号设置
setCheckable(true)
后单击按钮才会触发该信号;一般用于多个按钮组成
QButtonGroup(true)
设置按钮间互斥;其中
toggled()
信号主要关心的是按钮状态的变化, 而不是按钮是否被点击;
信号和槽本质上是这样的, 假设信号是clicked()
信号, 当一个按钮被单击(单击包含点击和释放)后, 这个单击本身就是一个事件, 按下和释放时将会自动调用对应的处理函数(按下的处理函数为mousePressEvent()
, 释放为mouseReleaseEvent()
), 判断按下和释放的时间(两个事件处理的时间), 按下和释放是否在同一控件区域内, 按下后鼠标是否移出按钮区域等条件来向控件发送对应的信号, 当发送clicked()
信号后, 由于connect()
绑定了对应信号的槽函数, 将会调用对应的信号处理函数进行一个处理;
按钮添加点击处理
上文提到, 若是需要使用点击需要设置信号和槽, 即通过connect()
函数来绑定对应信号的信号处理函数(槽函数);
-
connect()
函数经过版本的迭代,
QT
支持两种语法来实现连接信号与槽;这里主要演示的是新语法;
QObject::connect(sender, &SenderType::signal, receiver, &ReceiverType::slot, Qt::ConnectionType type = Qt::AutoConnection);
参数解释如下:
-
sender
发送信号的对象指针;
如果控件对象是通过
Qt Designer
来创建的, 那么需要使用ui -> button_name
的方式, 即通过ui
对象指向对应指针的方式来进行传参;否则可以直接传入一个
QObject
子类对象, 如使用代码创建控件时可以直接将控件变量名直接传入(前提是控件对象是new
出来的); -
&SenderType::signal
发送者类型的信号成员函数地址;
表示槽函数对应的信号种类, 可能为
pressed
,clicked
等信号, 信号一定要匹配对应的控件对象类型; -
receiver
接收信号的对象指针;
表示谁来处理这个信号, 通常为槽函数的拥有者, 需要传入该对象的指针;
当然如果槽函数是全局函数,
lamdba
表达式或是静态成员函数,receiver
可以是任意对象, 甚至是nullptr
, 因为本质上并不是由receiver
来调用这些函数,receiver
本质上是管理线程的生命周期, 即当信号被发射后Qt
内部将会形成一个事件或是直接调用槽(取决于第五个参数的连接方式), 而调用槽时所在的线程是不同的, 当槽位上述几种函数时分为两种情况:-
receiver
为nullptr
槽函数在信号发射的线程中执行;
-
receiver
不为nullptr
槽函数在
receiver
所在线程执行(具体方式由第五个参数Qt::ConnectionType type
决定);
-
-
&ReceiverType::slot
槽函数的函数指针, 可以是成员函数, 全局函数, 静态成员函数或者是
Lamdba
表达式; -
Qt::ConnectionType type = Qt::AutoConnection
连接情况分为以下几种:
-
Qt::DirectConnection
槽函数在信号发射线程立即执行;
-
Qt::QueuedConnection
槽函数在
receiver
所在线程的事件循环中执行; -
Qt::AutoConnection
Qt
自动判断(同线程情况下为DirectConnection
, 不同线程为QueuedConnection
); -
Qt::BlockingQueuedConnection
类似
Queued
, 但发送线程会阻塞等待;
-
-
在之前的操作中已经创建了一个QPushButton
按钮控件对象, 该按钮使用Designer
创建, 因此在使用connect()
函数时sender
需要通过ui
对象指向该控件对象的objectName
(本质上就是控件指针变量名), 此处变量名为pushButton_1
;
在此之前需要一个信号处理函数(槽函数), 这里的槽函数选择在Widget
中创建对应的成员函数, 同样在widget.h
中进行声明, 在widget.cpp
中进行实现;
本次实现为点击按钮进行文本的转换, 单击信号为clicked
信号;
////////////////////////////////////////
/////////////* widget.h */////////////
////////////////////////////////////////QT_BEGIN_NAMESPACE
namespace Ui {
class Widget;
}
QT_END_NAMESPACEclass Widget : public QWidget
{Q_OBJECTpublic:Widget(QWidget *parent = nullptr);void handleClick_1(); // 槽函数声明~Widget();private:Ui::Widget *ui;
};////////////////////////////////////////
/////////////* widget.cpp */////////////
////////////////////////////////////////Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);connect(ui->pushButton_1, &QPushButton::clicked, this, &Widget::handleClick_1); // 通过 connect 函数来绑定信号与槽
}void Widget::handleClick_1(){ // 槽函数定义// 判断 pushButton_1 控件中的文本 是否为 "Click me!" // 是则改变文本为 "Hello world"if(ui->pushButton_1->text() == QString("Click me!")){ ui->pushButton_1->setText("Hello world");}else{// 否则改变文本为 "Click me!"ui->pushButton_1->setText("Click me!");}
}Widget::~Widget()
{delete ui;
}
在这段代码中的Widget::Widget(QWidget *parent)
构造函数中的connect()
函数中第一个参数采用了ui->pushButton_1
进行传参, 这里需要传的参数是信号发送的控件指针;
在项目构建完毕后, 可以从项目结构里的ui_widget.h
文件中可以看到;
通过构建后, class Ui_Widget
类中出现了一个QPushButton *pushButton_1
的成员变量;
该成员则是所传的参数;
一般情况下当使用QtCreator
中的Designer
创建控件对象后, 当保存.ui
文件时, QtCreator
将会分析.ui
的xml
文件, 并在ui_xxx.h
中补全相应代码;
因此可以直接使用ui->
的方式对Designer
创建的控件对象进行指向;
用代码实现
此次用代码实现的按钮Button
功能与上文相同, 同样为点击前文本为"Click me!"
, 点击后为"Hello world"
;
通常情况下, 所创建的控件可能需要在槽中传入或是调用自己的成员, 而指针变量是局部变量, 当构造函数结束后这个局部变量(指针变量)的生命周期也会随之结束, 因此无法再槽中调用这个指针变量, 而较好的解决办法是将控件对象作为框架的成员变量进行创建定义;
class Widget : public QWidget
{Q_OBJECTpublic:Widget(QWidget *parent = nullptr);void handleClick();~Widget();private:Ui::Widget *ui;QPushButton *myButton;
};
通常也支持推荐使用这种方式创建, 这样既可以对其进行更好的生命周期管理, 也能增加访问便利性;
通常情况下若是将控件对象作为成员函数, 通常建议在类中声明, 在构造函数中的初始化列表或是构造函数函数体内进行初始化;
Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget), myButton(new QPushButton(this))
{ui->setupUi(this);myButton->setText("Click me!");connect(myButton, &QPushButton::clicked, this, &Widget::handleClick);
}void Widget::handleClick(){if(myButton->text() == QString("Click me!")){myButton->setText("Hello world");}else{myButton->setText("Click me!");}
}Widget::~Widget()
{delete ui;
}