2. Designer

前面两章我们主要熟悉Qt工具的使用,上一章使用Qt Creator创建工程,这一章演示Qt Designer的使用。

2.1. Qt Designer

Qt Designer 是用于使用 Widget 设计和构建图形用户界面(GUI)的Qt工具。

Qt Designer 有如下优点:

  • 直接编辑窗口或对话框,并使用不同的样式和分辨率对其进行测试;

  • 方便使用Qt的信号和插槽机制,使用Qt Designer创建的 Widget 和 From 可以与编程代码无缝集成;

  • 在Qt Designer中设置的所有属性都可以在代码中动态更改;

  • 在Qt Designer可将窗口或控件提升为自定义组件。

Qt Creator在设计模式下自动在集成的Qt Designer中打开所有.ui文件,当然也可以在独立的Qt Designer中进行使用

打开UI文件,便会预览到如下内容,大致由6个部分组成:

designer002

控件窗口提供了一些标准的Qt窗口控件,布局和其他可用于在UI窗口上创建用户界面的对象。 每个类别的控件框都包含具有类似用途或相关功能的控件。

designerui002

UI窗口,我们可以直接在该窗口上进行UI设计。

designerui003

对象窗口可以查看所有UI窗口中的对象,包括对象和类,点击列表可选中对应的对象。

designerui004

属性窗口,可通过对属性进行设置使对象表现出我们想要的效果,比如设置QLabel文字大小,居中等等。

designerui005

模式和布局,通过模式切换使能UI窗口的不同功能,如UI编辑,信号与槽编辑,tab健的顺序设置, 除此以外还能快速对UI进行布局调整。

designerui006

Action和Signal的管理。

designerui007

2.2. Qt中的布局概念

布局用于安排和管理组成用户界面的元素。

Qt提供了许多类以自动处理布局QHBoxLayout,QVBoxLayout,QGridLayout和QFormLayout。 这些类解决了自动布局窗口小部件的难题,提供了行为可预测的用户界面。

每个Qt控件都有一个建议的大小,称为sizeHint()。布局管理器将尝试调整窗口控件的大小,以满足其大小提示。 在某些情况下,不需要具有其他大小。例如QLineEdit的高度始终是固定值,具体取决于字体大小和样式。 在其他情况下,您可能需要更改大小,例如QLineEdit的宽度或项目视图小部件的宽度和高度。 这是小部件尺寸约束-minimumSize和maximumSize约束起作用的地方。

这些值可以在属性编辑器中进行设置。例如,要覆盖默认的sizeHint(),只需设置minimumSize和maximumSize为相同的值。 或者,要将当前大小用作大小限制值,请从小部件的上下文菜单中选择”大小限制”选项之一。 然后,布局将确保满足这些约束。要通过代码控制小部件的大小,可以在代码中重新实现sizeHint()。

简而言之,把布局想象成一个尽可能缩小的盒子,尝试以整齐的方式挤压小部件,同时最大程度地利用可用空间。

Qt的布局可在以下情况提供帮助:

  • 调整用户界面的大小以适合不同的窗口大小。

  • 在用户界面中调整元素的大小以适合不同的本地化。

  • 安排元素以遵守不同平台的布局准则。

2.3. 手动添加UI文件

前面我们就已经知道.ui文件就是xml格式的文本文件,经过Qt Designer解析之后就成了我们看到的UI界面。 当我们对UI界面进行可视化的操作和修改之后,Qt Designer又将修改的结果保存为xml文件, 如果我们对.ui文件足够熟悉,可直接使用xml来编写界面。

下面是一个简单的dialog的ui文件,文本中记录了窗体名,类名,窗体长宽和标题栏要显示的字符。

embed_qt_develop_tutorial_code/Designer/dialog_xml.ui
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>Dialog</class>
 <widget class="QDialog" name="Dialog">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>400</width>
    <height>300</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>Dialog</string>
  </property>
 </widget>
 <resources/>
 <connections/>
