Qt入门
安装了Qt 6.1,记录一下自己学到的内容,梳理思路。(更新中)
因为只是自己的理解,所以可能有错误及不准确的地方,请多指正。
一、简单的界面设计
1.概述
用Qt Creater创建一个项目,会生成 .pro 文件、.h 文件和 .cpp 文件。
.pro 文件记录了该项目使用的编译器,以及包含的头文件、源代码有哪些等信息。
.h 文件和 .cpp 文件就是面向对象设计的思路。将类的声明放在 .h 文件中,将类函数的实现放在 .cpp 文件中。
main.cpp 会生成一个 QApplication 对象以及一个类,类呈现用户的设计,QApplication 对象控制程序的进行。
#include "textchange2.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
TextChange2 w;
w.show();
return a.exec();
}
自己设计的类可以继承 QWidget 、QMainWindow 、QDialog 三个顶层窗体之一,独立显示。
在类里面先加入 Q_OBJECT 的宏,这样才可以使用元对象系统、信号/槽机制。类里面可以通过 "signals:" 和 "slots:" 来声明信号和槽。一般我会在这个类的生成函数里调用 iniUI(), iniSignalSlots(), iniAction() 等,把一些组件的设置、信号与槽的链接等写在这些函数里。
如果要把某个类联系到UI界面(创建时选择了Generate Form),在对应的头文件里把该类加入 Ui 命名空间
QT_BEGIN_NAMESPACE
namespace Ui { class QmyWidget; }
QT_END_NAMESPACE
然后在私有成员中声明 ui 指针
private:
Ui::QmyWidget *ui;
最后在构造函数里创建 ui 实例,接下来就可以通过 ui->... 来调用 UI 界面中的对象了。析构函数里删除 ui 指针。
QmyWidget::QmyWidget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::QmyWidget)
{
ui->setupUi(this);
}
QmyWidget::~QmyWidget()
{
delete ui;
}
直接在 ui designer 里通过拖动组件等方式进行的设计会体现在编译后生成的 ui_***.h 文件中。
2.信号与槽
有三种写法,下面是例子
void QWMainWind::iniAction()
{
//connect(ui->actPaste,SIGNAL(ui->actPaste->triggered()),ui->txtEdit,SLOT(ui->txtEdit->paste()));
connect(ui->actPaste,&QAction::triggered,ui->txtEdit,&QTextEdit::paste);
//connect(ui->actCopy,SIGNAL(triggered()),ui->txtEdit,SLOT(copy()));
connect(ui->actCopy,&QAction::triggered,ui->txtEdit,[&](){ui->txtEdit->copy();});
connect(ui->actCut,SIGNAL(triggered()),ui->txtEdit,SLOT(cut()));
connect(ui->actClose,SIGNAL(triggered()),this,SLOT(close()));
connect(ui->actClear,SIGNAL(triggered()),ui->txtEdit,SLOT(clear()));
}
如果用 SIGNAL() 和 SLOT() ,函数参数类型必须写上;如果用 &QAction::triggered 这种写法就无须写函数参数,因此有时会比较方便。还可以用 Lambda 表达式,较灵活。
3.在无Qt环境下运行写好的程序
https://blog.csdn.net/qq_44977889/article/details/107693034
4.写一个项目时踩的坑与收获
(1)指针对象初始化
指针对象一定要记得初始化!用 new 给它们分配空间。
不然的话可能报错 "no reference to ..." 或者闪退并提示 "the process was ended forcefully".
没有找到指针数组的一次性初始化的方法。也许只能给里面的对象挨个初始化。
(2)设置QPushButton背景为透明
在QPushButton上加载了图片之后,图片未填满的地方是白色背景,很显眼。
https://blog.csdn.net/weixin_44100850/article/details/90521859
(3)自定义按钮类的显示
只是自定义了一个按钮的话,它是不会显示在窗口上的。
在作为主界面的类里将该自定义类的对象的父亲设置为 centralWidget 才行。
比如我在自定义的类 myPos 里声明了 QPushButton 实例 button,那么我在继承了 QMainWindow 的类里面需要这样写(pos[]是myPos的实例):
pos[cur].button = new QPushButton(centralWidget());
但是这样虽然能让 QPushButton 上的图片显示出来,点击按钮这个事件仍然没有反应……
终于发现我是在 myPos 类里声明了 QPushButton 对象,但重载函数、信号等都写在 myPos 类里面。即使 myPos 类继承的是 QPushButton ,但重载的都是 myPos 而不是自己在用的那个 QPushButton !脑袋要清楚呢!
那应该在哪里重载这 QPushButton 呢?其实在继承 QMainWindow 的那个类里面重载即可。刚才那个语句也是把这个 button 的父亲设成了这个类。
方法一:把定义出的这些 button 依次重载一遍。
方法二:自定义 myPushButton.
方法三:lambda函数!好处是可以在连接的时候直接把参数写上。
connect(pos[cur].button,&QPushButton::clicked,this,[=](){btnPressed(cur);});
(4)停靠窗口QDockWidget的显示
在定义了自己的QDockWidget之后(假设名字为mydock),记得在继承QMainWindow的那个类里面调用addDockWidget(Qt::TopDockWidgetArea,mydock)!这里Qt::TopDockWidgetArea需要头文件<QMainWindow>,将窗口停靠在顶部。还可以用RightDockWidgetArea使其停靠在右侧之类。
QDockWidget的大小取决于内部组件的大小。一种写法是给QDockWidget设置最大、最小高度和宽度以设置其大小,另一种(感觉更优美的)写法是定义一个QWidget(假设名字为dockContents),然后把各种组件都加入 dockContents 里面,最后 mydock->setWidget(dockContents).这样可以直接调整mydock的大小而把QDockWidget“撑”起来。
此外要注意,setGeometry的位置是相对于自己的父亲而言的。
(5)关于QString
自己写了这样的初始化:
QString name[15]={
"Flag","Mine","Engineer","Platoon"
,"Company","Battalion","Head","Brigadier"
,"Division","Army","Commander","Bomb"
};
但是调用 name[0] 的时候会异常退出,报出 "D:\SogouInput\Components" 。最后还是一个一个赋值了。仍不知为何上面那种方法不行。
name[0]="Flag"; name[1]="Mine"; name[2]="Engineer";
name[3]="Platoon"; name[4]="Company"; name[5]="Battalion";
name[6]="Head"; name[7]="Brigadier"; name[8]="Division";
name[9]="Army"; name[10]="Commander"; name[11]="Bomb";
(6)用QTCPSocket读写数据的waitFor
发现不是一次write()引起一次readyRead(). 自己把数据长度和数据本身用两个句子write(),结果对面还是一次readAll()就全读完了。
https://blog.csdn.net/asklw/article/details/71591763
于是开始想如何把数据长度弄成4个字节。但还是不太成功。
最后改成按行读写。在写入的数据后面加'\n',读的时候用readLine().
记得在write()之后加一个waitForBytesWritten(),让数据写完之后再继续后面的操作。
也要记得如果需要的话在connect()之后加一个waitForConnected()!怪不得总是显示“连接失败”弹窗,但是实际上却连接成功,原来是因为我连接了connected信号和isConnected=1操作,然后在connect()请求之后写了一个if(isConnected==1){},但是在执行这个if语句的时候可能还没来得及连上。去掉那个信号/槽,改成isConnected=...->waitForConnected(),就没问题了~
当然,在disconnectFromHost()后面也可以写一个waitForDisconnected().
注意,除非是要利用构造函数设定某个对象的父亲,否则尽量在声明指针的时候或在类的构造函数里就给它划好空间,否则极容易出现野指针。
connect(...)也尽量在类的构造函数里写好,而别写在“创建服务器”函数里。否则如果创建两次的话就会出问题。
(7)用QTcp时连接信号/槽语句的位置
https://blog.csdn.net/qq_42784403/article/details/104349362
原来应该在QTcpSocket被赋值为nextPendingConnection之后再连接信号/槽!
仔细一看,nextPendingConnection()是一个 *QTcpSocket 类型的,怪不得!
(8)QTcpSocket的readyRead()使用注意
自己的程序将readyRead()信号与recvMessage()函数连接。在recvMessage()函数中,如果message是“点击”操作,那么就去执行点击指令。
数据是按行读写的,每条数据后面加了一个'\n'。每次写完之后都会waitForByteWritten().
但是出现问题:server端先“点击”,第一次点击的指令没有传到client端。但是client端的指令能传到server端。此后,server端每一次“点击”,传到client端的都恰是其上一次点击的数据。
现在的猜想是当readyRead()信号发出的时候,自己的程序可能还在执行“startNewRound()”之类的函数,所以无法开始执行“recvMessage()”.
在recvMessage()里面写上:
while(readWriteSocket->bytesAvailable())
{
...
}
就没有再发生那种问题了!
这篇博客把QTcpSocket的收发机制解释得很清楚!
https://blog.csdn.net/dengdew/article/details/79065608
QTcpSocket的flush()函数可以将缓冲区的数据立刻发送。waitForBytesWritten()则是等待bytesWritten()信号,该信号在有数据被写入缓冲区时触发。
所以刚才那个问题,通过bytesAvailable(),而是在waitForBytesWritten()后面再写上flush(),也能解决问题!
(9)QMessageBox阻塞线程
用QMessageBox会阻塞线程。因为每回合都要计时,所以现在变成双方在第一回合的时刻始于各自关掉“你的颜色是……”的消息框的时刻,也就是没有同步。
其实只要把开表的操作放到弹窗之前就行了。停表的操作也放到弹窗之前。总之就是一组操作的最后一步才是弹窗,关闭弹窗后这组操作也就结束,这样的思路比较好。