Bootstrap

Qt学习记录

Qt学习记录

以下示例如无特别说明,均在Clion,CMake,Qt6环境下测试

Qt6读取GBK文件

在Qt5中,有QTextCodec模块,支持各种编码设置。

// Qt5
QCoreApplication a(argc, argv);
auto desk = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation);
QFile file(QDir(desk).filePath("test.txt"));
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)){
    qDebug() << file.errorString() << Qt::endl;
    return a.exec();
}
QTextStream in(&file);
in.setCodec(QTextCodec::codecForName("GBK"));
while (!in.atEnd()){
    const QString &line = in.readLine();
    qDebug() << line;
}
return a.exec();

Qt6后,删除了QTextCodec模块,引入了QStringConverter,其中有几个常用的编码,其他编码都不支持,目前只能结合libconv等其他库实现编码转换。但对于windows默认的GBK编码,可以换种方式实现。

// Qt6
QCoreApplication a(argc, argv);
auto desk = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation);
QFile file(QDir(desk).filePath("test.txt"));
if (!file.open(QIODevice::ReadOnly)){
    qDebug() << file.errorString() << Qt::endl;
    return a.exec();
}
QByteArray arr = file.readAll();
auto s = QString::fromLocal8Bit(arr);
qDebug() << s;
return a.exec();

Qt6组件语言设置

部分组件默认语言是英文,使用下面的代码语言改为跟随系统。例如QMessageBox弹框中的Ok会变成确认,Cancle会变成取消等。

QApplication app(argc, argv);
QTranslator translator;
const QString &locale = QLocale::system().name();
const QString &path = QLibraryInfo::path(QLibraryInfo::TranslationsPath);
if (!translator.load(QString("qt_%1").arg(locale), path)) {
    qDebug() << "load qt_" + locale + ".qm failed";
} else {
    QApplication::installTranslator(&translator);
}
Widget w;
w.show();
return QApplication::exec();

Qt CMake设置应用程序图标等信息

Windows .rc 文件(资源脚本文件)是用来定义和描述程序中的各种资源的文本文件。这些资源可以包括图标、光标、对话框、菜单、字符串等。.rc 文件是 Windows 应用程序开发中的重要组成部分,通常与 C 或 C++ 代码一起使用,通过资源编译器将其编译成二进制 .res 文件,并在程序中加载这些资源。

  1. 首先准备一个ico文件,用作应用程序图标。
  2. 新建c++头文件version.h,宏定义应用程序一些信息。
//
// Created by chunf on 2024/12/26.
//

#ifndef VERSION_H
#define VERSION_H

#define PRODUCT_ICON           "resources/app.ico" // 图标,项目根目录/resources/app.ico

#define FILE_VERSION           1,0,0,0   // 文件版本
#define FILE_VERSION_STR       "1.0.0.0"
#define PRODUCT_VERSION        1,0,0,0   // 产品版本
#define PRODUCT_VERSION_STR    "1.0.0.0"
#define COMPANY_NAME           "公司名称"
#define INTERNAL_NAME          "zhd.exe"
#define FILE_DESCRIPTION       "职代会评测统计程序"  // 文件说明
#define LEGAL_COPYRIGHT        "Copyright 2024 The xxxx Company. All rights reserved." // 版权
#define ORIGINAL_FILE_NAME     "demo.exe"    // 原始文件名
#define PRODUCT_NAME           "zdh"        // 产品名称
#define ORGANIZATION_DOMAIN    "https://www.qt.io/"  // 域名

#endif // VERSION_H
  1. 新建一个.rc文件
#include "winres.h"       // 引入 Windows 资源文件头,包含常见的资源类型定义
#include "version.h"      // 引入自定义的版本信息头文件,定义了版本、公司等常量

// 图标资源定义
IDI_ICON1       ICON      PRODUCT_ICON  // 定义一个图标资源,使用 'PRODUCT_ICON' 作为图标的文件名

// 版本信息资源定义
VS_VERSION_INFO VERSIONINFO  // 定义版本信息资源块
    FILEVERSION FILE_VERSION  // 文件版本号,引用 FILE_VERSION 宏
    PRODUCTVERSION PRODUCT_VERSION  // 产品版本号,引用 PRODUCT_VERSION 宏
    FILEFLAGSMASK 0x3fL  // 允许的文件标志位掩码,0x3fL 表示保留所有标志
    #ifdef _DEBUG  // 如果是在调试模式下
        FILEFLAGS 0x1L  // 设置调试标志位,0x1L 表示调试版本
    #else  // 如果是发布模式
        FILEFLAGS 0x0L  // 设置文件标志位为 0,表示发布版本
    #endif
    FILEOS 0x40004L  // 操作系统类型,0x40004L 通常表示 Windows 32 位操作系统
    FILETYPE 0x1L  // 文件类型,0x1L 表示可执行文件
    FILESUBTYPE 0x0L  // 文件子类型,0x0L 通常表示通用子类型