</ui>

当我们在编译程序的时候,会使用到Qt的一个工具用户界面编译器(uic), 它负责将xml格式的代码编译转换成一个标准的C++类,下面的ui_dialog_xml.h就是由uic转换的结果。

embed_qt_develop_tutorial_code/build-Designer-xxx/ui_dialog_xml.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
39
40
41
42
43
44
45
46
/********************************************************************************
** Form generated from reading UI file 'dialog_xml.ui'
**
** Created by: Qt User Interface Compiler version 5.11.3
**
** WARNING! All changes made in this file will be lost when recompiling UI file!
********************************************************************************/

#ifndef UI_DIALOG_XML_H
#define UI_DIALOG_XML_H

#include <QtCore/QVariant>
#include <QtWidgets/QApplication>
#include <QtWidgets/QDialog>

QT_BEGIN_NAMESPACE

class Ui_Dialog
{
public:

    void setupUi(QDialog *Dialog)
    {
        if (Dialog->objectName().isEmpty())
            Dialog->setObjectName(QStringLiteral("Dialog"));
        Dialog->resize(400, 300);

        retranslateUi(Dialog);

        QMetaObject::connectSlotsByName(Dialog);
    } // setupUi

    void retranslateUi(QDialog *Dialog)
    {
        Dialog->setWindowTitle(QApplication::translate("Dialog", "Dialog", nullptr));
    } // retranslateUi

};

namespace Ui {
    class Dialog: public Ui_Dialog {};
} // namespace Ui

QT_END_NAMESPACE

#endif // UI_DIALOG_XML_H

我们要使用dialog_xml.ui这个类,在文件中引入即可;其写法如下:

embed_qt_develop_tutorial_code/Designer/dialog.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
#ifndef DIALOG_H
#define DIALOG_H

#include <QDialog>

namespace Ui {
class Dialog;
}

class Dialog : public QDialog
{
    Q_OBJECT

public:
    explicit Dialog(QWidget *parent = nullptr);
    ~Dialog();

...

private:
    Ui::Dialog *ui;
};

#endif // DIALOG_H

创建命名空间UI,避免和其他类产生冲突,声明 Ui::Dialog *ui 。

embed_qt_develop_tutorial_code/Designer/dialog.cpp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
#include "dialog.h"
#include "ui_dialog.h"

#include <QMessageBox>

Dialog::Dialog(QWidget *parent) :
    QDialog(parent),
    ui(new Ui::Dialog)
{
    ui->setupUi(this);
}

Dialog::~Dialog()
{
    delete ui;
}

...

引用头文件,默认初始化Dialog,调用setupUi设置UI,ui->setupUi(this)之后, 我们就可以在代码中通过 ui-> xxx 来控制UI中的对象(控件)了。

关于 ui_dialog.h 通常不会被放入我们的工程中,因为Ui文件随时都可能会被编辑, ui_dialog.h会自动被Designer自动修改,我们只需了解这一过程。 编译程序,预处理阶段,qmake会调用uic自动生成的界面类的头文件,默认存放在 build-xxx/目录下面。

我们也可以使用uic,手动将ui转换为.h。

uic xxx.ui -o ui_xxx.h

2.4. 工程管理

打开我们的工程目录我们会看到如下文件:

project001
  • .h .c++ 文件为头文件和源文件,我们的代码通常都是写在这两种文件中的

  • .ui 文件就是我们前面提到界面文件,其实质是xml格式的文本文件

  • .pro 文件就是这节要提到的工程管理文件,以qmake特有的语法记录了工程的文件和配置

  • .pri pri文件通常也用于工程管理,将代码整理成一个模块,通过引入pri文件来引入代码模块

  • .pro.user 为用户描述文件,记录了我们工程的开发环境构建环境等等,也是xml格式的文本文件

  • .qrc 除上面的文件,工程中一般还会有qrc资源文件,用于记录图片,音视频等文件

