
虽然Qt不是跨平台的C开发框架Qt的很多能力其实是操作系统提供的只不过Qt封装了系统的API程序是运行在操作系统上的需要系统提供给我们支撑。信号槽中用户进行的各种操作就可能会产生出信号可以给某个信号指定槽函数当信号触发时就能够自动得执行到对应得槽函数事件也非常类似用户进行得各项操作也会产生事件程序员同样可以给事件关联上处理函数当事件触发得时候就能执行对应得代码事件本身是操作系统提供得机制Qt也同样把操作系统事件机制进行了封装拿到Qt中但由于对于事件得代码写起来不太方便于是Qt就又进行了封装才有的信号槽所以信号槽是事件机制得进一步封装而信号槽得底层机制就是事件机制。信号槽搞不定得就需要重写事件处理函数来手动实现事件处理得响应逻辑Qt 事件事件介绍事件是应用程序内部或者外部产生的事情或者动作的统称。在 Qt 中使用一个对象来表示一个事件。所有的 Qt 事件均继承于抽象类 QEvent。事件是由系统或者 Qt 平台本身在不同的时刻发出的。当用户按下鼠标、敲下键盘或者是窗口需要重新绘制的时候都会发出一个相应的事件。一些事件是在用户操作时发出如键盘事件、鼠标事件等另一些事件则是由系统本身自动发出如定时器事件。常见的 Qt 事件如下事件名称描述鼠标事件鼠标左键、鼠标右键、鼠标滚轮鼠标的移动鼠标按键的按下和松开键盘事件按键类型、按键按下、按键松开定时器事件定时时间到达进入离开事件鼠标的进入和离开滚轮事件鼠标滚轮滚动绘屏事件重绘屏幕的某些部分显示隐藏事件窗口的显示和隐藏移动事件窗口位置的变化窗口事件是否为当前窗口大小改变事件窗口大小改变焦点事件键盘焦点移动拖拽事件用鼠标进行拖拽#include label.h #includeQDebug Label::Label(QWidget* parent):QLabel( parent) { } void Label::enterEvent(QEvent *event) { qDebug()eventEvent; } void Label::leaveEvent(QEvent *event) { qDebug()leaveEvent; }自己写一个类然后继承QLabel重写Label的leaveEvent和enterEvent然后得把该label在图像化界面提升成自己得类因为图形化界面里的默认是QLabel我们是写了一个子类来继承QLabel里面才有实现得逻辑头文件和类名等要和我们创建子类时设置得一样才行鼠标按下事件mousePressEvent这个函数按下左键右键滚轮等鼠标按键都能触发#include label.h #includeQDebug #includeQMouseEvent Label::Label(QWidget* parent):QLabel(parent) { } void Label::mousePressEvent(QMouseEvent *event) { if(event-button() Qt::LeftButton) { qDebug()按下左键; } else if(event-button() Qt::RightButton) { qDebug()按下右键; } //当前event对象就包含了鼠标点击位置得坐标 //相对于控件的位置 qDebug()event-x(), event-y(); //相对整个屏幕 qDebug()event-globalX(), event-globalY(); }鼠标释放事件void Label::mouseReleaseEvent(QMouseEvent *event) { if(event-button() Qt::LeftButton) { qDebug()左键释放; } else if(event-button() Qt::RightButton) { qDebug()释放右键; } }双击事件void Label::mouseDoubleClickEvent(QMouseEvent *event) { if(event-button() Qt::LeftButton) { qDebug()双击左键; } else if(event-button() Qt::RightButton) { qDebug()双击右键; }鼠标移动事件#include widget.h #include ui_widget.h #includeQDebug #includeQMouseEvent Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget) { ui-setupUi(this); //Qt默认不对鼠标移动响应因为负载太大要实现得话得手动打开 this-setMouseTracking(true); } Widget::~Widget() { delete ui; } void Widget::mouseMoveEvent(QMouseEvent *event) { qDebug()event-x()event-y(); }滚轮事件#include widget.h #include ui_widget.h #includeQDebug #includeQWheelEvent Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget) { ui-setupUi(this); total 0 ; } Widget::~Widget() { delete ui; } void Widget::WheelEvent(QWheelEvent *event) { totalevent-delta(); qDebug()total; }键盘事件 QShortCut#include widget.h #include ui_widget.h #includeQDebug #includeQKeyEvent Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget) { ui-setupUi(this); } Widget::~Widget() { delete ui; } void Widget::keyPressEvent(QKeyEvent *event) { if(event-key() Qt::Key_A event-modifiers() Qt::ControlModifier) { qDebug()按下了ctrl a 键; } else if(event-key() Qt::Key_A) { qDebug() 按下了A键; } }定时器Qt 中在进行窗口程序的处理过程中经常要周期性的执行某些操作或者制作一些动画效果使用定时器就可以实现。所谓定时器就是在间隔一定时间后去执行某一个任务。定时器在很多场景下都会使用到如弹窗自动关闭之类的功能等。Qt中的定时器分为 QTimerEvent 和 QTimer 这2个类。QTimerEvent类 用来描述一个定时器事件。在使用时需要通过 startTimer() 函数来开启一个定时器这个函数需要输入一个以毫秒为单位的整数作为参数来表明设定的时间它返回的整型值代表这个定时器。当定时器溢出时即定时时间到达就可以在 timerEvent() 函数中获取该定时器的编号来进行相关操作。QTimer类 来实现一个定时器它提供了更高层次的编程接口如可以使用信号和槽还可以设置只运行一次的定时器。#include widget.h #include ui_widget.h Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget) { ui-setupUi(this); //开启定时器事件 this-startTimer(1000); //此处timerId 是一个定时器得身份标识 timerId this -startTimer(1000); } Widget::~Widget() { delete ui; } void Widget::timerEvent(QTimerEvent *event) { //如果一次程序中存在多个startTimer定时器此时每个定时器都会触发timerEvent函数 //所以得先判断一下是哪个定时器触发的 if(event-timerId() ! this-timerId) { //不是忽略 return; } //是 int value ui-lcdNumber-intValue(); if(value 0) { //停止 this-killTimer(this-timerId); return; } value-1; ui-lcdNumber-display(value); }窗口改变事件#include widget.h #include ui_widget.h #includeQDebug #includeQMoveEvent #includeQResizeEvent Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget) { ui-setupUi(this); } Widget::~Widget() { delete ui; } void Widget::moveEvent(QMoveEvent *event) { qDebug()event-pos(); } void Widget::resizeEvent(QResizeEvent *event) { qDebug()event-size(); }Qt 文件Qt 文件概述文件操作是应用程序必不可少的部分。Qt 作为一个通用开发库提供了跨平台的文件操作能力。 Qt提供了很多关于文件的类通过这些类能够对文件系统进行操作如文件读写、文件信息获取、文件复制或重命名等。输⼊输出设备类在 Qt 中文件读写的类为 QFile 。QFile 的父类为 QFileDevice QFileDevice 提供了文件交互操作的底层功能。 QFileDevice 的父类是 QIODeviceQIODevice 的父类为 QObject 。QIODevice 是 Qt 中所有输入输出设备input/output device简称 I/O 设备的基础类I/O 设备就是能进行数据输入和输出的设备例如文件是一种 I/O 设备网络通信中的 socket 是 I/O 设备 串口、蓝牙等通信接口也是 I/O 设备所以它们也是从 QIODevice 继承来的。Qt 中主要的一些 I/O 设备类的继承关系如下图所示:上图中各类的说明如下QFile 是用于文件操作和文件数据读写的类使用 QFile 可以读写任意格式的文件。QSaveFile 是用于安全保存文件的类。使用 QSaveFile 保存文件时它会先把数据写入一个临时文件成功提交后才将数据写入最终的文件。如果保存过程中出现错误临时文件里的数据不会被写入最终文件这样就能确保最终文件中不会丢失数据或被写入部分数据。 在保存比较大的文件或复杂格式的文件时可以使用这个类例如从网络上下载文件等。QTemporaryFile 是用于创建临时文件的类。使用函数 QTemporaryFile::open() 就能创建⼀个文件名唯一的临时文件在 QTemporaryFile 对象被删除时临时文件被自动删除。QTcpSocket 和 QUdpSocket 是分别实现了 TCP 和 UDP 的类。QSerialPort 是实现了串口通信的类通过这个类可以实现计算机与串⼝设备的通信。QBluetoothSocket 是用于蓝牙通信的类。手机和平板计算机等移动设备有蓝牙通信模块笔记本电脑一般也有蓝牙通信模块。通过QBluetoothSocket类就可以编写蓝牙通信程。如编程实现笔记本电脑与手机的蓝牙通信。QProcess 类用于启动外部程序并且可以给程序传递参数。QBuffer 以一个 QByteArray 对象作为数据缓冲区将 QByteArray 对象当作一个 I/O 设备来读写。文件读写类在 Qt 中文件的读写主要是通过 QFile 类来实现。在 QFile 类中提供了一些用来读写文件的方法。对于文件的操作主要有读数据QFile 类中提供了多个方法用于读取文件内容如 read()、readAll()、readLine()等。写数据QFile 类中提供了多个方法用于往文件中写内容如 write()、writeData()等。关闭文件文件使用结束后必须用函数 close() 关闭文件。访问一个设备之前需要使用 open()函数 打开该设备而且必须指定正确的打开模式QIODevice 中所有的打开模式由 QIODevice::OpenMode 枚举变量定义其取值如下QIODevice::NotOpen没有打开设备QIODevice::ReadOnly以只读方式打开设备QIODevice::WriteOnly以只写方式打开设备QIODevice::ReadWrite以读写方式打开设备QIODevice::Append以追加方式打开设备数据将写到文件末尾QIODevice::Truncate每次打开文件后重写文件内容原内容将被删除QIODevice::Text在读文件时行尾终止符会被转换为 \n当写入文件时行尾终止符会被转换为本地编码。如 Win32上为\r\nQIODevice::Unbuffered无缓冲形式打开文件绕过设备中的任何缓冲区QIODevice::NewOnly文件存在则打开失败不存在则创建文件实现一个记事本#include mainwindow.h #include ui_mainwindow.h #includeQPlainTextEdit #includeQFileDialog MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) { ui-setupUi(this); this-setWindowTitle(记事本); //获取到菜单栏 QMenuBar * menuBar this-menuBar(); //添加菜单 QMenu* menu new QMenu(文件); menuBar-addMenu(menu); //添加菜单项 QAction* action1 new QAction(打开); QAction* action2 new QAction(保存); menu-addAction(action1); menu-addAction(action2); //指定一个输入框 edit new QPlainTextEdit(); QFont font; font.setPixelSize(20); edit-setFont(font); this-setCentralWidget(edit); //连接Qaction的信号槽 connect(action1,QAction::triggered,this,MainWindow::handleAction1); connect(action2,QAction::triggered,this,MainWindow::handleAction2); } MainWindow::~MainWindow() { delete ui; } void MainWindow::handleAction1() { //1.弹出一个打开文件的对话框 QString path QFileDialog::getOpenFileName(this); //2.把文件名显示到状态栏 QStatusBar * statubar this-statusBar(); statubar-showMessage(path); //3.根据用户选择的路径构造一个QFile对象 QFile file (path); bool ret file.open(QIODevice::ReadOnly); if(!ret) { //打开文件失败! statubar-showMessage(path 打开失败); return; } //4.读取文件 QString text file.readAll(); //5.关闭文件 file.close(); //6.读到了的内容设置到输入框中 edit-setPlainText(text); } void MainWindow::handleAction2() { //1.弹出一个保存文件的对话框 QString path QFileDialog::getSaveFileName(this); //2.在状态栏中显示这个文件名 QStatusBar* statusBar this-statusBar(); statusBar-showMessage(path); //根据用户选择的路径去构造一个QFile对象并打开 QFile file(path); bool ret file.open(QIODevice::WriteOnly); if (!ret){ statusBar-showMessage(path 打开失败); return; } //4.写文件 const QString text edit-toPlainText(); file.write(text.toUtf8()); //5.关闭文件 file.close(); }文件和目录信息类QFileInfo 是 Qt 提供的一个用于获取文件和目录信息的类如获取文件名、文件大小、文件修改日期等。QFileInfo类中提供了很多的方法常用的有isDir() 检查该文件是否是目录isExecutable() 检查该文件是否是可执行文件fileName() 获得文件名completeBaseName() 获取完整的文件名suffix() 获取文件后缀名completeSuffix() 获取完整的文件后缀size() 获取文件大小isFile() 判断是否为文件fileTime() 获取文件创建时间、修改时间、最近访问时间等#include widget.h #include ui_widget.h #includeQFileDialog #includeQDebug #includeQFileInfo Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget) { ui-setupUi(this); } Widget::~Widget() { delete ui; } void Widget::on_pushButton_clicked() { //弹出文件对话框并获取到文件的属性 QString path QFileDialog::getOpenFileName(this); //构造出一个QFileInfo对象 QFileInfo fileInfo(path); qDebug()fileInfo.fileName(); qDebug()fileInfo.suffix(); qDebug()fileInfo.path(); qDebug()fileInfo.size(); qDebug()fileInfo.isFile(); qDebug()fileInfo.isDir(); }Qt 多线程Qt 多线程概述在 Qt 中多线程的处理一般是通过 QThread类 来实现。QThread 代表一个在应应程序中可以独立控制的线程也可以和进程中的其他线程共享数据。QThread 对象管理程序中的一个控制线程。QThread 常用 APIrun()线程的入口函数..start()通过调用 run() 开始执行线程。操作系统将根据优先级参数调度线程。如果线程已经在运行这个函数什么也不做。currentThread()返回一个指向管理当前执行线程的 QThread的指针。isRunning()如果线程正在运行则返回true;否则返回false。sleep()/msleep()/usleep()使线程休眠单位为秒 / 毫秒 / 微秒wait()阻塞线程直到满足以下任何一个条件:与此 QThread 对象关联的线程已经完成执行(即当它从run()返回时)。如果线程已经完成这个函数将返回 true。如果线程尚未启动它也返回 true。已经过了几毫秒。如果时间是 ULONG_MAX(默认值)那么等待永远不会超时(线程必须从run()返回)。如果等待超时此函数将返回 false。这提供了与 POSIX pthread_join() 函数类似的功能。terminate()终止线程的执行。线程可以立即终止也可以不立即终止这取决于操作系统的调度策略。在terminate() 之后使用 QThread::wait() 来确保。finished()当线程结束时会发出该信号可以通过该信号来实现线程的清理⼯作。使用线程创建线程的步骤1. 自定义一个类继承于 QThread并且只有一个线程处理函数(和主线程不是同一个线程)这个线程处理函数主要就是重写父类中的 run() 函数。2. 线程处理函数里面写入需要执行的复杂数据处理3. 启动线程不能直接调用 run() 函数需要使用对象来调用 start() 函数实现线程启动因为存在线程安全问题多个线程同时对界面的状态进行修改此时就会导致界面出错Qt选择了一刀切针对界面的控件状态进行任何修改务必在主线程中执行4. 线程处理函数执行结束后可以定义一个信号来告诉主线程5. 最后关闭线程。//////widget.cpp #include widget.h #include ui_widget.h Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget) { ui-setupUi(this); //连接信号槽通过槽函数更新界面 connect(thread,Thread::notify,this,Widget::handle); //要启动一下线程 thread.start(); } Widget::~Widget() { delete ui; } void Widget::handle() { //修改界面 int value ui-lcdNumber-intValue(); value--; ui-lcdNumber-display(value); } //////thread.cpp #include thread.h Thread::Thread() { } void Thread::run() { //在这个run中能否直接进去修改界面内容 //虽然不可以直接修改界面但是可以针对事件来进行计时当每到了一秒钟的时候通过信号槽来通知主线程负责更新的界面内容 for(int i 0; i 10;i) { //sleep 本身是QThread的成员函数就可以直接调用 sleep(1); //发生信号通知线程 emit notify(); } }线程安全实现线程互斥和同步常用的类有互斥锁QMutex、QMutexLocker条件变量QWaitCondition信号量QSemaphore读写锁QReadLocker、QWriteLocker、QReadWriteLock互斥锁互斥锁是一种保护和防止多个线程同时访问同一对象实例的方法在 Qt 中互斥锁主要是通过QMutex类来处理。QMutex特点QMutex 是 Qt 框架提供的互斥锁类用于保护共享资源的访问实现线程间的互斥操作。用途在多线程环境下通过互斥锁来控制对共享数据的访问确保线程安全。QMutex mutex; mutex.lock(); //上锁 //访问共享资源 //... mutex.unlock(); //解锁QMutexLocker特点QMutexLocker 是 QMutex 的辅助类使用 RAIIResource Acquisition Is Initialization方式对互斥锁进行上锁和解锁操作。用途简化对互斥锁的上锁和解锁操作避免忘记解锁导致的死锁等问题。QMutex mutex; { QMutexLocker locker(mutex); //在作⽤域内⾃动上锁 //访问共享资源 //... } //在作⽤域结束时⾃动解锁QReadWriteLocker、QReadLocker、QWriteLocker特点QReadWriteLock 是读写锁类用于控制读和写的并发访问。QReadLocker 用于读操作上锁允许多个线程同时读取共享资源。QWriteLocker 用于写操作上锁只允许一个线程写入共享资源。用途在某些情况下多个线程可以同时读取共享数据但只有一个线程能够进行写操作。读写锁提供了更高效的并发访问方式。QReadWriteLock rwLock; //在读操作中使⽤读锁 { QReadLocker locker(rwLock); //在作⽤域内⾃动上读锁 //读取共享资源 //... } //在作⽤域结束时⾃动解读锁 //在写操作中使⽤写锁 { QWriteLocker locker(rwLock); //在作⽤域内⾃动上写锁 //修改共享资源 //... } //在作⽤域结束时⾃动解写锁通过锁来保持操作的原子性创建两个子线程来去累加各自累加五万/////thread.h #ifndef THREAD_H #define THREAD_H #includeQMutex #includeQThread #include QWidget class Thread : public QThread { Q_OBJECT public: Thread(); static int num ;//添加一个static成员 void run(); static QMutex mutex;//创建一个锁来保护线程安全 }; #endif // THREAD_H /////widget.h #ifndef WIDGET_H #define WIDGET_H #include QWidget QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACE class Widget : public QWidget { Q_OBJECT public: Widget(QWidget *parent nullptr); ~Widget(); private: Ui::Widget *ui; }; #endif // WIDGET_H ////thread.cpp #include thread.h int Thread::num 0; QMutex Thread::mutex; Thread::Thread() { } void Thread::run() { for(int i 0;i 50000;i) { mutex.lock(); num; mutex.unlock(); } } ////widget.cpp #include widget.h #include ui_widget.h #includethread.h #includeQDebug Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget) { ui-setupUi(this); //创建两个线程对象 Thread t1; Thread t2; t1.start(); t2.start(); //让主线程等待两个线程执行完 t1.wait(); t2.wait(); //打印结果 qDebug()Thread::num; } Widget::~Widget() { delete ui; }#include thread.h #includeQMutexLocker int Thread::num 0; QMutex Thread::mutex; Thread::Thread() { } void Thread::run() { for(int i 0;i 50000;i) { //mutex.lock(); QMutexLocker locker(mutex); num; // mutex.unlock(); } }和动态内存一样万一中间抛异常了程序跳出去了执行不到解锁那一步依旧是内存泄漏所以C引入了std::lock_guard相当于是std::mutex智能指针借助的是RAII机制当该指针包含的大括号内的生命周期执行完毕guard变量的生命周期也就结束在析构的时候制动执行unlock。Qt的锁和C的锁本质上都是封装系统提供的锁编写多线程程序的时候可以使用Qt的锁也可以使用C的锁C的锁可以锁Qt的线程底层一样混着用也行但是不建议条件变量在多线程编程中假设除了等待操作系统正在执行的线程之外某个线程还必须等待某些条件满足才能执行这时就会出现问题。这种情况下线程会很自然地使用锁的机制来阻塞其他线程因为这只是线程的轮流使用并且该线程等待某些特定条件人们会认为需要等待条件的线程在释放互斥锁或读写锁之后进入了睡眠状态这样其他线程就可以继续运行。当条件满足时等待条件的线程将被另一个线程唤醒。在 Qt 中专门提供了 QWaitCondition类 来解决像上述这样的问题。特点QWaitCondition 是 Qt 框架提供的条件变量类用于线程之间的消息通信和同步。用途在某个条件满足时等待或唤醒线程用于线程的同步和协调。QMutex mutex; QWaitCondition condition; //在等待线程中 mutex.lock(); //检查条件是否满⾜若不满⾜则等待 while (!conditionFullfilled()) { condition.wait(mutex); //等待条件满⾜并释放锁 } //条件满⾜后继续执⾏ //... mutex.unlock(); //在改变条件的线程中 mutex.lock(); //改变条件 changeCondition(); condition.wakeAll(); //唤醒等待的线程 mutex.unlock();信号量有时在多线程编程中需要确保多个线程可以相应的访问一个数量有限的相同资源。例如运行程序的设备可能是非常有限的内存因此我们更希望需要大量内存的线程将这一事实考虑在内并根据可用的内存数量进行相关操作多线程编程中类似问题通常用信号量来处理。信号量类似于增强的互斥锁不仅能完成上锁和解锁操作而且可以跟踪可用资源的数量。特点QSemaphore 是 Qt 框架提供的计数信号量类⽤于控制同时访问共享资源的线程数量。用途限制并发线程数量用于解决一些资源有限的问题。QSemaphore semaphore(2); //同时允许两个线程访问共享资源 //在需要访问共享资源的线程中 semaphore.acquire(); //尝试获取信号量若已满则阻塞 //访问共享资源 //... semaphore.release(); //释放信号量 //在另⼀个线程中进⾏类似操作