QGraphicsView移动图元/场景以及坐标转换

本文深入探讨了使用Qt进行绘图的三大核心组件:QGraphicsView(视图)、QGraphicsScene(场景)、QGraphicsItem(图元)之间的关系及其实现细节。通过自定义图元、场景和视图类,展示了如何实现图元的移动与场景的平移,以及如何处理鼠标事件。文章还提供了丰富的代码示例,帮助读者理解坐标转换的过程。

使用Qt来画图的时候,需要了解一下QGraphicsView(视图)、QGraphicsScene(场景)、QGraphicsItem(图元),已经他们之间的关系。

通过把各种 图元(基类都是:QGraphicsItem)组合在一起搭建成场景(QGraphicsScene),把搭建好的场景通过视图展示出来(QGraphicsView)。

通过一个示例来展示,选中图元的时候可以移动图元,选中空白的时候可以移动整个场景:
在这里插入图片描述

1.坐标转换

这三者之间的存在不同的坐标系,他们之间需要坐标转换,而且只用QGraphicsItem和QGraphicsView有坐标转换的功能,也就是说QGraphicsItem和QGraphicsView都可以QGraphicsScene直接进行坐标转换,但是QGraphicsItem和QGraphicsView之间的坐标转换需要通过QGraphicsScene进行转换。

所有的图元QGraphicsItem都是放到QGraphicsScene中,所以QGraphicsScene是所有的图元的父图元。

常用的坐标转换函数:

QGraphicsView::mapToScene() - 视图 -> 场景

QGraphicsView::mapFromScene() -  场景 -> 视图

QGraphicsItem::mapFromScene() -  场景 -> 图元

QGraphicsItem::mapToScene() - 图元 -> 场景

QGraphicsItem::mapToParent() - 子图元 -> 父图元

QGraphicsItem::mapFromParent() - 父图元 -> 子图元

QGraphicsItem::mapToItem() - 本图元 -> 其他图元

QGraphicsItem::mapFromItem() - 其他图元 -> 本图元

2.图元/场景移动

如果是移动单个图元的话,

	setFlag(QGraphicsItem::ItemIsMovable, true);//可以拖动
	setFlag(QGraphicsItem::ItemIsSelectable, true);//可以选中

贴上代码:
自定义图元:

#pragma once

#include <QGraphicsEllipseItem>

class IGraphicsCoord;

class AAAEllipseItem : public QGraphicsEllipseItem {

public:
	AAAEllipseItem(QGraphicsItem *parent);
	~AAAEllipseItem();

	inline void setCoord(IGraphicsCoord* coord){
		_graphicsCoord = coord;
	}
private:
	virtual void mousePressEvent(QGraphicsSceneMouseEvent *event)Q_DECL_OVERRIDE;
	virtual void mouseMoveEvent(QGraphicsSceneMouseEvent *event)Q_DECL_OVERRIDE;
	virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent *event)Q_DECL_OVERRIDE;

private:
	IGraphicsCoord* _graphicsCoord = nullptr;
	QPointF _mousePress;
};

#include "AAAEllipseItem.h"
#include <QDebug>
#include <QPen>
#include <QGraphicsSceneMouseEvent>
#include "IGraphicsCoord.h"


AAAEllipseItem::AAAEllipseItem(QGraphicsItem *parent)
	: QGraphicsEllipseItem(parent) {
	setFlag(QGraphicsItem::ItemIsMovable, true);
	setFlag(QGraphicsItem::ItemIsSelectable, true);

	QPen pen;
	pen.setColor(QColor(247, 99, 0));
	pen.setWidth(3);

	setPen(pen);
}

AAAEllipseItem::~AAAEllipseItem() {
}


void AAAEllipseItem::mousePressEvent(QGraphicsSceneMouseEvent *event) {
	QGraphicsEllipseItem::mousePressEvent(event);
	qDebug() << "AAAEllipseItem:" << event->pos().x() << event->pos().y();
	QPointF ptScene = mapToScene(event->pos());
	qDebug() << "AAAEllipseItem mapToScene:" << ptScene.x() << ptScene.y();
	if (_graphicsCoord != nullptr){
		_graphicsCoord->itemToSence(ptScene);
	}
}

void AAAEllipseItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) {
	QGraphicsEllipseItem::mouseMoveEvent(event);

}

void AAAEllipseItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) {
	QGraphicsEllipseItem::mouseReleaseEvent(event);

} 

自定义场景:

#pragma once

#include <QGraphicsScene>

class AAAGraphicsScene : public QGraphicsScene {
	Q_OBJECT

public:
	AAAGraphicsScene(QObject *parent);
	~AAAGraphicsScene();
private:
	virtual void mousePressEvent(QGraphicsSceneMouseEvent *event)Q_DECL_OVERRIDE;
	virtual void mouseMoveEvent(QGraphicsSceneMouseEvent *event)Q_DECL_OVERRIDE;
	virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent *event)Q_DECL_OVERRIDE;
};

#include "AAAGraphicsScene.h"
#include <QDebug>
#include <QGraphicsSceneMouseEvent>

AAAGraphicsScene::AAAGraphicsScene(QObject *parent)
	: QGraphicsScene(parent) {
}

AAAGraphicsScene::~AAAGraphicsScene() {
}

void AAAGraphicsScene::mousePressEvent(QGraphicsSceneMouseEvent *event) {
	QGraphicsScene::mousePressEvent(event);
	qDebug() << "AAAGraphicsScene:" << event->pos().x() << event->pos().y();
}

void AAAGraphicsScene::mouseMoveEvent(QGraphicsSceneMouseEvent *event) {
	QGraphicsScene::mouseMoveEvent(event);
	
}

void AAAGraphicsScene::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) {
	QGraphicsScene::mouseReleaseEvent(event);
}

自定义视图:

#pragma once

#include <QGraphicsView>
#include "IGraphicsCoord.h"

class IGraphicsCoord2;

class AAAGraphicsView : public QGraphicsView,public IGraphicsCoord {
	Q_OBJECT

public:
	AAAGraphicsView(QWidget *parent);
	~AAAGraphicsView();

	inline void setItemCoord(IGraphicsCoord2* coord){
		_graphicsCoord2 = coord;
	}

protected:
	void mousePressEvent(QMouseEvent *event) Q_DECL_OVERRIDE;
	void mouseMoveEvent(QMouseEvent *event) Q_DECL_OVERRIDE;
	void mouseReleaseEvent(QMouseEvent *event) Q_DECL_OVERRIDE;

private:
	virtual void itemToSence(QPointF pt)override;
private:
	IGraphicsCoord2* _graphicsCoord2 = nullptr;
	QPoint _mouseLBtnDown;
	bool _isLBtnDown = false;
};

#include "AAAGraphicsView.h"
#include <QDebug>
#include <QMouseEvent>

AAAGraphicsView::AAAGraphicsView(QWidget *parent)
	: QGraphicsView(parent) {

	//隐藏水平/竖直滚动条
	setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
	setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);

	//设置场景范围
	setSceneRect(INT_MIN / 2, INT_MIN / 2, INT_MAX, INT_MAX);
	//反锯齿
	setRenderHints(QPainter::Antialiasing);
}

AAAGraphicsView::~AAAGraphicsView() {
}

void AAAGraphicsView::mousePressEvent(QMouseEvent *event) {
	QGraphicsView::mousePressEvent(event);
	qDebug() << "AAAGraphicsView:" << event->pos().x() << event->pos().y();

	if (event->button() == Qt::LeftButton){
		if (scene()->itemAt(mapToScene(event->pos()), transform()) == nullptr) {//没有选中任何图元
			qDebug() << QStringLiteral("没有选中图元");
			_mouseLBtnDown = event->pos();
			_isLBtnDown = true;
		}
	}
}

void AAAGraphicsView::mouseMoveEvent(QMouseEvent *event) {
	QGraphicsView::mouseMoveEvent(event);
	if (_isLBtnDown){
		QPointF ptNow = mapToScene(event->pos());
		QPointF movePt = ptNow - mapToScene(_mouseLBtnDown);

		//根据鼠标当前的点作为定位点
		setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
		QPoint nowCenter(-movePt.x(),  -movePt.y());
		//qDebug() << "nowCenter:" << nowCenter.x()<< "  "<< nowCenter.y();
		centerOn((nowCenter));
		setTransformationAnchor(QGraphicsView::AnchorViewCenter);
	}
}

