Qt - 图形视图(Graphics View)
我们首先来介绍Qt的图形视图框架(Graphics View Framework),然后通过一个完整的示例来展示如何使用它。
-
概述
Qt的图形视图框架提供了一个基于图形项的模型视图编程方法。它主要由三个部分组成:场景(QGraphicsScene)、视图(QGraphicsView)和图形项(QGraphicsItem)。
-
QGraphicsScene:管理所有的图形项,并负责将事件传递给图形项,以及提供场景的视图功能。
-
QGraphicsView:用于显示场景的内容,可以多个视图同时显示同一个场景,并且支持缩放和旋转。
-
QGraphicsItem:场景中的图形项,可以是自定义的图形、文本、甚至控件。
-
基本使用步骤
a. 创建场景(QGraphicsScene)
b. 创建图形项(例如:QGraphicsRectItem,QGraphicsEllipseItem,或者自定义的QGraphicsItem)
c. 将图形项添加到场景中
d. 创建视图(QGraphicsView)并设置场景 -
示例:创建一个简单的图形视图应用,包括矩形、椭圆和文本,并且支持鼠标事件。
-
进阶:自定义图形项,实现更复杂的图形和交互。
一、图形视图框架概述
1.1 什么是图形视图框架
Qt图形视图框架是一个用于管理和交互大量2D图形对象的强大系统。它提供了渲染、缩放、旋转和编辑自定义图形项的能力,非常适合开发CAD软件、流程图工具、游戏等需要复杂图形交互的应用。
核心组件:
-
场景(Scene):所有图形项的容器,管理坐标系和项管理
-
视图(View):可视化场景的窗口部件,支持缩放、旋转、拖放
-
图形项(Item):场景中的可视对象,可以响应事件
1.2 图形视图与传统绘图对比
| 特性 | QPainter绘图 | 图形视图框架 |
|---|---|---|
| 项数量 | 数十到数百 | 数千到数百万 |
| 交互性 | 有限 | 每个项可独立交互 |
| 坐标系统 | 单一 | 场景、项、视图三级坐标 |
| 内存管理 | 手动 | 自动 |
| 渲染性能 | 中等 | 高度优化 |
二、基础组件使用
2.1 基本场景、视图和项
#include "widget.h"
#include <QApplication>
#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGraphicsRectItem>
#include <QGraphicsEllipseItem>
#include <QGraphicsTextItem>
#include <QPen>
#include <QBrush>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
// 1. 创建场景 (坐标系)
QGraphicsScene scene;
scene.setSceneRect(-200, -200, 400, 400); // 设置场景范围
scene.setBackgroundBrush(Qt::lightGray); // 设置背景
// 2. 创建各种图形项并添加到场景
// 矩形项
QGraphicsRectItem *rectItem = new QGraphicsRectItem(-50, -50, 100, 100);
rectItem->setBrush(QBrush(Qt::blue));
rectItem->setPen(QPen(Qt::black, 2));
rectItem->setFlag(QGraphicsItem::ItemIsMovable); // 可移动
rectItem->setFlag(QGraphicsItem::ItemIsSelectable); // 可选择
scene.addItem(rectItem);
// 椭圆项
QGraphicsEllipseItem *ellipseItem = new QGraphicsEllipseItem(-40, -30, 80, 60);
ellipseItem->setBrush(QBrush(Qt::red));
ellipseItem->setPen(QPen(Qt::black, 2));
ellipseItem->setFlag(QGraphicsItem::ItemIsMovable);
ellipseItem->setPos(100, 0); // 设置位置
scene.addItem(ellipseItem);
// 文本项
QGraphicsTextItem *textItem = new QGraphicsTextItem("Hello Qt Graphics");
textItem->setDefaultTextColor(Qt::white);
textItem->setFont(QFont("Arial", 14));
textItem->setFlag(QGraphicsItem::ItemIsMovable);
textItem->setPos(0, 100);
scene.addItem(textItem);
// 3. 创建视图来显示场景
QGraphicsView view(&scene);
view.setRenderHint(QPainter::Antialiasing); // 抗锯齿
view.setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
view.setDragMode(QGraphicsView::RubberBandDrag); // 框选模式
view.resize(600, 600);
view.setWindowTitle("Basic Graphics View Demo");
view.show();
return a.exec();
}
运行效果:

2.2 坐标系系统
void coordinateSystemDemo() {
QGraphicsScene scene(0, 0, 800, 600);
// 添加坐标轴
scene.addLine(0, 0, 500, 0, QPen(Qt::red, 2)); // X轴
scene.addLine(0, 0, 0, 500, QPen(Qt::green, 2)); // Y轴
// 场景坐标 (全局坐标)
QGraphicsRectItem *sceneRect = new QGraphicsRectItem(100, 100, 50, 50);
sceneRect->setBrush(Qt::blue);
scene.addItem(sceneRect);
// 项的局部坐标
QGraphicsRectItem *parentRect = new QGraphicsRectItem(0, 0, 100, 100);
parentRect->setBrush(Qt::yellow);
parentRect->setPos(200, 200); // 在场景中的位置
scene.addItem(parentRect);
// 子项 (相对于父项的坐标)
QGraphicsEllipseItem *childEllipse = new QGraphicsEllipseItem(-20, -20, 40, 40);
childEllipse->setBrush(Qt::red);
childEllipse->setParentItem(parentRect); // 设置为子项
childEllipse->setPos(50, 50); // 相对于父项的位置
// 坐标转换演示
QPointF scenePoint(250, 250); // 场景坐标
QPointF itemPoint = childEllipse->mapFromScene(scenePoint); // 转换为项坐标
QPointF scenePoint2 = childEllipse->mapToScene(itemPoint); // 转回场景坐标
qDebug() << "Scene Point:" << scenePoint;
qDebug() << "Item Local Point:" << itemPoint;
qDebug() << "Back to Scene:" << scenePoint2;
QGraphicsView view(&scene);
view.resize(800, 600);
view.show();
}
三、自定义图形项
3.1 基础自定义项
#include <QGraphicsItem>
#include <QPainter>
#include <QStyleOptionGraphicsItem>
class CustomGraphicsItem : public QGraphicsItem {
public:
explicit CustomGraphicsItem(QGraphicsItem *parent = nullptr)
: QGraphicsItem(parent) {
// 启用标志
setFlag(ItemIsMovable);
setFlag(ItemIsSelectable);
setFlag(ItemSendsGeometryChanges);
// 设置光标
setCursor(Qt::OpenHandCursor);
}
// 必须实现的纯虚函数:边界矩形
QRectF boundingRect() const override {
// 返回项的边界矩形(用于碰撞检测和重绘区域)
return QRectF(-50, -50, 100, 100).adjusted(-5, -5, 5, 5);
}
// 必须实现的纯虚函数:绘制项
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
QWidget *widget = nullptr) override {
Q_UNUSED(widget);
// 根据选中状态设置画笔和画刷
QPen pen(isSelected() ? Qt::red : Qt::black, 2);
pen.setCosmetic(true); // 保持线宽不随缩放改变
painter->setPen(pen);
QBrush brush(m_color);
painter->setBrush(brush);
// 绘制主体
painter->drawRoundedRect(-50, -50, 100, 100, 20, 20);
// 绘制装饰
painter->setPen(QPen(Qt::white, 1));
painter->drawEllipse(-20, -20, 40, 40);
// 绘制文本
painter->setPen(QPen(Qt::white, 1));
painter->drawText(QRectF(-50, -50, 100, 100),
Qt::AlignCenter, "Custom Item");
// 绘制细节(如选中时的边框)
if (option->state & QStyle::State_Selected) {
painter->setPen(QPen(Qt::red, 1, Qt::DashLine));
painter->setBrush(Qt::NoBrush);
painter->drawRect(boundingRect());
}
}
// 处理鼠标事件
void mousePressEvent(QGraphicsSceneMouseEvent *event) override {
if (event->button() == Qt::LeftButton) {
setCursor(Qt::ClosedHandCursor);
m_dragStartPos = pos();
}
QGraphicsItem::mousePressEvent(event);
}
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override {
setCursor(Qt::OpenHandCursor);
QGraphicsItem::mouseReleaseEvent(event);
}
void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override {
// 双击改变颜色
m_color = QColor::fromHsl(rand() % 360, 255, 200);
update(); // 触发重绘
QGraphicsItem::mouseDoubleClickEvent(event);
}
// 项属性变化时调用
QVariant itemChange(GraphicsItemChange change, const QVariant &value) override {
if (change == ItemPositionHasChanged) {
// 位置改变时发出信号(如果需要)
emit positionChanged(pos());
}
return QGraphicsItem::itemChange(change, value);
}
// 设置颜色
void setColor(const QColor &color) {
m_color = color;
update();
}
QColor color() const { return m_color; }
signals:
void positionChanged(const QPointF &newPos);
private:
QColor m_color = QColor(100, 150, 200);
QPointF m_dragStartPos;
};
3.2 高级自定义项:可连接节点
#include <QVector2D>
#include <QGraphicsPathItem>
class ConnectionPoint {
public:
enum Type { Input, Output };
ConnectionPoint(QGraphicsItem *parent, Type type, const QPointF &pos)
: m_parent(parent), m_type(type), m_localPos(pos) {}
QPointF scenePos() const {
return m_parent->mapToScene(m_localPos);
}
Type type() const { return m_type; }
QGraphicsItem *parentItem() const { return m_parent; }
private:
QGraphicsItem *m_parent;
Type m_type;
QPointF m_localPos;
};
class NodeItem : public QGraphicsItem {
public:
explicit NodeItem(const QString &title, QGraphicsItem *parent = nullptr)
: QGraphicsItem(parent), m_title(title) {
setFlag(ItemIsMovable);
setFlag(ItemIsSelectable);
setFlag(ItemSendsGeometryChanges);
// 创建连接点
m_inputPoints.append(ConnectionPoint(this, ConnectionPoint::Input,
QPointF(-60, -30)));
m_inputPoints.append(ConnectionPoint(this, ConnectionPoint::Input,
QPointF(-60, 30)));
m_outputPoints.append(ConnectionPoint(this, ConnectionPoint::Output,
QPointF(60, 0)));
}
QRectF boundingRect() const override {
return QRectF(-80, -50, 160, 100).adjusted(-5, -5, 5, 5);
}
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
QWidget *widget = nullptr) override {
Q_UNUSED(option);
Q_UNUSED(widget);
// 绘制节点主体
QLinearGradient gradient(-80, -50, -80, 50);
gradient.setColorAt(0, QColor(200, 220, 240));
gradient.setColorAt(1, QColor(150, 180, 220));
painter->setBrush(gradient);
painter->setPen(QPen(Qt::black, 2));
painter->drawRoundedRect(-80, -50, 160, 100, 10, 10);
// 绘制标题栏
painter->setBrush(QColor(80, 120, 180));
painter->drawRect(-80, -50, 160, 30);
// 绘制标题
painter->setPen(Qt::white);
painter->setFont(QFont("Arial", 10, QFont::Bold));
painter->drawText(QRectF(-80, -50, 160, 30), Qt::AlignCenter, m_title);
// 绘制连接点
painter->setBrush(Qt::gray);
painter->setPen(QPen(Qt::black, 1));
// 输入点
for (const auto &point : m_inputPoints) {
painter->drawEllipse(point.scenePos() - mapToScene(QPointF(0, 0)),
5, 5);
}
// 输出点
for (const auto &point : m_outputPoints) {
painter->drawEllipse(point.scenePos() - mapToScene(QPointF(0, 0)),
5, 5);
}
// 绘制连接点标签
painter->setPen(Qt::black);
painter->setFont(QFont("Arial", 8));
painter->drawText(-75, -25, "Input 1");
painter->drawText(-75, 35, "Input 2");
painter->drawText(55, 5, "Output");
}
// 获取最近的连接点
ConnectionPoint* nearestConnectionPoint(const QPointF &scenePos) {
double minDist = std::numeric_limits<double>::max();
ConnectionPoint *nearest = nullptr;
auto checkPoints = [&](const QVector<ConnectionPoint> &points) {
for (auto &point : points) {
double dist = QVector2D(point.scenePos() - scenePos).length();
if (dist < minDist && dist < 20) { // 20像素内有效
minDist = dist;
nearest = &point;
}
}
};
checkPoints(m_inputPoints);
checkPoints(m_outputPoints);
return nearest;
}
QVector<ConnectionPoint> inputPoints() const { return m_inputPoints; }
QVector<ConnectionPoint> outputPoints() const { return m_outputPoints; }
private:
QString m_title;
QVector<ConnectionPoint> m_inputPoints;
QVector<ConnectionPoint> m_outputPoints;
};
四、连接和交互
4.1 连接线实现
class ConnectionItem : public QGraphicsPathItem {
public:
ConnectionItem(ConnectionPoint *startPoint, ConnectionPoint *endPoint,
QGraphicsItem *parent = nullptr)
: QGraphicsPathItem(parent), m_startPoint(startPoint), m_endPoint(endPoint) {
setPen(QPen(Qt::black, 2));
setZValue(-1); // 确保在节点下方
updatePath();
}
void updatePath() {
if (!m_startPoint || !m_endPoint) return;
QPointF startPos = m_startPoint->scenePos();
QPointF endPos = m_endPoint->scenePos();
// 创建曲线路径
QPainterPath path(startPos);
// 计算控制点(贝塞尔曲线)
qreal dx = endPos.x() - startPos.x();
qreal dy = endPos.y() - startPos.y();
QPointF ctrl1(startPos.x() + dx * 0.5, startPos.y());
QPointF ctrl2(startPos.x() + dx * 0.5, endPos.y());
path.cubicTo(ctrl1, ctrl2, endPos);
setPath(path);
}
ConnectionPoint *startPoint() const { return m_startPoint; }
ConnectionPoint *endPoint() const { return m_endPoint; }
private:
ConnectionPoint *m_startPoint;
ConnectionPoint *m_endPoint;
};
class GraphicsScene : public QGraphicsScene {
Q_OBJECT
public:
explicit GraphicsScene(QObject *parent = nullptr)
: QGraphicsScene(parent), m_tempLine(nullptr), m_draggingLine(false) {
// 设置场景
setSceneRect(-1000, -1000, 2000, 2000);
// 创建网格背景
setBackgroundBrush(QBrush(QColor(240, 240, 240)));
// 添加一些初始节点
NodeItem *node1 = new NodeItem("Node 1");
node1->setPos(-200, 0);
addItem(node1);
NodeItem *node2 = new NodeItem("Node 2");
node2->setPos(200, 0);
addItem(node2);
}
protected:
void mousePressEvent(QGraphicsSceneMouseEvent *event) override {
if (event->button() == Qt::LeftButton) {
// 查找点击的连接点
QGraphicsItem *item = itemAt(event->scenePos(), QTransform());
if (NodeItem *node = dynamic_cast<NodeItem*>(item)) {
if (ConnectionPoint *point = node->nearestConnectionPoint(event->scenePos())) {
if (point->type() == ConnectionPoint::Output) {
// 开始拖拽连接线
m_draggingLine = true;
m_startConnectionPoint = point;
// 创建临时线
m_tempLine = new QGraphicsLineItem;
m_tempLine->setPen(QPen(Qt::black, 2, Qt::DashLine));
addItem(m_tempLine);
QPointF startPos = point->scenePos();
m_tempLine->setLine(startPos.x(), startPos.y(),
event->scenePos().x(), event->scenePos().y());
return;
}
}
}
}
QGraphicsScene::mousePressEvent(event);
}
void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override {
if (m_draggingLine && m_tempLine) {
// 更新临时线
QPointF startPos = m_startConnectionPoint->scenePos();
m_tempLine->setLine(startPos.x(), startPos.y(),
event->scenePos().x(), event->scenePos().y());
// 高亮潜在的连接点
QGraphicsItem *item = itemAt(event->scenePos(), QTransform());
if (NodeItem *node = dynamic_cast<NodeItem*>(item)) {
if (ConnectionPoint *point = node->nearestConnectionPoint(event->scenePos())) {
if (point->type() == ConnectionPoint::Input) {
// 可以在此处高亮显示
}
}
}
}
QGraphicsScene::mouseMoveEvent(event);
}
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override {
if (m_draggingLine && m_tempLine) {
// 查找释放点的连接点
QGraphicsItem *item = itemAt(event->scenePos(), QTransform());
if (NodeItem *node = dynamic_cast<NodeItem*>(item)) {
if (ConnectionPoint *endPoint = node->nearestConnectionPoint(event->scenePos())) {
if (endPoint->type() == ConnectionPoint::Input) {
// 创建永久连接
ConnectionItem *connection = new ConnectionItem(
m_startConnectionPoint, endPoint);
addItem(connection);
m_connections.append(connection);
}
}
}
// 清理临时线
removeItem(m_tempLine);
delete m_tempLine;
m_tempLine = nullptr;
m_draggingLine = false;
}
QGraphicsScene::mouseReleaseEvent(event);
}
void drawBackground(QPainter *painter, const QRectF &rect) override {
QGraphicsScene::drawBackground(painter, rect);
// 绘制网格
painter->setPen(QPen(QColor(200, 200, 200), 1));
qreal left = int(rect.left()) - (int(rect.left()) % 50);
qreal top = int(rect.top()) - (int(rect.top()) % 50);
for (qreal x = left; x < rect.right(); x += 50) {
painter->drawLine(x, rect.top(), x, rect.bottom());
}
for (qreal y = top; y < rect.bottom(); y += 50) {
painter->drawLine(rect.left(), y, rect.right(), y);
}
}
private:
QVector<ConnectionItem*> m_connections;
ConnectionPoint *m_startConnectionPoint = nullptr;
QGraphicsLineItem *m_tempLine = nullptr;
bool m_draggingLine;
};
五、视图控制和交互
5.1 自定义视图控件
class CustomGraphicsView : public QGraphicsView {
Q_OBJECT
public:
explicit CustomGraphicsView(QWidget *parent = nullptr)
: QGraphicsView(parent), m_zoomLevel(0), m_panning(false) {
setRenderHint(QPainter::Antialiasing);
setRenderHint(QPainter::SmoothPixmapTransform);
setRenderHint(QPainter::TextAntialiasing);
setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
setDragMode(QGraphicsView::RubberBandDrag);
setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
// 启用 OpenGL 加速(如果可用)
QOpenGLWidget *glWidget = new QOpenGLWidget;
QSurfaceFormat format;
format.setSamples(4); // 4倍多重采样抗锯齿
glWidget->setFormat(format);
setViewport(glWidget);
}
protected:
void wheelEvent(QWheelEvent *event) override {
// 缩放控制
if (event->modifiers() & Qt::ControlModifier) {
qreal factor = 1.0;
if (event->angleDelta().y() > 0) {
factor = 1.1; // 放大
m_zoomLevel++;
} else {
factor = 0.9; // 缩小
m_zoomLevel--;
}
// 限制缩放范围
if (m_zoomLevel > 10) {
m_zoomLevel = 10;
return;
}
if (m_zoomLevel < -10) {
m_zoomLevel = -10;
return;
}
scale(factor, factor);
emit zoomLevelChanged(m_zoomLevel);
} else {
QGraphicsView::wheelEvent(event);
}
}
void mousePressEvent(QMouseEvent *event) override {
if (event->button() == Qt::MiddleButton) {
// 中键拖动平移
m_panning = true;
m_panStartPos = event->pos();
setCursor(Qt::ClosedHandCursor);
return;
}
QGraphicsView::mousePressEvent(event);
}
void mouseMoveEvent(QMouseEvent *event) override {
if (m_panning) {
// 计算平移距离
QPointF delta = mapToScene(event->pos()) - mapToScene(m_panStartPos);
m_panStartPos = event->pos();
// 平移视图
centerOn(mapToScene(rect().center()) - delta);
return;
}
QGraphicsView::mouseMoveEvent(event);
}
void mouseReleaseEvent(QMouseEvent *event) override {
if (event->button() == Qt::MiddleButton) {
m_panning = false;
setCursor(Qt::ArrowCursor);
return;
}
QGraphicsView::mouseReleaseEvent(event);
}
void contextMenuEvent(QContextMenuEvent *event) override {
QMenu menu;
QAction *zoomIn = menu.addAction("Zoom In");
QAction *zoomOut = menu.addAction("Zoom Out");
QAction *resetZoom = menu.addAction("Reset Zoom");
menu.addSeparator();
QAction *fitView = menu.addAction("Fit in View");
QAction *showGrid = menu.addAction("Show Grid");
showGrid->setCheckable(true);
showGrid->setChecked(true);
QAction *selected = menu.exec(event->globalPos());
if (selected == zoomIn) {
scale(1.2, 1.2);
m_zoomLevel++;
} else if (selected == zoomOut) {
scale(0.8, 0.8);
m_zoomLevel--;
} else if (selected == resetZoom) {
resetTransform();
m_zoomLevel = 0;
} else if (selected == fitView) {
fitInView(sceneRect(), Qt::KeepAspectRatio);
}
}
void keyPressEvent(QKeyEvent *event) override {
switch (event->key()) {
case Qt::Key_Plus:
case Qt::Key_Equal:
if (event->modifiers() & Qt::ControlModifier) {
scale(1.2, 1.2);
m_zoomLevel++;
}
break;
case Qt::Key_Minus:
if (event->modifiers() & Qt::ControlModifier) {
scale(0.8, 0.8);
m_zoomLevel--;
}
break;
case Qt::Key_0:
if (event->modifiers() & Qt::ControlModifier) {
resetTransform();
m_zoomLevel = 0;
}
break;
case Qt::Key_F:
fitInView(sceneRect(), Qt::KeepAspectRatio);
break;
case Qt::Key_Delete:
// 删除选中项
if (scene()) {
QList<QGraphicsItem*> selectedItems = scene()->selectedItems();
for (QGraphicsItem *item : selectedItems) {
scene()->removeItem(item);
delete item;
}
}
break;
case Qt::Key_Escape:
if (scene()) {
scene()->clearSelection();
}
break;
}
QGraphicsView::keyPressEvent(event);
}
signals:
void zoomLevelChanged(int level);
private:
int m_zoomLevel;
bool m_panning;
QPoint m_panStartPos;
};
六、实战案例:流程图编辑器
6.1 完整流程图编辑器实现
#include <QMainWindow>
#include <QToolBar>
#include <QStatusBar>
#include <QActionGroup>
#include <QComboBox>
class FlowchartEditor : public QMainWindow {
Q_OBJECT
public:
FlowchartEditor() {
setupUI();
setupConnections();
setWindowTitle("Flowchart Editor");
resize(1200, 800);
}
private:
void setupUI() {
// 创建场景和视图
m_scene = new GraphicsScene(this);
m_view = new CustomGraphicsView;
m_view->setScene(m_scene);
setCentralWidget(m_view);
// 创建工具栏
QToolBar *mainToolBar = addToolBar("Main");
// 工具选择
QActionGroup *toolGroup = new QActionGroup(this);
QAction *selectTool = mainToolBar->addAction("Select");
selectTool->setCheckable(true);
selectTool->setChecked(true);
selectTool->setIcon(QIcon::fromTheme("edit-select"));
toolGroup->addAction(selectTool);
QAction *nodeTool = mainToolBar->addAction("Add Node");
nodeTool->setCheckable(true);
nodeTool->setIcon(QIcon::fromTheme("insert-object"));
toolGroup->addAction(nodeTool);
QAction *connectionTool = mainToolBar->addAction("Connect");
connectionTool->setCheckable(true);
connectionTool->setIcon(QIcon::fromTheme("draw-connector"));
toolGroup->addAction(connectionTool);
mainToolBar->addSeparator();
// 缩放控制
QComboBox *zoomCombo = new QComboBox;
zoomCombo->addItems({"25%", "50%", "75%", "100%", "150%", "200%", "400%"});
zoomCombo->setCurrentIndex(3); // 100%
mainToolBar->addWidget(zoomCombo);
mainToolBar->addSeparator();
// 对齐工具
mainToolBar->addAction("Align Left");
mainToolBar->addAction("Align Center");
mainToolBar->addAction("Align Right");
mainToolBar->addAction("Distribute");
// 创建菜单
setupMenus();
// 状态栏
m_statusBar = statusBar();
m_statusBar->showMessage("Ready");
}
void setupMenus() {
// 文件菜单
QMenu *fileMenu = menuBar()->addMenu("File");
fileMenu->addAction("New", this, &FlowchartEditor::newDocument,
QKeySequence::New);
fileMenu->addAction("Open", this, &FlowchartEditor::openDocument,
QKeySequence::Open);
fileMenu->addAction("Save", this, &FlowchartEditor::saveDocument,
QKeySequence::Save);
fileMenu->addAction("Save As", this, &FlowchartEditor::saveAsDocument,
QKeySequence::SaveAs);
fileMenu->addSeparator();
fileMenu->addAction("Export PNG", this, &FlowchartEditor::exportPNG);
fileMenu->addAction("Export PDF", this, &FlowchartEditor::exportPDF);
fileMenu->addSeparator();
fileMenu->addAction("Exit", this, &QMainWindow::close,
QKeySequence::Quit);
// 编辑菜单
QMenu *editMenu = menuBar()->addMenu("Edit");
editMenu->addAction("Undo", m_scene, &QGraphicsScene::undo,
QKeySequence::Undo);
editMenu->addAction("Redo", m_scene, &QGraphicsScene::redo,
QKeySequence::Redo);
editMenu->addSeparator();
editMenu->addAction("Cut", this, &FlowchartEditor::cut,
QKeySequence::Cut);
editMenu->addAction("Copy", this, &FlowchartEditor::copy,
QKeySequence::Copy);
editMenu->addAction("Paste", this, &FlowchartEditor::paste,
QKeySequence::Paste);
editMenu->addAction("Delete", this, &FlowchartEditor::deleteSelected,
QKeySequence::Delete);
editMenu->addSeparator();
editMenu->addAction("Select All", this, &FlowchartEditor::selectAll,
QKeySequence::SelectAll);
// 视图菜单
QMenu *viewMenu = menuBar()->addMenu("View");
viewMenu->addAction("Zoom In", m_view, [this]() { m_view->scale(1.2, 1.2); },
QKeySequence::ZoomIn);
viewMenu->addAction("Zoom Out", m_view, [this]() { m_view->scale(0.8, 0.8); },
QKeySequence::ZoomOut);
viewMenu->addAction("Reset Zoom", m_view, [this]() { m_view->resetTransform(); });
viewMenu->addAction("Fit in View", m_view, [this]() {
m_view->fitInView(m_scene->sceneRect(), Qt::KeepAspectRatio);
}, QKeySequence(Qt::Key_F));
viewMenu->addSeparator();
QAction *showGrid = viewMenu->addAction("Show Grid");
showGrid->setCheckable(true);
showGrid->setChecked(true);
// 节点菜单
QMenu *nodeMenu = menuBar()->addMenu("Node");
nodeMenu->addAction("Start", this, [this]() { addNode("Start"); });
nodeMenu->addAction("Process", this, [this]() { addNode("Process"); });
nodeMenu->addAction("Decision", this, [this]() { addNode("Decision"); });
nodeMenu->addAction("Input/Output", this, [this]() { addNode("I/O"); });
nodeMenu->addAction("End", this, [this]() { addNode("End"); });
}
void setupConnections() {
// 连接场景信号
connect(m_scene, &QGraphicsScene::selectionChanged,
this, &FlowchartEditor::updateSelectionStatus);
connect(m_scene, &QGraphicsScene::changed,
this, &FlowchartEditor::updateDocumentStatus);
}
private slots:
void newDocument() {
m_scene->clear();
m_currentFile.clear();
setWindowTitle("Flowchart Editor - Untitled");
m_statusBar->showMessage("New document created", 3000);
}
void openDocument() {
// 实现文件打开逻辑
}
void saveDocument() {
if (m_currentFile.isEmpty()) {
saveAsDocument();
} else {
// 实现保存逻辑
}
}
void saveAsDocument() {
// 实现另存为逻辑
}
void exportPNG() {
QString fileName = QFileDialog::getSaveFileName(this, "Export PNG",
"", "PNG Files (*.png)");
if (!fileName.isEmpty()) {
QRectF rect = m_scene->itemsBoundingRect();
QImage image(rect.size().toSize(), QImage::Format_ARGB32);
image.fill(Qt::white);
QPainter painter(&image);
m_scene->render(&painter, QRectF(), rect);
painter.end();
if (image.save(fileName, "PNG")) {
m_statusBar->showMessage("Exported to PNG", 3000);
}
}
}
void exportPDF() {
QString fileName = QFileDialog::getSaveFileName(this, "Export PDF",
"", "PDF Files (*.pdf)");
if (!fileName.isEmpty()) {
QPrinter printer(QPrinter::HighResolution);
printer.setOutputFormat(QPrinter::PdfFormat);
printer.setOutputFileName(fileName);
QPainter painter;
if (painter.begin(&printer)) {
m_scene->render(&painter);
painter.end();
m_statusBar->showMessage("Exported to PDF", 3000);
}
}
}
void cut() {
// 实现剪切逻辑
}
void copy() {
// 实现复制逻辑
}
void paste() {
// 实现粘贴逻辑
}
void deleteSelected() {
QList<QGraphicsItem*> items = m_scene->selectedItems();
for (QGraphicsItem *item : items) {
m_scene->removeItem(item);
delete item;
}
}
void selectAll() {
for (QGraphicsItem *item : m_scene->items()) {
item->setSelected(true);
}
}
void addNode(const QString &type) {
NodeItem *node = new NodeItem(type);
node->setPos(m_view->mapToScene(m_view->viewport()->rect().center()));
m_scene->addItem(node);
}
void updateSelectionStatus() {
int count = m_scene->selectedItems().size();
if (count == 0) {
m_statusBar->showMessage("No items selected");
} else {
m_statusBar->showMessage(QString("%1 item(s) selected").arg(count));
}
}
void updateDocumentStatus() {
// 更新文档修改状态
if (!windowTitle().startsWith("*")) {
setWindowTitle("*" + windowTitle());
}
}
private:
GraphicsScene *m_scene;
CustomGraphicsView *m_view;
QStatusBar *m_statusBar;
QString m_currentFile;
};
// 主函数
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
// 设置应用程序样式
app.setStyle("Fusion");
// 创建调色板
QPalette palette;
palette.setColor(QPalette::Window, QColor(53, 53, 53));
palette.setColor(QPalette::WindowText, Qt::white);
palette.setColor(QPalette::Base, QColor(25, 25, 25));
palette.setColor(QPalette::AlternateBase, QColor(53, 53, 53));
palette.setColor(QPalette::ToolTipBase, Qt::white);
palette.setColor(QPalette::ToolTipText, Qt::white);
palette.setColor(QPalette::Text, Qt::white);
palette.setColor(QPalette::Button, QColor(53, 53, 53));
palette.setColor(QPalette::ButtonText, Qt::white);
palette.setColor(QPalette::BrightText, Qt::red);
palette.setColor(QPalette::Link, QColor(42, 130, 218));
palette.setColor(QPalette::Highlight, QColor(42, 130, 218));
palette.setColor(QPalette::HighlightedText, Qt::black);
app.setPalette(palette);
FlowchartEditor editor;
editor.show();
return app.exec();
}

浙公网安备 33010602011771号