工程的目录和我们在 Qt Designer 中看到的工程结构不太一样, 是因为 Qt Designer 有对 pro 文件进行解析并按照其内容进行相关设置。

project002

使用文本编辑器打开Designer.pro文件

Qt项目管理文件示例代码
 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
#-------------------------------------------------
#
# Project created by QtCreator 2021-03-17T15:27:13
#
#-------------------------------------------------

QT       += core gui

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

TARGET = Designer
TEMPLATE = app

# The following define makes your compiler emit warnings if you use
# any feature of Qt which has been marked as 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 you use 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

CONFIG += c++11

SOURCES += \
        main.cpp

HEADERS +=

FORMS +=

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

DESTDIR         = $$PWD/../app_bin
MOC_DIR         = $$PWD/../build/designer
OBJECTS_DIR     = $$PWD/../build/designer
  • 首先工程会使用到Qt的一些特性,例如网络,多媒体等等,使用 Qt+= 添加,我们这里只使用到了Qt中Widgets

  • TARGET为目标名称,TEMPLATE用来标明最终被编译成app还是lib

  • DEFINES qmake将此变量的值添加为编译器C预处理程序宏

  • CONFIG 指定项目配置和编译器选项。这些值在内部由qmake识别,并具有特殊含义,c++11表示启用了C++11支持

  • SOURCES和HEADERS包含源文件和头文件;ROEMS为项目中使用到的资源文件

  • DESTDIR,指定放置目标文件的目录

  • MOC_DIR,指定应放置所有中间Moc文件的目录

  • OBJECTS_DIR,指定应放置所有中间对象的目录

除了上面提到的还有一些常用的:

  • include添加pri代码模块,可以理解为子项目。

  • INCLUDEPATH为链接的库的路径,LIBS为链接的库。

  • win32,unix用来区分不同平台。

在编译工程的时候,首先qmake工具会将pro文件转换成Makefile,再由编译工具编译。

当然呢,pro文件具备特定的使用方法,我们可以去Qt官方文档中查看 qmake Variables

2.5. 例程说明

野火提供的Qt Demo已经开源,仓库地址在:

文档所涉及的示例代码也提供下载,仓库地址在:

本章例程在 embed_qt_develop_tutorial_code/Designer

例程使用 Qt Designer 设计一个简单登录界面。

2.5.1. 编程思路

  • 手动创建Qt工程;

  • 创建一个Dialog的界面类;

  • 使用Qt Designer添加控件和布局;

  • 关联信号的槽。

2.5.2. 代码讲解

2.5.2.1. 手动创建工程

首先我们通过手动创建工程,来熟悉工程结构。

新建文件夹 Designer ,在文件夹下创建文件 Designer.pro 和 main.cpp

createproject001

打开Qt Creator,将 Designer.pro 文件拖动到Qt Creator中,再选择构建套件点击configure project。

createproject002

这个时候就会看到一个完全的空工程,复制上面 工程管理 中的 Qt项目管理文件示例代码 到 Designer.pro文件中并保存。

createproject003

保存之后,我们就会看到在左边的工程目录就会出现SOURCES目录,以及我们的main.cpp。

我们往main.cpp中添加下面的代码

#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    return a.exec();
}

点击编译,编译通过,并在我们前面创建的 Designer 目录的同级目录还会生成 app_bin 目录、build目录、以及 build-Designer-xxx-Debug,app_bin 就是存放编译的可执行文件的目录,当然也可以按照自己需求修改pro文件。

上面代码中,我们几乎什么都没做,所以可执行文件也没有什么现象。

接下来,我们向工程中添加一个对话框。鼠标右击目录树中任意一个文件,选择弹出菜单栏中的Add New。

createproject004

接着选择Qt,Qt设计师界面类,点击Choose。

createproject005

接着选择Dialog,添加Dialog对话框,

createproject006

