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个部分组成:
控件窗口提供了一些标准的Qt窗口控件,布局和其他可用于在UI窗口上创建用户界面的对象。 每个类别的控件框都包含具有类似用途或相关功能的控件。
UI窗口,我们可以直接在该窗口上进行UI设计。
对象窗口可以查看所有UI窗口中的对象,包括对象和类,点击列表可选中对应的对象。
属性窗口,可通过对属性进行设置使对象表现出我们想要的效果,比如设置QLabel文字大小,居中等等。
模式和布局,通过模式切换使能UI窗口的不同功能,如UI编辑,信号与槽编辑,tab健的顺序设置, 除此以外还能快速对UI进行布局调整。
Action和Signal的管理。
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文件,文本中记录了窗体名,类名,窗体长宽和标题栏要显示的字符。
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转换的结果。
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这个类,在文件中引入即可;其写法如下:
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 。
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. 工程管理¶
打开我们的工程目录我们会看到如下文件:
.h .c++ 文件为头文件和源文件,我们的代码通常都是写在这两种文件中的
.ui 文件就是我们前面提到界面文件,其实质是xml格式的文本文件
.pro 文件就是这节要提到的工程管理文件,以qmake特有的语法记录了工程的文件和配置
.pri pri文件通常也用于工程管理,将代码整理成一个模块,通过引入pri文件来引入代码模块
.pro.user 为用户描述文件,记录了我们工程的开发环境构建环境等等,也是xml格式的文本文件
.qrc 除上面的文件,工程中一般还会有qrc资源文件,用于记录图片,音视频等文件
工程的目录和我们在 Qt Designer 中看到的工程结构不太一样, 是因为 Qt Designer 有对 pro 文件进行解析并按照其内容进行相关设置。
使用文本编辑器打开Designer.pro文件
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
打开Qt Creator,将 Designer.pro 文件拖动到Qt Creator中,再选择构建套件点击configure project。
这个时候就会看到一个完全的空工程,复制上面 工程管理 中的 Qt项目管理文件示例代码 到 Designer.pro文件中并保存。
保存之后,我们就会看到在左边的工程目录就会出现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。
接着选择Qt,Qt设计师界面类,点击Choose。
接着选择Dialog,添加Dialog对话框,
会在工程下创建 dialog.h dialog.cpp 和 dialog.ui 等三个文件。
Qt Creator会在pro文件中自动添加上面的三个文件。
再次修改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添加到主窗口。
选中控件的同时,对象窗口也会被选中,属性窗口切换到该控件的属性,
比如我们点击一个QLabel,
修改控件属性 ObjectName 为 label_title; 修改Font属性 字号20,加粗; 修改text属性 登录对话框; 修改alignment属性 垂直水平都居中。
效果如下图所示:
这时候编译程序,看到的效果就是我们主窗口中的效果(所见即所得)。
但是,当我们对窗口进行缩放的时候,就会发现问题——控件并不随窗体的改变而改变。
因此我们需要对控件、窗口进行布局。当我们选中多个控件时,上面工具栏中的布局按钮就会被激活。
常用的布局有四种:
水平布局
垂直布局
表格布局
栅格布局
下面就是我们布局之后的窗口以及编译之后的效果
2.5.2.3. 添加资源文件¶
首先点击QtCreator菜单栏文件,新建文件或项目,会弹出如下对话框:
选择Qt以及Qt资源文件,并给资源文件命名,我给的名字为img。
这时候我们工程目录结构会创建一个新目录Resources,目录下有我们的img.qrc,这在工程管理文件pro文件由写明 RESOURCES += img.qrc ,创建资源文件时,由QtCreator自动完成。 实际上工程所在目录下也会多出一个img.qrc文件。
鼠标右键点击工程目录结构下的img.qrc,会弹出一个菜单,菜单上有添加现有文件的选项,点击。
这是会弹出文件对话框,选择我们要添加的资源文件,确认即可。
这个时候,ima.qrc下面就会多出我们添加的资源文件,点击资源文件,左边便会预览资源文件。
实际上,用记事本打开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中使用我们添加的资源文件。
当然也可以通过代码使用资源。
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)。 调用资源的时候,先加载库再使用对应的资源文件。
调用这个库采用如下方式:
#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进行关联。
上述操作的含义是,当我们点击 取消 这个按钮时,会产生一个clicked()的信号,经过我们的绑定之后, Dialog的reject()槽函数会来响应这个信号,reject()会关闭当前的对话框。
下一章有更多关于信号和槽的介绍,这里暂时知道这样用就可以了。
编译运行,点击 取消按钮 便会对话框就会被关闭。
除了使用图形化的关联信号和槽,实际应用更多的是采用代码来编写槽函数来响应控件的信号。
点击 登录按钮 ,右键菜单,直接跳转到槽, 弹出的对话框中,同样选择clicked()信号,点击确定。
这时Qt Creator会自动帮我们创建槽函数 on_pushButton_login_clicked() 用来响应 登录按钮 被按下的操作。
在这个函数中添加如下内容,登录时候去检查账号密码是否为空。
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. 编译构建¶
Ubuntu 选择 Desktop Qt 5.11.3 GCC 64bit 套件,编译运行
LubanCat 选择 ebf_lubancat,只编译
提示
当两种构建套件进行切换时,请重新构建项目或清除之前项目。针对我们的工程还需要手动重新构建QtUI和Skin。
2.5.4. 运行结果¶
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