虽然Qt是跨平台的C++开发框架,Qt的很多能力其实是操作系统提供的。只不过Qt封装了系统的API,程序是运行在操作系统上的,需要系统给我们提供支撑。

1. Qt事件

        用户进行的各种操作,就可能会产生出信号.可以给某个信号指定槽函数.当信号触发时,就能够自动的执行到对应的槽函数
        事件非常类似,用户进行的各种操作,也会产生事件.程序员同样可以给事件关联上处理函数(处理的逻辑),当事件触发的时候,就能够执行到对应的代码。

        事件本身是操作系统提供的机制.Qt也同样把操作系统事件机制进行了封装,拿到了Qt中
。但是由于事件对应的代码编写起来不是很方便,Qt对于事件机制又进行了进一步的封装,就得到了信号槽。
        信号槽就是对于事件的进一步封装,事件是信号槽的底层机制。

        所有的Qt事件均继承于抽象类QEvent。 

 1.1 事件的处理

        让一段代码和某个事件关联起来,当事件触发的时候,就能指定到这段代码,之前信号槽这里通过connect来完成上述关联的。
          对于事件来说,让当前的类,重写某个事件处理函数(这里用到的是“多态”机制,即创建子类,继承自Qt已有的类。在子类中重写父类的事件处理函数,后续事件触发过程中,就会通过多态这样的机制,执行到咱们自己写的子类的函数中)

1.2 关于鼠标事件

        处理一下鼠标进入和鼠标离开(enterEvent   leaveEvent)

[virtual protected] void QWidget::enterEvent(QEvent *event)

This event handler can be reimplemented in a subclass to receive widget enter events which are passed in the event parameter.

An event is sent to the widget when the mouse cursor enters the widget.

See also leaveEvent(), mouseMoveEvent(), and event().

         这里需要创建QLabel的子类,重写enterEvent 和leaveEvent

        通过“提升为”这样的方式,就可以把QtDesigner中拖上去的控件的类型转换成自定义的控件类型。

        通过事件获取到鼠标点击的位置

自定义label文件:

class Label : public QLabel
{
public:
    Label(QWidget* parent);

    void mousePressEvent(QMouseEvent *event);
    void mouseReleaseEvent(QMouseEvent *ev);
    void mouseDoubleClickEvent(QMouseEvent *event);
};

1.3 鼠标按下事件:mousePressEvent

void Label::mousePressEvent(QMouseEvent *event)
{
    if (event->button() == Qt::LeftButton) {
        qDebug() << "按下左键";
    } else if (event->button() == Qt::RightButton) {
        qDebug() << "按下右键";
    }
    // 当前 event 对象就包含了鼠标点击位置的坐标.
    // qDebug() << event->x() << ", " << event->y();
    // globalX 和 globalY 是以屏幕左上角为原点, 获取的坐标.
    // qDebug() << event->globalX() << ", " << event->globalY();
}

1.4 鼠标释放事件:mouseReleaseEvent

void Label::mouseReleaseEvent(QMouseEvent *event)
{
    if (event->button() == Qt::LeftButton) {
        qDebug() << "释放左键";
    } else if (event->button() == Qt::RightButton) {
        qDebug() << "释放右键";
    }
}

1.5 鼠标双击事件:mouseDoubleClickEvent

void Label::mouseDoubleClickEvent(QMouseEvent *event)
{
    if (event->button() == Qt::LeftButton) {
        qDebug() << "双击左键";
    } else if (event->button() == Qt::RightButton) {
        qDebug() << "双击右键";
    }
}

        刚才重写鼠标事件的操作,都是在自定义的Label中完成的.此时鼠标只有在Label范围内进行动作的时候,才能捕获到。
         也可以把这些操作直接放到Widget(QWidget子类)来完成这样的话,鼠标在整个窗口中进行的各种动作都能获取到了。

1.6 ⿏标移动事件

        鼠标移动不同于鼠标按下
        随便移动一下鼠标,就会产生出大量的鼠标移动事件.当你进行捕获事件的时候,无其是在这里再进行些复杂逻辑的时候,程序负担就很重,很容易产生卡顿之类的情况
        Qt为了保证程序的流畅性,默认情况下不会对鼠标移动进行追踪.鼠标移动的时候不会调用mouseMoveEvent.除非显式告诉Qt就要追踪鼠标位置

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    // 把这个选项设置为 true, 才能够追踪鼠标的移动位置.
    this->setMouseTracking(true);
}

Widget::~Widget()
{
    delete ui;
}

void Widget::mouseMoveEvent(QMouseEvent *event)
{
    qDebug() << event->x() << event->y();
}

 1.7 滚轮事件

void Widget::wheelEvent(QWheelEvent *event)
{
    total += event->delta();
    qDebug() << total;
}

 1.8 用户键盘操作