BEGIN
    BLOCK "StringFileInfo"  // 定义包含文件信息字符串的块
    BEGIN
        BLOCK "080404b0"  // 语言/区域标识符,"080404b0" 是简体中文(中国)的标识符
        BEGIN
            VALUE "CompanyName", COMPANY_NAME  // 公司名称,引用 COMPANY_NAME 宏
            VALUE "FileDescription", "职代会评测统计程序/[email protected]"  // 文件描述,描述程序用途
            VALUE "FileVersion", FILE_VERSION_STR  // 文件版本字符串,引用 FILE_VERSION_STR 宏
            VALUE "InternalName", INTERNAL_NAME  // 内部文件名称,引用 INTERNAL_NAME 宏
            VALUE "LegalCopyright", LEGAL_COPYRIGHT  // 法律版权信息,引用 LEGAL_COPYRIGHT 宏
            VALUE "OriginalFilename", ORIGINAL_FILE_NAME  // 原始文件名称,引用 ORIGINAL_FILE_NAME 宏
            VALUE "ProductName", PRODUCT_NAME  // 产品名称,引用 PRODUCT_NAME 宏
            VALUE "ProductVersion", PRODUCT_VERSION_STR  // 产品版本字符串,引用 PRODUCT_VERSION_STR 宏
        END
    END
    BLOCK "VarFileInfo"  // 版本信息中的可变数据块
    BEGIN
        VALUE "Translation", 0x804, 1200  // 翻译信息,0x804 表示简体中文(中国),1200 为编码(通常是 UTF-8)
    END
END
  1. CMake中加入编译
add_executable(zdh
        main.cpp
        utils.h
        utils.cpp
        widget.h
        widget.cpp
        widget.ui
        cardconfig.h
        res.qrc
        version.h
        zdh.rc
)
  1. 编译并查看结果
    在这里插入图片描述
    这里附上一些常用的配置项说明,更多使用可以查看微软的文档。
    在这里插入图片描述

QXlsx使用

QXlsx 是一个跨平台的非官方Qt库,允许开发者在不依赖 Microsoft Excel 或 COM 自动化的情况下读取和写入 Excel 文件。

  1. 下载QXlsx项目源代码到项目目录。
  2. CMake中加入
add_subdirectory(QXlsx-master/QXlsx)
target_link_libraries(qxlsx_test PUBLIC Qt::Core
        Qt::Gui
        Qt::Widgets)
target_link_libraries(qxlsx_test PRIVATE QXlsx::QXlsx)

经测试发现,QXlsx依赖于Qt Gui,未加入此库程序会直接奔溃
3. 基本操作

#include <QCoreApplication>
#include <QStandardPaths>
#include <QDir>
#include "xlsxdocument.h"


int main(int argc, char *argv[]) {
    QCoreApplication app(argc, argv);

    const QString &desktopPath = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation);
    const QString &xlsxPath = QDir(desktopPath).filePath("test.xlsx");

    // 这种读取方式不能获取失败信息
    // QXlsx::Document document(xlsxPath);
    // if (!document.load()) {
    //     qDebug() << "Error loading file";
    //     return 0;
    // }

    // 这种方式获取详细的读取失败信息
    QFile file(xlsxPath);
    if (!file.open(QIODevice::ReadOnly)) {
        qDebug() << "Error opening file";
        qDebug() << file.errorString();
        qDebug() << file.error();
        return 0;
    }
    QXlsx::Document document(&file);
    if (!document.load()) {
        qDebug() << "Error loading file";
        return 0;
    }

    qDebug() << document.sheetNames();
    QXlsx::AbstractSheet *curSheet = document.currentSheet();
    if (curSheet != nullptr) {
        qDebug() << curSheet->sheetName();
        auto *workSheet = dynamic_cast<QXlsx::Worksheet *>(curSheet);
        const QXlsx::CellRange &range = workSheet->dimension();
        qDebug() << range.firstRow() << range.lastRow() << range.firstColumn() << range.lastColumn();
        qDebug() << range.topLeft().toString() << range.bottomRight().toString() << range.toString();
    }

    const QXlsx::CellRange &range = document.dimension();
    qDebug() << range.firstRow() << range.lastRow() << range.firstColumn() << range.lastColumn();
    qDebug() << range.topLeft().toString() << range.bottomRight().toString() << range.toString();

    QXlsx::Worksheet *sheet = dynamic_cast<QXlsx::Worksheet *>(document.sheet("班子总打分"));
    if (sheet == nullptr) {
        qDebug() << "找不到工作表";
        return 0;
    }
    sheet->write(50, 50, "Hello World!");
    sheet->write("G50", "Hello World!");

    // 保存和读取一样,也有多种方式
    if (!document.saveAs(xlsxPath)) {
        qDebug() << "Error saving file";
        return 0;
    }

    return 0;
}

