前言
先来解释下标题:设计器pk手码……
设计器:是指Qt Designer,就是在Qt开发过程中,部分界面或全部界面采用Designer的拖拽完成,然后功能逻辑通过手敲代码完成,俗称“拖控件”,常见于MFC这类软件设计方式。
手码:就是在Qt开发过程中,不使用Designer,所有界面和功能逻辑都是手敲代码完成,手速快的,看起来比较牛掰。
解释完,突然发现,就这样结束似乎也没什么问题,上面就是二者的区别,还有啥好pk的呢?
pk之前,作者也仿照别人来一个心路历程好了。
- 作者以前是学C语言的,Qt是在自学嵌入式Linux时接触的。因为没学过C++,突然搞起来Qt,还是很有难度的(本来水平就菜),那时候霍亚飞还没出《Qt Creator快速入门》这本书,作者寒假中根据他的博客开始学起来的,不过也都是一知半解,能看懂但自己不会写,只会抄别人的然后修改之。
- 这本书后来作者也没看过,博客里面的细节也早都忘得一干二净了,但是有一点,多年之后,再接触Qt时,作者还可以通过Designer(主要根据博客中学了Designer,且以前也糊里糊涂搞过MFC)来拖出来一些类似于“Hello World”水平的界面,所有功能都在一个叫mainwindow.cpp的超级文件中完成。
- 工作后,再次接触Qt是看到前辈给的部分代码,老板让借鉴这个残本开发一个功能,那里面没有ui文件,我该怎么拖拽我的控件呀?当时反复折腾一个串口的属性配置界面(波特率,数据位、校验位……),通过Designer一会就拖完了,但用手写要写好久(打字又不快,控件又不熟悉,编码水平又水),手写尽管痛苦,但是作者已经开始学习怎么拆分mainwindow.cpp这个Super File了,功能最后没完成 ……苦涩的沙吹痛脸庞的感觉
- 多年后,又接触了Qt,尽管水平还是依菜如故,但是,这次不再使用Designer了,已经全部通过手敲代码来完成,彻底算是抛弃Designer了。
读者看过上面的描述,可能会有疑虑:你也没怎么用过Desinger,你哪来勇气要将Designer和手码进行pk,你懂Designer的好吗?
确实,作者后来项目上再也没用过Designer,它的优美和高效作者也没有体会到,作者也怕写这篇文章会被那些Designer控喷……先不管Designer是为了高效和易学易用,还是对标其他语言的“拖控件”,作者不是要诋毁它,或者说不让人去用它,作者想将设计器和手码两种方式做一个梳理和比较,也许能给看到这篇文章的读者一些思路……
声音
关于Qt开发到底该不该用Designer,其实互联网上也有很多声音,大底如下:
- 初学的话,还是手写代码,可以了解其中的机制,以后用Designer也会得心应手。
- 机制是假的,能弄出来才是真。
- 初学的话,还是用Desinger,能够快速拖拽出来,成就感满满。
- 觉得Designer设计的又快又好,那就用它;如果觉得Designer是个累赘,还不如手写来的快,那就手码。
- 还是自己手写代码,那个不爽。
- Designer就是香。
- 两者本质上是一样的,可以先用Designer设计个大概,不满意再手动修改。
- 既然提供了Designer,说明有一定优势,多练习,慢慢体会两者的优缺点,最后达到灵活的在两者之间进行取舍和复合应用。
- 如果用到大量布局管理器,建议手写。
- 简单的默认样式用Designer当然快,但如果还要进行各种详细界面设置还是写代码方便。
- 不用纠结这种问题,对于一个大型的项目,必然有很多自定义的widget,这些widget中有些纯代码方便(比如绘制全新的控件),有些Designer方便(比如组合现有的控件得到新的控件)。怎么方便怎么写就行了。
- 如果你需要的界面控件都是相对标准,那么直接用designer;如果你需要的界面相对复杂,但主界面相对标准,用designer,个别自定义控件用手写。如果你需要的界面大框架就是自定义控件为主,那就手写。原则上能用designer就没什么必要手写,当然手写是很必要的辅助手段。所以不在于项目大小的问题,更多的在于你使用的整体界面风格。
- 界面用Designer,逻辑手写,UI和逻辑分离。
- 建议用designer ,当然有些情况下必须用代码。
- 感觉直接拖会方便很多… 全自己写的话,布局感觉好麻烦,也不能马上看到效果。
- 用代码写的,比较自由吧,缺点就是比较烦。
- 用Designer的ui直接绘制吧,为什么要写代码呢?能省事就省吧。
- ……
上面都是用户使用过程中体会到的两者区别,再来看看官方是怎么说的:
特点如下:
- 所见即所得的方式编写和定义窗口或对话框,并可以使用不同的样式和分辨率对其进行测试。
- 使用Qt的信号和插槽机制,Designer创建的小部件和表单可以与编程代码无缝集成,因此您可以轻松地将行为分配给图形元素。
- 在Designer中设置的所有属性都可以在代码中动态更改。
- 可以将自定义的组件通过插件方式集成到Designer。
好了,作者试着总结一下这些声音:
- Designer所见即所得;手码靠空间想象。
- Designer设计标准、层级简单的界面方便;手码灵活多变,写起来麻烦。
- Designer设计界面,手码写逻辑,UI和逻辑分离。
暂时先不论这些声音的对错,下面用一个简单的例子看看真实情况是什么样的。
论证
Designer可以设计很复杂的界面,如果一个界面元素太多,可以分拆到不同的ui文件中完成,就是不必在一个ui文件(超级文件)上吊死。
Designer中有那么多控件,一个例子要多复杂才可以把它们拖拽清楚呢?还好,这里不是讲解Designer的使用教程,只需要通过一个小小的例子来探探其中的道道即可。
例子:实现一个窗口类MainWindow,其包含两个QPushButton,一个QLabel,一个QTextBrowser和多个Spacer,两个QPushButton文本分别显示“设计器”和“手码”,用户单击哪个按钮哪方就胜利,同时会改变显示“PK”的QLabel的底色。
主要代码如下:
- mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
void on_pushButton_clicked();
void on_pushButton_2_clicked();
private:
Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H
- mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
}
MainWindow::~MainWindow()
{
delete ui;
}
// designer
void MainWindow::on_pushButton_clicked()
{
ui->label->setStyleSheet(QString::fromUtf8("background-color: rgb(0, 255, 0);\n"
"color: rgb(255, 255, 255);"));
}
// manual
void MainWindow::on_pushButton_2_clicked()
{
ui->label->setStyleSheet(QString::fromUtf8("background-color: rgb(0, 0, 255);\n"
"color: rgb(255, 255, 255);"));
}
- ui_mainwindow.h
/********************************************************************************
** Form generated from reading UI file 'mainwindow.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_MAINWINDOW_H
#define UI_MAINWINDOW_H
#include <QtCore/QVariant>
#include <QtWidgets/QApplication>
#include <QtWidgets/QHBoxLayout>
#include <QtWidgets/QLabel>
#include <QtWidgets/QMainWindow>
#include <QtWidgets/QMenuBar>
#include <QtWidgets/QPushButton>
#include <QtWidgets/QSpacerItem>
#include <QtWidgets/QStatusBar>
#include <QtWidgets/QTextBrowser>
#include <QtWidgets/QVBoxLayout>
#include <QtWidgets/QWidget>
QT_BEGIN_NAMESPACE
class Ui_MainWindow
{
public:
QWidget *centralwidget;
QVBoxLayout *verticalLayout;
QSpacerItem *verticalSpacer_2;
QHBoxLayout *horizontalLayout_2;
QSpacerItem *horizontalSpacer;
QHBoxLayout *horizontalLayout;
QPushButton *pushButton;
QLabel *label;
QPushButton *pushButton_2;
QSpacerItem *horizontalSpacer_2;
QTextBrowser *textBrowser;
QSpacerItem *verticalSpacer;
QMenuBar *menubar;
QStatusBar *statusbar;
void setupUi(QMainWindow *MainWindow)
{
if (MainWindow->objectName().isEmpty())
MainWindow->setObjectName(QString::fromUtf8("MainWindow"));
MainWindow->resize(800, 600);
centralwidget = new QWidget(MainWindow);
centralwidget->setObjectName(QString::fromUtf8("centralwidget"));
verticalLayout = new QVBoxLayout(centralwidget);
verticalLayout->setObjectName(QString::fromUtf8("verticalLayout"));
verticalSpacer_2 = new QSpacerItem(20, 149, QSizePolicy::Minimum, QSizePolicy::Expanding);
verticalLayout->addItem(verticalSpacer_2);
horizontalLayout_2 = new QHBoxLayout();
horizontalLayout_2->setObjectName(QString::fromUtf8("horizontalLayout_2"));
horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);
horizontalLayout_2->addItem(horizontalSpacer);
horizontalLayout = new QHBoxLayout();
horizontalLayout->setObjectName(QString::fromUtf8("horizontalLayout"));
pushButton = new QPushButton(centralwidget);
pushButton->setObjectName(QString::fromUtf8("pushButton"));
horizontalLayout->addWidget(pushButton);
label = new QLabel(centralwidget);
label->setObjectName(QString::fromUtf8("label"));
label->setStyleSheet(QString::fromUtf8("background-color: rgb(255, 0, 0);\n"
"color: rgb(255, 255, 255);"));
label->setAlignment(Qt::AlignCenter);
horizontalLayout->addWidget(label);
pushButton_2 = new QPushButton(centralwidget);
pushButton_2->setObjectName(QString::fromUtf8("pushButton_2"));
horizontalLayout->addWidget(pushButton_2);
horizontalLayout_2->addLayout(horizontalLayout);
horizontalSpacer_2 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);
horizontalLayout_2->addItem(horizontalSpacer_2);
verticalLayout->addLayout(horizontalLayout_2);
textBrowser = new QTextBrowser(centralwidget);
textBrowser->setObjectName(QString::fromUtf8("textBrowser"));
verticalLayout->addWidget(textBrowser);
verticalSpacer = new QSpacerItem(20, 149, QSizePolicy::Minimum, QSizePolicy::Expanding);
verticalLayout->addItem(verticalSpacer);
MainWindow->setCentralWidget(centralwidget);
menubar = new QMenuBar(MainWindow);
menubar->setObjectName(QString::fromUtf8("menubar"));
menubar->setGeometry(QRect(0, 0, 800, 22));
MainWindow->setMenuBar(menubar);
statusbar = new QStatusBar(MainWindow);
statusbar->setObjectName(QString::fromUtf8("statusbar"));
MainWindow->setStatusBar(statusbar);
retranslateUi(MainWindow);
QMetaObject::connectSlotsByName(MainWindow);
} // setupUi
void retranslateUi(QMainWindow *MainWindow)
{
MainWindow->setWindowTitle(QCoreApplication::translate("MainWindow", "MainWindow", nullptr));
pushButton->setText(QCoreApplication::translate("MainWindow", "\350\256\276\350\256\241\345\231\250", nullptr));
label->setText(QCoreApplication::translate("MainWindow", "PK", nullptr));
pushButton_2->setText(QCoreApplication::translate("MainWindow", "\346\211\213\347\240\201", nullptr));
textBrowser->setHtml(QCoreApplication::translate("MainWindow", "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" \"http://www.w3.org/TR/REC-html40/strict.dtd\">\n"
"<html><head><meta name=\"qrichtext\" content=\"1\" /><style type=\"text/css\">\n"
"p, li { white-space: pre-wrap; }\n"
"</style></head><body style=\" font-family:'SimSun'; font-size:9pt; font-weight:400; font-style:normal;\">\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">PK\345\272\225\350\211\262\350\257\264\346\230\216\357\274\232</p>\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">\347\272\242\350\211\262\357\274\232\350\241\200\346\210\230\344\270\255</p>\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">\347\273\277\350\211\262\357\274\232\350\256\276\350\256\241\345\231\250\350\203\234</p>\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -q"
"t-block-indent:0; text-indent:0px;\">\350\223\235\350\211\262\357\274\232\346\211\213\347\240\201\350\203\234</p></body></html>", nullptr));
} // retranslateUi
};
namespace Ui {
class MainWindow: public Ui_MainWindow {};
} // namespace Ui
QT_END_NAMESPACE
#endif // UI_MAINWINDOW_H
看破
- 看:头文件中声明了
namespace Ui { class MainWindow; }
,并定义了私有成员变量Ui::MainWindow *ui;
源文件中包含了#include "ui_mainwindow.h"
,这个头文件哪里来的?
破:ui_mainwindow.h头文件是根据mainwindow.ui生成的,mainwindow.ui实际上是一个xml文件,里面定义了控件的一些信息,qmake编译过程,会调用一个uic工具,会将xxx_ui文件编译成ui_xxx.h头文件。 - 看:头文件中有两个按钮的槽函数,除了命名奇怪外,单击按钮是怎么将信号关联到槽的呢?
破:pushButton和pushButton_2实际上是两个按钮的对象名,可以在designer界面进行有意义的命名,此处为了演示就比较随意。至于如何关联到槽的,注意观察ui_mainwindow.h中的这句QMetaObject::connectSlotsByName(MainWindow);
,根据名称连接槽,官方介绍如下:
效果如下:
例子也看完了,是时候总结一下了:
- Designer适合初学者吗?
作者认为是适合的,你看Designer的界面,左侧是基础的控件集合,上侧是信号、布局等操作,右上侧是整个界面元素树,右下侧是不同控件的属性,中心是设计区……右键中心区中的控件,还可以快速设置样式、关联槽、提升(promote)类等,这多适合初学者全面快速了解Qt Widgets的类呀! - Designer高效吗?
中心区在布局的时候,所见即所得,很爽,即便有时需要打破布局重新布,也是动动鼠标分分钟解决,但如果元素特别多,会稍微麻烦点,有时候可能牵一发而动全身。 - Designer真的做到UI和逻辑分离了吗?
我们看到ui_xxx.h头文件中,Ui类中定义了一堆开放的成员变量,然后在setupUi接口中进行创建,只要Designer支持控件的操作,都可以在setupUi中自动生成,但对于那些自己设计的类呢?那些非标准的接口怎么设置?只能在ui文件之外单独动态的设置,这相当于初始化被强行分在了不同地方,实际上破坏了整体,而且手码就不能做到逻辑分离吗?手码一切!!况且这里都是属于界面,有什么要分离的呢。 - Designer中控件命名问题
如果不对这些控件进行有意义的命名,在ui外部获取成员变量的时候会很头疼,常常记不住名字,通过图形化的方式来命名不一定比手码快。控件命名和对象名被强绑成一样的,不知道是不是可以解绑。 - Designer的ui文件不利于阅读
ui文件是xml文件,不利于阅读,需要先编译生成ui_xxx.h头文件才行,不过拿来先编译,似乎是必须的流程,无图无真相,谁上来还看代码。 - Designer的ui文件编辑受限制
ui文件需要依赖Designer来编辑,对于一些环境上没有Designer的比较费事,当然你如果不装Designer的话,可以直接修改ui文件(靠本事吃饭),毕竟它本质是xml文件。 - Designer的模块化设计怎么样?
通常开发过程中,一个界面布局可能由多个部分组成,每个部分都可以设计成一个类,模块化设计,维护和复用等都方便。采用ui文件进行设计,如果把所有元素都布局在一起,当需要复用某一部分的时候,就有点不方便了,如果也采用模块化设计,可能需要提升类或多ui文件的方式……设计自己的类是常有的事,用提升来兼容Designer是不是显得多余…… - ……
上面总结的,是作者个人以为的,难免有的观点会片面,因为作者确实对Designer不是特别熟。
后语
为什么有人会觉得只会“拖控件”的开发是比较Low的?如果你只会拖控件,可能你没有参与过稍大点的项目,可能你不经常设计自己的类,可能你编码能力偏弱,可能你只是初学者,可能你很菜……
个人觉得,要尽快摆脱“拖控件”的束缚,你看那qt-creator的代码,你看那QtitanRibbon的代码,你看那qtcanpool的代码,基本上没有ui文件,所以,你懂得……