写在前面
本文主要是Qt常用控件与基础记录
常用控件
Qt程序组成有:应用程序类,窗口类
应用程序类有且仅有一个
基础学习
按钮(QPushButton等)
普通按钮
常用其父类QAbstrackButton的信号
新建一个按钮:
QPushButton(QWidget *parent = Q_NULLPTR)
QPushButton(const QString &text, QWidget *parent = Q_NULLPTR)
QPushButton(const QIcon &icon, const QString &text, QWidget *parent = Q_NULLPTR)
常用成员函数:
设置父类:setParent(QWidget *parent)
设置位置:move(int x, int y)
设置大小:resize(int w, int h)
设置文本:setText(const QString &text)
什么情况下不需要手动回收:
- 从QObject类直接或间接派生
- 直接或派生出的类,指定父对象,父对象析构时被释放
单选按钮(QRadioButton)
互相互斥,用groupBox将每组进行分组。
复选按钮(QCheckBox)
可进行多选
专属信号:QCheckBox::stateChanged();//显示
QToolButton按钮、
设置图片和图片大小
ui->last->setIcon(QIcon(":/Image/last.png"));
ui->last->setIconSize(QSize(200,30));
子窗口(QListWidget等)
QListWidget
可通过成员函数addItem()添加一行数据
//添加普通文本
ui->listWidget->addItem("状态栏");
//添加图标,方式1
ui->listWidget->addItem(new QListWidgetItem(QIcon(":/Fail/fail.png"),"失败"));
//添加图标,方式2()
QListWidgetItem *item = new QListWidgetItem(QIcon(":/Fail/affirm.png"), "affirm", ui->listWidget);
QTableWidget
需要先设定表的行列
//1.指定行
ui->tableWidget->setRowCount(50);
//2.指定列
ui->tableWidget->setColumnCount(2);
//设定表头,方式1
QStringList list;
list<<"姓名"<<"性别";
//QStringList list = QStringList()<<"姓名"<<"性别";
ui->tableWidget->setHorizontalHeaderLabels(list);
//设定表头,方式2
QStringList list;
list.push_back("姓名");
list.push_back("性别");
ui->tableWidget->setHorizontalHeaderLabels(list);
QGroupBox
将控件作为一组进行分组
QScrollArea
带滚动条的窗口,当控件过多窗口显示不下时使用
QToolBox
抽屉窗口,在每一页都能添加控件
QTableWidget
表型窗口,可切换窗口
QStackWidget
栈窗口,需要通过其他控件来实现窗口的切换
//通过按钮切换窗口
connect(ui->musicButton,&QPushButton::clicked,ui->stackedWidget,[=](){
ui->stackedWidget->setCurrentIndex(0);});
connect(ui->moiveButton,&QPushButton::clicked,ui->stackedWidget,[=](){
ui->stackedWidget->setCurrentIndex(1);});
connect(ui->dataButton,&QPushButton::clicked,ui->stackedWidget,[=](){
ui->stackedWidget->setCurrentIndex(2);});
文本编辑框(QLineEdit,QTextEdit,QPlainTextEdit)
区别:
- QLineEdit只能显示单行内容
- QTextEdit可显示多行内容
- QPlainTextEdit可显示多行,但是只能显示文本
新建一个文本编辑框:
QTextEdit(QWidget *parent = Q_NULLPTR)
QTextEdit(const QString &text, QWidget *parent = Q_NULLPTR)
常用信号
##标签(QLabel)
可显示文字,图片,动态图片等
//设置图片
ui->testLabel->setPixmap(QPixmap(":/Fail/cancel.png"));
//设置动态图片,只能播放gif类型,不能播放mp4等格式
QMovie *movie = new QMovie(":my/mario.gif");
ui->testLabel->setMovie(movie);
movie->start();//启动播放
文件对话框(QFileDialog)
常用其静态成员函数(Static public members)
- 获得文件名:getOpenFileName
#信号与槽
connect(&对象1,&信号函数,&对象2,&槽函数)
lamda表达式 ={} - ps:信号可以连接信号
##自定义信号
1.void
2.信号可以重载,重载之后需要通过函数指针访问
3.需要关键字:signals
4.不需要函数定义,只需要声明
##自定义槽
1.void
2.信号可以重载,重载之后需要通过函数指针访问
3.一个信号可以链接多个槽
4.槽函数的参数必须来自信号,即槽函数的参数只能少于等于信号的参数个数
函数指针:函数返回值类型 (* 指针变量名) (函数参数列表);void (QPushButton::*p)(int, int)
##QMainWindow
1.菜单栏
2.工具栏
3.浮动窗口
4.状态栏
菜单栏(QMenu):可为menu添加动作,动作为QAction类
- QAction类常用信号:&QAction::triggered
工具栏:菜单栏有的才能设置在工具栏中,将动作从ui界面往上拖动即可 - 在属性的QToolBar设置是否可以拖拽和停靠
浮动窗口(QDockWidget):在ui界面直接拖动添加,可在浮动窗口内添加控件
状态栏(statusBar):用addWidget()添加状态(常用label)
ui->statusBar->addWidget(new QLabel("ok"));
添加图片
为菜单栏添加图片
- 添加资源文件:添加新文件-Qt-Qt Resourse File
- 添加前缀,添加文件
- 为对应控件添加图片,使用setIcon()
ui->action_as->setIcon(QIcon(":添加的前缀+文件路径"));
对话框(QDialog)
-
非模态对话框:可以对其他窗口进行操作
-
模态对话框:不可以对其他窗口进行操作
QDialog dlg(this);//指定父窗口,也可不指定父窗口,不指定时为独立窗口 //显示非模态对话框 dlg.show(); //显示模态对话框 dlg.exec();//运行时,程序会阻塞,停在此处,要关闭对话框后才会继续执行
要使非模态对话框一闪而过,new一个对话框,防止随着父类一起被析构
QDialog *dlg = new QDialog(this); dlg->show(); //设置对话框属性 dlg->setAttribute()
消息对话框(QMessageBox)
常用其静态成员函数(Static public members)
静态成员函数(可根据返回值来设置按下不同按钮的功能):
QMessageBox::about();
QMessageBox::critical();//错误对话框
QMessageBox::warning();//警告对话框
QMessageBox::question();
QMessageBox::information();颜色与字体对话框
颜色对话框(QColorDialog):常用其静态成员函数(Static public members)
字体对话框(QFontDialog):常用其静态成员函数(Static public members)
ps.打印QString类型:font.family().toUtf8().data()
布局
inf: 每个控件都应该进行布局,否则容易被遮挡,显示不出来或显示不全
技巧:布局可以先添加副窗口,然后再将副窗口内进行布局。
添加弹簧可以让窗口发生拉伸时,相对位置不发生变化,还可以设置弹簧为fixed类型,然后设置为固定宽度。
自定义控件
对于使用率较高的功能,可以封装为一个控件(类)。
- add File-Qt-Qt设计师界面类,新建一个自定义类,在该类的ui界面进行编辑。
- 在主窗口添加一个空Widget,再在新建的空Widget内对该窗口进行提升,提升为自定义的类,运行即可实现自定义控件
- 在新建的自定义类中对各控件进行链接
- 再在自定义类中添加对应的接口函数(常见的如SetValue()和GetValue())
ps.重载的信号与槽函数,要通过函数指针类访问
//将滑动条与旋钮控件链接起来
int min =0;
int max = 1000;
ui->horizontalSlider->setRange(min,max);
ui->spinBox->setRange(min,max);
void(QSpinBox::*SigValueChange)(int) = &QSpinBox::valueChanged;
connect(ui->horizontalSlider, &QSlider::valueChanged, ui->spinBox, &QSpinBox::setValue);
connect(ui->spinBox,SigValueChange,ui->horizontalSlider,&QSlider::setValue);
事件
鼠标键盘事件在继承的虚函数中(Reimplemented Protected Functions)
新建一个c++类,并继承对应的控件类,之后将对应的控件提升为新建的C++类,之后即可在新建的C++类中对该控件进行事件处理。
鼠标事件
//继承自Widget类.h文件中声明
protected:
//鼠标进入
void enterEvent(QEvent *);
//鼠标离开
void leaveEvent(QEvent *);
//鼠标按下
void mousePressEvent(QMouseEvent *ev);
//鼠标释放
void mouseReleaseEvent(QMouseEvent *ev);
//鼠标移动
void mouseMoveEvent(QMouseEvent *ev);
//定时器
void timerEvent(QTimerEvent *);
/**************实现**********************/
//.cpp文件中的实现
void MyLabel::enterEvent(QEvent *)
{
setText("进来了");
}
void MyLabel::leaveEvent(QEvent *)
{
setText("你离开了");
}
//鼠标按下
void MyLabel::mousePressEvent(QMouseEvent *ev)
{
//"%1,%2,%3"为占位符,对应后面的第几个arg
if(ev->button() == Qt::RightButton){
QString str = QString("mouseLeftPres(%1,%2)").arg(ev->x()).arg(ev->y());
setText(str);
}
}
//鼠标移动,持续状态使用buttons()函数,
void MyLabel::mouseMoveEvent(QMouseEvent *ev)
{
QString btn;
if(ev->buttons() & Qt::RightButton){
btn = "RightButton";
}
else if(ev->buttons() & Qt::LeftButton)
{
btn = "LeftButton";
}
else if(ev->buttons() & Qt::MidButton)
{
btn = "MidButton";
}
QString str = QString("%3:(%1,%2)").arg(ev->x()).arg(ev->y()).arg(btn);
setText(str);
}
如果需要实时检测鼠标事件(默认不实时追踪),则需要在窗口构造函数中,设置窗口实时追踪鼠标事件
this->setMouseTracking(true);
定时器
声明如上所示
//方法一
//启动定时器,每过xx时间则加1,如需要使用两个定时器则启动两个定时器即可,通过id来访问不同定时器
//参数1:定时器的时间,单位ms
//参数2:默认
//返回值:定时器id
id = startTimer(100);
//关闭定时器
killTimer(id);
//方法二,常用
//包含QTimer头文件
QTimer *timer = new QTimer(this);
timer->start(100);//每100ms触发一次信号
connect(timer, &QTimer::timeout,this,[=](){
static int num = 0;
this->setText(QString::number(num++));
});
定时器常需要使用静态局部变量 static int num=10;静态局部变量在该函数被释放时,不会被释放,直到程序运行结束以后才释放。
- 问题:定义的static变量提示未定义。
- 原因:因为静态成员属于整个类,而不属于某个对象,如果在类内初始化,会导致每个对象都包含该静态成员,这是矛盾的。
- 解决方法:需要在类外添加初始化
//.h声明了一个static bool status时
bool Widget::status = true;
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
}
事件分发器(event())
- 虚函数
- 常用功能:
过滤事件
QObject::event(QEvent *e);//return true则认为已经执行不继续往下运行
//示例,让原来的鼠标按下事件不操作
if(e->enent()==QEvent::MouseButtonPress)
return true;
事件过滤器(eventFilter)
- 虚函数
- 使用方法:
- 为需要过滤的窗口安装过滤器 ui->mylabel->installEventFilter(this);//
- 在事件过滤器函数中进行处理
绘图类和绘图设备(QPainter)
- QPainter->QPainEngine->QPainDevice
画家->引擎(不影响使用)->绘图设备
QPixmap,QImage(图片);QPicture()
画家(QPainter)
重写虚函数 void QWidget::paintEvent(QPaintEvent *);
函数的特点:
- 回调函数
- 此函数不需要用户与调用,再刷新得时候会自行调用
-
- 窗口显示时;
-
- 最大化,最小化
-
- 窗口被遮挡,重新显示得时候
-
- 强制刷新得时候
-
- …
- 如果要使用画家类对窗口中进行画图,操作必须在paintEvent函数中完成
void Widget::paintEvent(QPaintEvent *)
{
QPainter painter(this);//this为给画家painter设置的绘图平台
//painter.load("D:/sunny.png"); //加载图片
painter.drawPixmap(10,10,QPixmap(":/Fail/affirm.png"));
}
- 画笔类(QPen)
- 创建画笔 (QPen pen;)
- 设置画笔属性(颜色,笔的粗细,)
- 画家拿笔(painter.setPen(pen);)
- 刷子(QBrush)
- 可以填充,包括填充图片pixmap,文字等
实时刷新窗口
updata();//推荐使用,原理:调用updata就会运行了paintEvent()
repaint();
绘图设备
QPixmap,QImage,QPicture,QBitmap
即画布,各画布的特点:
- QPixmap:主要用来显示,依赖平台(加载到内存中格式会变),只能用于UI线程
- QImage:不依赖平台,可在多线程中操作
- QBitmap:黑白图片显示,参考QPixmap
//QPixmap
QPixmap pix(300,300);//纸张大小
QPainter painter(&pix);//新建画家
/***可以新建画笔进行设置***/
painter.drawRect(10,10,200,230);//画图
pix.save("D:/myPixmap.jpg");//保存图像
//QImage
QImage img(200,200,QImage::Format_RGB32);
//更改绘图设备,因为之前画笔在pix上,所以要重新指定绘图设备
painter.begin(&img);
/**/
painter.end();
不规则窗口
this->setWindowFlags(Qt::FramelessWindowHint)//去窗口边框
#文件
文件读取和写入(QFile)
操作:
- 创建文件对象 QFile file(filename);
- 指定打开方式 file.open(QFile::ReadOnly);
- 读文件内容 QByteArray context = file.readAll();//read(),readline()
QString fileDir = "E:/Project/qt/lesson6";
static QString fileName;
connect(ui->pushButton,&QPushButton::clicked,this,[=]()
{
fileName = QFileDialog::getOpenFileName(this, "Open File", fileDir);
ui->lineEdit->setText(fileName);
//新建文件对象
QFile file(fileName);
//设置文件打开方式
bool readOk = file.open(QIODevice::ReadOnly);
if(!readOk)
{
QMessageBox::critical(this, "Error", "文件打开失败");
}
//读文件
QByteArray context = file.readAll();//返回为utf8格式
QByteArray context += file.readLine();
ui->textEdit->setText(context);
file.close();
});
//写文件
connect(ui->writePushButton,&QPushButton::clicked,this,[=]()
{
QString newContext = ui->textEdit->toPlainText();
QFile file(fileName);
bool writeOk = file.open(QIODevice::WriteOnly);
if(!writeOk)
{
QMessageBox::critical(this, "Error", "文件写入失败");
}
file.write(newContext.toUtf8());
//file.write()
});
文件流
- QTextStream //操作数据类型:int float string
- QDataStream //QImage QPoint QRect ;不依赖平台,应用于大量数据传输
QTextStream myStream(&file); //将文件对象的地址传给流
myStream.setCodec("utf8"); //设置流的编码格式为utf8
while(false == myStream.atEnd())
{
QString context = myStream.readAll();
ui->textEdit->setText(context);
}
//写文件
myStream<<QString("通过流写入的字")<<1234;
QDataStream用法一样,而且可以对内存进行操作
//对内存进行读写
QImage img("D:/test.png");
QByteArray buf;
QDataStream dataStream(&buf, QIODevice::ReadWrite);
dataStream<<img;//将图像写入内存空间
文件属性
QFileInfo info(“D:/lucy.png”);
成员函数:info.size();info.path();…
#include <QDtateTime>
//打印最后修改时间要包含头文件和按输出的格式写好
info.lastModified().toString("yyyy-MM-dd hh:mm:ss");
通信协议(TCP,UDP)
##TCP通信
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Hev9Ndpp-1626187970133)( https://i.loli.net/2021/05/04/6Q3h5sLUGl7f1xI.jpg)]
QTcpServer类(需要工程包含network)
QTcpSocket类
使用
//.pro
QT += network
//main函数
Client c;
c.setWindowTitle("客户端");
c.show();
Server w;
w.setWindowTitle("服务器");
w.show();
//服务端
/*服务端需要一个监听对象指针和一个链接指针,首先新建服务端对象,再监听,如果有链接则会收到信号,然后通过链接指针接收请求,之后就可以通过链接指针进行发送和接收数据了(write和read)*/
ui->ip->setText("127.0.0.1");
ui->port->setText("6000");
//实列化
server = new QTcpServer(this);
//监听
server->listen(QHostAddress(ui->ip->text()), 6000);
//链接
connect(server,&QTcpServer::newConnection,this,[=]()
{
//接收客户端的套接字
ui->recordTextEdit->setText("有新的链接....");
conn = server->nextPendingConnection();
//发送数据
//conn->write("服务器发的数据");
ui->recordTextEdit->setText("有新的链接....");
//接收数据
connect(conn,&QTcpSocket::readyRead,this,[=]()
{
QByteArray array = conn->readAll();
ui->recordTextEdit->append("客户端me:"+array);
});
});
//发送消息
connect(ui->sendButton,&QPushButton::clicked,this,[=]()
{
conn->write(ui->textEdit->toPlainText().toUtf8());
ui->recordTextEdit->append("Host:" + ui->textEdit->toPlainText().toUtf8());
ui->textEdit->clear();
});
//客户端
/*客户端只需要链接指针,首先实列化链接指针,然后链接上服务器,即可进行文件读写*/
ui->ip->setText("127.0.0.1");
ui->port->setText("6000");
client = new QTcpSocket(this);
//链接服务器
client->connectToHost(QHostAddress(ui->ip->text()), 6000);
//ui->recordTextEdit->setText("链接上服务器");
//接收数据
connect(client,&QTcpSocket::readyRead,this,[=]()
{
QByteArray array = client->readAll();
ui->recordTextEdit->append("Host:" + array);
});
//发送数据
connect(ui->sendButton,&QPushButton::clicked,this,[=]()
{
client->write(ui->textEdit->toPlainText().toUtf8());
ui->recordTextEdit->append("me:" + ui->textEdit->toPlainText());
ui->textEdit->clear();
});
! 192.0.0.1无法设置为端口,应该是被占用了
UDP
无客户端和服务器概念,直接发信息到端口即可
QUdpSocket类
服务端和客户端都要进行:
- bind套接字,并设置ip和端口
- 发数据writeDatagram和接收数据readDatagram
//服务器端
ui->sIp->setText("127.0.0.9");
ui->cPort->setText("2222");
ui->sPort->setText("3333");
udp = new QUdpSocket(this);
//链接
if(udp->bind(ui->cPort->text().toInt()))
{
ui->record->setPlainText("链接成功,服务器端口为:"+ui->sPort->text().toUtf8());
}
//send msg to server
connect(ui->send,&QPushButton::clicked,this,[=]()
{
udp->writeDatagram(ui->message->toPlainText().toUtf8(),QHostAddress(ui->sIp->text()),ui->sPort->text().toInt());
});
//recieve msg from server
connect(udp, &QUdpSocket::readyRead, this, [=]()
{
char data[4096];
qint64 size = udp->pendingDatagramSize();
udp->readDatagram(data, size);
ui->record->appendPlainText(data);
});
//客户端只需要将bind的端口换为客户端端口,发送数据的端口换成服务器端口即可
?发送大文件采用udp怎么分配内存,实现udp传输一个视频
disconnectFromHost
线程
多线程
继承QThread类
在程序处理过程中有个复杂过程,假设sleep()了,如果只有一个线程则程序直接死了,所以要采用多线程,默认线程为主线程(或称ui线程)
4.7版本之前多线程操作
- 重写一个类,继承QThread(在进行操作时,新建一个继承QObject的类,然后改为继承QThread)
- 在类内重写线程处理虚函数run()——又称入口函数,run函数处理这个线程的工作
- 重写run()函数后调用自定义信号sigDoen
- 在主线程可以通过实列化后的myThread的对象调用start()函数来启动子线程,也可通过对实列化后线程对象的信号来实现传参。
!只有run函数是在新线程里面,如果新建的类里有其他函数,他们与ui线程下的其他函数是出于一个线程的
4.7版本后能使用,较灵活
继承QObject类
- 写一个继承QObject的类,将需要进行复杂操作的入口函数声明为槽函数,在主线程中new一个对象objThread,不指定父对象,因为有父对象后不能move
- 声明一个QThread对象newThread
- 通过moveToThread方法把新建的objThread对象转移到新线程objThread.moveToThread(newThread)
- 把线程的finished信号和obj的deleteLater槽函数连接,必须连接不然会内存泄漏
- 正常连接其他信号与槽(在连接之前调用moveToThread)
- 调用QThread::start()启动线程
- 处理完逻辑,调用QThread::quit()退出事件循环,一般会调用QThread::wait()函数等线程结束,再释放内存。
(个人理解:新建一个object类写好子线程功能,然后创建对象,然后将该对象move到一个新建的线程Qthread对象中,之后启动新建的线程(只启动了线程,未调用函数功能),再通过自定义信号和槽的方式调用函数功能,最后通过quit()退出新建的进程)
**怎么理解基于QObiect类的多线程:
将一个普通obj对象move到一个子线程中,之后直接链接主线程的信号,即可实现对obj中的
子线程槽函数的处理,并在槽函数处理完后发出一个子线程处理结束信号,通过子线程处理结束信号,让子线程对象退出,并在子线程对象退出后删除obj对象
connect(myThread,&QThread::finished,obj,&QObject::deleteLater);//子线程处理结束后,删除obj对象,因为obj没有继承窗口,不手动删除会导致内存泄漏
connect(ui->begin,&QPushButton::clicked,obj,&objThread::somethingTodo);//通过主线程控制子线程中的复杂操作开始
connect(obj,&objThread::end,this,[=]()
{
myThread->quit();
myThread->wait();
});//子线程处理结束,退出子线程
myThread->start();//启动子线程
实战训练
1.计算器
- 通过lamda表达式来调用槽函数OnClick(type _type, QString _btn)。type为按钮类型(数字,操作运算符,等于,清除,退格,小数点),_btn为按钮的数。
- OnClick(type _type, QString _btn)通过switch来判断类型,处理对应逻辑。
- 在进行显示时,用字符串操作,只有在进行运算时将字符串转为double,并在最后转回字符串
- 显示栏实时显示textEdit->setText(num1 + op + num2)
需要用到的函数:data.chop(1) 删除字符串最后一位
链接:https://github.com/dawsoncaime/QT_EXAM
2.推箱子游戏
- 放置按钮并将加载图片至按钮
- 添加开始,重新开始等按钮控制逻辑
- 绘制背景地图,并读取地图文件lv1.txt,通过QString和QStringList实现对地图文件的读取,并通过QPainter绘制。
//void Widget::paintEvent(QPaintEvent *) QPainter painter(this); //绘制背景 painter.drawPixmap(rect(), QPixmap(":/Image/ground.png")); ...
- 处理人物移动逻辑,监听键盘事件
//void Widget::keyPressEvent(QKeyEvent * event) switch (event->key()) { case Qt::Key_W:{ pos_next = pos + QPoint(0, -1); if(!pos_wall.contains(pos_next)){ pos = pos_next; } break; } ... }
- 添加其他函数,如判断箱子是否进入洞里等
扩展:考虑如何添加提示按钮,在玩家不知道如何走的时候告知玩家下一步走哪
链接:https://github.com/dawsoncaime/QT_EXAM
发布程序
- 在开始菜单中找到Qt 5.3 for Desktop (MinGW 4.8 32 bit)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ssgdWztt-1626187970135)(https://i.loli.net/2021/07/13/SUFjnY964Hkzucy.jpg)] - 将写好的工程以release的方式编译,会生成一个名字带release的文件夹在工程目录下
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7e2H9chL-1626187970136)(https://i.loli.net/2021/07/13/1DRI2UNrdLnvsTu.png)] - 可以将其中的exe程序拷贝至其他位置,打开第一步中的Qt 5.3 for Desktop (MinGW 4.8 32 bit),运行windeployqt sokoban.exe
其中sokoban.exe为你的程序名字
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DpJpZI4R-1626187970137)(https://i.loli.net/2021/07/13/wYThUq9o82edlau.png)]
这样就配置好了对应的库文件