第41讲 UI美化遗留问题解决
如上图所示目前记事本的雏形已现,但是还是有待优化,比如右下角的拖动问题。
解决方法:
①首先修改了Widget类的构造函数。
Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget)
{ui->setupUi(this);this->setLayout(ui->verticalLayout);ui->widgetBottom->setLayout(ui->horizontalLayout);
}
下面开始逐步讲解,在Widget构造函数当中,我们首先初始化了成员变量:
ui(new Ui::Widget)
这里为类的成员变量 ui
分配内存并进行初始化。ui
通常是一个指向特定用户界面类的指针,这个类是由 Qt 的 UI 设计工具生成的。通过初始化 ui
,可以在后续的代码中访问和操作由设计师创建的用户界面元素。
然后设置父窗口和布局:
QWidget(parent)
调用父类 QWidget
的构造函数并传入 parent
参数。这使得当前创建的 Widget
实例能够与指定的父窗口建立关系。如果有父窗口,当父窗口被销毁时,子窗口也会被自动销毁,有助于管理窗口资源和层次结构。
ui->setupUi(this)
这个函数通常由 Qt 的 UI 设计工具生成的代码调用。它负责设置用户界面,将各种界面元素(如按钮、文本框等)与对应的成员变量关联起来,并根据设计设置初始状态和属性。
this->setLayout(ui->verticalLayout)
这一行是我们自己写的,将当前窗口的布局设置为 ui->verticalLayout
。布局管理器用于自动管理窗口内的子部件的大小和位置。通过设置布局,可以确保界面元素在窗口大小变化时能够正确地调整位置和大小,提高用户界面的适应性。
ui->widgetBottom->setLayout(ui->horizontalLayout)
这一行也是我们自己写的,这里为特定的子部件 ui->widgetBottom
设置布局为 ui->horizontalLayout
。这使得该子部件内的元素能够按照水平布局进行排列和调整。
②在UI底部添加两个弹簧控件;
左侧可拉伸,右侧选择fixed锁死长度。
第42讲 信号与槽的引入
基本概念
控件案例
下面我提供一个案例帮助大家理解。
①在主窗口放置了一个按键;
②切换到信号与槽的编辑器,点击绿色的+号增添一位信号发送者;
③双击“发送者”下面的单元格,一次选择发送者为pushButton,信号为click,接受者为MainWindow,槽选择为close;(达成的效果是按下按键后,组件发送信号给主窗口,主窗口关闭)
第43讲 信号与槽的四种代码实现方式
总览
使用QObject::connect
尝试在在构造函数内部进行信号与槽的绑定。
①在UI界面处布置控件并且命名
②对项目进行整体重构,让编译器识别到新建的控件对象
③使用QObject内部的静态函数connect实现信号量与槽的关联
connect函数原型:
static QMetaObject::Connection connect(const QObject *sender, const QMetaMethod &signal,const QObject *receiver, const QMetaMethod &method,Qt::ConnectionType type = Qt::AutoConnection);
参数列表介绍:
sender
参数
类型为const QObject*
,它是信号的发送者对象的指针。这个对象通常是继承自QObject
类的。例如,在一个简单的图形用户界面应用中,QPushButton
按钮对象就可以作为信号发送者。当按钮被点击时,它会发送clicked
信号。
signal
参数
类型为const QMetaMethod&
,它代表发送者对象的信号。信号是在QObject
的子类中使用signals
关键字声明的成员函数。信号没有函数体,它的作用是在特定事件发生时(如按钮点击、定时器超时等)发出通知。例如,QPushButton
类中的clicked(bool)
信号,当按钮被点击时会发出这个信号,并且可以携带一个布尔值(表示按钮是否被选中状态等相关信息)。
receiver
参数
类型为const QObject*
,它是信号接收者对象的指针。接收者也是QObject
或其派生类的对象。比如,可能是一个自定义的窗口类对象,用于处理按钮点击后的操作。
method
参数
类型为const QMetaMethod&
,它代表接收者对象中的槽函数。槽函数是普通的成员函数,使用slots
关键字(在旧版本的 Qt 中使用,现在也可以是普通成员函数)或者是普通的public
、private
等访问权限的成员函数来定义。当信号被发射时,对应的槽函数会被调用。例如,在接收者类中有一个onButtonClicked()
函数,它可以作为槽函数来处理按钮点击信号。
type
参数
类型为Qt::ConnectionType
,默认值是Qt::AutoConnection
。这个参数用于指定信号和槽的连接类型。主要有以下几种连接类型:
Qt::AutoConnection
- 这是默认的连接类型。如果信号发射和接收者在同一个线程中,那么会使用
Qt::DirectConnection
方式连接;如果信号发射和接收者在不同的线程中,会使用Qt::QueuedConnection
方式连接。这种自动选择的方式使得信号 - 槽机制在多线程和单线程环境下都能比较方便地工作。
- 这是默认的连接类型。如果信号发射和接收者在同一个线程中,那么会使用
Qt::DirectConnection
- 当信号被发射时,槽函数会立即被调用,就好像直接调用普通函数一样。这种连接方式在信号发射者和接收者在同一线程中使用时比较合适。但是如果在多线程环境下这样连接,并且槽函数执行时间较长,可能会导致信号发射线程阻塞。
Qt::QueuedConnection
- 当信号被发射时,槽函数的调用会被放到接收者对象所在线程的事件队列中。这意味着槽函数会在接收者线程的事件循环处理到这个队列项时才会被调用。这种方式在多线程环境下可以确保接收者线程的事件循环正常工作,不会被信号发射线程随意干扰。
Qt::BlockingQueuedConnection
- 这种连接方式和
Qt::QueuedConnection
类似,但是信号发射线程会阻塞,直到槽函数执行完毕。不过要注意,不能在同一线程中使用这种连接方式,否则会导致死锁。
- 这种连接方式和
Qt::UniqueConnection
- 这种连接方式可以确保相同的信号和槽之间只有一个连接。如果已经存在这样的连接,再次尝试建立连接时会被忽略。
实际使用:
Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);QObject::connect(ui->btnCon,SIGNAL(clicked()),this,SLOT(on_btnui_clickedMyself()));
}
其中ui->btn是我们在UI界面新建的控件,SIGNAL宏内包含了clicked代表按下控件时发出消息,on_btnui_clickedMyself()函数需要我们进行外部实现。
出于代码规范考虑优先在Widget.h2文件内部定义;
右键选择Refactor进行重构,位置在Widget.cpp文件中
在Widget.cpp文件中重构代码如下:
void Widget::on_btnui_clickedMyself()
{std::cout<<"ObjCon clicked"<<std::endl;
}
测试结果:
自动连接
①拖动控件并且完成命名
②右击选中控件->转到槽
③转到widget.cpp里面进行编辑,撰写槽的函数体,
简单编写一个标准输出语句进行功能测试:
使用Lamdba表达式
①添加了一枚Lambda按键,点击ctrl+b对项目进行整体构建。
②其实本质上依然是使用静态函数connect,只不过因为Lambda表达式是一种匿名函数,所以我们无需再次显式声明slot槽函数。
Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);//使用connect静态函数QObject::connect(ui->btnCon,SIGNAL(clicked()),this,SLOT(on_btnui_clickedMyself()));//使用Lambda表达式QObject::connect(ui->btnLambda,&QPushButton::clicked,[=](){std::cout<<"Lambda clicked"<<std::endl;});
}
使用函数指针
步骤大同小异
①在可视化界面拖动控件并且命名,顺手按下ctrl+b重构项目
②补充全部代码
Widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include <iostream>Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);//使用connect静态函数QObject::connect(ui->btnCon,SIGNAL(clicked()),this,SLOT(on_btnui_clickedMyself()));//使用Lambda表达式QObject::connect(ui->btnLambda,&QPushButton::clicked,[=](){std::cout<<"Lambda clicked"<<std::endl;});//使用函数指针QObject::connect(ui->btnPointer,&QPushButton::clicked,this,&Widget::on_Pointer_clicked);
}Widget::~Widget()
{delete ui;
}//使用UiDesigner
void Widget::on_btnui_clicked()
{std::cout<<"UiDesigner clicked"<<std::endl;
}void Widget::on_btnui_clickedMyself()
{std::cout<<"ObjCon clicked"<<std::endl;
}void Widget::on_Pointer_clicked()
{std::cout<<"Pointer clicked"<<std::endl;
}
Widget.h
#ifndef WIDGET_H
#define WIDGET_H#include <QWidget>QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACEclass Widget : public QWidget
{Q_OBJECTpublic:Widget(QWidget *parent = nullptr);~Widget();private slots:void on_btnui_clicked();void on_btnui_clickedMyself();void on_Pointer_clicked();
private:Ui::Widget *ui;
};
#endif // WIDGET_H
第44讲 自定义信号与槽
介绍
步骤
class Widget : public QWidget
{Q_OBJECTpublic:Widget(QWidget *parent = nullptr);~Widget();signals:void mySignal();void mySignalParams(int value);private:Ui::Widget *ui;
};
②定义槽:槽可以是任何普通的成员函数,但通常在类定义中用 slots 关键字标识。槽可以有返回类型,也可以接受参数,但它们的参数类型需要与发出信号的参数类型匹配。例如:
class Widget : public QWidget
{Q_OBJECTpublic:Widget(QWidget *parent = nullptr);~Widget();signals:void mySignal();void mySignalParams(int value);private slots:void mySlot();void mySlotParams(int value);private:Ui::Widget *ui;
};
③连接信号与槽
使用静态成员函数 QObject::connect 函数将信号与槽连接起来。当信号被发射时,连接到这个信号的槽将被调用。示例如下:
Widget::Widget(QWidget *parent): QWidget(parent),ui(new Ui::Widget)
{ui->setupUi(this);connect(this,SIGNAL(mySignal()),this,SLOT(mySlot()));connect(this,SIGNAL(mySignalParams(int y)),this,SLOT(mySlotParams(int x)));
}
注意:SIGNAL(mySignal())以及SLOT(mySlot())是QT环境中的特有语法,SIGNAL()是一个宏,作用是将括号内包含的函数声明为信号,SLOT()也是同理。
语法形式:SIGNAL()
宏接受一个函数签名作为参数,这里的函数签名就是mySignal()
,它表示定义了一个名为mySignal
且无参数的信号。在实际使用中,这个函数并不需要在类中实现具体的函数体内容(因为它只是一个信号声明,其实现由 Qt 的元对象系统在背后处理)。
原理:当在connect
语句中使用SIGNAL(mySignal())
时,Qt 的元对象编译器(MOC)会在编译阶段对其进行处理。MOC 会分析代码中的信号和槽的声明及连接关系,生成相应的代码来实现信号的发射和槽的调用机制。当对象的状态发生变化等情况需要发出信号时(如在代码中通过emit mySignal();
来发射信号),Qt 的元对象系统就会根据之前connect
建立的连接关系,找到并调用与之关联的槽函数。
④发射信号
使用emit关键字,语法:emit 函数名称 ();
Widget::Widget(QWidget *parent): QWidget(parent),ui(new Ui::Widget)
{ui->setupUi(this);connect(this,SIGNAL(mySignal()),this,SLOT(mySlot()));connect(this,SIGNAL(mySignalParams(int y)),this,SLOT(mySlotParams(int x)));emit mySignal();emit mySlotParams(100);
}
知识补充
#include <QDebug>
void Widget::mySlotParams(int value)
{qDebug()<<"SlotParams:"<<value;
}