使用<include>标签-控件空指针

项目使用toolbar时,因布局需给include标签加id,在activity中使用toolbar的id会报空指针。经排查发现,include标签加id后,不能用toolbar的id,只能用include的id。

项目里用到toolbar的时候 由于布局关系需要给include 标签加上一个id,然后在activity中使用toolbar的id以后就会报空指针,几经周转,终于找到原因,原来include标签加上id以后,toolbar的id就不能用,只能用include的id。

感谢这位大佬:使用include标签出现的空指针问题

#ifndef BASEINFO_H #define BASEINFO_H #include <QWidget> #include <QLabel> #include<QLineEdit> #include<QComboBox> #include<QTextEdit> #include<QGridLayout> #include<QPushButton> class BaseInfo : public QWidget { Q_OBJECT public: explicit BaseInfo(QWidget *parent = nullptr); signals: private: //左侧 QLabel *UserNameLabel; QLabel *NameLabel; QLabel *SexLabel; QLabel *DepartmentLabel; QLabel *AgeLabel; QLabel *OtherLabel; QLineEdit *UserNameLineEdit; QLineEdit *NameLineEdit; QComboBox *SexComboBox; QTextEdit *DepartmentTextEdit; QLineEdit *AgeLineEdit; QGridLayout *LeftLayout; //右侧 QLabel *HeadLabel; QLabel *HeadIconLabel; QPushButton *UpdateHeadBtn; QHBoxLayout *TopRightLayout; QLabel *IntroductionLabel; QTextEdit *IntroductionTextEdit; QVBoxLayout *RightLayout; }; #endif // BASEINFO_H #ifndef CONTACT_H #define CONTACT_H #include <QWidget> #include<QLabel> #include<QGridLayout> #include<QLineEdit> #include<QCheckBox> class Contact : public QWidget { Q_OBJECT public: explicit Contact(QWidget *parent = nullptr); signals: private: QLabel *EmailLabel; QLineEdit *EmailLineEdit; QLabel *AddrLabel; QLineEdit *AddrLineEdit; QLabel *CodeLabel; QLineEdit *CodeLineEdit; QLabel *MoviTelLabel; QLineEdit *MoviTelLineEdit; QCheckBox *MoviTelCheckBook; QLabel *ProTelLabel; QLineEdit *ProTelLineEdit; QGridLayout *mainLayout; }; #endif // CONTACT_H #ifndef CONTENT_H #define CONTENT_H #include <QDialog> #include<QStackedWidget> #include<QPushButton> #include"baseinfo.h" #include"contact.h" #include"detail.h" class Content : public QDialog { Q_OBJECT public: Content(QWidget *parent = nullptr); ~Content(); QStackedWidget *stack; QPushButton *AmendBtn; QPushButton *CloseBtn; BaseInfo *baseInfo; Contact *contact; Detail *detail; }; #endif // CONTENT_H #ifndef DETAIL_H #define DETAIL_H #include <QWidget> #include<QLabel> #include<QLineEdit> #include<QComboBox> #include<QPushButton> #include<QTextEdit> #include<QGridLayout> class Detail : public QWidget { Q_OBJECT public: explicit Detail(QWidget *parent = nullptr); signals: private: QLabel *NationalLabel; QComboBox *NationalComBox; QLabel *ProvinceLabel; QComboBox *ProvinceComboBox; QLabel *CityLabel; QLineEdit *CityLineEdit; QLabel *IntroductLabel; QTextEdit *IntroductTextEditl; QGridLayout *mainLayout; }; #endif // DETAIL_H #include "baseinfo.h" BaseInfo::BaseInfo(QWidget *parent) : QWidget{parent} { //左侧 UserNameLabel =new QLabel("用户名:"); UserNameLineEdit =new QLineEdit; NameLabel =new QLabel("姓名:"); NameLineEdit =new QLineEdit; SexLabel =new QLabel("性别"); SexComboBox =new QComboBox; SexComboBox->addItem("女"); SexComboBox->addItem("男"); DepartmentLabel =new QLabel("部门:"); DepartmentTextEdit =new QTextEdit; AgeLabel =new QLabel("年龄:"); AgeLineEdit =new QLineEdit; OtherLabel =new QLabel("备注:"); OtherLabel->setFrameStyle(QFrame::Panel|QFrame::Sunken); LeftLayout= new QGridLayout(); LeftLayout->addWidget(UserNameLabel,0,0); LeftLayout->addWidget(UserNameLineEdit,0,1); LeftLayout->addWidget(NameLabel,1,0); LeftLayout->addWidget(NameLineEdit,1,1); LeftLayout->addWidget(SexLabel,2,0); LeftLayout->addWidget(SexComboBox,2,1); LeftLayout->addWidget(DepartmentLabel, 3, 0); LeftLayout->addWidget(DepartmentTextEdit, 3, 1); LeftLayout->addWidget(AgeLabel, 4, 0); LeftLayout->addWidget(AgeLineEdit, 4, 1); LeftLayout->addWidget(OtherLabel, 5, 0, 1, 2); LeftLayout->setColumnStretch(0, 1); LeftLayout->setColumnStretch(1, 3); //右侧区域控件 HeadLabel = new QLabel("头像:"); HeadIconLabel = new QLabel; QPixmap icon; // 使用资源系统 icon.load(":/icons/C:/Users/DELL/Desktop/12.png"); // 使用/icons前缀 if(icon.isNull()) { qWarning() << "无法加载12.png,使用默认头像"; } HeadIconLabel->setPixmap(icon); HeadIconLabel->setMinimumSize(120, 120); // 足够大的最小尺寸 HeadIconLabel->setAlignment(Qt::AlignCenter); HeadIconLabel->setStyleSheet("border: 1px solid #ccc;"); // 添加边框方便调试 HeadIconLabel->setScaledContents(true); UpdateHeadBtn = new QPushButton("更新"); // 右上布局 TopRightLayout = new QHBoxLayout(); TopRightLayout->setSpacing(20); TopRightLayout->addWidget(HeadLabel); TopRightLayout->addWidget(HeadIconLabel); TopRightLayout->addWidget(UpdateHeadBtn); IntroductionLabel = new QLabel("个人说明:"); IntroductionTextEdit = new QTextEdit; // 右侧布局 RightLayout = new QVBoxLayout(); RightLayout->addLayout(TopRightLayout); RightLayout->addWidget(IntroductionLabel); RightLayout->addWidget(IntroductionTextEdit); // 设置右侧布局边距(替代已弃用的setMargin) RightLayout->setContentsMargins(10, 10, 10, 10); // 底部按钮 // 主布局 QGridLayout *mainLayout = new QGridLayout(this); mainLayout->setContentsMargins(15, 15, 15, 15); // 替代已弃用的setMargin mainLayout->setSpacing(10); // // 关键修复:左右布局并排放置 mainLayout->addLayout(LeftLayout, 0, 0); // 左布局 (0,0) mainLayout->addLayout(RightLayout, 0, 1); // 右布局 (0,1) - 第二列 mainLayout->setRowStretch(0, 1); // 添加此行确保内容区域可以扩展 mainLayout->setSizeConstraint(QLayout::SetFixedSize); } #include "contact.h" Contact::Contact(QWidget *parent) : QWidget{parent} { EmailLabel =new QLabel("电子邮件:"); EmailLineEdit=new QLineEdit; AddrLabel= new QLabel("联系地址:"); AddrLineEdit =new QLineEdit; CodeLabel =new QLabel("邮政编码:"); CodeLineEdit =new QLineEdit; MoviTelLabel =new QLabel("移动电话:"); MoviTelLineEdit =new QLineEdit; MoviTelCheckBook =new QCheckBox("接收留言"); ProTelLabel =new QLabel("办公电话:"); ProTelLineEdit =new QLineEdit; mainLayout =new QGridLayout(this); mainLayout->setContentsMargins(15,15,15,15); mainLayout->setSpacing(10); mainLayout->addWidget(EmailLabel,0,0); mainLayout->addWidget(EmailLineEdit,0,1); mainLayout->addWidget(AddrLabel,1,0); mainLayout->addWidget(AddrLineEdit,1,1); mainLayout->addWidget(CodeLabel,2,0); mainLayout->addWidget(CodeLineEdit,2,1); mainLayout->addWidget(MoviTelLabel,3,0); mainLayout->addWidget(MoviTelLineEdit,3,1); mainLayout->addWidget(MoviTelCheckBook,3,2); mainLayout->addWidget(ProTelLabel,4,0); mainLayout->addWidget(ProTelLineEdit,4,1); mainLayout->setSizeConstraint(QLayout::SetFixedSize); } #include "content.h" #include<QHBoxLayout> Content::Content(QWidget *parent) : QDialog(parent) { stack =new QStackedWidget(this); stack->setFrameStyle(QFrame::Panel|QFrame::Raised); //插入三个界面 baseInfo =new BaseInfo(); contact =new Contact(); detail =new Detail(); stack->addWidget(baseInfo); stack->addWidget(contact); stack->addWidget(detail); //创建两个BTN AmendBtn =new QPushButton("修改"); CloseBtn =new QPushButton("关闭"); QHBoxLayout *BtnLayout =new QHBoxLayout; BtnLayout->addStretch(1); BtnLayout->addWidget(AmendBtn); BtnLayout->addWidget(CloseBtn); //进行整体布局 QVBoxLayout *RightLayout =new QVBoxLayout(this); RightLayout->setContentsMargins(10,10,10,10); RightLayout->setSpacing(10); RightLayout->addWidget(stack); RightLayout->addLayout(BtnLayout); } Content::~Content() {} #include "detail.h" Detail::Detail(QWidget *parent) : QWidget{parent} { NationalLabel =new QLabel("国家/地址:"); NationalComBox =new QComboBox; NationalComBox->insertItem(0,"中国"); NationalComBox->insertItem(1,"中国"); NationalComBox->insertItem(2,"中国"); ProvinceLabel= new QLabel("省份:"); ProvinceComboBox =new QComboBox; ProvinceComboBox->insertItem(0,"江苏省"); ProvinceComboBox->insertItem(0,"浙江省"); ProvinceComboBox->insertItem(0,"上海"); CityLabel =new QLabel("城市:"); CityLineEdit =new QLineEdit; IntroductLabel =new QLabel("个人说明:"); IntroductTextEditl =new QTextEdit; mainLayout =new QGridLayout(this); mainLayout->setContentsMargins(15,15,15,15); mainLayout->setSpacing(10); mainLayout->addWidget(NationalLabel,0,0); mainLayout->addWidget(NationalComBox,0,1); mainLayout->addWidget(ProvinceLabel,1,0); mainLayout->addWidget(ProvinceComboBox,1,1); mainLayout->addWidget(CityLabel,2,0); mainLayout->addWidget(CityLineEdit,2,1); mainLayout->addWidget(IntroductLabel,3,0); mainLayout->addWidget(IntroductTextEditl,3,1); }#include "content.h" #include <QApplication> #include <QSplitter> #include <QListWidget> #include <QDebug> int main(int argc, char *argv[]) { QApplication a(argc, argv); QFont font("AR PL KaitiM GB", 12); a.setFont(font); QSplitter *splitterMain = new QSplitter(Qt::Horizontal, 0); splitterMain->setOpaqueResize(true); QListWidget *list = new QListWidget(splitterMain); list->insertItem(0, QObject::tr("基本信息")); list->insertItem(1, QObject::tr("联系方式")); list->insertItem(2, QObject::tr("详细资料")); Content *content = new Content(splitterMain); // 使用Qt5的新信号槽语法 QObject::connect(list, &QListWidget::currentRowChanged, content->stack, &QStackedWidget::setCurrentIndex); // 手动设置当前项为0,触发信号,显示第一页 list->setCurrentRow(0); splitterMain->setWindowTitle(QObject::tr("修改用户资料")); splitterMain->setMinimumSize(800, 400); splitterMain->show(); return a.exec(); } 分析为什么运行后,只显示了"基本信息""联系方式""详细资料",这三个list内容,对应的都是白,且点击也是显示的
08-26
// main.cpp - 结构化数据对比工具(Qt4 兼容版 + 线程池支持) // 本程序用于比较两组结构化文本数据(如 CSV 格式)中的对象差异 // 支持 Person 和 Product 两种类型,并使用 QThreadPool 避免界面阻塞 #include <QApplication> // QApplication:管理整个应用程序的核心,包括事件循环、窗口系统等 #include <QWidget> // QWidget:所有 GUI 控件的基类,主窗口继承自它 #include <QVBoxLayout> // QVBoxLayout:垂直布局容器,自动将子控件从上到下排列 #include <QHBoxLayout> // QHBoxLayout:水平布局容器,自动将子控件从左到右排列 #include <QLabel> // QLabel:显示不可编辑的文字标签,例如说明文字 #include <QTextEdit> // QTextEdit:多行文本输入/输出框,可用于显示或输入大段文本 #include <QPushButton> // QPushButton:可点击的按钮控件,用户通过点击触发操作 #include <QComboBox> // QComboBox:下拉选择框,允许用户在多个选项中选择一个 #include <QSplitter> // QSplitter:分割窗口区域,用户可以用鼠标拖动调整左右/上下大小 #include <QMessageBox> // QMessageBox:弹出标准对话框(信息提示、警告、错误等) #include <QFileDialog> // QFileDialog:打开或保存文件的标准图形界面对话框 #include <QFile> // QFile:对本地文件进行读写操作的基础类 #include <QTextStream> // QTextStream:以流的方式读写文本内容,支持设置编码格式 #include <QDateTime> // QDateTime:获取当前时间并格式化为字符串(如 yyyy-MM-dd HH:mm:ss) #include <QClipboard> // QClipboard:访问系统剪贴板,实现复制粘贴功能 #include <QThread> // QThread:提供线程相关功能,用于调试输出当前线程 ID #include <QDebug> // qDebug():打印调试信息到控制台,比 std::cout 更安跨平台 // 必须包含 QTextCodec 来处理中文字符编码(尤其在 Qt4 中) #include <QTextCodec> // 多线程相关头文件 #include <QThreadPool> // QThreadPool:局线程池,自动管理和复用工作线程 #include <QRunnable> // QRunnable:表示一个可在子线程中运行的任务接口 #include <QFutureWatcher> // (未直接使用)但常配合 QtConcurrent 使用,监听异步任务状态 #include <QObject> // QObject:信号与槽机制的基础类,也是 invokeMethod 所需 // C++ 标准库头文件 #include <memory> // std::shared_ptr:智能指针,自动管理动态分配的对象生命周期 #include <map> // std::map:基于红黑树的键值对容器,支持按 key 快速查找 #include <vector> // std::vector:动态数组,用于存储一组对象指针 #include <string> // std::string:C++ 标准字符串类型,用于保存字段内容 #include <sstream> // std::stringstream:字符串流,用于将一行 CSV 拆分为多个字段 #include <cstdlib> // std::atoi, std::atof:C 风格函数,将字符串转为整数或浮点数 #include <cmath> // std::fabs:浮点数绝对值函数,用于判断两个 double 是否“近似相等” // ==================== 抽象数据对象接口 ==================== // 所有具体的数据结构(如 Person、Product)都必须继承此类 class DataObject { public: // 定义别名 Ptr,简化 shared_ptr<DataObject> 的书写 typedef std::shared_ptr<DataObject> Ptr; // 虚析构函数:确保通过基类指针删除派生类对象时能正确调用其析构函数 virtual ~DataObject() {} // 获取该对象的唯一标识键(例如 ID 或 SKU),用于匹配 A 和 B 中的同一条记录 virtual std::string getKey() const = 0; // 将对象转换为易读的字符串表示形式,用于输出显示 virtual std::string toString() const = 0; // 比较当前对象与另一个对象之间的差异,返回修改字段描述 virtual std::string diff(const DataObject &other) const = 0; // 克隆方法:返回当前对象的一个深拷贝副本(实现多态复制) virtual Ptr clone() const = 0; }; // ==================== 示例结构体 1: Person ==================== // 表示一个人的信息:ID、姓名、年龄 struct Person : DataObject { int id; // 唯一编号 std::string name; // 姓名 int age; // 年龄 // 构造函数:从一行 CSV 文本创建 Person 对象 explicit Person(const std::string &line) : id(0), age(0) { std::stringstream ss(line); // 创建字符串流来逐个提取字段 std::string field; // 临时变量保存当前字段内容 if (std::getline(ss, field, ',')) id = atoi(field.c_str()); // 第一个字段是 id,使用 atoi 转成整数 if (std::getline(ss, field, ',')) name = field; // 第二个字段是 name,直接赋值 if (std::getline(ss, field, ',')) age = atoi(field.c_str()); // 第三个字段是 age,转成整数 } // 默认构造函数:初始化为对象 Person() : id(0), age(0) {} // 实现 getKey() 方法:返回唯一的键 "Person:ID" std::string getKey() const { return "Person:" + std::to_string(id); } // 实现 toString() 方法:生成可读的对象描述 std::string toString() const { return "Person{id=" + std::to_string(id) + ", name='" + name + "'" + ", age=" + std::to_string(age) + "}"; } // 实现 diff() 方法:比较当前对象与另一个 Person 是否有不同字段 std::string diff(const DataObject &other) const { const Person& p = static_cast<const Person&>(other); // 安类型转换(已知类型) std::string changes; // 存储变化信息 if (name != p.name) { changes += " name: '" + name + "' -> '" + p.name + "';"; } if (age != p.age) { changes += " age: " + std::to_string(age) + " -> " + std::to_string(p.age) + ";"; } // 如果没有任何变化,返回“(无变化)”提示 return changes.empty() ? "(无变化)" : changes; } // 实现 clone() 方法:返回一个新的 Person 副本(深拷贝) Ptr clone() const { return std::make_shared<Person>(*this); // 使用智能指针包装新对象 } }; // ==================== 示例结构体 2: Product ==================== // 表示商品信息:SKU 编号、标题、价格 struct Product : DataObject { std::string sku; // 商品唯一编号 std::string title; // 商品名称 double price; // 商品价格(浮点数) // 构造函数:从一行 CSV 文本创建 Product 对象 explicit Product(const std::string &line) : price(0.0) { std::stringstream ss(line); std::string field; if (std::getline(ss, field, ',')) sku = field; // 第一列:SKU if (std::getline(ss, field, ',')) title = field; // 第二列:title if (std::getline(ss, field, ',')) price = atof(field.c_str()); // 第三列:price,使用 atof 转换 } // 默认构造函数 Product() : price(0.0) {} // 返回唯一键:“Product:SKU” std::string getKey() const { return "Product:" + sku; } // 易读格式输出(价格保留两位小数) std::string toString() const { char buf[100]; snprintf(buf, sizeof(buf), "%.2f", price); // 格式化价格 return "Product{sku='" + sku + "', title='" + title + "', price=" + std::string(buf) + "}"; } // 比较两个 Product 是否有字段不同 std::string diff(const DataObject &other) const { const Product& p = static_cast<const Product&>(other); std::string changes; if (title != p.title) { changes += " title: '" + title + "' -> '" + p.title + "';"; } if (fabs(price - p.price) > 1e-5) { // 浮点数不能直接 == 比较,需用误差判断 char oldPrice[20], newPrice[20]; snprintf(oldPrice, sizeof(oldPrice), "%.2f", price); snprintf(newPrice, sizeof(newPrice), "%.2f", p.price); changes += " price: " + std::string(oldPrice) + " -> " + std::string(newPrice) + ";"; } return changes.empty() ? "(无变化)" : changes; } // 返回克隆对象(深拷贝) Ptr clone() const { return std::make_shared<Product>(*this); } }; // ==================== 数据解析器工厂 ==================== // 静态类:根据用户选择的类型将文本解析为对象列表 class DataParser { public: // 定义别名:一组数据对象的列表 typedef std::vector<DataObject::Ptr> ObjectList; // 静态方法:将输入文本按指定类型解析成对象列表 static ObjectList parse(const QString &text, const QString &type) { ObjectList result; // 存放成功解析的对象 QStringList lines = text.split('\n', QString::SkipEmptyParts); // 按换行拆分,跳过行 // 遍历每一行文本 for (int i = 0; i < lines.size(); ++i) { const QString &line = lines[i]; // 当前行 try { if (type == "Person") { // 解析为 Person 类型并加入结果集 result.push_back(std::make_shared<Person>(line.toStdString())); } else if (type == "Product") { // 解析为 Product 类型 result.push_back(std::make_shared<Product>(line.toStdString())); } else { // 不支持的类型,打印调试日志 qWarning("未知类型: %s", type.toLocal8Bit().data()); } } catch (...) { // 捕获任何异常(比如数字转换失败) // 注意:这里不能弹 QMessageBox(非主线程禁止操作 GUI) // 我们改为记录错误并在主线程提示 qDebug() << "[Worker] 解析失败行:" << line; } } return result; // 返回解析完成的对象列表 } }; // ==================== 异步任务类:继承 QRunnable ==================== // 表示一个异步加载并解析文件的任务 class LoadDataTask : public QRunnable { public: // 枚举标识是哪个输入源(A 左侧 / B 右侧) enum Source { SourceA, SourceB }; // 构造函数:传入文件路径、数据类型、来源(A 或 B)、接收结果的对象 LoadDataTask(const QString &filePath, const QString &dataType, Source src, QObject *receiver) : m_filePath(filePath), m_dataType(dataType), m_source(src), m_receiver(receiver) {} // run() 是 QRunnable 的纯虚函数,会被线程池自动调用,在子线程中执行 void run() override { // 输出调试信息:正在处理哪个文件、运行在线程几 qDebug() << "正在子线程中处理文件:" << m_filePath << " | 线程ID:" << QThread::currentThreadId(); // 1. 读取文件内容 QFile file(m_filePath); // 创建文件对象 QString content; // 用于存储读取的部文本 bool success = false; // 记录是否成功 QString error; // 存储错误信息(如果失败) // 尝试以只读文本模式打开文件 if (file.open(QIODevice::ReadOnly | QIODevice::Text)) { QTextStream stream(&file); // 创建文本流 stream.setCodec(QTextCodec::codecForName("UTF-8")); // 设置编码为 UTF-8(支持中文) content = stream.readAll(); // 一次性读取所有内容 file.close(); // 关闭文件 success = true; // 标记成功 } else { error = "无法打开文件"; // 设置错误消息 } // 2. 如果读取成功,则进行解析(也在子线程) std::vector<DataObject::Ptr> parsedData; if (success) { parsedData = DataParser::parse(content, m_dataType); // 调用解析器解析文本 } // 3. 准备结果对象,封装所有需要传回主线程的数据 AsyncTaskResult result; result.success = success; // 是否成功 result.content = content; // 文件原始内容(用于显示在 QTextEdit) result.parsedData = parsedData; // 解析后的对象列表(用于后续对比) result.source = m_source; // 来源是 A 还是 B result.errorMsg = error; // 错误信息(如果有) result.filePath = m_filePath; // 文件路径(用于错误提示) // 4. 使用元对象系统跨线程发送信号通知主线程 // Qt::QueuedConnection 确保槽函数在目标线程(GUI 主线程)排队执行 QMetaObject::invokeMethod(m_receiver, "onTaskFinished", Qt::QueuedConnection, Q_ARG(AsyncTaskResult, result)); } private: QString m_filePath; // 要加载的文件路径 QString m_dataType; // 当前选择的数据类型("Person" 或 "Product") Source m_source; // 来源标识(A 或 B) QObject *m_receiver; // 接收结果的对象(即主窗口 this) // 内部结构体:封装任务执行结果,便于跨线程传递 struct AsyncTaskResult { bool success; // 成功标志 QString content; // 文件内容(QString) std::vector<DataObject::Ptr> parsedData; // 解析后的对象列表 Source source; // 来源 A/B QString errorMsg; // 错误信息 QString filePath; // 文件路径 }; // 声明该结构体支持 Qt 元对象系统序列化(让 invokeMethod 能传递它) Q_DECLARE_METATYPE(AsyncTaskResult) }; // 手动注册类型到 Qt 元对象系统(必须放在类外) Q_DECLARE_METATYPE(LoadDataTask::AsyncTaskResult) // ==================== 主窗口类声明 ==================== // 继承自 QWidget,作为主界面窗口 class StructuredDataComparator : public QWidget { Q_OBJECT // 必须添加:启用 Qt 的元对象系统(信号槽、moc 工具需要) public: // 构造函数:parent 表示父窗口,默认为 explicit StructuredDataComparator(QWidget *parent = 0); private slots: // 槽函数:响应按钮点击事件 void onCompare(); // 开始对比 A 和 B 的数据 void onClear(); // 清所有输入输出内容 void onCopyResult(); // 复制对比结果到系统剪贴板 void onLoadFileA(); // 加载文件 A 到左侧输入框 void onLoadFileB(); // 加载文件 B 到右侧输入框 void onTaskFinished(const LoadDataTask::AsyncTaskResult &result); // 接收子线程完成的结果 private: // 私有方法:初始化用户界面布局和控件 void setupUI(); // 成员变量:GUI 控件指针 QTextEdit *inputA; // 左侧输入框(旧数据) QTextEdit *inputB; // 右侧输入框(新数据) QTextEdit *outputArea; // 下方输出区域(显示对比结果) QPushButton *compareBtn; // “开始对比”按钮 QPushButton *clearBtn; // “清”按钮 QPushButton *copyBtn; // “复制结果”按钮 QComboBox *dataTypeCombo; // 下拉框选择数据类型(Person / Product) // 缓存最后一次加载成功的解析数据(用于对比,避免重复解析) std::vector<DataObject::Ptr> m_lastParsedA; std::vector<DataObject::Ptr> m_lastParsedB; }; // ========== 主窗口构造函数实现 ========== StructuredDataComparator::StructuredDataComparator(QWidget *parent) : QWidget(parent) // 调用父类 QWidget 的构造函数 { // 注册自定义类型,使它可以跨线程传递(由 QMetaObject::invokeMethod 使用) qRegisterMetaType<LoadDataTask::AsyncTaskResult>("LoadDataTask::AsyncTaskResult"); setupUI(); // 初始化界面布局和控件 } // ========== setupUI 函数实现:构建整个用户界面 ========== void StructuredDataComparator::setupUI() { setWindowTitle("🧾 结构化数据对比工具(多线程安)"); // 设置窗口标题 resize(900, 700); // 设置初始窗口大小(宽900px,高700px) QVBoxLayout *mainLayout = new QVBoxLayout(this); // 创建主垂直布局,作为顶层容器 // --- 第一部分:顶部控件 —— 选择数据类型 --- QHBoxLayout *topLayout = new QHBoxLayout; // 水平布局放置标签和下拉框 topLayout->addWidget(new QLabel("数据类型:")); // 添加说明文字 dataTypeCombo = new QComboBox; // 创建下拉选择框 dataTypeCombo->addItem("Person"); // 添加选项:Person dataTypeCombo->addItem("Product"); // 添加选项:Product topLayout->addWidget(dataTypeCombo); // 将下拉框加入布局 topLayout->addStretch(); // 右侧留白,使控件靠左排列 mainLayout->addLayout(topLayout); // 将顶部布局加入主布局 // --- 第二部分:左右输入区(使用分割器)--- QSplitter *splitter = new QSplitter(Qt::Horizontal); // 创建水平分割器,左右可拖动 // 左侧容器:旧数据输入 QWidget *containerA = new QWidget; // 包裹左侧控件的容器 QVBoxLayout *layoutA = new QVBoxLayout; // 垂直布局 layoutA->addWidget(new QLabel("来源 A(旧数据)")); // 添加标签说明 inputA = new QTextEdit; // 创建多行文本框 inputA->setPlaceholderText("输入格式:\nid,name,age\n1,Alice,30"); // 占位提示文本 layoutA->addWidget(inputA); // 将文本框加入布局 containerA->setLayout(layoutA); // 容器应用此布局 // 右侧容器:新数据输入 QWidget *containerB = new QWidget; QVBoxLayout *layoutB = new QVBoxLayout; layoutB->addWidget(new QLabel("来源 B(新数据)")); inputB = new QTextEdit; inputB->setPlaceholderText("输入格式:\nid,name,age\n2,Bob,25"); layoutB->addWidget(inputB); containerB->setLayout(layoutB); // 将左右两个容器加入分割器 splitter->addWidget(containerA); splitter->addWidget(containerB); mainLayout->addWidget(splitter); // 将分割器加入主布局 // --- 第三部分:按钮行 --- QHBoxLayout *btnLayout = new QHBoxLayout; // 水平布局放置按钮 QPushButton *loadBtnA = new QPushButton("📁 加载 A"); // 加载文件 A 的按钮 QPushButton *loadBtnB = new QPushButton("📁 加载 B"); // 加载文件 B 的按钮 compareBtn = new QPushButton("🔍 开始对比"); // 开始对比按钮 clearBtn = new QPushButton("🗑️ 清"); // 清按钮 copyBtn = new QPushButton("📋 复制结果"); // 复制结果按钮 // 按顺序添加按钮到布局 btnLayout->addWidget(loadBtnA); btnLayout->addWidget(loadBtnB); btnLayout->addSpacing(40); // 插入白间距,美观分隔 btnLayout->addWidget(compareBtn); btnLayout->addWidget(clearBtn); btnLayout->addWidget(copyBtn); mainLayout->addLayout(btnLayout); // 将按钮行加入主布局 // --- 第四部分:输出结果显示区 --- outputArea = new QTextEdit; // 创建输出文本框 outputArea->setReadOnly(true); // 设置只读,防止用户误改 outputArea->setPlaceholderText("对比结果将显示在这里..."); // 提示语 mainLayout->addWidget(outputArea); // 加入主布局 // === 最后:连接信号与槽(使用 Qt4 宏语法)=== connect(compareBtn, SIGNAL(clicked()), this, SLOT(onCompare())); // 点击“开始对比”执行 onCompare connect(clearBtn, SIGNAL(clicked()), this, SLOT(onClear())); // 点击“清”执行 onClear connect(copyBtn, SIGNAL(clicked()), this, SLOT(onCopyResult())); // 点击“复制”执行 onCopyResult connect(loadBtnA, SIGNAL(clicked()), this, SLOT(onLoadFileA())); // 点击“加载 A”执行 onLoadFileA connect(loadBtnB, SIGNAL(clicked()), this, SLOT(onLoadFileB())); // 点击“加载 B”执行 onLoadFileB } // ========== 槽函数:执行数据对比逻辑 ========== void StructuredDataComparator::onCompare() { QString type = dataTypeCombo->currentText(); // 获取当前选择的数据类型 QString textA = inputA->toPlainText(); // 获取左侧输入的所有文本 QString textB = inputB->toPlainText(); // 获取右侧输入的所有文本 // 检查是否至少有一个输入为 if (textA.trimmed().isEmpty() || textB.trimmed().isEmpty()) { outputArea->setText("⚠️ 请确保两个输入框都有数据!"); // 提示用户 return; // 提前退出 } // 使用已缓存的 parsed 数据(由线程任务填充) std::vector<DataObject::Ptr> listA = m_lastParsedA; std::vector<DataObject::Ptr> listB = m_lastParsedB; // 如果没有缓存且文本不为,则临时同步解析一次(适用于手动粘贴的小数据) if (listA.empty() && !textA.isEmpty()) { listA = DataParser::parse(textA, type); } if (listB.empty() && !textB.isEmpty()) { listB = DataParser::parse(textB, type); } // 创建 map:key -> object,便于 O(log n) 查找 std::map<std::string, DataObject::Ptr> mapA, mapB; for (size_t i = 0; i < listA.size(); ++i) mapA[listA[i]->getKey()] = listA[i]; for (size_t i = 0; i < listB.size(); ++i) mapB[listB[i]->getKey()] = listB[i]; // 定义三个向量分别存储新增、删除、修改的对象描述 std::vector<std::string> added; std::vector<std::string> removed; std::vector<std::string> modified; // 在 A 中但不在 B 中 → 被删除 for (std::map<std::string, DataObject::Ptr>::iterator it = mapA.begin(); it != mapA.end(); ++it) { if (mapB.find(it->first) == mapB.end()) { removed.push_back(it->second->toString()); } } // 在 B 中但不在 A 中 → 新增 for (std::map<std::string, DataObject::Ptr>::iterator it = mapB.begin(); it != mapB.end(); ++it) { if (mapA.find(it->first) == mapA.end()) { added.push_back(it->second->toString()); } } // 同时存在于 A 和 B → 检查是否修改 for (std::map<std::string, DataObject::Ptr>::iterator it = mapB.begin(); it != mapB.end(); ++it) { std::string key = it->first; std::map<std::string, DataObject::Ptr>::iterator oldIt = mapA.find(key); if (oldIt != mapA.end()) { std::string diffStr = it->second->diff(*oldIt->second); if (diffStr != "(无变化)") { modified.push_back("🔹 " + key + "\n 修改: " + diffStr); } } } // 生成最终报告文本 QString result; result += "🧾 结构化数据对比报告\n"; result += "==============================\n"; result += QString("📅 时间: %1\n").arg(QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss")); result += QString("📊 类型: %1\n\n").arg(type); result += QString("🆕 新增 (%1):\n").arg(added.size()); for (size_t i = 0; i < added.size(); ++i) { result += " • " + QString::fromStdString(added[i]) + "\n"; } result += "\n"; result += QString("🗑️ 删除 (%1):\n").arg(removed.size()); for (size_t i = 0; i < removed.size(); ++i) { result += " • " + QString::fromStdString(removed[i]) + "\n"; } result += "\n"; result += QString("✏️ 修改 (%1):\n").arg(modified.size()); for (size_t i = 0; i < modified.size(); ++i) { result += QString::fromStdString(modified[i]) + "\n"; } // 显示在输出框 outputArea->setText(result); } // ========== 清所有内容 ========== void StructuredDataComparator::onClear() { inputA->clear(); // 清左侧输入框 inputB->clear(); // 清右侧输入框 outputArea->clear(); // 清输出框 m_lastParsedA.clear(); // 清除缓存的解析结果 m_lastParsedB.clear(); } // ========== 复制结果到剪贴板 ========== void StructuredDataComparator::onCopyResult() { // 获取 QApplication 的局剪贴板对象,并设置当前输出文本 QApplication::clipboard()->setText(outputArea->toPlainText()); // 弹出成功提示对话框 QMessageBox::information(this, "已复制", "结果已复制到剪贴板!"); } // ========== 加载文件 A(启动线程任务)========== void StructuredDataComparator::onLoadFileA() { // 弹出文件选择对话框,过滤 .csv 和 .txt 文件 QString filePath = QFileDialog::getOpenFileName(this, "加载 A 文件", "", "CSV/TXT (*.csv *.txt)"); if (!filePath.isEmpty()) { // 用户未取消选择 // 创建任务:传入文件路径、当前类型、来源 A、接收者(this) LoadDataTask *task = new LoadDataTask(filePath, dataTypeCombo->currentText(), LoadDataTask::SourceA, this); task->setAutoDelete(true); // 任务完成后由线程池自动 delete,避免内存泄漏 // 提交到局线程池执行(run() 将在后台线程被调用) QThreadPool::globalInstance()->start(task); // 显示加载中提示(用户体验优化) inputA->setPlainText("⏳ 正在加载并解析文件..."); } } // ========== 加载文件 B ========== void StructuredDataComparator::onLoadFileB() { QString filePath = QFileDialog::getOpenFileName(this, "加载 B 文件", "", "CSV/TXT (*.csv *.txt)"); if (!filePath.isEmpty()) { // 创建任务:来源为 B LoadDataTask *task = new LoadDataTask(filePath, dataTypeCombo->currentText(), LoadDataTask::SourceB, this); task->setAutoDelete(true); QThreadPool::globalInstance()->start(task); // 显示加载中提示 inputB->setPlainText("⏳ 正在加载并解析文件..."); } } // ========== 接收子线程完成的结果(主线程执行)========== void StructuredDataComparator::onTaskFinished(const LoadDataTask::AsyncTaskResult &result) { // 此函数在主线程执行,可以安地更新 UI qDebug() << "[MainThread] 收到任务完成通知,来源:" << (result.source == LoadDataTask::SourceA ? "A" : "B") << " | 线程ID:" << QThread::currentThreadId(); if (result.success) { // 更新对应的输入框文本 if (result.source == LoadDataTask::SourceA) { inputA->setPlainText(result.content); // 显示文件内容 m_lastParsedA = result.parsedData; // 缓存解析结果供对比使用 } else { inputB->setPlainText(result.content); m_lastParsedB = result.parsedData; } // 提示用户加载成功 QMessageBox::information(this, "完成", "文件加载并解析成功!"); } else { // 加载失败则显示错误信息 QString msg = "加载失败:" + result.errorMsg + "\n文件:" + result.filePath; if (result.source == LoadDataTask::SourceA) { inputA->setPlainText(msg); } else { inputB->setPlainText(msg); } // 弹出错误对话框 QMessageBox::critical(this, "错误", msg); } } // ========== MOC 编译支持(必须)========== // Qt 的元对象编译器(moc)需要处理含有 Q_OBJECT 的类 // 此行告诉 moc 处理这个文件中的信号槽 #include "main.moc" // ========== 程序入口点 ========== int main(int argc, char *argv[]) { QApplication app(argc, argv); // 创建 Qt 应用对象,管理事件循环 #if QT_VERSION < 0x050000 // Qt4 推荐:设置默认字符串编码为 UTF-8,以便正确显示中文 QTextCodec::setCodecForCStrings(QTextCodec::codecForName("UTF-8")); #endif StructuredDataComparator tool; // 创建主窗口实例 tool.show(); // 显示窗口 return app.exec(); // 启动事件循环,等待用户交互 } 这段代码在加入了多线程处理之后,可能是将代码进行头文件和源文件分离时出现了问题,请帮我将这段代码按头文件和源文件的方式进行分离,并说明引用关系
最新发布
09-25
#ifndef WIDGET_H #define WIDGET_H #include <QWidget> #include <QStackedWidget> #include <QLabel> #include <QPushButton> #include <QVBoxLayout> #include <QGridLayout> #include <QMovie> #include "childwidget.h" #include "login.h" #include "register.h" #include"database.h" #include "roleselection.h" #include "levelselection.h" #include"gamewidget.h" #include"settingmusic.h" // 在widget.h中添加 #include <QMediaPlayer> #include <QMediaPlaylist> class Widget : public QWidget { Q_OBJECT public: Widget(QWidget *parent = nullptr); ~Widget(); private: QStackedWidget *stackedWidget; QWidget *mainPage; childwidget *loginRegisterPage; Login *pageLogin; RegisterWidget *pageRegister; // 主页面控件 QLabel *titleLabel; QPushButton *btnRegisterLogin; QPushButton *btnStartGame; QPushButton *btnSettings; QPushButton *btnLeaderboard; QPushButton *btnExit; QLabel *gifLabel; QMovie *movie; DatabaseManager*dbManager; RoleSelection *roleSelectionPage; // 新增角色选择页 LevelSelection *levelSelectionPage; int selectedRoleIndex; QMediaPlayer *bgmPlayer; // 背景音乐播放器 settingmusic *settingsDialog; // 背景音乐播放器 QMediaPlaylist *bgmPlaylist; signals: void loginSuccess(); private slots: void onRegisterLoginClicked(); void onBackToMainPage(); void onGotoLogin(); void onGotoRegister(); void handleLogin(const QString &username, const QString &password); void handleRegister(const QString &username, const QString &password, const QString &confirmPassword); void handleRoleSelected(int index); // 处理角色选择 void handleLevelSelected(int index); // 处理关卡选择 // 在Widget构造函数中 }; #endif // WIDGET_H #include "widget.h" #include <QDebug> #include <QMessageBox> #include <QFont> #include <QMediaPlaylist> // 添加这个头文件 Widget::Widget(QWidget *parent) : QWidget(parent), stackedWidget(nullptr), mainPage(nullptr), loginRegisterPage(nullptr), pageLogin(nullptr), pageRegister(nullptr), titleLabel(nullptr), btnRegisterLogin(nullptr), btnStartGame(nullptr), btnSettings(nullptr), btnLeaderboard(nullptr), btnExit(nullptr), gifLabel(nullptr), movie(nullptr), bgmPlayer(nullptr), bgmPlaylist(nullptr), // 添加这个成员变量的初始化 settingsDialog(nullptr), dbManager(nullptr), // 添加数据库管理器的初始化 roleSelectionPage(nullptr), levelSelectionPage(nullptr), selectedRoleIndex(-1) // 初始化角色索引 { setWindowTitle("哈基迷大探险"); resize(800, 700); move(550, 50); setWindowIcon(QIcon(":/picture/mimi1.png")); // 1. 初始化堆栈窗体 stackedWidget = new QStackedWidget(this); QVBoxLayout *mainContainerLayout = new QVBoxLayout(this); mainContainerLayout->addWidget(stackedWidget); mainContainerLayout->setContentsMargins(0, 0, 0, 0); setLayout(mainContainerLayout); // 2. 初始化主页面 mainPage = new QWidget(); QVBoxLayout *mainPageLayout = new QVBoxLayout(mainPage); // ① 标题标签 titleLabel = new QLabel(mainPage); titleLabel->setAlignment(Qt::AlignCenter); titleLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); titleLabel->setMinimumSize(800, 200); QPixmap originalPixmap(":/picture/title.png"); if (!originalPixmap.isNull()) { QPixmap scaledPixmap = originalPixmap.scaled( titleLabel->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation ); titleLabel->setPixmap(scaledPixmap); } else { titleLabel->setText("图片加载失败"); titleLabel->setStyleSheet("color: red"); } mainPageLayout->addWidget(titleLabel); mainPageLayout->addSpacing(40); // ② 按钮网格 QGridLayout *gridLayout = new QGridLayout(); btnRegisterLogin = new QPushButton("注册/登录", mainPage); btnStartGame = new QPushButton("开始游戏", mainPage); btnSettings = new QPushButton("游戏设置", mainPage); btnLeaderboard = new QPushButton("排行榜", mainPage); btnExit = new QPushButton("退出游戏", mainPage); QFont btnFont("Arial", 14); QPushButton* btns[] = {btnRegisterLogin, btnStartGame, btnSettings, btnLeaderboard, btnExit}; for (auto btn : btns) { btn->setFont(btnFont); btn->setMinimumSize(200, 50); btn->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); btn->setStyleSheet( "QPushButton {" " background-color: pink;" " color: white;" " border: 2px solid white;" " font: bold 14px;" " border-radius: 10px;" "}" "QPushButton:hover {" " background-color: #ff88aa;" "}" "QPushButton:pressed {" " background-color: white;" " color: pink;" "}" ); } gridLayout->addWidget(btnRegisterLogin, 0, 0); gridLayout->addWidget(btnStartGame, 0, 1); gridLayout->addWidget(btnSettings, 1, 0); gridLayout->addWidget(btnLeaderboard, 1, 1); gridLayout->addWidget(btnExit, 2, 0, 1, 2); gridLayout->setSpacing(20); gridLayout->setContentsMargins(50, 20, 50, 20); mainPageLayout->addLayout(gridLayout); // ③ 动图标签 gifLabel = new QLabel(mainPage); gifLabel->setAlignment(Qt::AlignCenter); movie = new QMovie(":/gif/mimi2.gif", QByteArray(), mainPage); if (movie->isValid()) { gifLabel->setMovie(movie); movie->start(); gifLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); } else { gifLabel->setText("动图加载失败"); gifLabel->setStyleSheet("color: red"); } mainPageLayout->addWidget(gifLabel); mainPageLayout->setContentsMargins(10, 10, 10, 10); mainPageLayout->setSpacing(10); mainPage->setLayout(mainPageLayout); // 3. 初始化登录/注册相关页面 loginRegisterPage = new childwidget(); pageLogin = new Login(); pageRegister = new RegisterWidget(); // 初始化背景音乐播放器和播放列表 bgmPlayer = new QMediaPlayer(this); bgmPlaylist = new QMediaPlaylist(this); bgmPlaylist->setPlaybackMode(QMediaPlaylist::Loop); // 设置循环模式 bgmPlayer->setPlaylist(bgmPlaylist); // 将播放列表关联到播放器 // 添加背景音乐(根据实际文件路径修改) bgmPlaylist->addMedia(QUrl("qrc:/music/bgm1.mp3")); bgmPlayer->setVolume(70); // 设置初始音量 bgmPlayer->play(); // 开始播放 // 初始化设置窗口 settingsDialog = new settingmusic(bgmPlayer, this); stackedWidget->addWidget(settingsDialog); // 添加到堆栈窗口 // 4. 添加页面到堆栈 stackedWidget->addWidget(mainPage); stackedWidget->addWidget(loginRegisterPage); stackedWidget->addWidget(pageLogin); stackedWidget->addWidget(pageRegister); stackedWidget->setCurrentWidget(mainPage); // 默认显示主页面 // 初始化数据库 dbManager = new DatabaseManager(this); if (!dbManager->initDatabase()) { QMessageBox::critical(this, "数据库错误", "无法初始化数据库,请检查应用权限"); } roleSelectionPage = new RoleSelection(); stackedWidget->addWidget(roleSelectionPage); // 添加到堆栈 levelSelectionPage = new LevelSelection(); // 初始化关卡选择页 stackedWidget->addWidget(levelSelectionPage); // 5. 连接信号与槽 connect(btnRegisterLogin, &QPushButton::clicked, this, &Widget::onRegisterLoginClicked); connect(loginRegisterPage, &childwidget::backToMainPage, this, &Widget::onBackToMainPage); connect(loginRegisterPage, &childwidget::gotoLoginPage, this, &Widget::onGotoLogin); connect(loginRegisterPage, &childwidget::gotoRegisterPage, this, &Widget::onGotoRegister); connect(pageLogin, &Login::loginRequested, this, &Widget::handleLogin); connect(pageRegister, &RegisterWidget::registerRequested, this, &Widget::handleRegister); connect(pageLogin, &Login::backToPrevious, this, &Widget::onRegisterLoginClicked); connect(pageRegister, &RegisterWidget::backToPrevious, this, &Widget::onRegisterLoginClicked); connect(btnExit, &QPushButton::clicked, this, &Widget::close); connect(btnSettings, &QPushButton::clicked, this, [=]() { stackedWidget->setCurrentWidget(settingsDialog); settingsDialog->show(); }); // 连接设置窗口返回信号(修复了缺少的闭合括号) connect(settingsDialog, &settingmusic::backToMainPage, this, [=]() { stackedWidget->setCurrentWidget(mainPage); }); // 连接登录成功后跳转角色选择页 connect(this, &Widget::loginSuccess, this, [=]() { stackedWidget->setCurrentWidget(roleSelectionPage); qDebug() << "登录成功,跳转至角色选择页"; }); // 连接角色选择页返回按钮(返回主页面) connect(roleSelectionPage, &RoleSelection::backToPrevious, this, [=]() { stackedWidget->setCurrentWidget(mainPage); }); // 连接角色选择页的信号 connect(roleSelectionPage, &RoleSelection::roleConfirmed, this, [=](int index) { QMessageBox::information(this, "选择成功", QString("你选择了角色:%1").arg(roleSelectionPage->getRoleInfo(index).name)); stackedWidget->setCurrentWidget(levelSelectionPage); }); connect(levelSelectionPage, &LevelSelection::backToRoleSelection, this, [=]() { stackedWidget->setCurrentWidget(roleSelectionPage); }); connect(levelSelectionPage, &LevelSelection::levelConfirmed, this, &Widget::handleLevelSelected); connect(btnStartGame, &QPushButton::clicked, this, [=]() { stackedWidget->setCurrentWidget(roleSelectionPage); // 跳转到角色选择 }); } Widget::~Widget() { delete movie; // 手动释放动图 // 其他对象由Qt的父对象机制自动释放,无需手动delete } void Widget::onRegisterLoginClicked() { stackedWidget->setCurrentWidget(loginRegisterPage); qDebug() << "切换到登录/注册选择页面"; } void Widget::onBackToMainPage() { stackedWidget->setCurrentWidget(mainPage); qDebug() << "切回主页面"; } void Widget::onGotoLogin() { stackedWidget->setCurrentWidget(pageLogin); qDebug() << "切换到登录页面"; } void Widget::onGotoRegister() { stackedWidget->setCurrentWidget(pageRegister); qDebug() << "切换到注册页面"; } void Widget::handleLogin(const QString &username, const QString &password) { qDebug() << "登录请求 - 用户名:" << username << "密码:" << password; // 检查数据库连接状态 if (!dbManager->m_db.isOpen() && !dbManager->m_db.open()) { QMessageBox::critical(this, "登录失败", "数据库连接失败,请重试!"); return; } // 调用数据库管理器验证用户 if (dbManager->verifyUser(username, password)) { QMessageBox::information(this, "登录成功", "登录成功,请选择角色!"); emit loginSuccess(); // 发送登录成功信号,触发跳转角色选择页 } else { QMessageBox::warning(this, "登录失败", "用户名或密码不正确!"); } } void Widget::handleRegister(const QString &username, const QString &password, const QString &confirmPassword) { // 1. 检查密码一致性 if (password != confirmPassword) { QMessageBox::warning(this, "注册失败", "两次输入的密码不一致!"); return; } // 2. 调用数据库管理器注册用户 if (dbManager->registerUser(username, password)) { QMessageBox::information(this, "注册成功", "账号注册成功,可直接登录!"); stackedWidget->setCurrentWidget(mainPage); // 跳回主页面 } else { // 注册失败:用户名已存在或数据库错误 if (dbManager->isUsernameExists(username)) { QMessageBox::warning(this, "注册失败", "用户名已存在!"); } else { QMessageBox::critical(this, "注册失败", "数据库操作错误,请稍后重试!"); } } } void Widget::handleRoleSelected(int index) { selectedRoleIndex = index; QMessageBox::information(this, "选择成功", QString("你选择了角色:%1").arg(roleSelectionPage->getRoleInfo(index).name)); stackedWidget->setCurrentWidget(levelSelectionPage); // 跳转到关卡选择页 } void Widget::handleLevelSelected(int index) { const LevelInfo &level = levelSelectionPage->getLevelInfo(index); // 创建游戏窗口并显示 GameWidget *gameWidget = new GameWidget(); // 可以在这里设置游戏关卡信息 gameWidget->show(); // 显示游戏窗口 this->hide(); // 隐藏主窗口 // 游戏结束后返回主窗口 connect(gameWidget, &GameWidget::destroyed, this, [=]() { this->show(); delete gameWidget; // 确保释放内存 }); } #ifndef SETTINGMUSIC_H #define SETTINGMUSIC_H #include <QDialog> #include <QMediaPlayer> #include <QSlider> #include <QComboBox> #include <QPushButton> class settingmusic : public QDialog { Q_OBJECT public: explicit settingmusic(QMediaPlayer *player, QWidget *parent = nullptr); signals: void backToMainPage(); // 返回主界面信号 private slots: void onVolumeChanged(int value); void onMusicSelected(int index); void onBackClicked(); private: QMediaPlayer *musicPlayer; // 音乐播放器指针 QSlider *volumeSlider; // 音量滑块 QComboBox *musicSelector; // 音乐选择下拉框 QVector<QString> musicFiles; // 音乐文件路径列表 }; #endif // settingmusic_H #include "settingmusic.h" #include <QVBoxLayout> #include <QHBoxLayout> #include <QLabel> #include <QDebug> settingmusic::settingmusic(QMediaPlayer *player, QWidget *parent) : QDialog(parent), musicPlayer(player) { setWindowTitle("游戏设置"); setFixedSize(400, 300); setWindowIcon(QIcon(":/picture/mimi1.png")); // 初始化音乐文件列表(根据实际资源修改) musicFiles = { ":/music/unknowflowers.mp3" }; // 音乐选择下拉框 QLabel *musicLabel = new QLabel("选择背景音乐:", this); musicSelector = new QComboBox(this); musicSelector->addItems({"轻松旋律"}); // 音量控制 QLabel *volumeLabel = new QLabel("音量:", this); volumeSlider = new QSlider(Qt::Horizontal, this); volumeSlider->setRange(0, 100); volumeSlider->setValue(player->volume()); // 返回按钮 QPushButton *backBtn = new QPushButton("返回主界面", this); backBtn->setMinimumHeight(40); backBtn->setStyleSheet( "QPushButton {" " background-color: pink;" " color: white;" " border: 2px solid white;" " font: bold 14px;" " border-radius: 10px;" "}" "QPushButton:hover {" " background-color: #ff88aa;" "}" ); // 布局设置 QVBoxLayout *mainLayout = new QVBoxLayout(this); mainLayout->addSpacing(30); QHBoxLayout *musicLayout = new QHBoxLayout(); musicLayout->addWidget(musicLabel); musicLayout->addWidget(musicSelector); mainLayout->addLayout(musicLayout); QHBoxLayout *volumeLayout = new QHBoxLayout(); volumeLayout->addWidget(volumeLabel); volumeLayout->addWidget(volumeSlider); mainLayout->addLayout(volumeLayout); mainLayout->addStretch(); mainLayout->addWidget(backBtn, 0, Qt::AlignCenter); mainLayout->setContentsMargins(50, 20, 50, 30); mainLayout->setSpacing(20); // 连接信号槽 connect(volumeSlider, &QSlider::valueChanged, this, &settingmusic::onVolumeChanged); connect(musicSelector, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &settingmusic::onMusicSelected); connect(backBtn, &QPushButton::clicked, this, &settingmusic::onBackClicked); // 初始播放第一首音乐 if (!musicFiles.isEmpty()) { musicPlayer->setMedia(QUrl(musicFiles.first())); musicPlayer->setVolume(volumeSlider->value()); musicPlayer->play(); } } void settingmusic::onVolumeChanged(int value) { if (musicPlayer) { musicPlayer->setVolume(value); } } void settingmusic::onMusicSelected(int index) { if (musicPlayer && index >= 0 && index < musicFiles.size()) { musicPlayer->stop(); musicPlayer->setMedia(QUrl(musicFiles[index])); musicPlayer->play(); } } void settingmusic::onBackClicked() { emit backToMainPage(); hide(); // 隐藏设置窗口而不是关闭,保持音乐播放 } 其他功能可以使用音乐没有声音
09-21
// main.cpp - 支持多种结构体的数据对比工具(每行带详细注释) #include <QApplication> // QApplication:管理应用程序事件循环和局设置 #include <QWidget> // QWidget:所有 GUI 控件的基类 #include <QVBoxLayout> // QVBoxLayout:垂直布局容器 #include <QHBoxLayout> // QHBoxLayout:水平布局容器 #include <QLabel> // QLabel:用于显示文本标签 #include <QTextEdit> // QTextEdit:多行可编辑文本框(支持富文本) #include <QPushButton> // QPushButton:按钮控件 #include <QComboBox> // QComboBox:下拉选择框 #include <QSplitter> // QSplitter:可拖动分割窗口区域 #include <QMessageBox> // QMessageBox:弹出消息对话框(提示、错误等) #include <QFileDialog> // QFileDialog:打开或保存文件的标准对话框 #include <QFile> // QFile:操作文件读写 #include <QTextStream> // QTextStream:以文本流方式读写文件(支持编码) #include <QDateTime> // QDateTime:获取当前时间并格式化输出 #include <QClipboard> // QClipboard:访问系统剪贴板(复制粘贴功能) #include <memory> // std::shared_ptr 智能指针,用于安管理对象生命周期 #include <map> // std::map 键值映射,用于主键 -> 对象查找 #include <vector> // std::vector 动态数组,存储一组对象 #include <set> // std::set 有序集合(本例未直接使用,预留扩展) #include <string> // std::string 字符串类型 #include <sstream> // std::stringstream 字符串流,用于解析 CSV 行 #include <algorithm> // std::find, sort 等算法(本例中用于查找) // ==================== 抽象数据对象接口 ==================== // 定义一个通用接口,所有具体的结构体都必须继承它 class DataObject { public: // 使用别名简化 shared_ptr<DataObject> 的书写 using Ptr = std::shared_ptr<DataObject>; // 虚析构函数:确保通过基类指针删除派生类对象时能正确调用析构 virtual ~DataObject() = default; // 获取该对象的唯一标识键(如 ID 或 SKU),用于匹配两个来源中的同一条记录 virtual std::string getKey() const = 0; // 将对象转换为可读字符串(用于显示在结果中) virtual std::string toString() const = 0; // 比较当前对象与另一个对象的差异,返回修改字段描述 virtual std::string diff(const DataObject &other) const = 0; // 克隆方法:返回当前对象的一个副本(实现多态复制) virtual Ptr clone() const = 0; }; // ==================== 示例结构体 1: Person ==================== // 表示一个人的信息:ID、姓名、年龄 struct Person : DataObject { int id{}; // 成员变量初始化为 0 std::string name; // 姓名 int age{}; // 年龄 // 构造函数:从一行 CSV 文本创建 Person 对象 explicit Person(const std::string &line) { std::stringstream ss(line); // 创建字符串流处理输入 std::string field; // 临时变量保存每个字段 std::getline(ss, field, ','); id = std::stoi(field); // 第一个字段是 id,转成整数 std::getline(ss, field, ','); name = field; // 第二个字段是 name std::getline(ss, field, ','); age = std::stoi(field); // 第三个字段是 age } // 默认构造函数(允许创建对象) Person() = default; // 实现 getKey():使用 "Person:ID" 作为唯一键 std::string getKey() const override { return "Person:" + std::to_string(id); } // 实现 toString():生成易读的对象表示 std::string toString() const override { return "Person{id=" + std::to_string(id) + ", name='" + name + "'" + ", age=" + std::to_string(age) + "}"; } // 实现 diff():比较两个 Person 是否有字段不同 std::string diff(const DataObject &other) const override { const auto &p = dynamic_cast<const Person&>(other); // 安向下转型 std::string changes; // 存储变化信息 if (name != p.name) { changes += " name: '" + name + "' -> '" + p.name + "';"; } if (age != p.age) { changes += " age: " + std::to_string(age) + " -> " + std::to_string(p.age) + ";"; } return changes.empty() ? "(无变化)" : changes; // 如果没变,返回提示 } // 实现 clone():返回一个新的 Person 副本 Ptr clone() const override { return std::make_shared<Person>(*this); // 深拷贝当前对象 } }; // ==================== 示例结构体 2: Product ==================== // 表示商品信息:SKU、标题、价格 struct Product : DataObject { std::string sku; // 商品编号 std::string title; // 标题 double price{}; // 价格 // 构造函数:从 CSV 行解析数据 explicit Product(const std::string &line) { std::stringstream ss(line); std::string field; std::getline(ss, field, ','); sku = field; // 第一列:SKU std::getline(ss, field, ','); title = field; // 第二列:title std::getline(ss, field, ','); price = std::stod(field); // 第三列:price(转为 double) } // 默认构造函数 Product() = default; // 唯一键基于 SKU std::string getKey() const override { return "Product:" + sku; } // 易读格式输出 std::string toString() const override { char buf[100]; snprintf(buf, sizeof(buf), "%.2f", price); // 格式化价格保留两位小数 return "Product{sku='" + sku + "', title='" + title + "', price=" + std::string(buf) + "}"; } // 比较字段差异 std::string diff(const DataObject &other) const override { const auto &p = dynamic_cast<const Product&>(other); std::string changes; if (title != p.title) { changes += " title: '" + title + "' -> '" + p.title + "';"; } if (std::abs(price - p.price) > 1e-5) { // 浮点数不能直接 == 比较 char oldPrice[20], newPrice[20]; snprintf(oldPrice, sizeof(oldPrice), "%.2f", price); snprintf(newPrice, sizeof(newPrice), "%.2f", p.price); changes += " price: " + std::string(oldPrice) + " -> " + std::string(newPrice) + ";"; } return changes.empty() ? "(无变化)" : changes; } // 返回克隆对象 Ptr clone() const override { return std::make_shared<Product>(*this); } }; // ==================== 数据解析器工厂 ==================== // 静态类:根据用户选择的类型解析文本为对象列表 class DataParser { public: // 定义类型别名:一组数据对象的列表 using ObjectList = std::vector<DataObject::Ptr>; // 静态方法:将文本内容按指定类型解析成对象列表 static ObjectList parse(const QString &text, const QString &type) { ObjectList result; // 存放解析出的对象 QStringList lines = text.split('\n', Qt::SkipEmptyParts); // 按换行拆分,跳过行 for (const QString &line : lines) { // 遍历每一行 try { if (type == "Person") { // 创建 Person 对象并加入结果集 result.push_back(std::make_shared<Person>(line.toStdString())); } else if (type == "Product") { // 创建 Product 对象 result.push_back(std::make_shared<Product>(line.toStdString())); } else { // 类型不支持时打印警告(Qt 日志) qWarning() << "未知类型:" << type; } } catch (...) { // 捕获任何异常(比如 stoi 失败) // 弹出警告对话框提示哪一行解析失败 QMessageBox::warning(nullptr, "解析错误", "无法解析行: " + line); } } return result; // 返回解析完成的对象列表 } }; // ==================== 主窗口类 ==================== // 主界面窗口,继承自 QWidget class StructuredDataComparator : public QWidget { Q_OBJECT // 必须添加:启用 Qt 的信号槽机制(元对象系统) public: // 构造函数,parent 表示父窗口(默认为) explicit StructuredDataComparator(QWidget *parent = nullptr); private slots: // 槽函数:响应按钮点击事件 void onCompare(); // 开始对比两组数据 void onClear(); // 清所有输入和输出 void onCopyResult(); // 复制结果到剪贴板 void onLoadFileA(); // 加载文件 A 到左侧输入框 void onLoadFileB(); // 加载文件 B 到右侧输入框 private: QTextEdit *inputA; // 左侧输入框(旧数据) QTextEdit *inputB; // 右侧输入框(新数据) QTextEdit *outputArea; // 下方输出区域 QPushButton *compareBtn; // “开始对比”按钮 QPushButton *clearBtn; // “清”按钮 QPushButton *copyBtn; // “复制结果”按钮 QComboBox *dataTypeCombo; // 下拉框选择结构体类型(Person / Product) // 私有方法:初始化 UI 界面 void setupUI(); }; // ========== 主窗口构造函数 ========== StructuredDataComparator::StructuredDataComparator(QWidget *parent) : QWidget(parent) // 调用父类构造函数 { setupUI(); // 初始化界面布局和控件 } // ========== setupUI 函数实现:构建整个用户界面 ========== void StructuredDataComparator::setupUI() { // 设置窗口标题 this->setWindowTitle("🧾 结构化数据对比工具"); // 设置初始大小(宽900,高700像素) this->resize(900, 700); // 创建主垂直布局,作为整个窗口的顶层容器 auto mainLayout = new QVBoxLayout(this); // --- 第一部分:顶部控件 —— 选择数据类型 --- auto topLayout = new QHBoxLayout; // 水平布局放置标签和下拉框 topLayout->addWidget(new QLabel("数据类型:")); // 添加说明文字 dataTypeCombo = new QComboBox; // 创建下拉选择框 dataTypeCombo->addItem("Person"); // 添加选项:Person dataTypeCombo->addItem("Product"); // 添加选项:Product topLayout->addWidget(dataTypeCombo); // 把下拉框加入布局 topLayout->addStretch(); // 右侧留白,使控件靠左 mainLayout->addLayout(topLayout); // 将顶部布局加入主布局 // --- 第二部分:左右输入区(使用分割器)--- auto splitter = new QSplitter(Qt::Horizontal); // 水平分割器,左右可拖动 // 左侧容器:旧数据输入 auto containerA = new QWidget; auto layoutA = new QVBoxLayout; layoutA->addWidget(new QLabel("来源 A(旧数据)")); // 添加标签 inputA = new QTextEdit; // 创建文本输入框 inputA->setPlaceholderText("输入格式:\nid,name,age\n1,Alice,30"); // 占位提示 layoutA->addWidget(inputA); // 加入布局 containerA->setLayout(layoutA); // 容器应用此布局 // 右侧容器:新数据输入 auto containerB = new QWidget; auto layoutB = new QVBoxLayout; layoutB->addWidget(new QLabel("来源 B(新数据)")); inputB = new QTextEdit; inputB->setPlaceholderText("输入格式:\nid,name,age\n2,Bob,25"); layoutB->addWidget(inputB); containerB->setLayout(layoutB); // 将左右两个容器加入分割器 splitter->addWidget(containerA); splitter->addWidget(containerB); // 设置两个区域都可以伸展(比例相同) mainLayout->addWidget(splitter); // --- 第三部分:按钮行 --- auto btnLayout = new QHBoxLayout; // 水平布局放按钮 auto loadBtnA = new QPushButton("📁 加载 A"); // 加载文件 A auto loadBtnB = new QPushButton("📁 加载 B"); // 加载文件 B compareBtn = new QPushButton("🔍 开始对比"); // 开始对比按钮 clearBtn = new QPushButton("🗑️ 清"); // 清按钮 copyBtn = new QPushButton("📋 复制结果"); // 复制结果按钮 // 按顺序添加按钮 btnLayout->addWidget(loadBtnA); btnLayout->addWidget(loadBtnB); btnLayout->addSpacing(40); // 中间插入白间距,美观 btnLayout->addWidget(compareBtn); btnLayout->addWidget(clearBtn); btnLayout->addWidget(copyBtn); // 将按钮行加入主布局 mainLayout->addLayout(btnLayout); // --- 第四部分:输出结果显示区 --- outputArea = new QTextEdit; outputArea->setReadOnly(true); // 设置只读,防止用户误改 outputArea->setPlaceholderText("对比结果将显示在这里..."); // 提示语 mainLayout->addWidget(outputArea); // 加入主布局 // --- 最后:连接信号与槽 --- connect(compareBtn, &QPushButton::clicked, this, &StructuredDataComparator::onCompare); connect(clearBtn, &QPushButton::clicked, this, &StructuredDataComparator::onClear); connect(copyBtn, &QPushButton::clicked, this, &StructuredDataComparator::onCopyResult); connect(loadBtnA, &QPushButton::clicked, this, &StructuredDataComparator::onLoadFileA); connect(loadBtnB, &QPushButton::clicked, this, &StructuredDataComparator::onLoadFileB); } // ========== 槽函数:执行数据对比逻辑 ========== void StructuredDataComparator::onCompare() { QString type = dataTypeCombo->currentText(); // 获取当前选择的结构体类型 QString textA = inputA->toPlainText(); // 获取左侧输入文本 QString textB = inputB->toPlainText(); // 获取右侧输入文本 // 如果任意一方为,则提示用户输入 if (textA.trimmed().isEmpty() || textB.trimmed().isEmpty()) { outputArea->setText("⚠️ 请确保两个输入框都有数据!"); return; } // 使用 DataParser 解析文本为对象列表 auto listA = DataParser::parse(textA, type); auto listB = DataParser::parse(textB, type); // 创建 map:key -> object,方便快速查找 std::map<std::string, DataObject::Ptr> mapA, mapB; for (const auto &obj : listA) mapA[obj->getKey()] = obj; for (const auto &obj : listB) mapB[obj->getKey()] = obj; // 定义三个向量存放三类变化 std::vector<std::string> added; // 新增 std::vector<std::string> removed; // 删除 std::vector<std::string> modified; // 修改 // 在 A 中但不在 B 中 → 被删除 for (const auto &[key, obj] : mapA) { if (!mapB.count(key)) { removed.push_back(obj->toString()); } } // 在 B 中但不在 A 中 → 新增 for (const auto &[key, obj] : mapB) { if (!mapA.count(key)) { added.push_back(obj->toString()); } } // 同时存在于 A 和 B → 检查是否修改 for (const auto &[key, objB] : mapB) { auto it = mapA.find(key); // 查找对应的旧对象 if (it != mapA.end()) { std::string diffStr = objB->diff(*it->second); // 调用 diff 方法 if (diffStr != "(无变化)") { modified.push_back("🔹 " + key + "\n 修改: " + diffStr); } } } // 生成最终报告文本 QString result; result += "🧾 结构化数据对比报告\n"; result += "==============================\n"; result += QString("📅 时间: %1\n").arg(QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss")); result += QString("📊 类型: %1\n\n").arg(type); result += QString("🆕 新增 (%1):\n").arg(added.size()); for (const auto &item : added) { result += " • " + QString::fromStdString(item) + "\n"; } result += "\n"; result += QString("🗑️ 删除 (%1):\n").arg(removed.size()); for (const auto &item : removed) { result += " • " + QString::fromStdString(item) + "\n"; } result += "\n"; result += QString("✏️ 修改 (%1):\n").arg(modified.size()); for (const auto &item : modified) { result += QString::fromStdString(item) + "\n"; } // 显示在输出框 outputArea->setText(result); } // ========== 清所有内容 ========== void StructuredDataComparator::onClear() { inputA->clear(); // 清左侧输入 inputB->clear(); // 清右侧输入 outputArea->clear(); // 清输出 } // ========== 复制结果到剪贴板 ========== void StructuredDataComparator::onCopyResult() { // 使用 QApplication 提供的局剪贴板 QApplication::clipboard()->setText(outputArea->toPlainText()); // 弹出成功提示 QMessageBox::information(this, "已复制", "结果已复制到剪贴板!"); } // ========== 加载文件 A ========== void StructuredDataComparator::onLoadFileA() { // 打开文件选择对话框,过滤 .csv 和 .txt 文件 QString filePath = QFileDialog::getOpenFileName(this, "加载 A 文件", "", "CSV/TXT (*.csv *.txt)"); if (!filePath.isEmpty()) { // 用户没有取消 QFile file(filePath); // 创建文件对象 if (file.open(QIODevice::ReadOnly | QIODevice::Text)) { // 以只读文本模式打开 QTextStream stream(&file); // 创建文本流 stream.setCodec("UTF-8"); // 设置编码为 UTF-8(支持中文) inputA->setPlainText(stream.readAll()); // 读取部内容并设置到输入框 file.close(); // 关闭文件 } else { // 打开失败则弹出错误提示 QMessageBox::critical(this, "错误", "无法打开文件"); } } } // ========== 加载文件 B ========== void StructuredDataComparator::onLoadFileB() { QString filePath = QFileDialog::getOpenFileName(this, "加载 B 文件", "", "CSV/TXT (*.csv *.txt)"); if (!filePath.isEmpty()) { QFile file(filePath); if (file.open(QIODevice::ReadOnly | QIODevice::Text)) { QTextStream stream(&file); stream.setCodec("UTF-8"); inputB->setPlainText(stream.readAll()); file.close(); } else { QMessageBox::critical(this, "错误", "无法打开文件"); } } } // ========== MOC 编译支持(Qt 元对象系统需要)========== #include "main.moc" // ========== 程序入口点 ========== int main(int argc, char *argv[]) { QApplication app(argc, argv); // 创建 Qt 应用对象 StructuredDataComparator tool; // 创建主窗口实例 tool.show(); // 显示窗口 return app.exec(); // 启动事件循环,等待用户交互 } 请帮我将代码改为QT4可以用的代码
09-25
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值