会在工程下创建 dialog.h dialog.cpp 和 dialog.ui 等三个文件。

createproject007

Qt Creator会在pro文件中自动添加上面的三个文件。

createproject008

再次修改main.cpp如下

#include "dialog.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Dialog w;
    w.resize(800, 480);
    w.show();

    return a.exec();
}

编译程序,程序启动就会显示我们创建的空白对话框了。

2.5.2.2. 简单UI布局

点击ui文件,使用集成在Qt Creator中的Qt Designer中打开ui文件,从控件窗口中拖动QLabel,QLineEdit,QPushButton添加到主窗口。

designer002

选中控件的同时,对象窗口也会被选中,属性窗口切换到该控件的属性,

比如我们点击一个QLabel,

修改控件属性 ObjectName 为 label_title; 修改Font属性 字号20,加粗; 修改text属性 登录对话框; 修改alignment属性 垂直水平都居中。

效果如下图所示:

designer003

这时候编译程序,看到的效果就是我们主窗口中的效果(所见即所得)。

designer004

但是,当我们对窗口进行缩放的时候,就会发现问题——控件并不随窗体的改变而改变。

designer005

因此我们需要对控件、窗口进行布局。当我们选中多个控件时,上面工具栏中的布局按钮就会被激活。

designer006

常用的布局有四种:

  • 水平布局

designer007
  • 垂直布局

designer008
  • 表格布局

designer009
  • 栅格布局

designer010

下面就是我们布局之后的窗口以及编译之后的效果

designer011

2.5.2.3. 添加资源文件

首先点击QtCreator菜单栏文件,新建文件或项目,会弹出如下对话框:

source001

选择Qt以及Qt资源文件,并给资源文件命名,我给的名字为img。

source002

这时候我们工程目录结构会创建一个新目录Resources,目录下有我们的img.qrc,这在工程管理文件pro文件由写明 RESOURCES += img.qrc ,创建资源文件时,由QtCreator自动完成。 实际上工程所在目录下也会多出一个img.qrc文件。

鼠标右键点击工程目录结构下的img.qrc,会弹出一个菜单,菜单上有添加现有文件的选项,点击。

source003

这是会弹出文件对话框,选择我们要添加的资源文件,确认即可。

source004

这个时候,ima.qrc下面就会多出我们添加的资源文件,点击资源文件,左边便会预览资源文件。

source005

实际上,用记事本打开img.qrc,我们会看到如下内容:

embed_qt_develop_tutorial_code/Editor/img.qrc
1
2
3
4
5
6
<RCC>
    <qresource prefix="/">
        <file>img/passwd.png</file>
        <file>img/user.png</file>
    </qresource>
</RCC>

实际上这个文件就是XML格式的文本文件,记录着我们要添加的资源文件的相对位置。

接下来就在我们UI中使用我们添加的资源文件。

source006

当然也可以通过代码使用资源。

QPixmap map(":/img/passwd.png");
map.scaled(40,40);
ui->label_passwd->setPixmap(map);

使用资源文件的时候,直接调用资源文件的url或者path(鼠标右键资源文件,弹出的菜单栏有url或path选项)。 编译程序的时候,资源文件会作为程序的一部分被编译成二进制,所以在应用程序中访问资源文件是非常快的。

2.5.2.3.1. 野火App中的资源文件

在回顾我们野火demo,我们会发现野火demo使用到的资源文件略微有点不相同。 是的,野火demo的资源文件统一放在Skin子项目中,最终这些资源文件会被编译成库(libSkin.so或Skin.dll)。 调用资源的时候,先加载库再使用对应的资源文件。

source007

调用这个库采用如下方式:

#include "dialog.h"
#include "skin.h"

#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    Skin::InitSkin();

    Dialog w;

    w.resize(800, 480);
    w.show();

    return a.exec();
}

调用资源的方法和直接使用资源文件一样,:/images/keyled/ic_btn.png 直接用资源文件url调用资源文件。

