四时宝库

程序员的知识宝库

MFC转QT:Qt高级特性 - QGraphicsScene/QGraphicsView



QGraphicsScene框架概述


QGraphicsScene/QGraphicsView框架是Qt提供的一套强大的图形视图系统,用于处理大量图形项的显示、交互和动画。这是MFC所不具备的功能,对于需要复杂交互图形界面的应用来说是一个巨大的优势。

核心组件

图形视图框架主要由三个核心类组成:

  1. QGraphicsScene - 管理场景中的所有图形项
  2. QGraphicsView - 显示场景的视图窗口,提供滚动、缩放等功能
  3. QGraphicsItem - 场景中的各种图形项的基类

这种场景-视图分离的架构提供了极大的灵活性:

  • 一个场景可以被多个视图显示
  • 图形项可以独立于视图的坐标系统
  • 支持深度排序、分组和嵌套

与MFC的对比

MFC没有直接对应的框架,最接近的可能是:

  • 使用自定义CView子类实现绘图
  • 自行管理绘图对象的集合
  • 手动处理缩放、滚动和选择

Qt的图形视图框架提供了这些功能的完整解决方案,极大减少了开发时间和代码复杂度。

基本使用

创建场景和视图

 // 创建场景
 QGraphicsScene *scene = new QGraphicsScene(this);
 scene->setSceneRect(-200, -200, 400, 400); // 设置场景坐标范围
 
 // 创建视图
 QGraphicsView *view = new QGraphicsView(scene, this);
 view->setRenderHint(QPainter::Antialiasing); // 启用抗锯齿
 view->setDragMode(QGraphicsView::RubberBandDrag); // 启用框选模式
 view->setOptimizationFlags(QGraphicsView::DontSavePainterState);
 view->setViewportUpdateMode(QGraphicsView::SmartViewportUpdate);
 view->setTransformationAnchor(QGraphicsView::AnchorUnderMouse); // 缩放围绕鼠标
 
 // 将视图添加到布局
 QVBoxLayout *layout = new QVBoxLayout;
 layout->addWidget(view);
 setLayout(layout);

添加图形项

 // 添加矩形
 QGraphicsRectItem *rect = scene->addRect(-50, -50, 100, 100);
 rect->setPen(QPen(Qt::black, 2));
 rect->setBrush(QBrush(Qt::blue));
 rect->setFlag(QGraphicsItem::ItemIsMovable);
 rect->setFlag(QGraphicsItem::ItemIsSelectable);
 
 // 添加椭圆
 QGraphicsEllipseItem *ellipse = scene->addEllipse(50, 50, 100, 60);
 ellipse->setPen(QPen(Qt::red, 2));
 ellipse->setBrush(QBrush(Qt::green));
 ellipse->setFlag(QGraphicsItem::ItemIsMovable);
 ellipse->setFlag(QGraphicsItem::ItemIsSelectable);
 
 // 添加文本
 QGraphicsTextItem *text = scene->addText("Hello QGraphicsScene");
 text->setPos(-100, 80);
 text->setFont(QFont("Arial", 12));
 text->setFlag(QGraphicsItem::ItemIsMovable);
 text->setFlag(QGraphicsItem::ItemIsSelectable);
 
 // 添加简单图像
 QPixmap pixmap(":/images/logo.png");
 QGraphicsPixmapItem *pixmapItem = scene->addPixmap(pixmap);
 pixmapItem->setPos(0, -100);
 pixmapItem->setFlag(QGraphicsItem::ItemIsMovable);

视图控制

 // 缩放视图
 view->scale(1.2, 1.2); // 放大1.2倍
 
 // 旋转视图
 view->rotate(45); // 旋转45度
 
 // 滚动视图
 view->centerOn(rect); // 中心对准指定项目
 
 // 设置背景
 view->setBackgroundBrush(QBrush(Qt::lightGray));
 
 // 自定义变换
 QTransform transform;
 transform.scale(0.8, 0.8);
 transform.rotate(30);
 view->setTransform(transform);

自定义图形项

基本图形项