API见名知意,使用简单方便。

QXlsx复制工作表

bool copyWorkSheet(QXlsx::Document &document, const QString &srcSheetName, const QString &destSheetName,
                   bool replaceIfExists = false) {
    auto *srcSheet = dynamic_cast<QXlsx::Worksheet *>(document.sheet(srcSheetName));
    if (srcSheet == nullptr) {
        throw std::runtime_error(QString("%1 not found").arg(srcSheetName).toStdString());
    }
    auto *testSheet = document.sheet(destSheetName);
    if (testSheet != nullptr && !replaceIfExists) {
        return false;
    }

    if (!document.copySheet(srcSheetName, destSheetName)) {
        return false;
    }

    auto *destSheet = dynamic_cast<QXlsx::Worksheet *>(document.sheet(destSheetName));
    const QXlsx::CellRange &range = srcSheet->dimension();
    const int rowCount = range.rowCount();
    const int colCount = range.columnCount();
    for (int i = 1; i <= rowCount; ++i) {
        destSheet->setRowHeight(i, i, srcSheet->rowHeight(i));
    }
    for (int i = 1; i < colCount; ++i) {
        destSheet->setColumnWidth(i, i, srcSheet->columnWidth(i));
    }
    return true;
}

Qt多线程

  1. 编写Worker类继承QObject,在Controller中moveToThread(推荐)。
  2. 继承QThread重写run方法。
  3. GUI线程与子线程之间通过信号和槽进行通信并控制行为,不能直接在子线程中操作UI
  4. 不同线程之间的信号和槽连接方式为异步,虽然如此,以局部变量的引用作为信号函数和槽函数的参数时,Qt会对其进行拷贝,无需考虑访问非法内存问题。
 class Worker : public QObject
 {
     Q_OBJECT

 public slots:
     void doWork(const QString &parameter) {
         QString result;
         /* ... here is the expensive or blocking operation ... */
         emit resultReady(result);
     }

 signals:
     void resultReady(const QString &result);
 };

 class Controller : public QObject
 {
     Q_OBJECT
     QThread workerThread;
 public:
     Controller() {
         Worker *worker = new Worker;
         worker->moveToThread(&workerThread);
         connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater);
         connect(this, &Controller::operate, worker, &Worker::doWork);
         connect(worker, &Worker::resultReady, this, &Controller::handleResults);
         workerThread.start();
     }
     ~Controller() {
         workerThread.quit();
         workerThread.wait();
     }
 public slots:
     void handleResults(const QString &);
 signals:
     void operate(const QString &);
 };

QItemSelectionModel中clearSelection, clearCurrentIndex和clear区别

  • clearSelection(): 清除所有已选择的项时使用,但保持当前项不变。
  • clearCurrentIndex(): 该方法清除当前项(即当前索引),但不会影响选中的项。这意味着当前项的显示或激活状态将被清除,但其他选项的状态保持不变。
    适用场景:如果你希望取消当前项的激活状态,但不想清除选中的项目时使用。
  • clear():这个方法会清除选区(选择的项)和当前项。相当于同时调用 clearSelection() 和 clearCurrentIndex(),即清除所有选择项和当前项。
    适用场景:如果你希望同时取消所有选中的项并清除当前项时使用。

总结:

  • clearSelection():只清除选择的项。
  • clearCurrentIndex():只清除当前项。
  • clear():清除选择的项和当前项。

QCoreApplication sendEvent和sendPostedEvent

  • sendEvent():立即发送事件并调用目标对象的事件处理函数,同步阻塞式。
  • sendPostedEvent():将事件添加到事件队列中,并在适当的时候异步处理该事件,异步非阻塞式。