void AAAGraphicsView::mouseReleaseEvent(QMouseEvent *event) {
	QGraphicsView::mouseReleaseEvent(event);
	if (event->button() == Qt::LeftButton) {
		_isLBtnDown = false;
	}
}

void AAAGraphicsView::itemToSence(QPointF pt) {
	QPointF ptFromScene = mapFromScene(pt);
	qDebug() << "AAAGraphicsView mapFromSence:" << ptFromScene.x() << ptFromScene.y();
}

定义接口:

#pragma once
#include <QPointF>

class IGraphicsCoord {
public:
	IGraphicsCoord();
	~IGraphicsCoord();

	virtual void itemToSence(QPointF pt);
}; 
#include "IGraphicsCoord.h"

IGraphicsCoord::IGraphicsCoord() {
}

IGraphicsCoord::~IGraphicsCoord() {
}

void IGraphicsCoord::itemToSence(QPointF pt) {

}

创建场景:

void QtGuiOPenGL::init3() {
	AAAGraphicsScene* scene = new AAAGraphicsScene(this);
	AAAEllipseItem* e1 = new AAAEllipseItem(nullptr);
	e1->setRect(10, 10, 100, 100);
	scene->addItem(e1);

	AAAEllipseItem* e2 = new AAAEllipseItem(nullptr);
	e2->setRect(20, 20, 200, 100);
	scene->addItem(e2);

	scene->addLine(0.0, 0.0, 100.0, 0.0);
	scene->addLine(0.0, 0.0, 0.0, 100.0);

	AAAGraphicsView* view = new AAAGraphicsView(this);
	e1->setCoord(view);

	//view->setAlignment(Qt::AlignLeft | Qt::AlignTop);
	view->setScene(scene);

	ui.verticalLayout->addWidget(view)                                          ;
}

运行之后,当点击一个图元的时候,输出的信息:

AAAEllipseItem: 54 43
AAAEllipseItem mapToScene: 54 43
AAAGraphicsScene: 54 43
AAAGraphicsView: 357 304

这也说明了,鼠标事件传递的顺序:
图元-> 场景->视图
QGraphicsItem -> QGraphicsScene -> QGraphicsView
视图和图元的交互是通过场景来装换的。

aaa

