QT学习笔记
#### 一.QT目录结构
1.FirstDemo.pro
#-------------------------------------------------
#
# Project created by QtCreator 2022-08-08T11:15:30
#
#-------------------------------------------------
QT += core gui // QT包含的模块
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets // 大于版本4以上,包含widget模块
TARGET = FirstDemo // 生成的exe程序名称
TEMPLATE = app // 应用程序模板
SOURCES += main.cpp\
widget.cpp
HEADERS += widget.h
FORMS += widget.ui
2.main.cpp
#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
// a应用程序对象
QApplication a(argc, argv);
// 窗口对象 Widget父类 -> QWidget
Widget w;
w.show();
// 让应用程序对象进入消息循环,阻塞在这一行,等待消息
return a.exec();
}
3.widget.h
#ifndef WIDGET_H // 相当于#pragma once
#define WIDGET_H
#include <QWidget>
namespace Ui {
class Widget;
}
class Widget : public QWidget // widget继承于QWidget窗口类
{
Q_OBJECT // Q_OBJECT宏,允许类中使用信号和槽的机制
public:
explicit Widget(QWidget *parent = 0); // 有参构造函数
~Widget(); // 析构函数
private:
Ui::Widget *ui;
};
#endif // WIDGET_H
4.widget.cpp
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget) // 初始化列表
{
ui->setupUi(this);
}
Widget::~Widget()
{
delete ui;
}
二.QPushButton控件
QPushButton* btn = new QPushButton(this);
// QPushButton* btn = new QPushButton("点击", this);
// 设置文本
btn->setText("点击");
// 显示控件,可省略
btn->show();
// 移动
btn.move(200, 100);
// 按钮控件自定义大小
btn.resize(50, 30);
// 设置窗口大小
this->resize(600, 400);
// 设置固定窗口大小
this->setFixedSize(600, 400);
// 设置窗口标题
this->setWindowTitle("例子");
三.对象树
1.当创建的对象在堆区的时候,如果其指定的父亲是QObject派生类或者其子类的派生类,可以不用管new对象以后的释放操作,该对象会放入对象树中。
2.一定程度上简化了内存回收机制,再关闭窗口时,其子类将会被自动释放。
注:调试打印
// QDebug
#include "QDebug"
qDebug() << "打印调试信息: " << message; // 会自动换行
四.信号和槽
1.信号和槽
连接函数:connect ( )
参数: 1 信号的发送者(发送端)
2 发送的信号(函数地址)
3 信号的接收者(接收端)
4 处理的槽函数(函数地址)
松散耦合:信号的发送端和接收端本身是没有关联的,他们是通过connect()连接,将两端耦合在一起。
// 信号和槽
connect(btn, &QPushButton::clicked, this, &QWidget::close);
2.自定义信号和槽
// 触发信号
emit p->hungry();
1.自定义信号:
(1)写到 signals 下
(2)返回void,需要声明,不需要实现
(3)可以有参数,可以重载
2.自定义槽
(1)写到 public slot 下
(2)返回void,需要声明,也需要实现
(3)可以有参数,可以重载
// 自定义信号和槽
this->sgl = new mySignals(this);
this->slt = new mySlot(this);
// 绑定信号和槽
connect(sgl, &mySignals::mySgl, slt, &mySlot::mySlt);
// 触发
emit this->sgl->mySgl();
3.自定义信号和槽发生重载
// 自定义信号和槽的重载
this->sgl = new mySignals(this);
this->slt = new mySlot(this);
// 函数指针 ---> 函数地址
void (mySignals:: *mySignal_sgl)(QString) = &mySignals::mySgl;
void (mySlot:: *mySlot_slt)(QString) = &mySlot::myslt;
// 绑定
connect(sgl, mySignal_sgl, slt, mySlot_slt);
注意:
QString类型输出的时候会默认加上 ” “,双引号。
解决办法:
**QString 转为 char ***
QString str; // 先利用 .toUtf8() 转为 QByteArray // 再利用 .Data() 转为 char* str.toUtf8().data()
拓展:
1、信号是可以连接信号
2、一个信号可以连接多个槽函数
3、多个信号可以连接同一个槽函数
4、信号和槽函数,参数类型必须一一对应,信号函数的参数个数大于等于槽函数
5、断开连接:disconnect ( 参数一致 )
五.Lambda表达式
[ ] ( ) {
} ( ) ;
>[ ] 里的参数:
>
> **=:值传递(推荐)**
>
> &:引用传递
>
>( ) 重载参数:
>
> 标识重载的 ( ) 操作符的参数
注意:
// 1.
// mutable 声明,在需要对值传递进来的拷贝进行修改时,加上mutable关键字,否则会报错
// 注意是能修改拷贝,而不是值本身。
// 按钮1
QPushButton* btn1 = new QPushButton("按钮1", this);
btn1->move(240,100);
// 按钮2
QPushButton* btn2 = new QPushButton("按钮2", this);
btn2->move(240,200);
// 窗口大小
this->resize(600, 400);
int m = 10;
// 绑定信号和槽
connect(btn1, &QPushButton::clicked, this, [m]() mutable {m+=1; qDebug()<<m;});
connect(btn2, &QPushButton::clicked, this, [=](){qDebug()<<m;});
// 2.
// lambda表达式若有返回值
int ret = [] () -> int {
return 10;
}()
总结:
信号和槽注意点:
-
发送者和接受者都需要时Object子类 ( 槽函数是全局函数 / lambda表达式无需接受者除外 );
-
使用 signals 标记信号时,信号是一个函数的声明,返回 void ,不需要实现;
-
槽函数是普通的成员函数,会受到 public、private、protected 的影响;
-
使用 emit 发送信号
六.QMainWindow
1.QMenuBar、QToolBar、QLabel、QStatusBar、
{
this->resize(800,500);
// 菜单栏 QMenuBar
QMenuBar* menubar = new QMenuBar();
// 放入窗体中
setMenuBar(menubar);
// 添加菜单项
QMenu* fileBar = menubar->addMenu("文件");
menubar->addMenu("编辑");
// 添加菜单项子项
fileBar->addAction("新建");
fileBar->addAction("打开");
QLabel* label = new QLabel("1234567", this);
label->move(123,123);
}
七.常用控件---面向百度
八.自定义控件
-
Add New --> Qt --> Qt设计师界面类(.ui .cpp .h) --> 选择界面
-
在新ui文件中设计窗口
-
按照新ui类型(QWidget),在主ui界面中放置一个Widget框,右击提升为,填写类名与.h名,注意大小写,添加,提升
// SPinBox change Slider void(QSpinBox:: *mySpinBox)(int) = &QSpinBox::valueChanged; connect(ui->spinBox, mySpinBox, ui->horizontalSlider, &QSlider::setValue); // Slider change SPinBox connect(ui->horizontalSlider, &QSlider::valueChanged, ui->spinBox, &QSpinBox::setValue);
-
举例
smallWidget.h
public: int getNum(); void setNum(int num);
smallWidget.cpp
int SamllWidget::getNum() { qDebug() << "getNum function"; return ui->spinBox->value(); } void SamllWidget::setNum(int num) { qDebug() << "setNum function"; ui->spinBox->setValue(num); }
widget.cpp
// click btn_get connect(ui->pushButton_get, &QPushButton::clicked, this, [=](){ int num = ui->widget->getNum(); QString str = QString::number(num); qDebug() << str; ui->lineEdit_1->setText(str); }); // click btn_set connect(ui->pushButton_set, &QPushButton::clicked, this, [=](){ QString str = ui->lineEdit_2->text(); ui->widget->setNum(str.toInt()); });
注意逻辑:我在自定义控件里提供了可以获取控件值的接口,然后再主界面里用 ui->widget->getNum() 就可以拿到自定义控件中的值了。
九.Qt中的鼠标事件
-
对鼠标事件进行处理时,通常要重新实现以下几个鼠标事件处理函数:
-
设置鼠标追踪
// 鼠标追踪,默认为false,不会一进入就触发 // true 一进入就会触发 setMouseTracking(true);
// 鼠标移动 QWidget::mouseMoveEvent(QMouseEvent *e); // 鼠标按下 QWidget::mousePressEvent(QMouseEvent *e); // 鼠标释放 QWidget::mouseReleaseEvent(QMouseEvent *e); // 鼠标双击 QWidget::mouseDoubleClickEvent(QMouseEvent *e); // 鼠标滚轮事件 QWidget::QWheelEvent(QWheelEvent *e);
注意:在小部件内按下或释放鼠标按钮或移动鼠标光标时,会发生鼠标事件。
关于 小部件 :建个 .cpp 与 .h 文件,然后将需要的控件提升为一个小部件。
-
拉个label,新建 .cpp 和 .h 文件,将新建的文件继承的类与label继承的改为一致(.h .cpp 有三处需要修改)。
-
myLabel.h
public: // 重写鼠标事件 // 鼠标移动 void mouseMoveEvent(QMouseEvent *e); // 鼠标按下 void mousePressEvent(QMouseEvent *e); // 鼠标释放 void mouseReleaseEvent(QMouseEvent *e); // 鼠标双击 void mouseDoubleClickEvent(QMouseEvent *e);
-
myLabel.cpp
// 鼠标移动 void myLabel::mouseMoveEvent(QMouseEvent *e) { QString opt = QString("x = %1 , y = %2 , globalX = %3 , globalY = %4").arg(e->x()).arg(e->y()).arg(e->globalX()).arg(e->globalY()); qDebug() << opt; } // 鼠标按下 void myLabel::mousePressEvent(QMouseEvent *e) { qDebug() << "mousePressEvent"; } // 鼠标释放 void myLabel::mouseReleaseEvent(QMouseEvent *e) { qDebug() << "mouseReleaseEvent"; } // 鼠标双击 void myLabel::mouseDoubleClickEvent(QMouseEvent *e) { qDebug() << "mouseDoubleClickEvent"; }
十. 定时器(两种方式)
- 利用事件 virtual void timerEvent ( QTimerEvent * e );
- 启动定时器 int id1 = startTimer ( 1000 ) 单位毫秒;
- 开启定时器的唯一标识id, 可以与事件对象e的 e->timerid 作比较;
// .h 定时器函数重写
void timerEvent(QTimerEvent *e);
int id1;
int id2;
// .cpp 定时器处理函数
void Widget::timerEvent(QTimerEvent *e)
{
if(e->timerId() == id1)
{
static int num1 = 1;
qDebug() << QString::number(num1++);
}
if(e->timerId() == id2)
{
static int num2 = 1;
qDebug() << QString::number(num2++);
}
}
// 调用
id1 = startTimer(1000);
id2 = startTimer(2000);
- 利用QTimer新建一个定时器对象; Qtimer timer = new Timer(this);*
- 启动定时器; timer -> start ( 1000 );
- 为启动的定时器添加响应函数,即信号 ( QTimer::timeout ) 与槽 ( lambda 表达式 );connect ( timer, &QTimer::timeout, [=] ( ) { } );
#include <QTimer>
QTimer* timer = new QTimer(this); // 创建定时器对象
timer->start(50); // 开启定时器
connect(timer, &QTimer::timeout, [=](){ // 定时器触发时做出响应
static int num = 1;
if(num<=100)
{
qDebug() << QString::number(num++);
}else
{
timer->stop();
}
});
十一.事件分发器 与 事件过滤器
事件过滤器:
- 给小部件安装事件过滤器;
- 重写 eventFilter 事件
// 1.给label组件安装事件过滤器
ui->label->installEventFilter(this);
// 2.重写事件过滤函数
bool eventFilter(QObject* obj, QEvent* e);
bool Widget::eventFilter(QObject* obj, QEvent* e)
{
// 拦截鼠标按下事件
if(e->type() == QEvent::MouseButtonPress)
{
qDebug() << "filter mouse button press.";
return true;
}
// 其余事件交给父亲QWidget处理
return QWidget::eventFilter(obj, e);
}
补充:
// 若父类想往子类转
QEvent* e;
QMouseEvent* ev = static_cast<QMouseEvent *>(e);
QEvent *event;
QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
十二.绘图事件
// .h
void paintEvent(QPaintEvent* event);
// .cpp
#include <QPainter>
void Widget::paintEvent(QPaintEvent* event)
{
// 实例化 画家对象
QPainter painter(this);
// 设置画笔颜色
QPen pen(QColor)
// 线
painter.drawLine(QPoint(0, 0), QPoint(200, 200));
// 圆和椭圆
painter.drawEllipse(QPoint(100, 100), 80, 80);
// 画矩形
painter.drawRect(QRect(20, 20, 50, 50));
// 画文字
painter.drawText(QRect(10, 200, 150, 150), "hello world");
}
// 高级设置
// 1. 抗锯齿
painter.setRenderHint();
// 2. 利用画家画图片
painter.drawPixmap(x, y, QPixmap());
十三.文件读写
- 读文件
// 读取全部
connect(ui->pushButton, &QPushButton::clicked, this, [=](){
// open file dialog get file path
QString path = QFileDialog::getOpenFileName(this,"open file", "C:\\Users\\王韩六六\\Desktop");
// set edit text
ui->lineEdit->setText(path);
// read txt file
QFile file(path);
if(!file.open(QIODevice::ReadOnly))
{
return;
}
QByteArray fileArr = file.readAll();
ui->textEdit->setText(fileArr);
file.close();
});
// 一行一行读取全部
QByteArray fileArr;
while(!file.atEnd())
{
fileArr += file.readLine();
}
// 对其他格式gbk读取
QTextCodec* codec = QTextCodec::codecForName("gbk");
ui->textEdit->setText( codec->toUnicode(array) );
- 写文件
if(!file.open(QIODevice::Append)) // 追加的方式
{
return;
}
file.write("1111111111111111111111 \n");
file.close();
- 文件信息获取 QFileInfo
QFileInfo info(path);
qDebug() << "大小:" << info.size(); // 字节大小
qDebug() << "后缀:" << info.suffix();
注意:
-
在将QString写入csv文件时中文乱码
// 获取路径 QString path = QCoreApplication::applicationDirPath(); path = path.section("/", 0, 3) + "/ini/info.csv"; qDebug() << path; // 打开文件 m_file.setFileName(path); if(!m_file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { qDebug() << "Open info.csv fail!"; return; } else { QString data = "id,姓名,年龄,年级,班级,学号,电话,微信\n"; // m_file.write(data.toLocal8Bit()); // }
解决方法:改为 file.write(data.toLocal8Bit()) 即可解决。
十四.数据库
-
Sqllite数据库
void xxxx::init() { // 设置数据库驱动类型 db = QSqlDatabase::addDatabase("QSQLITE"); #if 0 // 设置动态数据库地址 autho str = QCoreApplication::applicationDirPath(); qDebug() << str; #endif // 设置数据库名称,加载数据库 db.setDatabaseName("E:\\QT\\workspace\\data.db"); // 打开数据库 if(!db.open()) qDebug() << "Unable to open database."; }
// 构造数据库对象 QSqlQuery sql(db); // 执行 sql 查询 QString str = QString("delete from student where id = %1").arg(id); sql.exec(str); // 一个一个查询,需要用.next(),以轮询的方式查询下一个,有执行操作返回true,结束返回false while(sql.next()) { }
// 捕捉错误信息 void xxxxx::errorInfo(QSqlQuery sql) { // 捕捉查询过程中发生的错误信息 QSqlError e = sql.lastError(); // e.isValid() 有错误信息返回true,isValid 是可用的 if(e.isValid()) { qDebug() << e.text(); } }
-
单例模式--懒汉版
实现要素:
- 定义一个单例类;
- 要使用类的私有静态指针变量指向类的唯一实例,并用一个公有的静态方法获取该实例;
- 把构造函数定义为 protected 或 private;
private: // 单例模式,一个类对外只有一个对象存在 static StuSql* ptr_instance; // 静态的 私有的 public: static StuSql* GetInstance() { if(nullptr == ptr_instance) { ptr_instance = new StuSql(); } return ptr_instance; }
上述单例,没有释放 ptr_instance 指针,存在内存泄漏问题,改进:
class del { public: ~del() { if(nullptr != StuSql::ptr_instance) { delete StuSql::ptr_instance; StuSql::ptr_instance = NULL; } } }; static del d; // 静态变量会在程序结束时调用它的析构函数
该实现会在程序结束时调用静态变量的析构函数,从而 delete ptr_instance 指针;
在使用这种方法释放单例对象有以下特征:
- 在单例内部定义有专有的嵌套类;
- 在单例内部定义私有的专门用于释放单例对象的静态成员;
- 利用程序在结束时析构全局变量的特征,选择最终的释放时机。
注意点:
- 类内声明的静态成员变量,必须要在类外初始化;
// .h class person { public: static int m_A; static int *m_p; static Person* p; }; // .cpp 初始化 int Person::m_A = 0; int *Person::m_p = new int; Person* Person::p = nullptr;