【软件】qt creator
前言
b站的cpp看的差不多了,得找个方向溜溜。因为看的是黑马的,所以直接看看qt了。好像最后是个翻金币的小游戏。
正文
软件安装
实际上也有别的方法,比如在清华大学镜像站下或者在官网把下载的连接粘到迅雷去,因为qt的服务器其实还是部署在国外,没有科学道具挺慢的。
我这种方法因为都是下的最新的,所以对部分人来说不太适合。
所以具体的安装请自行百度,百度的教程提到的是分开下的模式。
吐槽:
- qt的界面对高分辨率显示器不友好,有的时候不太正经,建议下.2的版本稍微好点。除此之外就是两个不同大小的显示器之间切换你就懂了哈哈。。。有些地方不按比例来的
- qt对中文支持很糟糕,尽量避免使用gbk,不然编译可能有点问题,最重要的是安装路径或者文件路径最好都不要带中文
- 官方的图晒得都是配合mac的。虽然我在ubuntu也玩过,但是感觉也就那样
介绍
作为图形化框架,当然也能开发非GUI的程序,qt拥有很好的跨平台能力,基于面向对象,一般都是用cpp或者python吧,貌似有Android的支持。暂时用不到。除此之外,qt除了桌面端,对于嵌入式开发也有布局。
qt有名的例子:vlc、kde、virtualbox等。
注意,咱要用的都是开源版,所以发布的软件一版也都是采用开源协议的,貌似有能闭源的,但是我对那些协议目前不够了解,不敢指手画脚
微软的mfc虽然也是一个良好的图形化框架,但是微软的尿性基本不开源,所以mfc只能在windows用,到目前为止mfc的项目好像都是老项目了。未来究竟是什么样咱也不知道。
示例
图形框架最经典的就是拖控件,然后设置id,参数,在到代码上设置。
除次之外,qt的控件可以通过引入头文件,然后去创建,一般好像比较喜欢用new,然后在析构函数释放。
1 |
|
哈哈哈,很草率的一个东西。QPushButton* btn = new QPushButton;
创建一个按钮对象btn->setText("hello");
给按钮设置文本btn->setParent(this);
设置父类,咱这在widget这个窗口下,就以他为大爹。btn->resize(200,250);
resize就是设置大小很好理解btn->move(100,200);
move就是在这个父类窗口中通过xy的偏移得到。btn->show();
最后show,显示出来。
可能会好奇为什么要在这个widget函数里面设置和显示,包括销毁也要在widget的析构函数
1 |
|
关于qt项目的main.cpp
,咱可以看到他是创建一个对象的,然后把w.show,这就是我们widget这个主窗口。
widget主要是在上面放置布局和控件;所有用户界面对象的基类。
窗口部件是用户界面的一个基本单元:它从窗口系统接收鼠标、键盘和其它事件,并且在屏幕上> 绘制自己。每一个窗口部件都是矩形的,并且它们按Z轴顺序排列。一个窗口部件可以被它的父窗口部件或者它前面的窗口部件盖住一部分
mainwindow可以有menu菜单、tool工具栏、status状态栏、电脑显示屏右下脚的托盘等。提供更好的可视化操作;一个正常window软件呈现给客户的可视化界面。
当然关于qt他自己是有帮助文档的
额虽然大概率新手看不懂,不如直接看视频或者百度哈。
拖拽控件
额关于其他控件,b站啥的看一下就行了,文字说明没啥灵魂的。
而且控件类型也不少,建议视频教学。
信号和槽
这个可以说是qt的精髓了,有了信号和槽,对设计者可以更直观的操作组件交互。
信号(Signal)
信号会在特定的情况下发生的事件,咱最常见的PushButton,他最常见的信号就是来自鼠标的点击时发送的clicked()
信号,一个combobox最常见的就是选择列表项时列表发生改变的CurrentIndexChanged()
信号
对于GUI程序设计主要是整合各组件信号的响应,合理的去安排什么时候发送信号。
槽(Slot)
信号是发送的,那么槽就是用来接收响应的。表现形式就是一个函数,与一般c++函数差不多,也可以封装到类里面。
槽可以有参数,也可以被直接调用,与一般的函数不同处就是槽可以与一个信号关联,信号发射时,关联的这个槽会自动触发。
信号和槽关联通过QObject::connect()函数实现
基本格式示例QObject::connect(sender, SIGNAL(signal()), receiver, SLOT(sloet()));
- 参数1表示信号发送者
- 参数2表示发送的信号
- 参数3表示信号的接收者
- 参数4表示信号处理(槽)函数
最简单的使用
1 | QMetaObject::Connection ret = connect(btn, &QPushButton::clicked, this, &Widget::close); |
以咱前面的那个例子,此处的操作主要就是通过btn按钮,触发点击事件之后,把这个父类窗口直接关闭。
注意的是,咱这里是通过自带的点击事件和窗口自带的关闭事件响应
如果不知道可以百度,或者打开qt助手搜索查看一下,qt助手的路径为qt安装目录下/版本号/比如mingw或者mscv,32位还是64位看你自己选择/bin/assistant.exe
可能这样描述有些人看不懂,例如我使用的msvc2019,那么路径应该是D:\Qt\5.15.2\msvc2019\bin\assistant.exe
打开后是这样的
如何自定义信号和槽
先创建俩类
一个boy,一个gril,信号是love,槽就是ack_love哈哈
1 | //boy.h |
qt的信号是可以不实现的,与原本的cpp有一点相违背,但是我们自己能理解就行,毕竟这个只是一个触发点,调用了这个就相当于发射了信号,内部有无实现并不重要
1 | //gril.h |
1 | //gril.cpp |
槽函数是肯定要实现的,不然鬼知道它有没有响应
然后设计完了,该在哪调用。
首先咱用不到按钮,但是还是要承载在主窗口,主窗口是在main.cpp完成创建的,所以咱可以直接在main.cpp就开始,当然在窗口里面写其实问题也不大,关联this就行。
1 | Widget w; |
当我们启动的时候,在下面可以看到有输出,而且代码除了connect以外,并没有调用ack_love,说明通过love信号,的确触发了ack_love槽来响应。
关于qdebug,我们不可能直接把文字输出到窗口里吧,想搞也行,但肯定要个文本控件。最简单的就是当然调试信息直接打印在调试框里
所以对于信号
- 要声明在头文件的signals下
- 信号函数只需要声明,不需要实现
- 信号函数必须是void类型,都不要实现了自然也不用你返回值啥的
- 信号函数可以有参数,可以重载
- 调用信号的时候,可以在前面加一个
emit
,新版其实加不加都问题不大,还是那句话方便区分
老版本应该是指qt4了吧,现今都是qt5,未来还有qt6,虽然不知道具体差异
对于槽
- 槽函数不能写到signals下面,你要写在public slots下面,新版的好像可以不写slots,但是写了主要是明确直观的
- 槽函数除了声明还得去实现
- 槽函数也是void类型
- 也可以有参数和重载,但是大部分情况应该不会用到
自定义信号带参数和重载
1 | //boy.h |
1 | //gril.cpp |
1 | //gril.cpp |
注意,此处编译是不通过的,因为涉及到函数重载,而connect却没有指明
随机补充几个点
cpp补充重载
- 函数名相同,带有不同参数(个数和类型),这样的函数构成重载
- 作用域要相同,比如同个类,同个源文件
- 重载与返回类型可同可不同,不重要
cpp补充重写
- 其实就是类中函数被定义为虚函数了,子类或者派生类继承之后重写了它
- 参数列表必须与父类的结构完全一致
- 派生类重写之后函数前缀可不加
virtual
关键字
那么如何解决,有两个办法,但都涉及函数指针,因为地址是最好分别的。
1 | int main(int argc, char *argv[]) |
当函数指针带参数,执行我们类中的函数他会自己匹配上。
哦刚才run的时候发现没输出,一看是lov忘记填字符串了哈哈。随便填一下x_ming.love("你好");
然后就可以看到有输出了。
另外一种也类似w.connect(&x_ming,(void(boy::*)(QString))&boy::love, &x_hong,(void(gril::*)(QString))&gril::ack_love);
相对而言他就是强制转换了这个函数为这个带参数类型的那个,虽然效果一样,但是代码的可读性差很多。所以并不推荐这种方式。
信号和槽的拓展
上述的例子,我们将两个类的信号和槽直接完成关联,那么实际开发中这是一种不可控的情况,并不会被采取,所以是例子!
最合理的是什么,比如我们说过的pushbutton,他有自带的点击信号,那么就有路数了,给个提示,让btn的点击信号触发。
1 |
|
此处稍微修改了下按钮的创建时机,show在widget里面设置了。
效果是有的,不过同样存在一个小问题,无法带参数,因为这个clicked是个bool类型的,她不像我们可以通过函数指针去匹配带参数,这个人家自定义好的我们没法改。
这个特色就是连锁的connect,将信号绑定一个信号,再用这个信号触发我们的槽。
那么换个玩法,套个娃
1 |
|
让按钮先通过点击信号触发boy的love信号,再用love触发gril的ack槽,最后btn通过点击信号关闭窗口。
x_ming和x_hong的定义放在头文件了
最后还有一个断开连接,信号和槽能连接自然也能断开disconnect
可以将信号断开连接,使用方式和连接一样
1 | connect(btn, &QPushButton::clicked, &x_ming, pLove); |
加上这一一句,最开始的btn触发plove就失效了,那么后面的通过plove触发pack自然也不存在了。
所以我们得到结论
- 多个信号可以连接同一个槽函数
- 信号和槽的参数必须要一一对应
Lambda
有些教程叫Lambda表达式,也有的叫Lambda函数,总之它是一个匿名函数,就是没有名字的函数。跟函数指针不一样
Lambda是c++11引入的新概念,格式:[捕捉列表](参数)mutable->返回值类型,可以不设置{函数体}
1 | [](){ |
- [],标识一个Lambda函数的开始,不能省略
- 为空时,没有使用任何的函数对象参数
- =,函数体内使用Lambda所在范围的局部变量包括类的this传值,都会通过复制一份给Lambda函数
- &,与=相似,区别就是=是复制,而&是引用,意味着=不能修改原来变量的值,而引用可以
- this,函数体内可以使用Lambda所在的内部成员
- a,这里表示一个变量名,指Lambda内拷贝一个变量a使用
- &a,表示表达式内引用变量a
- a,&b 表示拷贝a,引用b
- =,&a,&b 表示除了ab引用,其他都拷贝
- &,a,b 表示除了ab拷贝,其余都引用
- 函数参数,与常规函数规则一致
- mutable,修改关键字,在对应位置处设置关键字,才能修改函数对象参数否则报错。可有可无,必须是有才能修改。lambda默认是const,也就是不能修改本地变量
- ->return->type返回值的方式,既然是函数就可能会有返回值,所以->表示有返回值的函数
- 函数体就是常规函数内容
为什么要使用Lambda函数,通过上述概念,其实就知道,有些函数只是临时随便用,而且业务逻辑简单,就没有必要特意搞个函数出来,通过Lambda可以简化一些步骤。
使用Lambda,需要保证你编译器支持c++11
qt中,你新建的项目中有个.pro的文件,里面能看到
老样子依旧写个例子
1 |
|
此处我们使用了=
的lambda表达式,但是似乎btn1的改变对于btn2没有影响。先前提到过=
只是能够拷贝一个a,但是不能对其改变。
对于直接传递变量a,和=类似,就是拷贝一个变量。
1 | connect(btn1, &QPushButton::clicked, this, [a]() mutable{ |
其结果和[=]
一样,[=]
的范围更大,指定变量肯定范围小。
mutable我们后面讲
要想改变可以通过&
引用的方式
1 | connect(btn1, &QPushButton::clicked, this, [&](){ |
这里倒是改变了,但是如果你在connect后面在打印个a,嘿嘿,就会看到好玩的,原因就是有些东西先编译好了,局部变量也是在栈上操作的。
那么常见的还有通过this操作:
1 |
|
变量a添加在头文件了,变成类成员。所以通过this,我们让按钮1每按下一次就修改一次成员变量a的值,然后btn2打印出来的也是被修改过后的值。
介么一看,其实lambda是不是感觉还挺简单。
Lambda拓展
也就是之前提到的mutable
这个东西。
1 | connect(btn1, &QPushButton::clicked, this, [=]() mutable{ |
在这个例子中,我们使用了mutable
,如果不加,会有报错,提示a是一个只读变量,不能够被修改。
所以第一个作用是会使得该“值捕获变量”的值,可以在被捕获的值的基础上进行变化。
mutable->type{}()
的用法
1 | int ret = 100; |
这样一来,一些小算术什么的就可以通过表达式完成。
比如斐波那契数列
1 | int x=0,y=1,z=0; |
虽然写法可能让人不习惯哈哈
它的作用我感觉就是让lambda设置返回值
- ->有这玩意才代表有返回值
- ->后面跟的是返回值类型
- 最后的()是调用函数,没有则不是,也就跟上面的相似
而且吧,对于这个匿名表达式,比较实用的还是像咱这样
1 | connect(btn, &QPushButton::clicked, &x_ming, [=](){ |
这样发个带参数的还好弄点
结果也能正常显示。就是会看到男孩说的话多了个双引号,咱传递的时候带双引号是为了表示字符串类型。而打印却多了双引号。
这是因为QString,是qt自己封装的,咱常用的字符串以右值形式存在时是const char*
,想要去掉这个双引号,就是让QString转为char*类型。在文档里面可以搜到,Qstring有一个方法叫toUtf8
返回的是一个QByteArray的字节数组应该是叫,然后搜索这个QByteArray,发现有个叫data的方法返回值是char*,那么就可以在头文件修改一下qDebug() << "男孩:" << str.toUtf8().data() << " 女孩:hai";
此时在输出就没有双引号了。
lambda表达式还能代码变得简洁高效,比如咱之前写的按钮点击然后关闭窗口
1 | connect(btn2, &QPushButton::clicked, this, [=](){ |
并且,如果信号接收者是this,可以直接忽略不写
1 | connect(btn2, &QPushButton::clicked, [=](){ |
ui文件
这就是拖控件用的可视化操作。
其实里面好像算是个xml什么的配置信息,双击这个文件会跳到qt的设计师界面,然后就可以拖拽控件了,包括添加子控件的操作。
从代码上,我们一般都是要先定义一个控件,比如菜单栏,然后在往里面add菜单。可视化自然是省去了一些步骤。
不过学习嘛,总是先苦后甜,不能说有方便的就不去了解了。
资源文件添加
先找到一个带图片或者音频啥的文件,拖到项目路径下,然后在qt里右击项目选择添加新文件
名字看着取,尽量有意义。
这里的前缀我们一般习惯用/
就行了。
然后通过addfiles,去打开这个资源文件,那么他就会把这个路径加载进来。
资源文件最好最好最好跟项目打包,别搞什么外面的资源。
额改了下主题,还是黑色带点感觉哈哈,通过add files之后就能看到多了路径和图片,剩下的就是怎么用,随便举个例子。
用之前可以在ui文件下,拖一个Graphics view到窗口上,咱好设置图片。
1 |
|
从这里的加载资源文件路径可以看到,为什么要把前缀设置成斜杠,这也是比较通俗易懂的方式
额这里偷个懒没搞大小哈哈,只是说可以在这个控件里直接加载咱们的资源文件。随便演示一下
模态对话框和非模态
其实可以一句话概括:
模态对话框打开之后,你是没办法对后面的窗口进行操作的;反之,非模态不会独占这个操作,你仍可以对后面或者其他窗口进行操作。 ——首先基于一个软件
模态对话框的场景一般都是一些配置修改,或者文件操作,这种对下层窗口有影响的肯定是要以模态对话框出现。
而软件中一般都会有一个版本提示的信息,那个就无关紧要,因为你改变其他的对他不会有啥影响。
一个小例子:
先创建个菜单吧,懒得搞按钮没意思。
注意,子菜单要先变成中文可以在属性的text修改
1 |
|
因为没有设置大小姑且这样看,建议自己尝试一下。
非模态的显示就是方法不同,不过因为我们设置在lambda中,所以生命周期结束得太快了会一闪而过,只能通过new到堆区上活的久一些。
1 | connect(ui->actionnew_file, &QAction::triggered, this, [=](){ |
这里比较有意思的是,因为非模态没有阻塞的效果,所以你新建可以创建好多个窗口。
- qt的dialog不止这一种
- 这个dialog其实还有挺多可以设置的
关于中文乱码
这里创建的项目的时候好像选了qmake编译,不知道是不是这个问题,cmake的区别暂时也不知道。除此之外就是选的msvc编译器,没准是这个的锅,因为微软就比较喜欢ANSI和UNICODE这俩,但是实际上utf-8大部分软件用的比较多。
我们的配置一般是,编译器的语言按照china,或者system,对于windows地区选了中国那这俩就一样。
然后下面的textcode 一般也选system,这个选中文的化地下的工具栏会乱码挺恶心的。
然后就是给控件从代码上设置中文加了u8和什么修饰都无效的话,可以尝试下在.pro文件下加入
1 | ##pro文件中,解决中文乱码 |
未完结~待续
总结
瞎写就完事,不知道再翻在百度。
因为ui这种东西自己不做一遍或者不看看视频,理解起来就比较抽象;并且这个方法有很多,不是我这个初学者一句两句能说清的。
网上也有电子书看得进去也可以。