使用QGraphics实现支持地图放缩的场景

QGraphics族类的控件用来实现地图放缩的场景确实很方便,但是初次接触,对于某些结构设计还是有些难以理解。在此,将我成功实现的案例以及实现中途步入的误区记录下来。

1.思路
  1. 继承QGraphicsView实现窗口,并重写mousePressEvent、wheelEvent等函数,对场景的点击事件进行处理。
  2. 创建(QGraphicsPixmapItem*)m_map,承载地图。
  3. 创建vector容器,保存特定类型的Item指针,如std::vector<GuiCamera*> m_vecGuiCamera,std::vector<GuiLandMark*> m_vecGuiLandmark等。
  4. 在wheelEvent函数中,实现地图的放缩功能。
  5. 继承QGraphicsRectItem,QGraphicsEllipseItem,重写paint函数,去除选中时,难看的虚线边框。
  6. 实现单击空白处,取消选中当前item的功能。
2. 部分细节记录

      这一节主要记录我在实现过程中遇到的难点。

     1. 对于item.boundingRect() 和 item.pos() 的理解。boundingRect是指的他的item可接受外部事件的区域,它的坐标是相对于pos的,一般设置的大小应该是(0,0,width,height)。

     2. 通过父子控件,可实现多Item组合成一个整体的效果,即同步拖动,同步放缩。

     3. 接上一点,如果不设置父控件,只是通过 QGraphicsScene::addItem() 进行添加,是不能达到上述效果的。另外,放缩时,是需要在重写的wheelEvent 中,设置父控件的 setScale() 完成放缩。

  1. 继承 QGraphicsRectItem 重写paint函数,去除选中时,难看的虚线边框。
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) Q_DECL_OVERRIDE
{
	QStyleOptionGraphicsItem op;
	op.initFrom(widget);

	if (option->state & QStyle::State_Selected)
		op.state = QStyle::State_None;

	QGraphicsRectItem::paint(painter, &op, widget);
}
  1. 继承QGraphicsEllipseItem 重写paint函数。同上。
  2. 对 item 的属性(可拖动,可选中等)的设置。
	m_item->setToolTip(QStringLiteral("camera"));	// 提示
	m_item->setAcceptDrops(true);					// 接受拖拽事件
	m_item->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsFocusable
		| QGraphicsItem::ItemIsSelectable);			// 设置可移动,可聚焦,可选择
	m_item->setRect(0, 0, 2 * m_radius, 2 * m_radius);	// item是继承于QGraphicsEllipseItem,这里设置大小。(0,0)相对于自身的坐标。
	m_item->setEnabled(true);	// 注意:此属性默认为true。但是如果父控件设为false,子控件也会默认为false。
  1. 另外一些附属控件,可进行如下设置。(m_text 是 m_item 上面的文字标注,不接受事件)
	m_text = new QGraphicsTextItem(m_item);
	m_text->setEnabled(false);
	m_text->setAcceptedMouseButtons(Qt::NoButton);
	// m_lineCenter->setFlags(QGraphicsItem::ItemStacksBehindParent);	// 此属性可设置,子控件在父控件下面绘制,避免遮挡父控件
	
	// QGraphicsTextItem 类设置文本的居中比较麻烦,下面是我找到一个方法,不知道还有没有别的方法
	m_text->setPlainText(QString::number(m_camera->getConfig().nCameraID));
	m_text->setDefaultTextColor(QColor(0, 0, 0));
	QTextBlockFormat format;
	format.setAlignment(Qt::AlignCenter);
	QTextCursor cursor = m_text->textCursor();
	cursor.select(QTextCursor::Document);
	cursor.mergeBlockFormat(format);
	cursor.clearSelection();
	m_text->setTextCursor(cursor);
  1. 放缩部分。参见第三点,想要在放缩地图时,保持 items 与地图的相对位置不变,需要构建与 m_map 同等大小同等位置的一个透明控件,作为缩放的母体。甚至可以直接以 m_map 作为这些 items 的父控件(我就是 ,☺)。不过,其实 QT 提供了更加方便的方式,就是使用 QGraphicsItemGroup(我开始尝试用过,但是出现问题的时候,怀疑是它引起的,就让我删了,后来没再用)。不过,无论谁作为父控件,想要子项处理自己的事件,都要保证下条语句。
	m_map->setHandlesChildEvents(false); // 默认就是false,但是不设置的话,同样会受到他的父项的影响
void wheelEvent(QWheelEvent * event)
{
	int angle = event->angleDelta().y();

	if (angle > 0)
		zoomIn();
	else
		zoomOut();

	QGraphicsView::wheelEvent(event);
}

void zoomIn()
{
	if (m_scene->items().isEmpty())
		return;

	if (nullptr != m_map)
	{
		qreal scale = m_map->scale() + c_fWheelScaleRate;
		
		m_map->setScale(scale);
		m_scene->setSceneRect(m_map->sceneBoundingRect());
	}
}

void zoomOut()
{
	if (m_scene->items().isEmpty())
		return;

	if (nullptr != m_map)
	{
		qreal scale = m_map->scale() - c_fWheelScaleRate;

		if (scale < 1.0)				// 保证不小于原图
			scale += c_fWheelScaleRate;
	
		m_map->setScale(scale); 
		m_scene->setSceneRect(m_map->sceneBoundingRect());
	}
}
  1. 单击空白处,取消选择当前 item。
    focusItemChanged 信号,我们可以在 MyView (QGraphicsView 的继承类)中设置一个表示当前 item 的指针,然后在重写的 mousePressEvent 函数中,判断点击的点,是否在当前 item 的区域内。
void mousePressEvent(QMouseEvent *event)
{
	QGraphicsView::mousePressEvent(event); // 一定要在前面,

	// use scene coordinate
	QPointF p = mapToScene(event->pos());
	if (m_map != nullptr)
		p /= m_map->scale();			// 这里需要对放缩后的坐标,进行一个转换,QT并没有默认提供这个功能
		
	emit signalIsContainPoint(p);		// 这里不能直接判断,因为此时被选择的 item 还为完成更改,
										// 应该在这里先将实践压入 Qt 的槽处理栈内,
										// 在QGraphicsView::mousePressEvent 响应部分之后处理。
}

void slotIsContainPoint(QPointF p)
{
	// 我用到了两种不同类型的Item,可以直接代表不同item,从而使用不用的区域计算方法
	auto ellipse = dynamic_cast<QGraphicsEllipseItem*>(m_currentGuiWidget->getItem());
	auto rect = dynamic_cast<QGraphicsRectItem*>(m_currentGuiWidget->getItem());
	if (ellipse != nullptr)
	{
		QRectF rr(ellipse->pos(), QSizeF(2*c_nSizeCamera, 2*c_nSizeCamera));
		if (!rr.contains(point))
			setCurrentGui(nullptr);
	}
	else if (rect != nullptr)
	{
		QRectF rr(rect->pos(), QSizeF(rect->boundingRect().width() , rect->boundingRect().height()));
		if (!rr.contains(point))
			setCurrentGui(nullptr);
	}
}
3. 效果演示

在这里插入图片描述

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值