通过继承QGraphicsItem可以创建自定义图形项:

 class MyCustomItem : public QGraphicsItem
 {
 public:
     MyCustomItem(QGraphicsItem *parent = nullptr)
         : QGraphicsItem(parent)
     {
         setFlag(QGraphicsItem::ItemIsMovable);
         setFlag(QGraphicsItem::ItemIsSelectable);
         setFlag(QGraphicsItem::ItemSendsGeometryChanges);
     }
 
     // 定义项目边界
     QRectF boundingRect() const override
     {
         return QRectF(-50, -50, 100, 100);
     }
 
     // 提供精确碰撞检测形状(可选)
     QPainterPath shape() const override
     {
         QPainterPath path;
         path.addEllipse(boundingRect());
         return path;
     }
 
     // 绘制项目
     void paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
                QWidget *widget) override
     {
         // 绘制选中状态效果
         if (option->state & QStyle::State_Selected) {
             painter->setPen(QPen(Qt::blue, 2, Qt::DashLine));
             painter->drawRect(boundingRect());
         }
 
         // 绘制实际内容
         painter->setPen(QPen(Qt::black, 1));
         painter->setBrush(QBrush(Qt::yellow));
         painter->drawEllipse(-30, -30, 60, 60);
 
         // 绘制文本
         painter->setFont(QFont("Arial", 10));
         painter->drawText(boundingRect(), Qt::AlignCenter, "自定义项");
     }
 
 protected:
     // 处理项目状态变化
     QVariant itemChange(GraphicsItemChange change, const QVariant &value) override
     {
         if (change == ItemPositionChange) {
             // 限制移动范围
             QPointF newPos = value.toPointF();
             QRectF rect = scene()->sceneRect();
             
             if (!rect.contains(newPos)) {
                 newPos.setX(qMin(rect.right(), qMax(newPos.x(), rect.left())));
                 newPos.setY(qMin(rect.bottom(), qMax(newPos.y(), rect.top())));
                 return newPos;
             }
         }
         
         return QGraphicsItem::itemChange(change, value);
     }
 
     // 处理鼠标事件
     void mousePressEvent(QGraphicsSceneMouseEvent *event) override
     {
         setCursor(Qt::ClosedHandCursor);
         QGraphicsItem::mousePressEvent(event);
     }
 
     void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override
     {
         setCursor(Qt::OpenHandCursor);
         QGraphicsItem::mouseReleaseEvent(event);
     }
 };
 
 // 使用自定义项目
 MyCustomItem *customItem = new MyCustomItem();
 customItem->setPos(0, 0);
 scene->addItem(customItem);

复合项目和分组

QGraphicsItemGroup可以将多个项目分组,作为一个整体移动和变换:

 // 创建项目组
 QGraphicsItemGroup *group = new QGraphicsItemGroup();
 scene->addItem(group);
 
 // 创建组内项目
 QGraphicsRectItem *groupRect = new QGraphicsRectItem(-40, -40, 80, 80);
 groupRect->setBrush(Qt::yellow);
 
 QGraphicsEllipseItem *groupEllipse = new QGraphicsEllipseItem(-30, -30, 60, 60);
 groupEllipse->setBrush(Qt::red);
 
 // 将项目添加到组
 group->addToGroup(groupRect);
 group->addToGroup(groupEllipse);
 
 // 设置组属性
 group->setFlag(QGraphicsItem::ItemIsMovable);
 group->setFlag(QGraphicsItem::ItemIsSelectable);
 group->setPos(100, 100);

父子项关系

QGraphicsItem支持项目间的父子关系,子项会跟随父项移动和变换:

 // 创建父项
 QGraphicsRectItem *parentItem = new QGraphicsRectItem(-50, -50, 100, 100);
 parentItem->setBrush(QBrush(Qt::blue));
 parentItem->setFlag(QGraphicsItem::ItemIsMovable);
 scene->addItem(parentItem);
 
 // 创建子项
 QGraphicsEllipseItem *childItem = new QGraphicsEllipseItem(-20, -20, 40, 40, parentItem);
 childItem->setBrush(QBrush(Qt::red));
 childItem->setFlag(QGraphicsItem::ItemIsSelectable);
 
 // 子项的坐标是相对于父项的
 QGraphicsTextItem *textItem = new QGraphicsTextItem("子项", parentItem);
 textItem->setPos(0, 0); // 相对于父项的中心

交互功能

事件处理

