3.1 Qt Designer的使用与技巧
虽然在之前介绍了如何用Qt UI Class创建一个Qt的项目,但其中最为重要的部分——如何高效的对.ui文件进行可视化的编辑并没有进行介绍,将会在本篇介绍。
我们已经知道,利用UIC工具,编译器可以轻易的将UI文件转化成C++能够识别的文件。以及UI文件是一个XML格式的文件。以及上篇画的大饼,也是这一篇要重点讲的工具,可以高效的编辑UI文件的Qt Designer。
让我们从最简单的开始!
首先先按照上一篇的内容创建一个空窗口Qt UI Class 这就是一个简单的空窗口而已。(虽然不见得很重要,但是创建时继承的基类是QWidget)
然后我们找到example_three_widget.ui文件,右键找到我们的工具—Qt Designer
找不到这个工具?检查一下安装过程是否有遗漏
然后我们将会得到这样一个界面
试着向其中加入一个Label和一个Push Button。
可以在Property Editor中的QLabel栏中更改Label文本的字体。在这个栏目的标签中可以看到整个类结构,非常适合初学者去熟悉Qt中每一个件的结构和功能。
另外在右上角的inspecter中可以在第一列修改每个件在代码层面的名字。
然后我们可以在右下角的Signal/Slot Editor中加入一个信号与槽的关系。
意思也比较好理解,当MyPushButton的clicked()信号被触发的时候(就是close按钮时),会让整个Widgets触发close()槽函数(字面意思,就会关闭窗口)
然后保存按钮或者⌘+s保存。
退出运行一下就可以看到我们刚刚设计的窗口了。
然后单击close按钮就可以关闭窗口。
接下来我们就去探索一下这个ui文件究竟修改了我们代码文件的什么部分。
实际上稍微比对一下刚创建的example_three_widget.h, example_three_widget.cpp, 就能轻易发现,这两个c++代码其实没有变化,example_three_widget.ui文件确实有变化,但是我们知道C语言是不识别.ui文件的,甚至等程序可以成功运行以后删除UI文件,都可以继续运行,所以这里不是出现的代码变化的部分。
那其实发生改变的正是UIC处理后生成的文件,ui_example_three_widget.h:
/ Form generated from reading UI file 'example_three_widget.ui' Created by: Qt User Interface Compiler version 5.15.2 WARNING! All changes made in this file will be lost when recompiling UI file! / #ifndef UI_EXAMPLE_THREE_WIDGET_H #define UI_EXAMPLE_THREE_WIDGET_H #include <QtCore/QVariant> #include <QtWidgets/QApplication> #include <QtWidgets/QLabel> #include <QtWidgets/QPushButton> #include <QtWidgets/QWidget> namespace Exp3Widget {
class Ui_ExampleThreeWidget {
public: QLabel *MyLabel; QPushButton *MyPushButton; void setupUi(QWidget *Exp3Widget__ExampleThreeWidget) {
if (Exp3Widget__ExampleThreeWidget->objectName().isEmpty()) Exp3Widget__ExampleThreeWidget->setObjectName(QString::fromUtf8("Exp3Widget__ExampleThreeWidget")); Exp3Widget__ExampleThreeWidget->resize(303, 189); MyLabel = new QLabel(Exp3Widget__ExampleThreeWidget); MyLabel->setObjectName(QString::fromUtf8("MyLabel")); MyLabel->setGeometry(QRect(50, 30, 181, 91)); MyPushButton = new QPushButton(Exp3Widget__ExampleThreeWidget); MyPushButton->setObjectName(QString::fromUtf8("MyPushButton")); MyPushButton->setGeometry(QRect(170, 130, 113, 32)); retranslateUi(Exp3Widget__ExampleThreeWidget); QObject::connect(MyPushButton, SIGNAL(clicked()), Exp3Widget__ExampleThreeWidget, SLOT(close())); QMetaObject::connectSlotsByName(Exp3Widget__ExampleThreeWidget); } // setupUi void retranslateUi(QWidget *Exp3Widget__ExampleThreeWidget) {
Exp3Widget__ExampleThreeWidget->setWindowTitle(QCoreApplication::translate("Exp3Widget::ExampleThreeWidget", "ExampleThreeWidget", nullptr)); MyLabel->setText(QCoreApplication::translate("Exp3Widget::ExampleThreeWidget", "<html><head/><body><p><span style=\" font-size:36pt;\">Hello, world</span></p></body></html>", nullptr)); MyPushButton->setText(QCoreApplication::translate("Exp3Widget::ExampleThreeWidget", "Close", nullptr)); } // retranslateUi }; } // namespace Exp3Widget namespace Exp3Widget {
namespace Ui {
class ExampleThreeWidget: public Ui_ExampleThreeWidget {
}; } // namespace Ui } // namespace Exp3Widget #endif // UI_EXAMPLE_THREE_WIDGET_H
以下为对比:
自动生成ui_my_qt_dialog_second.h 文件的分析
/ Form generated from reading UI file 'my_qt_dialog_second.ui' Created by: Qt User Interface Compiler version 5.15.2 WARNING! All changes made in this file will be lost when recompiling UI file! / #ifndef UI_MY_QT_DIALOG_SECOND_H #define UI_MY_QT_DIALOG_SECOND_H #include <QtCore/QVariant> #include <QtWidgets/QApplication> #include <QtWidgets/QDialog> namespace MyQtSecond {
class Ui_MyQtDialogSecond {
public: void setupUi(QDialog *MyQtSecond__MyQtDialogSecond) {
if (MyQtSecond__MyQtDialogSecond->objectName().isEmpty()) MyQtSecond__MyQtDialogSecond->setObjectName(QString::fromUtf8("MyQtSecond__MyQtDialogSecond")); MyQtSecond__MyQtDialogSecond->resize(320, 195); retranslateUi(MyQtSecond__MyQtDialogSecond); QMetaObject::connectSlotsByName(MyQtSecond__MyQtDialogSecond); } // setupUi void retranslateUi(QDialog *MyQtSecond__MyQtDialogSecond) {
MyQtSecond__MyQtDialogSecond->setWindowTitle(QCoreApplication::translate("MyQtSecond::MyQtDialogSecond", "MyQtDialogSecond", nullptr)); } // retranslateUi }; } // namespace MyQtSecond namespace MyQtSecond {
namespace Ui {
class MyQtDialogSecond: public Ui_MyQtDialogSecond {
}; } // namespace Ui } // namespace MyQtSecond #endif // UI_MY_QT_DIALOG_SECOND_H
可以看到首先在public部分生成了各个组件的类成员变量定义。
然后再setupUi函数中增加了对组件位置大小等属性进行设置的内容。同时也能看到,所有界面上能够看到的文本是在一个retranslateUi中统一进行初始化的,字面意思这个函数是用来帮助构建多语言界面的翻译函数,不按照这个规则去写程序也能正常运行。
在这之后就是最关键的关于信号与槽的链接的语句。
QObject::connect(MyPushButton, SIGNAL(clicked()), Exp3Widget__ExampleThreeWidget, SLOT(close())); QMetaObject::connectSlotsByName(Exp3Widget__ExampleThreeWidget);
第一句很轻易就能对应上,这和我们在GUI界面中进行设置的内容是一致的。实现一个单击按钮造成界面关闭的效果的语句。
第二句则是设置槽函数的关联方式,适用于将UI设计器自动生成的组件信号的槽函数与组件信号相关联的。在这个例子中没有决定性的作用。而且我也并不是很希望各位使用这个语句的特性去做实现,能写进代码里就写进去吧,多不了几行。
connectSlotsByName(-)函数做了什么
更进一步:定义自己的槽函数/更好的界面设计
在上文中,我们使用可视化的手段建立了一个信号与槽的链接,其中关键语句如下:
QObject::connect(MyPushButton, SIGNAL(clicked()), Exp3Widget__ExampleThreeWidget, SLOT(close()));
但是其中调用的函数都是Qt帮我们实现好了的,难道我们不能自己准备一套自己的信号与槽吗,自由的Qt当然是有这样的方法的(甚至在第2篇纯代码编程中就已经能看到了)。我们在之前的项目的基础上继续操作。
继续用Qt Designer建立一组复选框(QCheckBox)和一组单选框(QRadioButton),将原来的标签换成文本输入框(QPlainTextEdit)。为了简洁美观可以使用布局组件帮助布局。
注:一定记得在Object Inspector修改控件名称,才能在后续跟进新的代码的时候能够有较好的编写体验。
布局组件/Designer界面工具栏介绍
保存后打开我们的example_three_widget.h和example_three_widget.cpp文件,用代码直接定义我们的槽函数以及控制信号与槽函数的链接。此处可以注意一下和纯代码编程有什么区别。
// // example_three_widget.h // #ifndef EXAMPLE3_1_EXAMPLE_THREE_WIDGET_H #define EXAMPLE3_1_EXAMPLE_THREE_WIDGET_H #include <QWidget> namespace Exp3Widget {
QT_BEGIN_NAMESPACE namespace Ui {
class ExampleThreeWidget; } QT_END_NAMESPACE class ExampleThreeWidget : public QWidget {
Q_OBJECT public: explicit ExampleThreeWidget(QWidget *parent = nullptr); ~ExampleThreeWidget() override; private: Ui::ExampleThreeWidget *ui; void MySetUpUi(); private slots: void on_setTextFontColor(); void on_setUnderline(bool); void on_setItalic(bool); void on_setBold(bool); }; } // Exp3Widget #endif //EXAMPLE3_1_EXAMPLE_THREE_WIDGET_H
该头文件只添加了四个槽函数,和一个私有的函数MySetUpUi()。
在cpp文件中除了进行了这几个函数的实现,还修改了构造函数来在合适的时候运行MySetUpUi()。具体如下:
// // example_three_widget.cpp // // You may need to build the project (run Qt uic code generator) to get "ui_example_three_widget.h" resolved #include "example_three_widget.h" #include "ui_example_three_widget.h" namespace Exp3Widget {
ExampleThreeWidget::ExampleThreeWidget(QWidget *parent) : QWidget(parent), ui(new Ui::ExampleThreeWidget) {
ui->setupUi(this); MySetUpUi(); } ExampleThreeWidget::~ExampleThreeWidget() {
delete ui; } void ExampleThreeWidget::MySetUpUi() {
ui->TxtEdit->setStyleSheet("background-color:gray;");//这个在可视化界面中是可以做到的 connect(ui->BtnBlack, SIGNAL(clicked()),this, SLOT(on_setTextFontColor())); connect(ui->BtnBlue, SIGNAL(clicked()),this, SLOT(on_setTextFontColor())); connect(ui->BtnRed, SIGNAL(clicked()),this, SLOT(on_setTextFontColor())); connect(ui->CheckBoxUnderline,SIGNAL(clicked(bool)), this,SLOT(on_setUnderline(bool))); connect(ui->CheckBoxItalic,SIGNAL(clicked(bool)), this,SLOT(on_setItalic(bool))); connect(ui->CheckBoxBold,SIGNAL(clicked(bool)), this,SLOT(on_setBold(bool))); } void ExampleThreeWidget::on_setTextFontColor() {
QPalette plet=ui->TxtEdit->palette(); if(ui->BtnBlack->isChecked()) plet.setColor(QPalette::Text,Qt::black); else if (ui->BtnRed->isChecked()) plet.setColor(QPalette::Text,Qt::red); else if (ui->BtnBlue->isChecked()) plet.setColor(QPalette::Text,Qt::blue); else plet.setColor(QPalette::Text,Qt::darkGray); ui->TxtEdit->setPalette(plet); } void ExampleThreeWidget::on_setUnderline(bool checked) {
QFont font=ui->TxtEdit->font(); font.setUnderline(checked); ui->TxtEdit->setFont(font); } void ExampleThreeWidget::on_setItalic(bool checked) {
QFont font=ui->TxtEdit->font(); font.setItalic(checked); ui->TxtEdit->setFont(font); } void ExampleThreeWidget::on_setBold(bool checked) {
QFont font=ui->TxtEdit->font(); font.setBold(checked); ui->TxtEdit->setFont(font); } } // Exp3Widget
如果与纯代码进行比对会发现,在这份代码中,窗口上的空间的获取需要使用ui→才能实现
此外也看到信号与槽函数对应关系的一部分—一个槽函数可以对应多个信号。此外在实现中插入了一句ui->TxtEdit->setStyleSheet("background-color:gray;");
用来控制文本输入框的背景颜色,这在Designer中其实是无法直接实现的,也体现出了一些Designer的局限性。
小结一下,利用Designer进行程序设计可以很高效的进行界面的布局和一些属性的更改,但是其包含的设置方法相比纯程序实现要少得多,两者结合可以很高效的实现编程过程。
3.2 信号与槽
讲了这么多,总会发现在Qt的编程过程中,各个功能的实现几乎都离不开信号与槽。这也是Qt的一大创新,正是因为有了信号与槽的机制,在Qt中处理哥哥界面的交互操作时也变得更加直观和简单。
信号:指在特定情况下被发射的事件,很多组件都有预设的信号,比如PushButton中鼠标单击时会发射的信号clicked(),信号是可以自己设定的:
(可以在包括或继承自QObject的类中使用如右图的方法定义信号。)
当一个信号被发射时,与其相关联的槽将被立刻执行,就像一个正常的函数调用一样。
signals: void mySignal(); void mySignal(int x); void mySignalParam(int x,int y);
信号一般没有函数体定义,信号的返回类型都是 void。
槽:是对信号相应的函数,槽函数与一般的C++函数是一样的,可以定义在含有或者继承自Qobject的类的人一部分,可以具有任意参数,也可以直接被调用。他与一般函数唯一的不同就是槽函数可以与一个信号关联,当信号发射时,槽函数会被自动执行。
public slots: void mySlot(); void mySlot(int x); void mySignalParam(int x,int y);
信号和槽能携带任意数量和任意类型的参数,他们是类型完全安全的。
信号与槽的关联是通过QObject::connect这一静态函数实现的,其函数原型如下:
//新版本的格式 C++11之后可用 QObject::connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type = Qt::AutoCompatConnection) //基本原型 基于C++98 前文的程序中都用这个 connect(sender, SIGNAL(valueChanged(QString, QString)), receiver, SLOT(updateValue(QString)), Qt::ConnectionType type = Qt::AutoCompatConnection);
关于connect的新写法
两个原型对应位置参数的意义都是一样的,只是其具体写法不同。
先说前面四个参数,第一个是发出信号的对象,第二个是发送对象发出的信号,第三个是接收信号的对象,第四个是接收对象在接收到信号之后所需要调用的函数。也就是说,当 sender 发出了 signal 信号之后,会自动调用 receiver 的 slot 函数。如果发射者与接收者属于同一个对象的话,那么在 connect 调用中接收者参数(也就是第三个参数)可以省略。此外信号也可以与信号相连,在发射者信号发送的时候接受对象(虽然是信号)也会发送,但除非是为了代码整洁好看,不然显得有点多余。
然后是第五个参数Qt::ConnectionType, 他是一个枚举类型:
(1) Qt::DirectConnection :信号发送后立即传递给相关联的槽函数,只有槽函数执行完毕返回后,发送信号"emit <信号>" 之后的代码才被执行
(2) Qt::QueuedConnection : 信号发送后排队,直到事件循环(event)有能力将它传递给槽; 而不管槽函数有没有执行,发送信号"emit <信号>" 之后的代码都会立即得到执行
(3) Qt::AutoConnection : 如果信号和槽函数在同一线程, 信号发出后,槽函数将立即执行, 等于Qt::DirectConnection; 如果信号和槽不在同一个线程,信号将排队,等待事件循环的处理,效果等同于Qt::QueuedConnection
信号可以连接,自然也可以断开,初学可以不了解→
断开连接disconnect
在此之前所有的信号都是使用的Qt定义过的信号,如果想要发射自己的信号,可以使用emit 关键字加对应的信号的函数(及其参数)
信号与槽的使用有一些规则:
- 一个信号可以连接多个槽,信号发射时,各个槽的激活顺序是随机的
- 多个信号可以连接到一个槽,比如之前历程中的改变颜色的实现方法
- 一个信号可以连接另一个信号,这样当一个信号发射时,也会发射另一个信号,来实现某些功能(请自行开脑洞)
- 严格的情况下,信号与槽的个数和类型需要保持一致,至少信号的参数不能少于槽的参数。若不匹配会出现编译或运行错误
- 在使用信号和槽的类中,必须加入Q_OBJECT宏
- 当一个信号被发射时,根据建立的链接的类型。如果是直接模式,直到与其关联的槽函数执行完毕后才会执行发射信号处后面的代码(就像中断一样)如果是排队模式,发射信号处后边的代码会忽略槽函数的执行情况继续执行。
- 宏定义不能用在 signal 和 slot 的参数中。信号与槽关键字的定义范围内最好只定义信号与槽。
总的来说,信号与槽是非常灵活好用的实现软件内信息传递的方法(相比于自己造轮子),后续的所有Qt程序也基本离不开信号与槽的使用(但凡你用到了GUI上的按钮…),信号与槽这部分是对之前代码的一种理论上的补充,所以也没有涉及太多代码。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://sigusoft.com/clion/3665.html