ui->pushButton_login->setStyleSheet(QString("QPushButton{border-image: url(:/images/keyled/ic_btn.png);} QPushButton:pressed{border-image: url(:/images/keyled/ic_btn_pre.png);}"));
ui->pushButton_logout->setStyleSheet(QString("QPushButton{border-image: url(:/images/keyled/ic_btn.png);} QPushButton:pressed{border-image: url(:/images/keyled/ic_btn_pre.png);}"));

在工程管理文件pro中需要添加如下内容,告诉编译器需要要到哪里去链接skin库。

INCLUDEPATH += $$PWD/../thirdpart/libskin/include
LIBS += -L$$PWD/../thirdpart/libskin/lib -lSkin

2.5.2.4. 信号与槽

最后我们再对信号和槽进行连接,比如实现点击取消的时候,关闭登录对话框

先切换到信号与槽的模式,选中取消按钮,并拖动到dialog窗口

这时会弹出一个对话框,供我们选择要关联的信号和槽。 选中PushButton的clicked()信号和Dialog的reject()槽函数,点击OK进行关联。

designer013

上述操作的含义是,当我们点击 取消 这个按钮时,会产生一个clicked()的信号,经过我们的绑定之后, Dialog的reject()槽函数会来响应这个信号,reject()会关闭当前的对话框。

下一章有更多关于信号和槽的介绍,这里暂时知道这样用就可以了。

编译运行,点击 取消按钮 便会对话框就会被关闭。

designer014

除了使用图形化的关联信号和槽,实际应用更多的是采用代码来编写槽函数来响应控件的信号。

点击 登录按钮 ,右键菜单,直接跳转到槽, 弹出的对话框中,同样选择clicked()信号,点击确定。

designer015

这时Qt Creator会自动帮我们创建槽函数 on_pushButton_login_clicked() 用来响应 登录按钮 被按下的操作。

在这个函数中添加如下内容,登录时候去检查账号密码是否为空。

embed_qt_develop_tutorial_code/Designer/dialog.cpp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
void Dialog::on_pushButton_login_clicked()
{
    if(ui->lineEdit_account->text().isEmpty())
    {
        QMessageBox::information(this,"提示","账号不能为空",QMessageBox::Yes);
        return ;
    }
    if(ui->lineEdit_passwd->text().isEmpty())
    {
        QMessageBox::information(this,"提示","密码不能为空",QMessageBox::Yes);
        return;
    }

    accept();
}

2.5.3. 编译构建

build001
  • Ubuntu 选择 Desktop Qt 5.11.3 GCC 64bit 套件,编译运行

  • LubanCat 选择 ebf_lubancat,只编译

提示

当两种构建套件进行切换时,请重新构建项目或清除之前项目。针对我们的工程还需要手动重新构建QtUI和Skin。

build002

2.5.4. 运行结果

2.5.4.1. PC实验

直接点击编译并运行程序,效果如下:

result001

2.5.4.2. LubanCat实验

通过SCP或者NFS将编译好的程序拷贝到LubanCat上

NFS环境搭建 参考这里

SCP命令如下:

# scp传输文件
# scp 文件名 服务器上的某个用户@服务器ip:/文件保存路径
scp filename server_user_name@192.168.0.205:server_file_path
# 从服务器拉取文件
# scp 服务器上的某个用户@服务器ip:/服务器文件存放路径 拉取文件保存路径
scp server_user_name@192.168.0.229:server_file_path local_path

编译好的程序在 embed_qt_develop_tutorial_code/app_bin 目录中,通过scp命令将编译好的程序拉到LubanCat。

scp root@192.168.0.174:/home/embed_qt_develop_tutorial_code/app_bin/Designer /usr/local/qt-app/

在LubanCat运行程序,使用run_myapp.sh配置好环境,并执行 Designer 。

sudo /usr/local/qt-app/run_myapp.sh /usr/local/qt-app/Designer
result002