QGraphicsItem可以处理多种鼠标、键盘等交互事件:

 class InteractiveItem : public QGraphicsRectItem
 {
 public:
     InteractiveItem(QGraphicsItem *parent = nullptr)
         : QGraphicsRectItem(-50, -30, 100, 60, parent)
     {
         setFlag(QGraphicsItem::ItemIsMovable);
         setFlag(QGraphicsItem::ItemIsSelectable);
         setFlag(QGraphicsItem::ItemIsFocusable);
         
         setBrush(QBrush(Qt::lightGray));
         setPen(QPen(Qt::black, 2));
     }
 
 protected:
     // 鼠标按下
     void mousePressEvent(QGraphicsSceneMouseEvent *event) override
     {
         setBrush(QBrush(Qt::yellow));
         QGraphicsRectItem::mousePressEvent(event);
     }
 
     // 鼠标释放
     void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override
     {
         setBrush(QBrush(Qt::lightGray));
         QGraphicsRectItem::mouseReleaseEvent(event);
     }
 
     // 鼠标双击
     void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override
     {
         // 改变大小
         setRect(rect().adjusted(-10, -10, 10, 10));
         QGraphicsRectItem::mouseDoubleClickEvent(event);
     }
 
     // 鼠标悬停
     void hoverEnterEvent(QGraphicsSceneHoverEvent *event) override
     {
         setPen(QPen(Qt::red, 3));
     }
 
     void hoverLeaveEvent(QGraphicsSceneHoverEvent *event) override
     {
         setPen(QPen(Qt::black, 2));
     }
 
     // 键盘事件
     void keyPressEvent(QKeyEvent *event) override
     {
         switch (event->key()) {
         case Qt::Key_Plus:
             // 放大
             setScale(scale() * 1.1);
             break;
         case Qt::Key_Minus:
             // 缩小
             setScale(scale() / 1.1);
             break;
         case Qt::Key_Space:
             // 旋转
             setRotation(rotation() + 15);
             break;
         default:
             QGraphicsRectItem::keyPressEvent(event);
         }
     }
 };
 
 // 使用交互项
 InteractiveItem *interactiveItem = new InteractiveItem();
 interactiveItem->setPos(0, 0);
 scene->addItem(interactiveItem);
 
 // 激活悬停事件
 interactiveItem->setAcceptHoverEvents(true);

拖放支持

QGraphicsItem可以支持拖放操作:

 class DraggableItem : public QGraphicsRectItem
 {
 public:
     DraggableItem() : QGraphicsRectItem(-50, -30, 100, 60)
     {
         setFlag(QGraphicsItem::ItemIsMovable);
         setFlag(QGraphicsItem::ItemIsSelectable);
         setBrush(QBrush(Qt::green));
         setAcceptDrops(true);
     }
 
 protected:
     void dragEnterEvent(QGraphicsSceneDragDropEvent *event) override
     {
         if (event->mimeData()->hasText()) {
             event->accept();
             setBrush(QBrush(Qt::yellow));
         } else {
             event->ignore();
         }
     }
 
     void dragLeaveEvent(QGraphicsSceneDragDropEvent *event) override
     {
         setBrush(QBrush(Qt::green));
         QGraphicsRectItem::dragLeaveEvent(event);
     }
 
     void dropEvent(QGraphicsSceneDragDropEvent *event) override
     {
         if (event->mimeData()->hasText()) {
             QString text = event->mimeData()->text();
             
             // 处理拖放的文本
             QGraphicsTextItem *textItem = new QGraphicsTextItem(this);
             textItem->setPlainText(text);
             textItem->setPos(-textItem->boundingRect().width()/2, 0);
             
             setBrush(QBrush(Qt::green));
             event->accept();
         }
     }
 };

高级功能

缓存与性能优化

对于复杂或大量的图形项,可以使用项目缓存提高性能:

// 启用项目缓存
myComplexItem->setCacheMode(QGraphicsItem::DeviceCoordinateCache);

// 不同缓存模式
// ItemCoordinateCache - 适合不经常变换但内容复杂的项目
// DeviceCoordinateCache - 适合经常移动但不旋转/缩放的项目
// NoCache - 适合频繁改变外观的项目

自定义视图与场景交互

通过子类化QGraphicsView可以自定义视图交互行为:

class CustomView : public QGraphicsView
{
public:
    CustomView(QGraphicsScene *scene) : QGraphicsView(scene)
    {
        // 设置视图属性
        setRenderHint(QPainter::Antialiasing);
        setDragMode(QGraphicsView::RubberBandDrag);
        setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
    }

protected:
    // 鼠标滚轮事件 - 实现缩放
    void wheelEvent(QWheelEvent *event) override
    {
        // 缩放因子
        double scaleFactor = 1.15;
        
        if (event->angleDelta().y() > 0) {
            // 放大
            scale(scaleFactor, scaleFactor);
        } else {
            // 缩小
            scale(1.0 / scaleFactor, 1.0 / scaleFactor);
        }
    }

    // 键盘事件 - 实现平移
    void keyPressEvent(QKeyEvent *event) override
    {
        switch (event->key()) {
        case Qt::Key_Up:
            translate(0, 10);
            break;
        case Qt::Key_Down:
            translate(0, -10);
            break;
        case Qt::Key_Left:
            translate(10, 0);
            break;
        case Qt::Key_Right:
            translate(-10, 0);
            break;
        case Qt::Key_Home:
            // 重置变换
            resetTransform();
            break;
        default:
            QGraphicsView::keyPressEvent(event);
        }
    }

