入职培训总结之Qt项目QSsh开发笔记
一、信号与槽机制
信号与槽机制的实质就是观察者模式。
原理
信号与槽,分为信号与槽两部分,信号就是某种事件发生时,一个对象主动(通常是自动事件)产生的消息,如pushbutton的点击、slider的滑动等;而槽就是对信号的处理操作。
信号与槽的绑定,有三种方式:
在UI设计界面,通过右键控件,直接转到对应的槽(信号)函数,编写函数体即可;
在页面对应的
.cpp
文件中,通过类名::on_对象名_事件名
的格式手动编写函数体,函数声明需要写在private slots
下;1
2
3
4
5
6
7
8
9
10
11
12void Stack::on_mend_clicked()
{
int id;
if(ui->id->text().isEmpty()){
QMessageBox::warning(this,"Failed","请将信息填完整!");
}else{
id=ui->id->text().toInt();
ui->id->clear();
QSqlQuery query,query1,query2;
//……下面是一些数据库操作
}
}如果是自己自定义的函数,不方便改成上面的命名规范,可以使用connect函数进行手动连接:
1
2
3
4
5
6
7
8
9
10//mainwindow.cpp
connect(ui->logout_2,SIGNAL(triggered(bool)),this,SLOT(doLogOut()));
void MainWindow::doLogOut(){
this->close();
Dialog l;
if(!l.exec()) return;
this->show();
this->initSystem(l.reIndex());
}上面的示例代码是将mainwindow页面中,id为的logout_2的对象产生的triggered信号,与doLogout()函数进行了绑定。connect函数的四个参数,分别为信号发送者,信号名称、信号的接收者、接收者对应的槽函数;
在Qt quick2中,如果使用qml进行编程,则信号处理的方式略微不同:需要在qml文件中设置自己的id,指定target(另一个对象),使用connect函数对signal和receive进行绑定,最后在target中实现receive函数。下面是一个示例:
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
47//sender.qml
Circle {
id:sender
property int counter: 0
signal send(string value)
property Receiver target: null
onTargetChanged : {
send.connect(target.receive);
}
MouseArea{
anchors.fill: parent
onClicked: {
sender.counter++;
sender.send(counter);
console.log("clicked");
}
onPressed: {
sender.circleColor="blue" ;
}
onReleased: {
sender.circleColor= "red" ;
}
}
}
//receiver.qml
Circle {
id:receiver
function receive(value){
contentText=value;
colornotify.running=true;
}
SequentialAnimation on circleColor {
id:colornotify
running: false
ColorAnimation {
from: "red"
to: "blue"
duration: 200
}
ColorAnimation {
from : "blue"
to: "red"
duration:200
}
}
}这两个qml文件中的Circle是实现设计好的一个圆圈类,用来形象化展示不同的实体。
即:两个实体均继承自Circle(主要的属性有颜色和文字)。sender和receiver都放置在main.qml中,这样两个对象的信号就能连接。
上面的示例代码中,send相当于是一个广播,信号管道,对于其内部实现这里并没有涉及。相反更重要的是receive信号处理,其函数体内进行了文字变更和变色渲染的调用。
slot槽函数可以当做普通函数使用,即可以直接调用
关于QT4与QT5中信号和槽绑定的不同语法
QT4中,信号和槽必须使用SIGNAL和SLOT关键字进行包含,但QT本身在编译的时候不会对其进行类型检查,如果信号或槽不存在也不会报错,而是等到运行过程中才会debug:no such signal(slot) :xxx
connect(sender,SIGNAL(signal(params)),recever,SLOT(receiver(params)));
需要注意的是signal和receiver都需要在对应的类里面进行声明(分别声明在signal、slot下面),且类型都必须是void,信号只能声明不能实现
QT5中,信号与槽不需要使用关键字进行包围,相应地会在编译阶段进行存在检查和类型校验。
connect(sender, &ClassSender::signal, receiver, &ClassReceiver::slot);
这种方式中,槽函数不必声明在头文件”slot”域下。
可以看到,这种情况下signal和slot没有括号包围,即默认不能带参数,这给函数的重载带来了阻碍,解决方案可以参考Qt5教程: (4) 带参数信号与槽以及:【Qt教程】1.7 - Qt5带参数的信号、信号重载、带参数的槽函数、槽函数重载
以服务器远程连接工具为例,有两个函数是重载关系:
1
2void CSshUtils::slotSend(QString cmd);
void CSshUtils::slotSend(QString hostAddress,QString cmd);这两个函数作为槽函数响应当前类(CFileWidget)的信号,则我们在connect的时候,需要指明具体连接的是哪一个槽:
1
2void (CSshUtils::*slotSend_no_add)(QString cmd)=&CSshUtils::slotSend;
connect(this,&CFileWidget::sigSend,sshSocket,slotSend_no_add);
二、Qt页面组织方式
Qt中的页面显示,以及不同页面间的跳转,其基本原理就是创建不同的页面对象,并在信号关键点,对Widget或Mainwindow对象的show()或Dialog对象的exec()方法进行调用,从而实现页面的跳转。
QWidget类是所有用户界面对象的基类,即所有界面类都直接或继承自QWidget,上面提到的不同页面对象,在创建并初始化时,会调用根据设计的UI文件自动生成的setupUi函数(可通过ui_对象名.h文件找到),对页面及内部的对象树进行渲染。当调用show()当接口时,页面就会显示出来。
1. 通过QWidget(QMainwindow)
这种方式直白描述,就是如下步骤:
预先定义好多个不同的继承自QWidget或QMainwindow的类并设计好其页面。
程序运行时,根据首先运行的
main.cpp
,如果未加配置且项目是继承自QMainWindow,则首先展示的是mainwindow.ui
页面。下面稍作了修改:1
2
3
4
5
6
7
8
9
10
11
12//main.cpp
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Dialog l;
MainWindow w;
if(l.exec()==Dialog::Accepted){
w.show();
w.initSystem(l.reIndex());
return a.exec();
}
}这里Dialog是一个登录框。程序的执行逻辑是:首先弹出登录框;如果登录框执行(显示,根据用户的输入进行处理)的结果是Dialog::Accepted,则显示MainWindow实例的页面。最后返回值是
a.exec()
,代表程序会一直保持页面打开不退出(如果没有这句,页面就会一闪而过)在
mainwindow.ui
中设计好一些按钮,并在main.cpp
中实现并绑定对应的槽函数(方式如上1、2),在槽函数中指向其余的页面即可1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21//mainwindow.cpp
#include<bookborrow.h>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
this->resize( QSize( 1300, 800));
setWindowTitle("主界面");
connect(ui->borrowButton,SIGNAL(clicked(bool)),this,SLOT(doBookBorrow()));
……
}
void MainWindow::doBookBorrow(){
Bookborrow bb;
this->hide();
bb.initSystem(username,major,phone);
bb.exec();
this->show();
}
……上面的示例中,信号发送者为borrowButton,绑定到了槽函数doBookBorrow()上,去创建并打开另一个借书页面,隐藏当前页面。当借书页面被关闭,mainwindow就会重新显示。
2. 通过QML
这种方式基本原理和上面并没有太大区别,但是由于QML(Qt Meta-Object Language)独特的继承方式,使得页面的嵌套、信号与槽的绑定变得更加简单:
首先程序运行的同样是
main.cpp
点击展开
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17#include <QGuiApplication>
#include <QQmlApplicationEngine>
int a=0;
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
const QUrl url(QStringLiteral("qrc:/main.qml"));
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
&app, [url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
}, Qt::QueuedConnection);
engine.load(url);
return app.exec();
}这段代码是一个Qtquick项目创建后
main.cpp
的默认模板,可以看到其内部引入了页面文件main.qml
,通过QQmlApplicationEngine来对其进行load,最后依然是阻塞式app.exec()
防止退出。用标准组件设计、拼装自己的页面,下面是一个实现登录功能的
main.qml
示例:点击展开
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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93import QtQuick 2.6
import QtQuick.Window 2.2
import QtQuick.Controls 1.1
import QtQuick.Controls.Styles 1.1
import QtQuick.Layouts 1.1
Window {
id:win_home
visible: true
width: 480
height: 360
title: qsTr("Ev_viewer")
SelectData
{
id:select_data
}
MainForm {
AnimatedImage //动画图
{
id:png
width: 480
height: 150
source: "images/png.gif"
}
Text //用户名称
{
id:textname
x:80
y:png.y + png.height + 20;//文本顶部距离rocket图片底部20
text:"用户名:"
color: "Black"
font.family: "楷体"; //设置字体
font.pixelSize: 25;//字体大小
}
Text //密码
{
id:textPsd
x:80
y:png.y + png.height + 70;//文本顶部距离rocket图片底部70
text:"密 码:"
color: "Black"
font.family: "楷体"; //设置字体
font.pixelSize: 25;//字体大小
}
TextField{ //用户名输入框
x:180
y:png.y + png.height + 18;//输入框顶部距离rocket图片底部18
width:200
height:28
id:textfield_Name
}
TextField{ //密码输入框
x:180
y:png.y + png.height + 68;//输入框顶部距离rocket图片底部68
width:200
height:28
id:textfield_Psd
}
Component{ //登录与退出按钮
id: btnStyle;
ButtonStyle {
background: Rectangle {
implicitWidth: 70;
implicitHeight: 25;
color: "#DDDDDD";
border.width: control.pressed ? 2 : 1;
border.color: (control.hovered || control.pressed) ? "green" : "#888888";
}
}
}
Button {
id: openButton;
text: "登录";
onClicked:{
select_data.show();
}
anchors.left: parent.left;
anchors.leftMargin: 100;
anchors.bottom: parent.bottom;
anchors.bottomMargin: 60;
style: btnStyle;
}
Button {
text: "退出";
onClicked: Qt.quit();
anchors.left: openButton.right;
anchors.leftMargin: 150;
anchors.bottom: openButton.bottom;
style: btnStyle;
}
anchors.fill: parent
color: "gray";
}
}在这个例子中,定义了两个信号事件:一个是点击登录按钮时,select_data页面显示,另一个是点击退出按钮时当前程序退出。而整个main.qml页面上的组件,是放在一个自定义组件MainForm中,下面是其内容:
1
2
3
4
5
6
7
8
9
10import QtQuick 2.6
Rectangle {
property alias mouseArea: mouseArea
width: 360
height: 240
MouseArea {
id: mouseArea
anchors.fill: parent
}
}可以看到其中只有一个Rectangle(长方形)元素,是QtQuick的基本组件。
通过上面的简介,可以发现QtQuick中一个qml文件对应一个组件或一个页面,对于页面的跳转,可以直接使用Button的onClicked等事件,但前提必须提前在组件中手动声明(如本例中的select_data)。
三、APP打包构建相关
通过Qt开发APP,不是跳过了JAVA,而是借助Qt,帮我们自动完成了这一步。Qt for android,底层是通过Gradle将编译好的程序打包构建生成apk,并在真机调试的过程中通过USB调试安装到客户机上。具体的apk文件可以通过如下路径找到:
apk文件默认保存在:
项目编译输出路径\android-build\build\outputs\apk\debug
下如果希望控制APP的图标,将相关资源在Qt中也能统一管理,可按如下步骤开启安卓模板文件管理:
设置完成后,点击项目文件视图中的
OtherFiles->AndroidManifest.xml
,即可进入app设置界面:此界面设置的选项包括应用图标(上图中间那三个空白的按钮)、构建目标API(这个太低的话会导致构建失败)、包名等等。
按上面的方式设置的话,会将用于生成apk的文件转移到一个共同目录下:
四、关于APP适配的问题
屏幕尺寸
获取屏幕尺寸,并设置创建页面的大小:参照Qt For Android 如何获取手机屏幕大小
关于屏幕分辨率不同级别及gradle打包时生成的不同目录:参照教你来彻底理解ldpi、mdpi、hdpi、xhdpi、xxhdpi
屏幕方向问题
在屏幕转向时,Qt控制台会输出windowmode=1
五、关于QSsh的交叉编译
参照这篇博客:QSsh之android版编译,使用QtCreator成功编译出了安卓(arm-v7a)的SSH库

