开源 C++ QT QML 开发(九)文件--文本和二进制
文章的目的为了记录使用QT QML开发学习的经历。开发流程和要点有些记忆模糊,赶紧记录,防止忘记。
相关链接:
开源 C++ QT QML 开发(一)基本介绍
开源 C++ QT QML 开发(二)工程结构
开源 C++ QT QML 开发(三)常用控件
开源 C++ QT QML 开发(四)复杂控件--Listview
开源 C++ QT QML 开发(五)复杂控件--Gridview
开源 C++ QT QML 开发(六)自定义控件--波形图
开源 C++ QT QML 开发(七)自定义控件--仪表盘
开源 C++ QT QML 开发(八)自定义控件--圆环
开源 C++ QT QML 开发(九)文件--文本和二进制
推荐链接:
开源 C# 快速开发(一)基础知识
开源 C# 快速开发(二)基础控件
开源 C# 快速开发(三)复杂控件
开源 C# 快速开发(四)自定义控件--波形图
开源 C# 快速开发(五)自定义控件--仪表盘
开源 C# 快速开发(六)自定义控件--圆环
开源 C# 快速开发(七)通讯--串口
开源 C# 快速开发(八)通讯--Tcp服务器端
开源 C# 快速开发(九)通讯--Tcp客户端
开源 C# 快速开发(十)通讯--http客户端
开源 C# 快速开发(十一)线程
开源 C# 快速开发(十二)进程监控
开源 C# 快速开发(十三)进程--管道通讯
开源 C# 快速开发(十四)进程--内存映射
开源 C# 快速开发(十五)进程--windows消息
开源 C# 快速开发(十六)数据库--sqlserver增删改查
本章节主要内容是:实现了一个简单文件编辑器应用程序,可以对文本文件和二进制文件打开和保存。
1.代码分析
2.所有源码
3.效果演示
一、代码分析
1. FileHandler 头文件分析
1.1 类声明和属性定义
class FileHandler : public QObject
{Q_OBJECTQ_PROPERTY(QString fileName READ fileName NOTIFY fileNameChanged)Q_PROPERTY(bool isBinary READ isBinary NOTIFY isBinaryChanged)Q_PROPERTY(bool fileModified READ fileModified NOTIFY fileModifiedChanged)Q_PROPERTY(QVariantList hexData READ hexData NOTIFY hexDataChanged)Q_PROPERTY(QString fileContent READ fileContent NOTIFY fileContentChanged)
分析:
使用 Qt 属性系统,所有属性都是只读的(只有 READ,没有 WRITE)
NOTIFY 信号确保属性变化时 QML 能自动更新
QVariantList 用于在 C++ 和 QML 之间传递复杂数据
1.2 信号定义
signals:void fileNameChanged();void isBinaryChanged();void fileModifiedChanged();void hexDataChanged();void fileContentChanged();void fileOpened(const QString &fileName, bool isBinary);void fileSaved(const QString &fileName);void fileClosed();void errorOccurred(const QString &errorMessage);
分析:
前5个信号对应属性变化通知
后4个信号是业务逻辑事件通知
fileOpened 和 errorOccurred 携带参数传递详细信息
2. FileHandler 实现文件分析
2.1 构造函数
FileHandler::FileHandler(QObject *parent): QObject(parent), m_isBinary(false), m_fileModified(false)
{
}
分析:
初始化成员变量,默认文件类型为文本,未修改状态
2.2 openFile 函数
bool FileHandler::openFile(const QString &filePath)
{QFile file(filePath);if (!file.exists()) {emit errorOccurred(tr("文件不存在: %1").arg(filePath));return false;}
文件存在性检查:
使用 QFile::exists() 检查文件是否存在
如果不存在,发射错误信号并返回 false
if (!file.open(QIODevice::ReadOnly)) {emit errorOccurred(tr("无法打开文件: %1").arg(filePath));return false;}
文件打开检查:
尝试以只读模式打开文件
如果打开失败(权限问题、文件被占用等),发射错误信号
m_fileName = filePath;m_isBinary = isBinaryFile(filePath);if (m_isBinary) {// 读取二进制文件QByteArray data = file.readAll();m_originalData = data;m_fileContent.clear();processBinaryData(data);} else {// 读取文本文件QTextStream stream(&file);stream.setCodec("UTF-8");m_fileContent = stream.readAll();m_originalData = m_fileContent.toUtf8();// 清空二进制数据m_hexData.clear();emit hexDataChanged();emit fileContentChanged();emit fileOpened(filePath, false);}
文件内容处理:
二进制文件:直接读取原始数据,调用 processBinaryData 处理
文本文件:使用 QTextStream 以 UTF-8 编码读取,保存内容和原始数据
清理不相关的数据并发射相应信号
file.close();m_fileModified = false;emit fileNameChanged();emit isBinaryChanged();emit fileModifiedChanged();return true;
}
收尾工作:
关闭文件,重置修改状态
发射属性变化信号通知界面更新
2.3 processBinaryData 函数
void FileHandler::processBinaryData(const QByteArray &data)
{m_hexData.clear();// 预分配空间,提高性能int lineCount = (data.size() + 15) / 16; // 计算总行数m_hexData.reserve(lineCount);const uchar *bytes = reinterpret_cast<const uchar*>(data.constData());int dataSize = data.size();
性能优化:
预计算行数:每行16字节,计算需要的行数
预分配 QVariantList 容量避免重复分配
使用原始指针访问提高效率
for (int i = 0; i < dataSize; i += 16) {QString hexLine;hexLine.reserve(48); // 预分配空间:16字节 * 3字符for (int j = 0; j < 16 && i + j < dataSize; j++) {uchar byte = bytes[i + j];QString hexByte = QString("%1 ").arg(byte, 2, 16, QChar('0')).toUpper();hexLine.append(hexByte);}// 移除最后一个空格if (!hexLine.isEmpty() && hexLine.endsWith(' ')) {hexLine.chop(1);}
十六进制格式化:
每行处理最多16个字节
使用 QString::arg() 格式化为2位十六进制,不足补零
移除行末多余的空格
QVariantMap lineData;lineData["address"] = QString("%1").arg(i, 8, 16, QChar('0')).toUpper();lineData["hex"] = hexLine;m_hexData.append(lineData);}emit hexDataChanged();emit fileOpened(m_fileName, true);
}
数据结构构建:
使用 QVariantMap 存储每行的地址和十六进制数据
地址格式化为8位十六进制数
发射信号通知数据就绪2.4 saveFile 函数
bool FileHandler::saveFile(const QString &content)
{if (m_fileName.isEmpty()) {emit errorOccurred(tr("没有指定文件名"));return false;}QFile file(m_fileName);if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {emit errorOccurred(tr("无法保存文件: %1").arg(m_fileName));return false;}QTextStream stream(&file);stream.setCodec("UTF-8");stream << content;file.close();
文本文件保存:
检查文件名有效性
以文本模式写入,使用 UTF-8 编码
使用 QTextStream 确保编码正确
m_fileContent = content;m_originalData = content.toUtf8();m_fileModified = false;emit fileContentChanged();emit fileSaved(m_fileName);emit fileModifiedChanged();return true;
}
状态更新:
更新内存中的内容和原始数据
重置修改标志
发射保存成功信号2.5 saveBinaryFile 函数
bool FileHandler::saveBinaryFile(const QVariantList &hexData)
{// ... 文件名和文件打开检查类似 saveFile ...// 将十六进制数据转换回二进制QByteArray binaryData;for (const QVariant &line : hexData) {QString hexLine = line.toString();QByteArray lineData = hexStringToByteArray(hexLine);binaryData.append(lineData);}file.write(binaryData);file.close();
二进制数据重建:
遍历十六进制数据列表
调用 hexStringToByteArray 转换每行数据
直接写入二进制数据2.6 hexStringToByteArray 函数
QByteArray FileHandler::hexStringToByteArray(const QString &hexString)
{QByteArray byteArray;QStringList hexBytes = hexString.split(' ', Qt::SkipEmptyParts);byteArray.reserve(hexBytes.size());for (const QString &hexByte : hexBytes) {bool ok;char byte = static_cast<char>(hexByte.toInt(&ok, 16));if (ok) {byteArray.append(byte);}}return byteArray;
}
十六进制解析:
按空格分割十六进制字符串
使用 toInt(&ok, 16) 将十六进制字符串转换为整数
只添加成功转换的字节2.7 isBinaryFile 函数
bool FileHandler::isBinaryFile(const QString &filePath)
{QFileInfo fileInfo(filePath);QString extension = fileInfo.suffix().toLower();static const QStringList binaryExtensions = {"exe", "dll", "bin", "dat", "img", "so", "dylib","jpg", "jpeg", "png", "gif", "bmp", "ico","pdf", "doc", "docx", "xls", "xlsx","zip", "rar", "7z", "tar", "gz"};return binaryExtensions.contains(extension);
}
文件类型判断:
基于文件扩展名的简单判断
使用 static const 避免重复构造列表
包含常见二进制文件格式
3. QML 界面分析
3.1 二进制显示更新函数
function updateBinaryDisplay() {var displayText = ""for (var i = 0; i < fileHandler.hexData.length; i++) {var line = fileHandler.hexData[i]displayText += line.address + " " + line.hex + "\n"}textArea.text = displayText
}
显示格式化:
遍历十六进制数据,构建显示字符串
格式:地址 十六进制数据
每行以换行符结束3.2 保存二进制文件逻辑
var lines = textArea.text.split('\n')
var hexData = []for (var i = 0; i < lines.length; i++) {if (lines[i].trim() === "") continuevar lineParts = lines[i].split(' ')if (lineParts.length >= 2) {hexData.push(lineParts[1].trim())}
}
fileHandler.saveBinaryFile(hexData)
数据提取:
从显示的文本中解析出十六进制数据
跳过空行,按分隔符分割地址和十六进制数据
只提取十六进制部分传递给 C++
二、所有源码
FileHandler.h文件源码
#ifndef FILEHANDLER_H
#define FILEHANDLER_H#include <QObject>
#include <QString>
#include <QByteArray>
#include <QFile>
#include <QTextStream>
#include <QDataStream>
#include <QVector>
#include <QVariantList>
#include <QFileInfo>class FileHandler : public QObject
{Q_OBJECTQ_PROPERTY(QString fileName READ fileName NOTIFY fileNameChanged)Q_PROPERTY(bool isBinary READ isBinary NOTIFY isBinaryChanged)Q_PROPERTY(bool fileModified READ fileModified NOTIFY fileModifiedChanged)Q_PROPERTY(QVariantList hexData READ hexData NOTIFY hexDataChanged)Q_PROPERTY(QString fileContent READ fileContent NOTIFY fileContentChanged)public:explicit FileHandler(QObject *parent = nullptr);QString fileName() const { return m_fileName; }bool isBinary() const { return m_isBinary; }bool fileModified() const { return m_fileModified; }QVariantList hexData() const { return m_hexData; }QString fileContent() const { return m_fileContent; }Q_INVOKABLE bool openFile(const QString &filePath);Q_INVOKABLE bool saveFile(const QString &content);Q_INVOKABLE bool saveBinaryFile(const QVariantList &hexData);Q_INVOKABLE void closeFile();Q_INVOKABLE bool isBinaryFile(const QString &filePath);signals:void fileNameChanged();void isBinaryChanged();void fileModifiedChanged();void hexDataChanged();void fileContentChanged();void fileOpened(const QString &fileName, bool isBinary);void fileSaved(const QString &fileName);void fileClosed();void errorOccurred(const QString &errorMessage);private:void processBinaryData(const QByteArray &data);QByteArray hexStringToByteArray(const QString &hexString);QString m_fileName;bool m_isBinary;bool m_fileModified;QByteArray m_originalData;QVariantList m_hexData;QString m_fileContent;
};#endif // FILEHANDLER_H
FileHandler.cpp文件源码
#include "filehandler.h"
#include <QDebug>
#include <QFileInfo>FileHandler::FileHandler(QObject *parent): QObject(parent), m_isBinary(false), m_fileModified(false)
{
}bool FileHandler::openFile(const QString &filePath)
{QFile file(filePath);if (!file.exists()) {emit errorOccurred(tr("文件不存在: %1").arg(filePath));return false;}if (!file.open(QIODevice::ReadOnly)) {emit errorOccurred(tr("无法打开文件: %1").arg(filePath));return false;}m_fileName = filePath;m_isBinary = isBinaryFile(filePath);if (m_isBinary) {// 读取二进制文件 - 使用更高效的方式QByteArray data = file.readAll();m_originalData = data;m_fileContent.clear();processBinaryData(data);} else {// 读取文本文件QTextStream stream(&file);stream.setCodec("UTF-8");m_fileContent = stream.readAll();m_originalData = m_fileContent.toUtf8();// 清空二进制数据m_hexData.clear();emit hexDataChanged();// 发送文件内容变化信号emit fileContentChanged();// 发送信号通知文件已打开emit fileOpened(filePath, false);}file.close();m_fileModified = false;emit fileNameChanged();emit isBinaryChanged();emit fileModifiedChanged();return true;
}bool FileHandler::saveFile(const QString &content)
{if (m_fileName.isEmpty()) {emit errorOccurred(tr("没有指定文件名"));return false;}QFile file(m_fileName);if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {emit errorOccurred(tr("无法保存文件: %1").arg(m_fileName));return false;}QTextStream stream(&file);stream.setCodec("UTF-8");stream << content;file.close();m_fileContent = content;m_originalData = content.toUtf8();m_fileModified = false;emit fileContentChanged();emit fileSaved(m_fileName);emit fileModifiedChanged();return true;
}bool FileHandler::saveBinaryFile(const QVariantList &hexData)
{if (m_fileName.isEmpty()) {emit errorOccurred(tr("没有指定文件名"));return false;}QFile file(m_fileName);if (!file.open(QIODevice::WriteOnly)) {emit errorOccurred(tr("无法保存文件: %1").arg(m_fileName));return false;}// 将十六进制数据转换回二进制QByteArray binaryData;for (const QVariant &line : hexData) {QString hexLine = line.toString();QByteArray lineData = hexStringToByteArray(hexLine);binaryData.append(lineData);}file.write(binaryData);file.close();m_originalData = binaryData;m_fileModified = false;emit fileSaved(m_fileName);emit fileModifiedChanged();return true;
}void FileHandler::closeFile()
{m_fileName.clear();m_isBinary = false;m_fileModified = false;m_originalData.clear();m_hexData.clear();m_fileContent.clear();emit fileNameChanged();emit isBinaryChanged();emit fileModifiedChanged();emit hexDataChanged();emit fileContentChanged();emit fileClosed();
}bool FileHandler::isBinaryFile(const QString &filePath)
{QFileInfo fileInfo(filePath);QString extension = fileInfo.suffix().toLower();static const QStringList binaryExtensions = {"exe", "dll", "bin", "dat", "img", "so", "dylib","jpg", "jpeg", "png", "gif", "bmp", "ico","pdf", "doc", "docx", "xls", "xlsx","zip", "rar", "7z", "tar", "gz"};return binaryExtensions.contains(extension);
}void FileHandler::processBinaryData(const QByteArray &data)
{m_hexData.clear();// 预分配空间,提高性能int lineCount = (data.size() + 15) / 16; // 计算总行数m_hexData.reserve(lineCount);// 使用更高效的处理方式const uchar *bytes = reinterpret_cast<const uchar*>(data.constData());int dataSize = data.size();for (int i = 0; i < dataSize; i += 16) {QString hexLine;hexLine.reserve(48); // 预分配空间:16字节 * 3字符(2位十六进制+1空格)for (int j = 0; j < 16 && i + j < dataSize; j++) {uchar byte = bytes[i + j];// 构建十六进制字符串 - 修复的方法QString hexByte = QString("%1 ").arg(byte, 2, 16, QChar('0')).toUpper();hexLine.append(hexByte);}// 移除最后一个空格if (!hexLine.isEmpty() && hexLine.endsWith(' ')) {hexLine.chop(1);}// 创建行数据 - 只包含地址和十六进制数据QVariantMap lineData;lineData["address"] = QString("%1").arg(i, 8, 16, QChar('0')).toUpper();lineData["hex"] = hexLine;m_hexData.append(lineData);}emit hexDataChanged();emit fileOpened(m_fileName, true);
}QByteArray FileHandler::hexStringToByteArray(const QString &hexString)
{QByteArray byteArray;QStringList hexBytes = hexString.split(' ', Qt::SkipEmptyParts);byteArray.reserve(hexBytes.size());for (const QString &hexByte : hexBytes) {bool ok;char byte = static_cast<char>(hexByte.toInt(&ok, 16));if (ok) {byteArray.append(byte);}}return byteArray;
}
main.qml文件源码
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.12
import QtQuick.Dialogs 1.3
import QtQuick.Layouts 1.12
import FileEditor 1.0ApplicationWindow {id: mainWindowwidth: 800height: 600title: "文件编辑器 - " + (fileHandler.fileName ? fileHandler.fileName : "未命名")visible: trueFileHandler {id: fileHandleronFileOpened: {console.log("文件已打开:", fileName, "二进制:", isBinary)if (isBinary) {updateBinaryDisplay()} else {// 文本文件:直接将内容设置到 TextAreatextArea.text = fileHandler.fileContent}mainWindow.title = "文件编辑器 - " + fileName}onFileContentChanged: {// 当文件内容变化时更新显示(对于文本文件)if (!fileHandler.isBinary && fileHandler.fileName) {textArea.text = fileHandler.fileContent}}onFileSaved: {console.log("文件已保存:", fileName)infoDialog.text = "文件已保存: " + fileNameinfoDialog.open()}onFileClosed: {console.log("文件已关闭")textArea.text = ""mainWindow.title = "文件编辑器 - 未命名"}onErrorOccurred: {console.error("错误:", errorMessage)errorDialog.text = errorMessageerrorDialog.open()}onHexDataChanged: {if (fileHandler.isBinary) {updateBinaryDisplay()}}}// 更新二进制显示的函数function updateBinaryDisplay() {var displayText = ""for (var i = 0; i < fileHandler.hexData.length; i++) {var line = fileHandler.hexData[i]// 只显示地址和十六进制数据,移除 ASCII 部分displayText += line.address + " " + line.hex + "\n"}textArea.text = displayText}// 菜单栏menuBar: MenuBar {Menu {title: "文件"MenuItem {text: "打开"onTriggered: openDialog.open()}MenuItem {text: "保存"onTriggered: {if (fileHandler.isBinary) {// 对于二进制文件,需要从显示的文本中提取十六进制数据var lines = textArea.text.split('\n')var hexData = []for (var i = 0; i < lines.length; i++) {if (lines[i].trim() === "") continuevar lineParts = lines[i].split(' ')if (lineParts.length >= 2) {hexData.push(lineParts[1].trim())}}fileHandler.saveBinaryFile(hexData)} else {fileHandler.saveFile(textArea.text)}}}MenuSeparator {}MenuItem {text: "关闭"onTriggered: fileHandler.closeFile()}MenuSeparator {}MenuItem {text: "退出"onTriggered: Qt.quit()}}Menu {title: "查看"MenuItem {text: "文本模式"enabled: fileHandler.fileName && fileHandler.isBinaryonTriggered: {// 重新以文本模式打开文件var currentFile = fileHandler.fileNamefileHandler.closeFile()fileHandler.openFile(currentFile)}}MenuItem {text: "十六进制模式"enabled: fileHandler.fileName && !fileHandler.isBinaryonTriggered: {// 重新以二进制模式打开文件var currentFile = fileHandler.fileNamefileHandler.closeFile()fileHandler.openFile(currentFile)}}}}// 主编辑区域ScrollView {anchors.fill: parentanchors.margins: 10TextArea {id: textAreawidth: parent.widthheight: parent.heightfont.family: fileHandler.isBinary ? "Courier New" : "Arial"font.pointSize: 10wrapMode: TextArea.WrapselectByMouse: trueplaceholderText: "请打开一个文件开始编辑..."// 监听文本变化,用于设置修改状态onTextChanged: {if (fileHandler.fileName && !fileHandler.isBinary) {// 可以在这里添加修改状态的逻辑}}}}// 状态栏footer: ToolBar {RowLayout {anchors.fill: parentLabel {text: {if (fileHandler.fileName) {if (fileHandler.isBinary) {return "十六进制模式 - " + fileHandler.fileName} else {return "文本模式 - " + fileHandler.fileName}} else {return "就绪"}}Layout.fillWidth: true}Label {text: "字符数: " + textArea.length}}}// 文件对话框FileDialog {id: openDialogtitle: "选择文件"selectMultiple: falsenameFilters: ["所有文件 (*)"]onAccepted: {var filePath = openDialog.fileUrl.toString().replace("file:///", "")fileHandler.openFile(filePath)}}// 信息对话框MessageDialog {id: infoDialogtitle: "信息"icon: StandardIcon.Information}// 错误对话框MessageDialog {id: errorDialogtitle: "错误"icon: StandardIcon.Critical}// 键盘快捷键Shortcut {sequence: "Ctrl+O"onActivated: openDialog.open()}Shortcut {sequence: "Ctrl+S"onActivated: {if (fileHandler.isBinary) {var lines = textArea.text.split('\n')var hexData = []for (var i = 0; i < lines.length; i++) {if (lines[i].trim() === "") continuevar lineParts = lines[i].split(' ')if (lineParts.length >= 2) {hexData.push(lineParts[1].trim())}}fileHandler.saveBinaryFile(hexData)} else {fileHandler.saveFile(textArea.text)}}}Shortcut {sequence: "Ctrl+W"onActivated: fileHandler.closeFile()}
}
main.cpp文件源码
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "filehandler.h"int main(int argc, char *argv[])
{QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);QGuiApplication app(argc, argv);// 设置应用信息app.setApplicationName("文件编辑器");app.setApplicationVersion("1.0");app.setOrganizationName("MyCompany");// 注册 C++ 类型到 QMLqmlRegisterType<FileHandler>("FileEditor", 1, 0, "FileHandler");QQmlApplicationEngine engine;// 加载 QML 文件const QUrl url(QStringLiteral("qrc:/main.qml"));QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,&app, [url](QObject *obj, const QUrl &objUrl) {if (!obj && url == objUrl)QCoreApplication::exit(-1);}, Qt::QueuedConnection);engine.load(url);return app.exec();
}
三、效果演示
打开二进制文件的效果,可以进行编辑保存。