<think>我们之前讨论了如何在QGraphicsView中实现图层功能,现在用户的问题聚焦于图层内部图元的选择和拖动操作。根据引用[1]的内容,我们知道在Qt的图形视图框架中,事件处理是通过重写事件处理函数来实现的。特别是对于拖拽操作,需要重写dragEnterEvent、dragMoveEvent等函数。但用户的问题更偏向于图层内部图元的选择和拖动,这属于交互操作。 在QGraphicsView的框架中,图元QGraphicsItem)本身可以处理鼠标事件。为了实现选择和拖动,我们需要设置图元的标志(flags)。同时,由于图元位于图层(QGraphicsItemGroup)内,我们还需要注意事件的传递和处理。 以下是实现步骤: 1. 设置图元可选择和可移动: 通过设置图元的标志,我们可以让图元支持选择和移动。 ```cpp // 创建图元(例如矩形) QGraphicsRectItem *rectItem = new QGraphicsRectItem(0, 0, 100, 100); // 设置标志:可选择、可移动 rectItem->setFlags(QGraphicsItem::ItemIsSelectable | QGraphicsItem::ItemIsMovable); ``` 2. 将图元添加到图层(QGraphicsItemGroup)中: 注意,当图元被添加到图层组中时,图元会成为图层组的子项。图层组默认会合并子项的事件处理,但有时我们可能需要独立处理子项事件。 如果希望图层组内的图元独立响应事件,可以设置图层组的标志: ```cpp // 创建图层组 QGraphicsItemGroup *layer = new QGraphicsItemGroup; // 设置图层组不处理子事件(这样事件会传递给子项) layer->setHandlesChildEvents(false); ``` 如果不设置,那么图层组会处理事件,可能会拦截子项的事件。 3. 图层组的事件传递: 默认情况下,图层组(QGraphicsItemGroup)会合并子项的事件。也就是说,如果点击一个子项,事件首先会传递给子项,但如果子项不接受,事件会传递给图层组。如果我们希望每个图元独立响应事件,那么应该设置图层组不处理子事件(setHandlesChildEvents(false)),这样事件就会直接由子项处理。 4. 选择操作: QGraphicsScene提供了选择区域的功能,可以通过设置视图的拖拽模式来实现矩形框选。 ```cpp // 设置视图的拖拽模式为矩形选择 view->setDragMode(QGraphicsView::RubberBandDrag); ``` 这样用户在视图上拖拽时,会出现一个矩形框,松开鼠标后,矩形框内的图元会被选中。 5. 拖动操作: 当图元设置了ItemIsMovable标志后,用户可以直接拖动图元。但是,如果图元位于图层组内,并且图层组没有设置setHandlesChildEvents(false),则拖动操作可能会被图层组捕获,导致整个图层组被移动。为了避免这种情况,应该确保图层组不处理子事件(如前所述)。 6. 事件传递顺序: 在图形视图框架中,事件传递顺序为:视图(QGraphicsView)-> 场景QGraphicsScene)-> 图元QGraphicsItem)。如果图层组作为图元的父项,事件会先传递给图元,如果图元不接受,则传递给图层组,再传递给场景。 7. 自定义事件处理: 如果需要对选择和拖动进行更精细的控制,可以重写图元的事件处理函数(如mousePressEvent, mouseMoveEvent, mouseReleaseEvent)。例如,我们可以限制图元移动范围,或者在移动时更新其他图元。 示例:重写图元鼠标事件 ```cpp class CustomItem : public QGraphicsRectItem { public: CustomItem(qreal x, qreal y, qreal w, qreal h) : QGraphicsRectItem(x, y, w, h) { setFlags(ItemIsSelectable | ItemIsMovable); } protected: void mousePressEvent(QGraphicsSceneMouseEvent *event) override { // 在鼠标按下时处理 QGraphicsRectItem::mousePressEvent(event); } void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override { // 限制移动范围 QPointF newPos = event->scenePos(); // 例如,限制在场景移动 if (newPos.x() < 0) newPos.setX(0); if (newPos.y() < 0) newPos.setY(0); setPos(newPos); } void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override { QGraphicsRectItem::mouseReleaseEvent(event); } }; ``` 8. 图层组内的图元选择和拖动注意事项: - 图层组本身也可以设置标志。如果图层组设置为可移动(ItemIsMovable),那么移动图层组会移动整个组内的图元。这可以用于整体移动一个图层。 - 如果希望同时支持单个图元移动和整个图层的移动,需要设计一个切换机制(例如,通过键盘修饰键)。 9. 参考引用[1]中提到的拖拽事件(drag and drop)与这里的鼠标拖动(move)是不同的概念。拖拽事件通常指从外部拖入文件或在应用程序内部进行拖拽操作。而这里的图元拖动是指用鼠标移动图元的位置。 总结: 为了实现图层内部图元的选择和拖动: 1. 为图元设置标志:ItemIsSelectable和ItemIsMovable。 2. 将图层组设置为不处理子事件(setHandlesChildEvents(false)),确保事件传递到图元。 3. 如果需要矩形框选,设置视图的拖拽模式为RubberBandDrag。 4. 如有特殊需求,重写图元鼠标事件。 注意:如果图层组需要整体移动,则不应设置setHandlesChildEvents(false),并且应该为图层组设置ItemIsMovable标志。此时,组内的单个图元不应该设置ItemIsMovable,否则它们会独立移动。因此,需要根据应用场景设计不同的交互模式。 §§相关问题§§ 1. 如何在QGraphicsView中同时支持单个图元移动和整个图层移动? 2. 如何限制图元在图层内的移动范围? 3. 如何实现图元移动时的实时对齐功能? </think>### 在 QGraphicsView 图层内实现图元选择和拖动的完整方案 在 QGraphicsView 图层系统中实现图元选择和拖动操作,需要结合图层管理和事件处理机制。以下是具体实现步骤: --- #### 一、图层内图元选择实现 1. **设置图元可选标志** 创建图元时启用选择标志: ```cpp // 创建矩形图元并设置为可选 QGraphicsRectItem* item = new QGraphicsRectItem(0, 0, 100, 50); item->setFlag(QGraphicsItem::ItemIsSelectable, true); // 关键设置[^1] item->setFlag(QGraphicsItem::ItemIsMovable, true); // 同时启用拖动 // 添加到图层 mainLayer->addToGroup(item); ``` 2. **批量选择模式配置** 在视图中启用框选模式: ```cpp // 设置视图为矩形选择模式 view->setDragMode(QGraphicsView::RubberBandDrag); ``` 3. **图层选择过滤** 重写事件处理函数避免选择到其他层: ```cpp void CustomScene::mousePressEvent(QGraphicsSceneMouseEvent* event) { if (event->button() == Qt::LeftButton) { // 只允许当前激活图层被选择 QPointF scenePos = event->scenePos(); QGraphicsItem* item = itemAt(scenePos, QTransform()); if (item && item->group() != currentActiveLayer) { event->ignore(); // 阻止选择非当前层图元 return; } } QGraphicsScene::mousePressEvent(event); // 传递给基类 } ``` --- #### 二、图层内图元拖动实现 1. **启用拖动标志** 图元需启用移动标志: ```cpp item->setFlag(QGraphicsItem::ItemIsMovable, true); // 关键设置[^1] ``` 2. **限制拖动范围** 通过重写 `mouseMoveEvent` 约束拖动区域: ```cpp void CustomItem::mouseMoveEvent(QGraphicsSceneMouseEvent* event) { // 获取当前图层边界 auto layerRect = parentGroup()->boundingRect(); // 计算新位置 QPointF newPos = mapToParent(event->pos() - event->buttonDownPos(Qt::LeftButton)); // 约束在图层范围内 if (layerRect.contains(newPos)) { setPos(newPos); // 移动图元 } } ``` 3. **图层同步移动** 移动整个图层时保持相对位置: ```cpp // 移动图层内所有图元 void moveLayer(QGraphicsItemGroup* layer, qreal dx, qreal dy) { foreach(QGraphicsItem* item, layer->childItems()) { item->moveBy(dx, dy); } } ``` --- #### 三、事件处理优化 1. **防止事件冲突** 禁用图层的事件处理: ```cpp // 图层只作为容器,不处理事件 layer->setHandlesChildEvents(false); // 关键设置[^1] ``` 2. **拖拽事件处理** 实现完整拖拽流程: ```cpp void CustomItem::dragEnterEvent(QGraphicsSceneDragDropEvent* event) { if (event->mimeData()->hasFormat("text/plain")) event->acceptProposedAction(); } void CustomItem::dropEvent(QGraphicsSceneDragDropEvent* event) { // 处理拖放数据 QString data = event->mimeData()->text(); qDebug() << "Dropped data:" << data; } ``` --- #### 四、完整工作流示例 ```cpp // 创建场景和图层 QGraphicsScene scene; QGraphicsItemGroup* drawingLayer = new QGraphicsItemGroup; scene.addItem(drawingLayer); // 添加可拖动矩形 QGraphicsRectItem* rect = new QGraphicsRectItem(0, 0, 80, 60); rect->setBrush(Qt::blue); rect->setFlags(QGraphicsItem::ItemIsSelectable | // 可选 QGraphicsItem::ItemIsMovable); // 可动 drawingLayer->addToGroup(rect); // 配置视图 QGraphicsView view(&scene); view.setDragMode(QGraphicsView::RubberBandDrag); // 框选模式 ``` --- ### 关键注意事项 1. **性能优化** - 对静态图层设置 `ItemIgnoresTransformations` 避免重绘 - 批量操作时使用 `scene->blockSignals(true)` 抑制信号 2. **坐标转换** 图层内坐标转换公式: $$ \text{图元局部坐标} = \text{图层} \cdot \text{mapFromScene}(\text{场景坐标}) $$ 3. **图层锁定** 禁用交互时设置: ```cpp layer->setEnabled(false); // 禁用整个图层交互 ``` --- ### 相关问题 1. 如何实现图元在图层间的移动和复制? 2. 怎样处理图层重叠时的图元选择优先级? 3. 如何保存/加载分层的 QGraphicsScene 场景数据? 4. 在有多图层时如何优化渲染性能?
评论 7
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wb175208

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值