    // 鼠标按下事件 - 实现拖动
    void mousePressEvent(QMouseEvent *event) override
    {
        if (event->button() == Qt::MiddleButton) {
            // 中键拖动
            setDragMode(QGraphicsView::ScrollHandDrag);
            setInteractive(false);
            QMouseEvent fakeEvent(QEvent::MouseButtonPress,
                                 event->pos(), Qt::LeftButton,
                                 Qt::LeftButton, Qt::NoModifier);
            QGraphicsView::mousePressEvent(&fakeEvent);
            event->accept();
            return;
        }
        
        QGraphicsView::mousePressEvent(event);
    }

    void mouseReleaseEvent(QMouseEvent *event) override
    {
        if (event->button() == Qt::MiddleButton) {
            // 恢复默认模式
            setDragMode(QGraphicsView::RubberBandDrag);
            setInteractive(true);
            QMouseEvent fakeEvent(QEvent::MouseButtonRelease,
                                 event->pos(), Qt::LeftButton,
                                 Qt::LeftButton, Qt::NoModifier);
            QGraphicsView::mouseReleaseEvent(&fakeEvent);
            event->accept();
            return;
        }
        
        QGraphicsView::mouseReleaseEvent(event);
    }
};

导出和打印

场景可以导出为图像或直接打印:

// 导出场景为图像
void exportScene(QGraphicsScene *scene, const QString &fileName,
                const QSize &imageSize = QSize(800, 600))
{
    QImage image(imageSize, QImage::Format_ARGB32_Premultiplied);
    image.fill(Qt::white);
    
    QPainter painter(&image);
    painter.setRenderHint(QPainter::Antialiasing);
    scene->render(&painter);
    painter.end();
    
    image.save(fileName);
}

// 打印场景
void printScene(QGraphicsScene *scene)
{
    QPrinter printer(QPrinter::HighResolution);
    QPrintDialog dialog(&printer);
    
    if (dialog.exec() == QDialog::Accepted) {
        QPainter painter(&printer);
        painter.setRenderHint(QPainter::Antialiasing);
        scene->render(&painter);
        painter.end();
    }
}

实际应用示例

简单流程图编辑器

以下是一个流程图编辑器的简化示例:

// 流程图节点
class FlowChartNode : public QGraphicsRectItem
{
public:
    FlowChartNode(const QString &text)
        : QGraphicsRectItem(-75, -30, 150, 60)
    {
        setBrush(QBrush(Qt::white));
        setPen(QPen(Qt::black, 2));
        setFlags(QGraphicsItem::ItemIsMovable | 
                QGraphicsItem::ItemIsSelectable |
                QGraphicsItem::ItemSendsGeometryChanges);
        
        // 添加文本
        textItem = new QGraphicsTextItem(this);
        textItem->setPlainText(text);
        textItem->setPos(-textItem->boundingRect().width()/2, 
                        -textItem->boundingRect().height()/2);
        
        // 创建连接点
        createConnectionPoints();
    }
    
    QPointF connectionPoint(int index) const
    {
        if (index >= 0 && index < connectionPoints.size())
            return connectionPoints[index]->scenePos();
        return QPointF();
    }
    
    void setText(const QString &text)
    {
        textItem->setPlainText(text);
        textItem->setPos(-textItem->boundingRect().width()/2, 
                        -textItem->boundingRect().height()/2);
    }
    
protected:
    QVariant itemChange(GraphicsItemChange change, const QVariant &value) override
    {
        if (change == ItemPositionHasChanged) {
            // 通知连接线更新
            foreach (QGraphicsItem *item, scene()->items()) {
                FlowChartConnection *connection = 
                    dynamic_cast<FlowChartConnection*>(item);
                if (connection && 
                    (connection->sourceNode() == this || 
                     connection->destNode() == this)) {
                    connection->updatePosition();
                }
            }
        }
        
        return QGraphicsRectItem::itemChange(change, value);
    }
    
private:
    void createConnectionPoints()
    {
        // 上、右、下、左四个连接点
        connectionPoints.append(createConnectionPoint(0, -30)); // 上
        connectionPoints.append(createConnectionPoint(75, 0));  // 右
        connectionPoints.append(createConnectionPoint(0, 30));  // 下
        connectionPoints.append(createConnectionPoint(-75, 0)); // 左
    }
    
