Qt编程-初步进阶
前言
- 通过上篇文章我们对Qt有了初步的认识,Qt编程(一)-认识
- 这里我们开始对Qt深入研究一下,通过之前我们知道,Qt中重要的是信号和槽,通过connect()函数连接。这里我们深入一下信号和槽
一,Qt自定义信号和槽
1,自定义信号和槽
- 1,首先需要建两个类,一个信号类,一个槽函数类。右键项目->Add New,,新建类
- 2,设置类的属性:类名,父类,位置
在声明的槽函数里面,右键函数名,选择Refactor->在.cpp文件里面添加定义
自定义信号类:
在ZMSignals的头文件里面添加信号声明,不需要实现
自定义槽函数类:
在ZMSlots类的头文件和源文件中添加自定义槽函数的定义和实现。
- 3,把信号和槽联系起来。
信号和槽,需要依托一个窗口来联系,eg:widget,window 等
//emit是发送信号的标识,不写会报警告,版本问题,(非必须)
emit zmSignal.zm_sig_start();
2,自定义信号带参数重载问题
- 1,添加重载信号和槽函数
- 2,发现connect的地方报错了,是因为connect时,上下文重载函数出现歧义
- 3,我们可以使用带参数的函数指针来指向我们要指向的函数,来消除歧义,下面两个方法,个人推荐第一个
#if 1 //两个方式一样,只是指针函数开放与不开放的问题
//方法一:
//设置两个带参数的函数指针
void (ZMSignals::*str_sig_start)(QString) = &ZMSignals::zm_sig_start;
void (ZMSlots::*str_slot_accept)(QString) = &ZMSlots::zm_slot_accept;
//关联带参数的函数指针
w.connect(&zmSignal, str_sig_start, &zmSlot, str_slot_accept);
#else
//方法二:强制转换型,代码可读性低
w.connect(&zmSignal, ( void (ZMSignals::*)(QString)) &ZMSignals::zm_sig_start, &zmSlot, (void (ZMSlots::*)(QString)) &ZMSlots::zm_slot_accept);
#endif
3,函数重载问题总结
- 注意信号重载函数指针指向了哪一个函数,对重载函数的信号连接时,要指明到底连接的是哪一个槽函数
4,信号和槽的扩展
1,信号连接(connect):
- 我们通过一个按钮button的clicked,触发一个信号,进而通过信号与槽的connect连接,去触发信号对应的槽函数。即:信号连接信号,一个消息信号可以绑定多个槽,同样的,一个槽函数也可以绑定多个消息信号。(多对多关系)
eg:触发按钮点击,同时触发ZMSlots对应的槽函数执行,和触发MainWindow对应的关闭槽函数
2,信号的断开(disconnect):
- 可以通过disconnect函数来断开连接,使用方式与信号连接connect一样
- disconnect(信号发送者,信号,信号接收者,槽函数);
3,信号和槽的参数
- 信号发送的是什么,槽函数接收的就是什么,类型必须一致。
- 信号的参数个数可以多于槽函数的参数个数,但前面相同数量的参数,其参数类型必须一一对应。反之则不可以。
二,Lambda函数
- 为解决项目中各种信号的传递和连接,在大型项目中,信号连接和传递过度使用会使工程可阅读性降低,增加熟悉成本。
1,Lambda函数认识
- Lambda函数也叫Lambda表达式,是匿名函数(没有名字的函数),和传统函数不一样。
- Lambda表达式是C++11之后引入的概念,用于定义并创建匿名函数对象。
2, Lambda表达式结构:
[捕捉列表](参数)mutable-> 返回类型{函数体},
eg:
[](){
qDebug("Hello Qt");
}
- [],标识一个Lambda匿名函数的开始,这个必须有,不能省略,函数对象参数是 传递给 编译器自动生成的函数对象类的构造函数的。函数对象参数只能使用到Lambda定义所在的作用域范围内可见的局部变量,包括Lambda所在类的this。
- []中括号函数对象里面对应参数有以下形式:
- 空,没有使用任何函数对象参数
- =,函数体内使用Lambda所在范围内的可见局部变量,包括所在类的this的传值方式,相当于编译器给Lambda所在范围的所有局部变量**赋值**一份给Lambda函数
- &,函数体内使用Lambda所在范围内的可见局部变量,包括所在类的this的引用方式,相当于编译器给Lambda所在范围的所有局部变量**引用**一份给Lambda函数
- this,函数体内可以使用Lambda所在内的成员变量
- a,不是字母,而是指具体一个变量a,Lambda内拷贝一个变量a使用
- &a,Lambda内引用变量a
- a, &b,拷贝a,引用b
- =,&a,&b,除a,b引用外,其他变量做拷贝操作
- &,a,b,除a,b拷贝外,其他变量做引用操作
- mutable:标记可更改
3,为什么使用Lambda函数
- 有些函数只是临时使用,而且它的业务逻辑也很简单,就没必要非给它取名字。在Qt中也可简化一些操作。
4,Lambda实例
三,Lambda函数扩展
1,Lambda的返回值
程序能正常输出结果:40;
- 注意点:
- -> 是有返回值的标志,int是返回值类型,在函数中直接返回,
- 最后的()是调用函数,没有()不是函数调用,这里的函数可以定义后直接调用。
Lambda表达式,实现 斐波那契数
//斐波那契数:Fibonacci数
int a=0, b=1;
[](int& a,int& b, int count){
int ret = 0;
for (int i =0; i<count; i++) {
ret = a+b;
a=b,b=ret;
qDebug()<<ret;
}
}(a, b, 10);//函数调用时,传a,b进到Lambda表达式里
运行结果:
2,Lambda的应用
- 无参数的信号,调用有参数的槽函数,这时候使用Lambda
实现原理解析:因为按钮点击无参数,匿名函数也没有参数,但匿名函数中调用了一个有参数的信号。
3,QString转char*
- 由于QDebug打印时会把QString的双引号一并带上
- QString类中的一些public方法中,有一个toUtf8方法,返回为QByteArray,先转成一个字节数组。然后还有一个data方法,调用data方法,再将这个数组转成char*
4,Lambda意义
- 1,使代码更简洁高效
- 2,如果connect,信号的接收者是this的话,this可以省略不写
connect(btn, &QPushButton::clicked,[=](){
//信号接收者为this,可以省略不写
this->close();
});//发送信号
四,常用控件
1,QTextEdit控件
- QTextEdit:文本编辑框,
- 实例:
2,QMainWindow
- QMainWindow是为用户提供界面的主窗口类,包含菜单栏(menubar),多个工具栏(toobar),多个铆接部件(dockwidgets浮动窗口),一个状态栏(status),及一个中心部件(centralwidget),是app的基础,eg:文本编辑器,图片编辑器等。
3,菜单栏(QMenuBar)
- 菜单栏有且仅有一个,设置方法是:setMenuBar
4,添加工具栏(QToolbar)
- 工具栏可以随意拖动的,可以放在软件左侧,上侧,下侧,右侧,甚至可以浮动在app的界面中。
- 工具栏与菜单栏区别:菜单栏只能有一个,且在最上方;而工具栏可以有多个,并且位置多样。因此设置方式为addToolBar
5,添加状态栏(QStatusBar)
-
状态栏有且仅能添加一个,因此,设置方式为setStatusBar
-
添加状态栏,由于系统的不同,以及中文和中文符号的数量为奇数个的时候易出现乱码,
-
解决方式一,编辑里面重新设置编码规则, 并且有中文的地方前面全部加上u8,即可
-
解决方式二:使用QStringLiteral(),方法,因为内部是Lambda表达式实现,高频次调用影响性能
-
6,铆接部件、中心部件(QDockWidget、centralWidget)
1,铆接部件(dockWidget)
-铆接部件、中心部件、浮动窗口是同一个意思,通过查Qt助手文档,我们看到QDockWidget基于QWidget,浮动窗口可以有多个,因此 addDockWidget:
- 有一个铆接部件时,拖到中间会变成浮动,默认停靠的地方有些奇怪。这是因为,在QMainWindow中,我们还没有添加中心部件,默认中心部件是空的,因此它就直接在顶部了
- 铆接部件的设置,通过查询Qt助手文档,其跟工具栏一样
2,中心部件(centralWidget)
- 在QMainWindow中,有个中心部件,但这个中心部件不是具体的中心部件,而是我们可以将一个具体的部件设置为中心部件。中心部件只能有一个,因此 setCentralWidget
五,工程及界面UI
1,UI设计器
- 每个项目中都会有个:.ui的文件,其实就是Qt界面文件,打开文件时自动启动Qt ui设计器,在这个设计器中编辑控件,会方便直观很多,大大提高开发效率
- 添加一个tableview
设置完新的action后,直接拖动action到相应位置 - 多注意 属性设置的使用,信号和槽的使用,以及右键属性设置的使用
2,Qt资源文件添加
- 资源添加分两种方式:源码添加,和使用UI设计器添加,比如:一个QAction对象,对它添加图标,还是设置图标,添加代表可以多个,一般来说,一个工具,一个选项卡就是一个图标,因此,我们应该是设置图标,也就是设置icon,那么方法就是set开头,setIcon
1,源码添加方式
- 源码添加图片,局限在路径,使用相对路径需要一些设置,而且路径一旦修改,图片可能又加载不出来了
2,UI设计器添加方式
- 添加前提需要创建好Resource File,右键工程->add New->Qt->Qt Resource File,新建好工程资源文件,然后把图片资源放入其中
- 创建好资源文件后,设置前置和添加图片资源,添加完资源后记得保存
- 保存好,上步添加的资源后,打开UI设计器,我们往 “保存” 这个选项里面添加个icon
- 选择好icon后,在UI设计器里面直接就能看到了
- 如果添加完资源文件后,我们也可以用代码去访问资源文件
在构造QIcon的时候,需要“ : + 前缀名 + 资源路径 ”
3,对话框–模态&非模态
- 在图形用户界面,对话框是比较特色的视图,用来向用户显示信息,或者在需要的时候获得用户输入响应。构成了人机交互。
- 标准对话框是Qt内置的一些列对话框的工具,用于简化开发,
- Qt内置的对话框包括:
- QColorDialog 选择颜色对话框
- QFileDialog 选择文件或目录
- QFontDialog 选择字体
- QInputDialog 允许用户输入值,并将其返回
- MessageBoxDialog 模态对话框,用于显示信息、咨询或警告等。
- 等等
- 模态:用户交互阻塞在当前对话框
- 非模态:用户可以跳开当前对话框,在别的对话框交互
1,QDialog
- Qt的入口main函数里面返回了一个exec,保持了Qt的主窗口一直显示,其时exec里面是个死循环,在里面一直等待控制消息,直到收到关闭信息,就会退出窗口,否则,一直循环阻塞在消息里,让原来的窗口不能继续执行。
- 注意,lambda表达式作用域的问题,(堆栈)
- 注意内存泄漏的隐患,添加删除部件的方法
2,QMessageBox
- 1,认识
- QMessageBox是模态对话框,在app中主要用来提供显示信息,弹出询问,警告等等。例如:我们在记事本中编辑文本,没有保存的时候,就点击关闭,那么app会弹出个QMessageBox对话框,来询问是否保存文本。
- QMessageBox基于QDialog类的对话框
- 2,使用
- 实例:
QMessageBox::information(this, “询问”,“你在干嘛?”);,information,question,warn,cerical,都为静态函数,有自己的内存控件,因此可以直接使用类去调用。不用再去 堆中创建
- 注意静态成员函数的返回类型,和调用方式
- 参数的意义
- 部分源码:
- 实例:
connect(ui->pb02, &QPushButton::clicked, [=](){
//在堆中创建msg,需要控制释放,但对于模态来说,会阻塞函数不能结束,如果用户触发结束后,自动跟随函数释放内存。因此不建议这么写
// QMessageBox* msg = new QMessageBox(this);
// msg->information(this, "询问","你在干嘛?");
//比较合理的编写方式,information为静态函数,因此可以直接使用类调用
int ret = QMessageBox::information(this, "提示","你上班摸鱼5分钟了,请合理安排时间!", QMessageBox::Yes|QMessageBox::No);
if (ret == QMessageBox::Yes) {
qDebug()<<"好的,知道了!";
} else {
qDebug()<<"就是为了让老板知道!";
}
});
connect(ui->pb03, &QPushButton::clicked, [=](){
//比较合理的编写方式,question为静态函数,因此可以直接使用类调用
QMessageBox::question(this, "询问","你在干嘛?");
});
connect(ui->pb04, &QPushButton::clicked, [=](){
//比较合理的编写方式,warning为静态函数,因此可以直接使用类调用
QMessageBox::warning(this, "警告","注意:你上班摸鱼10分钟了,可能要被老板发现了");
critical_func(this);
});
void critical_func(QWidget* widget)
{
// QMessageBox::critical(widget, "致命","不好,你摸鱼被老板发现了!");
QMessageBox* msg = new QMessageBox(widget);
msg->resize(200,100);
QPushButton* btn = new QPushButton();
btn = msg->addButton("确定", QMessageBox::AcceptRole);
msg->show();
widget->connect(btn,&QPushButton::clicked,widget, [=](){
msg->critical(widget, "致命","不好,你摸鱼被老板发现了!");
});
}
3,QColorDialog
- 在QColorDialog中的静态函数区:
- 四个参数:颜色,父类,标题名称,ColorDialogOptions 颜色选项,如果只调用getColor(),分别都添加了默认。返回值为QColor。
static QColor getColor(const QColor &initial = Qt::white,
QWidget *parent = nullptr,
const QString &title = QString(),
ColorDialogOptions options = ColorDialogOptions());
4,QFileDialog
- 在Qt助手里面搜索QFileDialog,静态函数区,getOpenFileName和getOpenFileNames这个就是我们获取文件路径+文件名的两个方法,对文件进行操作,我们只需要获取到路径+文件名即可,:
- 两个函数,一个不加s返回QString,另一个加s的函数返回一个QStringList,即,一个返回具体文件,一个返回文件列表。
static QString getOpenFileName(QWidget *parent = nullptr,
const QString &caption = QString(),
const QString &dir = QString(),
const QString &filter = QString(),
QString *selectedFilter = nullptr,
Options options = Options());
对应五个参数:
父类:一般为this
caption:标题
dir:默认路径
selectedFilter:文件筛选,筛选显示后缀符合条件的文件
opthons:文件器,默认即可
- 简单示例:
六,总结
- 1,通过对Qt的初步认识,我们了解到Qt界面开发的一些常用控件,知道了控件的继承关系和控件里面开放的方法。
- 2,工程开发中有两种方式,我们在编程时,使用代码或使用UI设计器,根据不同的需求场景去做设计(性能和开发速度的博弈)
- 3,Qt助手文档很实用,建议多多使用。
- 4,这里我们只是对Qt的一些认识,涉及编程设计模式的地方以及更多优化性能的地方,在本内容中还没体现,后续账号会不断更新,感兴趣的同学还请关注。