QListView和QListWidget

特性QListViewQListWidget
模型-视图架构需要外部模型与视图分离内部自带默认模型
灵活性高,可自定义模型和代理较低,适用于简单列表显示
适用复杂数据结构适合复杂的数据和自定义的交互方式适合简单的、静态的数据
易用性需要更多的代码来管理模型和视图简单易用,直接操作项
功能支持更丰富的自定义,例如代理、排序等提供基础的添加、删除、选择等功能

Qt中QMouseEvent,pos, position, scenePosition和globalPosition

  1. pos():
    返回相对于事件接收者的坐标。也就是说,返回的是鼠标点击位置在接收该事件的控件(例如 QWidget)的坐标系中的位置。这个位置是相对于该控件的左上角的。
    示例:如果你在一个 QWidget 内点击位置 (10, 20),pos() 将返回 (10, 20)。
  2. position():
    从 Qt 5.15 开始,position() 是 QMouseEvent 的新方法,返回鼠标事件的精确坐标,通常使用浮点数而不是整数。它与 pos() 类似,但精度更高。position() 返回的坐标也通常是相对于事件接收者的坐标系。
    示例:如果鼠标在位置 (10.5, 20.5) 点击,position() 将返回 (10.5, 20.5)。
  3. scenePosition():
    返回相对于 QGraphicsScene 的坐标。如果你在一个 QGraphicsView 中处理鼠标事件,这个方法特别有用,因为它给出的坐标是相对于整个 QGraphicsScene 的,而不是视图或控件的。
    示例:在 QGraphicsView 内,鼠标点击时 scenePosition() 返回的坐标会相对于 QGraphicsScene 的原点,而不是视图或窗口的原点。
  4. globalPosition():
    返回的是鼠标在屏幕上的全局坐标,通常是相对于显示设备的左上角。这个方法非常有用,特别是当你想获取鼠标位置与其他窗口或屏幕的关系时。
    示例:如果鼠标点击位置在屏幕的 (100, 200) 处,globalPosition() 将返回 (100, 200)。

总结:
pos() 和 position():相对于事件接收者的坐标系,position() 提供更精确的浮点数值。
scenePosition():相对于 QGraphicsScene 的坐标系,通常用于图形视图场景中。
globalPosition():相对于屏幕的全局坐标。

Qt Enter和Hover事件

  • QEvent::Enter,QEvent::Leave,当鼠标指针进入和离开一个控件的区域时触发。
  • QEvent::HoverEnter,QEvent::HoverMove,QEvent::HoverLeave 当鼠标指针进入控件,在控件上方移动,离开控件时触发。当鼠标在控件的区域内移动时,会多次触发HoverMove。HoverEnter和HoverMove需要设置setAttribute(Qt::WA_Hover, true),HoverMove需要setMouseTracking(true)。

QImage,QPixmap和QPicture

  1. QImage
    用于处理图像数据的类,提供了对图像像素的直接访问和操作。
    支持多种图像格式(如 PNG、JPEG、BMP 等)。
    可以直接访问和修改像素数据。
    适合用于图像处理、像素级操作和图像格式转换。
    不依赖于底层平台的图形系统,因此可以在没有图形界面的环境下使用。
    使用场景: 图像处理、图像格式转换、像素级操作等。

  2. QPixmap
    用于在屏幕上显示图像的类,通常用于绘制图像到窗口或控件上。
    针对显示进行了优化,适合用于 GUI 应用程序中的图像显示。
    依赖于底层平台的图形系统(如 X11、Windows GDI 等),因此在没有图形界面的环境下可能无法使用。
    不支持直接访问像素数据,但可以通过 QImage 进行转换。
    使用场景: 在 GUI 应用程序中显示图像、图标、背景等。

  3. QPicture
    记录和回放绘图操作的类,可以看作是一个绘图命令的容器。
    不存储像素数据,而是存储绘图命令(如绘制线条、矩形、文本等)。
    可以保存到文件或从文件加载,适合用于存储和重现绘图操作。
    与 QImage 和 QPixmap 不同,QPicture 不直接处理图像数据,而是记录绘图指令。
    使用场景: 记录和回放绘图操作、保存和加载绘图命令等。

如果需要处理图像数据,使用 QImage;如果需要显示图像,使用 QPixmap;如果需要记录绘图操作,使用 QPicture。

;