1. 从静态到动态:为什么选择GraphicsView?
如果你用过XMind这类专业的思维导图软件,一定会被它流畅的拖拽、灵活的布局和丰富的样式所吸引。作为一个有多年Qt开发经验的“老鸟”,我经常被问到:用Qt能做出这种效果吗?我的回答是:不仅能,而且用Qt的GraphicsView框架来做,简直是“天作之合”。
很多朋友一开始可能会想到用QPainter直接在Widget上画。我试过,这条路走不远。当你画几个静态节点和连线时,感觉还行。但一旦需要实现“点击选中节点”、“拖拽改变位置”、“实时编辑文本”,代码复杂度就会指数级上升,你得自己处理所有的鼠标事件、碰撞检测、重绘逻辑,简直是一场噩梦。
而Qt的GraphicsView框架,就是为这种复杂的、交互式的2D图形应用而生的。你可以把它理解为一个功能强大的“画布系统”。它有三个核心角色:
- QGraphicsScene(场景):就像一个大舞台,所有演员(节点、连线)都在这个舞台上。
- QGraphicsView(视图):就像观众手里的望远镜,你可以通过它来观看舞台,并且能放大、缩小、平移视角。
- QGraphicsItem(图元):就是舞台上的演员,比如一个矩形节点、一段曲线连线。最关键的是,每个Item都是独立的对象,自带坐标、旋转、缩放、鼠标键盘事件响应等能力。
这意味着,我们要实现的思维导图节点,本质上就是一个自定义的QGraphicsItem。我们只需要专注于设计这个Item长什么样(重写paint函数),以及它如何与用户交互(重写mousePressEvent, mouseMoveEvent等)。至于这个Item在场景里怎么被管理、视图怎么滚动缩放,GraphicsView框架都帮我们搞定了。这让我们从繁琐的底层图形管理中解放出来,可以集中精力实现业务逻辑,比如节点的父子关系、连线的动态计算、数据的序列化保存等等。
所以,用GraphicsView来打造思维导图,不是“能不能”的问题,而是“非常合适”的选择。接下来,我就带你一步步从零开始,把这个“舞台”搭起来,把“演员”调教好,最终实现一个功能丰富、交互流畅的动态思维导图工具。
2. 搭建舞台:创建场景与自定义图元
万事开头难,但第一步其实很简单。我们先来把最基本的架子搭好。
2.1 创建主窗口与视图
首先,用Qt Creator新建一个Qt Widgets Application项目。在主窗口(比如MainWindow)的构造函数里,我们需要创建场景和视图。
// MainWindow.cpp
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
// 1. 创建场景
m_scene = new QGraphicsScene(this);
// 可以设置一个初始的边界,比如一个很大的矩形,让我们的思维导图有足够的空间展开
m_scene->setSceneRect(-5000, -5000, 10000, 10000);
// 2. 创建视图,并将场景设置给它
m_view = new QGraphicsView(this);
m_view->setScene(m_scene);
// 设置一些视图属性,让体验更好
m_view->setRenderHint(QPainter::Antialiasing); // 抗锯齿,让线条更平滑
m_view->setDragMode(QGraphicsView::RubberBandDrag); // 启用框选模式
m_view->setViewportUpdateMode(QGraphicsView::FullViewportUpdate); // 避免刷新残影
// 3. 将视图设置为中心部件
setCentralWidget(m_view);
}
这几行代码,我们的“舞台”(Scene)和“望远镜”(View)就准备好了。RubberBandDrag模式允许用户用鼠标拖出一个矩形框来选中多个图元,这个功能在思维导图中整理多个节点时非常有用。
2.2 设计核心:自定义思维导图节点Item
接下来是重头戏,我们要创造思维导图的“主角”——节点。我们需要继承QGraphicsItem(或者它的子类,如QGraphicsRectItem、QGraphicsTextItem,但为了完全控制,我更喜欢直接继承QGraphicsItem)。
// mindmapnodeitem.h
class MindMapNodeItem : public QGraphicsItem
{
public:
explicit MindMapNodeItem(const QString& text = "新节点", QGraphicsItem* parent = nullptr);
QRectF boundingRect() const override; // 必须重写:定义Item的边界
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override; // 必须重写:绘制Item
// 自定义属性和方法
void setText(const QString& text);
QString text() const;
void setBackgroundColor(const QColor& color);
// ... 其他样式属性
protected:
// 重写事件处理函数,实现交互
void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override;
private:
QString m_text;
QColor m_bgColor;
QRectF m_rect; // 节点矩形区域
bool m_isDragging;


6826

被折叠的 条评论
为什么被折叠?