void Widget::keyPressEvent(QKeyEvent *event)
{
    // qDebug() << event->key();
    if (event->key() == Qt::Key_A && event->modifiers() == Qt::ControlModifier) {
        qDebug() << "按下了 ctrl + A 键";
    }
}

1.9 Qtimer 定时器功能

        在QTimer背后是QTimerEvent定时器事件进行支撑的~QObject提供了一个timerEvent这个函数
     startTimer启动定时器
     killTimer关闭定时器

        使用timerEvent比QTimer还是要更复杂一点.手动管理timerld,还需要区分这次函数调用是哪个timer引起的,在后续实际开发中,使用QTimer即可。

 moveEvent窗口移动时触发的事件;

resizeEvent窗口大小改变时触发的事件。

void Widget::moveEvent(QMoveEvent *event)
{
    qDebug() << event->pos();
}

void Widget::resizeEvent(QResizeEvent *event)
{
    qDebug() << event->size();
}

2. Qt⽂件

Qt中主要的⼀些I/O设备类的继承关系如下图所⽰:

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QFileDialog>

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* statusBar = this->statusBar();
    statusBar->showMessage(path);

    // 3. 根据用户选择的路径, 构造一个 QFile 对象. 并打开文件
    QFile file(path);
    bool ret = file.open(QFile::ReadOnly);
    if (!ret) {
        // 打开文件失败!
        statusBar->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);

    // 3. 根据用户选择的路径, 构造一个 QFile 对象, 并打开文件.
    QFile file(path);
    bool ret = file.open(QFile::WriteOnly);
    if (!ret) {
        statusBar->showMessage(path + " 打开失败!");
        return;
    }

    // 4. 写文件.
    const QString& text = edit->toPlainText();
    file.write(text.toUtf8());

    // 5. 关闭文件.
    file.close();
}

 

 2.1 QFilelnfo

        QFilelnfo可以获取到Qt的文件的相关属性

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();

    // ......
}

3. Qt多线程

        Qt多线程和Linux中的线程本质是一个东西。

        QThread  要想创建线程,就需要创建出这样类的实例,创建线程的时候,需要重点指定线程的入口函数
        创建一个QThread的子类,重写其的run函数,起到指定入口函数的方式.(多态)。

3.1 QThread常⽤API

         start:这个操作就是真正调用系统API创建线程,新的线程创建出来之后自然就会自动的执行run函数。

        之前基于定时器,写过倒计时这样的程序。可以通过线程,来完成类似的功能
        创建另一个线程,新线程中,进行计时(搞一个循环,每循环一次,sleep1s,sleep完成,就可以更新界面了.)

        由于存在线程安全问题,多个线程同时对于界面的状态进行修改,此时就会导致界面就出错!qt针对界面的控件状态进行任何修改,务必在主线程中执行

         加锁
         把多个线程要访问的公共资源,通过锁保护起来=>把并发执行变成串行执行。

        Qt同样也提供了对应的锁,来针对系统提供的锁进行封装

int Thread::num = 0;

QMutex Thread::mutex;

Thread::Thread()
{

}

void Thread::run()
{
    for (int i = 0; i < 50000; i++) {
        QMutexLocker locker(&mutex);

        // mutex.lock();
        num++;
        // mutex.unlock();
//        是一个两个线程访问的公共变量
//        之前如果是并发执行,就可能第一个线程修改了一半,第
//        二个线程也进行修改.就容易出现问题,(Linux中详细介绍,++操作对应三个cpu指令)
//        加了锁之后,第一个线程顺利拿到锁,继续执行++,在第一个线程没执行完的事后,
//        第二个线程也尝试加锁就会阻塞等待,一直等到第一个线程释放锁,第二个线程才能从阻塞中被唤醒
    }
}

4. Qt⽹络

        和多线程类似,Qt为了⽀持跨平台,对⽹络编程的API也进⾏了重新封装.

        网络编程,操作系统提供的一组APl(SocketAPI),在C++标准库中,并没有提供网络编程的api的封装。我们在进行网络编程的时候,本质上是在编写应用层代码.需要传输层提供支持。
        使用Qt网络编程的API需要先在.pro文件中添加network模块。之前学过的Qt的各种控件,各种内容.都是包含在QtCore模块中的。

4.1UDPSocket

4.1.1核⼼API概览

        主要的类有两个. QUdpSocket 和 QNetworkDatagram

        QUdpSocket 表⽰⼀个UDP的socket⽂件.

        QNetworkDatagram 表⽰⼀个UDP数据报.

写一个带有界面的Udp回显服务器:

4.2 TCPSocket

ps:关于qt的学习就到这里了,谢谢观看!!!

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