六、关于QWidget如何一直显示的问题
QWidget不像QDialog可以直接调用exec函数一直显示,当槽函数close调用时退出,因此需要
自己利用QEventloop进行实现,参考了:Qt :QWidget 实现QDialog exec() 模态显示效果
实现的过程中还遇到了下一个问题(其实和本问题是无关的):
七、关于指针导致的问题/Bug
1.delete this导致程序崩溃
场景描述:一个主页面调用一个外部页面(其他类),当外部页面点击退出时关闭页面,重新显示主页面。问题发生在外部页面点击了退出后,外部页面关闭了,但同时主页面也推出了,程序报错:
1 |
|
一开始以为是自定义的exec函数不兼容,导致外部页面的close函数把主页面也关闭掉了,但是经过多次尝试发现问题根源在于外部页面(类)的析构函数
这是一开始没有认真分析,参考之前的示例代码加的一行,用于清除申请的资源。但经过后面查询得知,页面退出后Qt会自动释放掉内存
Qt有父子对象机制,即如果创建的类是直接或者间接继承QObject类,动态内存由Qt来管理,无须手动delete。
如果手动delete掉,就会破坏掉父子对象树,导致主页面也崩溃。
参考了Qt QCloseEvent中delete this的bug
用代码绘制Qcharts图表:Qt 绘制图表 - Qt Charts版
程序当前路径
- qApp->applicationDirPath()
- qApp->applicationFilePath()
- QDir::currentPath();
Qtchart单页版:QT – QChart画饼状图
八、Qt常见报错集锦
编译过程:
1.undefined reference to ‘MyClass::Myfunction()’(自定义函数)
这种情况多出现于一个自定义类还未实现完全的时候,想要编译一下看有没有错误
但是由于声明了一些函数还未进行实现(尤其是槽函数),而Qt会为每个类自动编译生成moc_xxx.cpp,其中会引用到这些函数,因此就会判定为未知引用
解决方法:
- 注释或删除掉未实现的槽函数
- 若槽函数后面必须实现,可先写一些简单的语句如qDebug()、return等
参考了QT:error: undefined reference to ‘xxxx’错误提示,解决方式
2.skipping incompatible xxx when searching for最后报错:undefine reference to xxx (库函数)
这种情况一般是由于库文件与使用的编译器不兼容所导致的,具体可分为:
32位与64位不兼容,这种情况,如果本机有对应的环境,在QtCreator中切换一下项目构建环境即可
构建库所使用的工具与当前编译工具不兼容,比如:编译时使用的是MSVC,而当前构建工具是mingw。这种情况多出现在Qt早期版本(5.9以前)
如果本机没有对应的构建环境,而所需的库是开源项目建议直接下载源代码,跳过对库的链接进行开发,(也可自行编译后对库进行链接)
参考了windows10环境下QtCreator中出现skipping incompatible xxx when searching for xxx 问题解决办法
3.invlaid use of incomplete type ‘class UI::xxx’(自定义类)
一般出现在自定义类的源文件中,且多出现于构造函数初始化UI时。由于Qt中新建一个类的时候并不会同时创建其对应的界面文件,因此需要自行在源文件中对界面文件的命名空间进行声明,即:导致这项报错的原因无非下面几条
- 头文件中没有引入“ui_xxx.h”
- 界面文件最外层的对象没有改名,或者改了但没改对(此处报错中声明的对象名)
- 头文件中没有声明Ui的命名空间和ui对象。
- 前面配错了,后面把名字改过来了但是由于缓存的存在导致没有更新ui_xx文件,此时将对应文件删除,清除项目在重新编译即可。
4.expected class name

