You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于QT Qmake/C++的思维导图工具保存功能实现咨询

思维导图保存功能实现方案(Qt Qmake/C++)

一、保存为JPG格式

实现思路

借助Qt的QPainter将整个思维导图渲染到QImage,再将QImage导出为JPG格式。核心是准确捕获导图的完整显示区域,避免内容裁剪,同时适配高DPI屏幕。

步骤与代码示例

  1. 计算导图完整边界
    遍历所有节点和连线,计算覆盖全部内容的最小矩形:
QRectF getMindMapBoundingRect(const QList<MindMapNode*>& nodes) {
    QRectF boundingRect;
    for (auto node : nodes) {
        boundingRect = boundingRect.united(node->geometry());
        // 包含节点到父节点的连线区域
        if (node->parentNode()) {
            QLineF linkLine(node->pos(), node->parentNode()->pos());
            boundingRect = boundingRect.united(linkLine.boundingRect());
        }
    }
    // 增加边距,避免内容紧贴图片边缘
    boundingRect.adjust(-20, -20, 20, 20);
    return boundingRect;
}
  1. 渲染并保存为JPG
    创建对应尺寸的QImage(适配高DPI),绘制导图后保存:
bool saveAsJpg(const QString& filePath, const QList<MindMapNode*>& nodes, int quality = 90) {
    QRectF mapRect = getMindMapBoundingRect(nodes);
    // 适配高DPI屏幕,获取设备像素比
    qreal dpr = QGuiApplication::primaryScreen()->devicePixelRatio();
    QImage image(mapRect.size().toSize() * dpr, QImage::Format_RGB32);
    image.setDevicePixelRatio(dpr);
    image.fill(Qt::white); // JPG不支持透明,填充白色背景

    QPainter painter(&image);
    painter.setRenderHint(QPainter::Antialiasing);
    // 平移坐标系,让导图左上角对齐QImage左上角
    painter.translate(-mapRect.left(), -mapRect.top());

    // 调用你的导图绘制逻辑
    drawMindMap(painter, nodes);

    painter.end();
    // 保存为JPG,质量参数0-100,值越高清晰度越好
    return image.save(filePath, "JPG", quality);
}

最佳实践

  • 高DPI适配:必须处理devicePixelRatio,否则导出图片在高分屏上会模糊。
  • 质量可选:给用户提供JPG质量调节滑块(50-100),平衡文件大小与清晰度。
  • 异步处理:大导图保存时用QtConcurrent::run异步执行,避免UI冻结。

二、保存为可重新编辑格式

实现思路

将思维导图的结构化数据(节点属性、父子关系、样式等)序列化到文件,读取时反序列化重建导图。推荐用JSON格式(可读性强、易调试),或Qt内置的QDataStream(性能更高、文件体积小)。

方案1:JSON格式序列化

步骤与代码示例

  1. 节点数据结构与序列化方法
    确保MindMapNode包含所有需保存的属性,并实现JSON转换:
class MindMapNode {
public:
    QString id;
    QString text;
    QPointF pos;
    QSizeF size;
    QColor bgColor;
    QFont font;
    QString parentId;

    // 转换为QJsonObject
    QJsonObject toJson() const {
        QJsonObject obj;
        obj["id"] = id;
        obj["text"] = text;
        obj["x"] = pos.x();
        obj["y"] = pos.y();
        obj["width"] = size.width();
        obj["height"] = size.height();
        obj["bgColor"] = bgColor.name();
        obj["font"] = font.toString();
        obj["parentId"] = parentId;
        return obj;
    }

    // 从QJsonObject重建节点
    static MindMapNode fromJson(const QJsonObject& obj) {
        MindMapNode node;
        node.id = obj["id"].toString();
        node.text = obj["text"].toString();
        node.pos = QPointF(obj["x"].toDouble(), obj["y"].toDouble());
        node.size = QSizeF(obj["width"].toDouble(), obj["height"].toDouble());
        node.bgColor = QColor(obj["bgColor"].toString());
        node.font.fromString(obj["font"].toString());
        node.parentId = obj["parentId"].toString();
        return node;
    }
};
  1. 保存与读取操作
// 保存为JSON文件
bool saveAsEditableJson(const QString& filePath, const QList<MindMapNode*>& nodes) {
    QJsonArray nodesArray;
    for (auto node : nodes) {
        nodesArray.append(node->toJson());
    }
    QJsonObject rootObj;
    rootObj["version"] = "1.0"; // 加入版本号,方便后续格式兼容
    rootObj["nodes"] = nodesArray;

    QJsonDocument doc(rootObj);
    QFile file(filePath);
    if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
        return false;
    }
    file.write(doc.toJson(QJsonDocument::Indented)); // 格式化输出,便于调试
    file.close();
    return true;
}

// 从JSON文件读取
QList<MindMapNode*> loadFromEditableJson(const QString& filePath) {
    QList<MindMapNode*> nodes;
    QFile file(filePath);
    if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
        return nodes;
    }
    QByteArray data = file.readAll();
    QJsonDocument doc = QJsonDocument::fromJson(data);
    if (!doc.isObject()) {
        return nodes;
    }
    QJsonObject rootObj = doc.object();
    // 版本兼容处理
    QString version = rootObj["version"].toString();
    if (version != "1.0") {
        // 可添加旧版本格式兼容逻辑
    }
    QJsonArray nodesArray = rootObj["nodes"].toArray();
    for (const auto& jsonNode : nodesArray) {
        if (jsonNode.isObject()) {
            nodes.append(new MindMapNode(MindMapNode::fromJson(jsonNode.toObject())));
        }
    }
    // 重建节点父子关系
    for (auto node : nodes) {
        if (!node->parentId.isEmpty()) {
            auto parent = std::find_if(nodes.begin(), nodes.end(), [&](MindMapNode* n) {
                return n->id == node->parentId;
            });
            if (parent != nodes.end()) {
                // 执行你的父节点设置逻辑,如node->setParent(*parent);
            }
        }
    }
    return nodes;
}

方案2:QDataStream序列化

若追求性能和小体积,可使用二进制序列化,为MindMapNode重载流运算符:

QDataStream& operator<<(QDataStream& out, const MindMapNode& node) {
    out << node.id << node.text << node.pos << node.size << node.bgColor << node.font << node.parentId;
    return out;
}

QDataStream& operator>>(QDataStream& in, MindMapNode& node) {
    in >> node.id >> node.text >> node.pos >> node.size >> node.bgColor >> node.font >> node.parentId;
    return in;
}

保存和读取时直接操作QDataStream即可,逻辑与JSON类似,但文件为二进制格式,可读性差但效率更高。

最佳实践

  • 版本控制:序列化数据中加入版本号,后续修改格式时可兼容旧文件。
  • 数据校验:读取时检查必填字段是否存在,避免文件损坏导致程序崩溃。
  • 备份机制:保存时自动生成.bak备份文件,防止保存失败丢失数据。

内容的提问来源于stack exchange,提问作者loczusek

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.30 05:37:52