    QGraphicsRectItem *createConnectionPoint(qreal x, qreal y)
    {
        QGraphicsRectItem *point = new QGraphicsRectItem(-4, -4, 8, 8, this);
        point->setBrush(QBrush(Qt::black));
        point->setPen(Qt::NoPen);
        point->setPos(x, y);
        return point;
    }
    
    QList<QGraphicsRectItem*> connectionPoints;
    QGraphicsTextItem *textItem;
};

// 流程图连接线
class FlowChartConnection : public QGraphicsPathItem
{
public:
    FlowChartConnection(FlowChartNode *sourceNode, int sourcePointIndex,
                       FlowChartNode *destNode, int destPointIndex)
        : sourceNode_(sourceNode), destNode_(destNode),
          sourcePointIndex_(sourcePointIndex), destPointIndex_(destPointIndex)
    {
        setPen(QPen(Qt::black, 2));
        setFlag(QGraphicsItem::ItemIsSelectable);
        updatePosition();
    }
    
    void updatePosition()
    {
        if (!sourceNode_ || !destNode_)
            return;
            
        QPointF sourcePoint = sourceNode_->connectionPoint(sourcePointIndex_);
        QPointF destPoint = destNode_->connectionPoint(destPointIndex_);
        
        QPainterPath path;
        path.moveTo(sourcePoint);
        
        // 创建贝塞尔曲线连接
        QPointF control1, control2;
        calculateControlPoints(sourcePoint, destPoint, control1, control2);
        
        path.cubicTo(control1, control2, destPoint);
        setPath(path);
    }
    
    FlowChartNode *sourceNode() const { return sourceNode_; }
    FlowChartNode *destNode() const { return destNode_; }
    
private:
    void calculateControlPoints(const QPointF &source, const QPointF &dest,
                               QPointF &control1, QPointF &control2)
    {
        // 简单控制点计算
        qreal dx = dest.x() - source.x();
        qreal dy = dest.y() - source.y();
        
        control1 = QPointF(source.x() + dx * 0.4, source.y());
        control2 = QPointF(dest.x() - dx * 0.4, dest.y());
    }
    
    FlowChartNode *sourceNode_;
    FlowChartNode *destNode_;
    int sourcePointIndex_;
    int destPointIndex_;
};

// 使用示例
void setupFlowChart(QGraphicsScene *scene)
{
    // 创建节点
    FlowChartNode *startNode = new FlowChartNode("开始");
    startNode->setPos(0, -150);
    scene->addItem(startNode);
    
    FlowChartNode *processNode = new FlowChartNode("处理");
    processNode->setPos(0, 0);
    scene->addItem(processNode);
    
    FlowChartNode *decisionNode = new FlowChartNode("判断");
    decisionNode->setPos(0, 150);
    scene->addItem(decisionNode);
    
    FlowChartNode *endNode = new FlowChartNode("结束");
    endNode->setPos(150, 300);
    scene->addItem(endNode);
    
    // 创建连接
    FlowChartConnection *conn1 = 
        new FlowChartConnection(startNode, 2, processNode, 0);
    scene->addItem(conn1);
    
    FlowChartConnection *conn2 = 
        new FlowChartConnection(processNode, 2, decisionNode, 0);
    scene->addItem(conn2);
    
    FlowChartConnection *conn3 = 
        new FlowChartConnection(decisionNode, 2, endNode, 0);
    scene->addItem(conn3);
}

从MFC迁移的建议

1. 功能映射

MFC实现

Qt Graphics框架

自定义CView

QGraphicsView

手动管理对象集合

QGraphicsScene

自定义CObject子类

QGraphicsItem子类

滚动管理

自动处理

缩放实现

简单的view->scale()

自定义绘制

项目的paint()方法

选择处理

内置的选择功能

拖放支持

内置的拖放功能

2. 迁移策略

  1. 先构建骨架: 创建基本的场景和视图框架
  2. 分批转换绘图代码: 将MFC应用中的绘图对象逐个转换为QGraphicsItem子类
  3. 重构交互逻辑: 基于项目的事件系统重构交互代码
  4. 利用内置功能: 使用Qt内置的选择、拖放、变换功能简化代码
  5. 提高性能: 利用缓存模式和其他优化

3. 常见挑战

  1. 坐标系差异: MFC通常使用基于窗口的坐标,而Qt Graphics使用场景坐标
  2. 交互模型不同: 基于消息的MFC交互与基于对象的Qt交互有较大差别
  3. 选择管理: MFC通常需要手动管理选择,而Qt有内置功能
  4. 性能考虑: 大型场景需要考虑缓存和其他性能优化技术

发表评论:

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言
    友情链接