QGraphicsScene框架概述
QGraphicsScene/QGraphicsView框架是Qt提供的一套强大的图形视图系统,用于处理大量图形项的显示、交互和动画。这是MFC所不具备的功能,对于需要复杂交互图形界面的应用来说是一个巨大的优势。
核心组件
图形视图框架主要由三个核心类组成:
- QGraphicsScene - 管理场景中的所有图形项
- QGraphicsView - 显示场景的视图窗口,提供滚动、缩放等功能
- 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. 迁移策略
- 先构建骨架: 创建基本的场景和视图框架
- 分批转换绘图代码: 将MFC应用中的绘图对象逐个转换为QGraphicsItem子类
- 重构交互逻辑: 基于项目的事件系统重构交互代码
- 利用内置功能: 使用Qt内置的选择、拖放、变换功能简化代码
- 提高性能: 利用缓存模式和其他优化
3. 常见挑战
- 坐标系差异: MFC通常使用基于窗口的坐标,而Qt Graphics使用场景坐标
- 交互模型不同: 基于消息的MFC交互与基于对象的Qt交互有较大差别
- 选择管理: MFC通常需要手动管理选择,而Qt有内置功能
- 性能考虑: 大型场景需要考虑缓存和其他性能优化技术