一般出现于自定义的类,且继承自其他类,而未在头文件中进行包含,上图中加入#include <QDialog>
即可。
5.undefined reference to ‘vtable for xxx(自定义类)’

问题出现的原因是,虚函数没有函数体(或者直接没有虚函数),直接在源文件中加一个空的函数体即可。
注意,由于之前编译时会生成缓存文件,如果添加之后问题还是存在,可尝试右键项目名->清除,去掉Makefile中过时的信息后重新编译即可。
参考了[undefined reference to `vtable for 原因解决](https://icharle.com/undefinedreferencevtable.html)
6.error: ‘QChartView’ does not name a type
参考https://blog.csdn.net/u011046042/article/details/104749154
运行过程:
7.error: invalid use of ‘this’ outside of a non-static member function
没写类名,导致QT找不到this域,加上域名和双冒号即可
8.call to non-static member function without an object argument
信号和槽要加取地址符
9.static assertion failed: Signal and slot arguments are not compatible
具体原因,槽和信号声明和定义不一致,或类型不对应https://blog.csdn.net/weixin_41320969/article/details/105579389
10.QSqlquery.size()返回值为-1
这个问题的根本原因是:QSqlquery的size()方法,如果大小不能确认或者如果数据库不支持报告有关查询大小的信息,会直接返回-1。因此返回-1并不代表获取失败。如果想要判断是否查询成功以及获取查询结果数量,可通过如下方式:
1 |
|
11.QSqlquery.exec()不成功
有可能是bindvalue不成功导致的。当sql语句中含有字符串,需要将字符串使用单引号进行包围,而又不能直接写进去然后bindvalue,应当使用字符串拼接一点一点连接起来。下面是范例:
1 |
|
MaintenanceTool相关
- 首页,“此操作至少需要一个处于启用状态的有效资料档案库。”
添加一个临时的镜像站即可。点击左下角“设置”,选择“资料档案库”->临时资料档案库,选择一个开源镜像站,填入其URL,这里我选择的是清华源。
但是经过本人多次测试,如果只填一个主站点而不写到具体版本的话,后续信息会出错,导致只能卸载已安装的组件,无法更新。
因此,要确认已安装Qt的版本,选择到镜像源的正确路径。以windows平台下5.13.2版本为例,需添加以下四个连接,缺一不可:

最后
有时Qt的编译过程中间文件与实际不符会导致报错,此时删除编译中间文件夹可解决问题。但大部分情况下,问题都是出在源码上,要仔细核对库引入及其版本匹配、槽函数与信号、头文件等容易忽略的地方