在图形视图框架中我们可以通过QTransform和setTransform(const QTransform &matrix, bool combine = false),对来图元进行位移变换,包括移动、旋转、缩放等等。有的时候,我们在变换一些图形的时候,希望视图中一部分保持不变。这时候,我们就需要让某些图元忽略整个视图的位移变换。下面介绍一下忽略位移变化的实现方式:
绘制固定线宽的直线
我们可以通过 QPen::setCosmetic(true)开启固定线宽功能,这样绘制出来的直线在位移变化的过程中就可以保持固定宽度了。参考示例如下:
#pragma execution_character_set("utf-8")
#include
#include
#include
#include
#include
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QGraphicsScene scene;
scene.addLine(-50, 0, 50, 0);
//让绘制的线保持固定宽度
QGraphicsLineItem* row_item = scene.addLine(0, -50, 0, 50);
QPen pen;
pen.setCosmetic(true);
row_item->setPen(pen);
QGraphicsView view(&scene);
view.setRenderHint(QPainter::Antialiasing);
view.scale(4,4);
view.show();
return a.exec();
}
显示效果如下:
绘制位置保持不变的文字
这里以一个两端是圆形的哑铃状图元为例进行说明,在默认状态下所有视图中的图元都会受位移变换的影响,对应的示例如下:
#pragma execution_character_set("utf-8")
#include
#include
#include
#include
#include
QGraphicsLineItem *createComplexItem(qreal width, qreal radius,bool is_fixed)
{
QLine main_line(QPoint(-width/2,20),QPoint(width/2,20));
//绘制直线
QGraphicsLineItem *parent = new QGraphicsLineItem(main_line);
QPen pen = parent->pen();
pen.setCosmetic(true);
parent->setPen(pen);
QRectF circleBoundary(-radius, -radius, 2 * radius, 2 * radius);
for(int index=1;index<=2; ++index)
{
QGraphicsEllipseItem * child = new QGraphicsEllipseItem(circleBoundary, parent);
child->setBrush(QBrush(Qt::black));
if(index == 1)
{
child->setPos(main_line.p1());
}
else
{
child->setPos(main_line.p2());
}
QGraphicsSimpleTextItem *text = new QGraphicsSimpleTextItem(QString::number(index));
text->setParentItem(child);
text->setBrush(Qt::green);
text->setPos(-text->boundingRect().width() / 2, -text->boundingRect().height() / 2);
}
return parent;
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
//绘制原始图元
QGraphicsScene scene;
QGraphicsLineItem *item1 = createComplexItem(100,10,false);
scene.addItem(item1);
//绘制旋转之后的图元
QGraphicsLineItem *item2 = createComplexItem(100,10,true);
scene.addItem(item2);
item2->setPos(150, 0);
item2->setRotation(20);
QGraphicsView view(&scene);
view.show();
view.setRenderHint(QPainter::Antialiasing);
//对整个视图进行缩放
view.scale(2, 2);
return a.exec();
}
显示效果如下:
为了查看方便,我们并不希望图形中的文字跟随着视图的位移变化发生变化,我们可以通过下面的配置让图形中的文字保持不变。
text->setFlag(QGraphicsItem::ItemIgnoresTransformations);
显示效果如下:
虽然图形中的文字没有发生位移变化,但也出现了一个非常诡异的问题。那就是文字不再显示在图形中央了。这是因为setPos()使用的是父对象的坐标系,而boundingRect()使用的是当前对象的坐标系,两个坐标系本来是重合的,但是设置了对应属性并发生位移变化之后,两个坐标系不再重合才引起了该问题。
解决方案一
ItemIgnoresTransformations不会影响Item自身的位移变化。我们可以通过下面的位移变化方式来移动图元,也就是用setTransform()替代setPos()。setTransform()依据自身的坐标系作为基准进行移动,不会受父对象的坐标系变化影响。
QTransform transform;
transform.translate(-text->boundingRect().width() / 2,-text->boundingRect().height() / 2);
text->setTransform(transform);
text->setFlag(QGraphicsItem::ItemIgnoresTransformations);
显示效果如下:
解决方案二
方案二是通过添加一个辅助的矩形图元来进行定位的。矩形图元和文本图元的boundingRect()保持一致。然后对应的文本内容只要显示在辅助定位的图元左上角就可以了。其实方案二和方案一的本质相同,都是不再直接使用父对象图元的坐标系,改用自身坐标系了。
QGraphicsLineItem *createComplexItem(qreal width, qreal radius,bool is_fixed)
{
QLine main_line(QPoint(-width/2,20),QPoint(width/2,20));
//绘制直线
QGraphicsLineItem *parent = new QGraphicsLineItem(main_line);
QPen pen = parent->pen();
pen.setCosmetic(true);
parent->setPen(pen);
//绘制两头的圆形
QRectF circleBoundary(-radius, -radius, 2 * radius, 2 * radius);
for(int index=1;index<=2; ++index)
{
QGraphicsEllipseItem * child = new QGraphicsEllipseItem(circleBoundary, parent);
child->setBrush(QBrush(Qt::black));
if(index == 1)
{
child->setPos(main_line.p1());
}
else
{
child->setPos(main_line.p2());
}
//添加一个矩形图元辅助定位
QGraphicsSimpleTextItem *text = new QGraphicsSimpleTextItem(QString::number(index));
QRectF textRect = text->boundingRect();
textRect.translate(-textRect.center());
QGraphicsRectItem *rectItem = new QGraphicsRectItem(textRect, child);
rectItem->setPen(QPen(Qt::green));
rectItem->setFlag(QGraphicsItem::ItemIgnoresTransformations);
text->setParentItem(rectItem);
text->setPos(textRect.topLeft());
text->setBrush(Qt::green);
}
return parent;
}
显示效果如下:
辅助矩形图元,在实际使用的时候是不需要显示的,我们可以过下面的配置隐藏矩形外边框的显示。
//隐藏矩形边框
rectItem->setPen(Qt::NoPen);
在开发一些类似于2D游戏的应用的时候,我们有时候需要将整个Scene场景内容进行图片保存。QGraphicsView实现图片保存还是比较简单的,只需要将真个场景的内容渲染到图片容器中即可,对应的实现如下:
QGraphicsView view(&scene);
view.show();
QRect rect = scene.sceneRect().toAlignedRect();
QImage image(rect.size(), QImage::Format_ARGB32);
//背景设置为透明
image.fill(Qt::transparent);
//将渲染的绘图保存到本地
QPainter painter(&image);
scene.render(&painter);
image.save("picture.png");
在QGraphicsView应用中,我们有时候需要通过图元的坐标来对图元进行定位。图元视图中也提供了对应的接口,如果我们只想要最顶层的图元可以调用itemAt()接口,对应的实现如下:
//如果存在返回对象,没有的话返回空
//@1坐标点
//@2View位移变化矩阵,如果图形中包含忽略transform的图元的时候,需要提供该矩阵。
QGraphicsItem *QGraphicsScene::itemAt(const QPointF &position,
const QTransform &deviceTransform) const
很多时候视图框架中位于某个坐标点的图元,可能有很多,如果我们需要所有图元的话,那么就得用到items()接口了,QGraphicsScene中存在很多同名的重载函数,可以根据自己的需要选择使用,对应的接口定义如下:
//return: 返回图元对象的列表
//@1:坐标点
//@2:选择模式
//@3:排序方式 Qt::DescendingOrder降序排列 最上层的图元显示在列表的首位
//Qt::AscendingOrder增序排列,排列方式相反,最上层的图元显示在列表的末尾
//@4View位移变化矩阵,如果图形中包含忽略transform的图元的时候,需要提供该矩阵。
QList<QGraphicsItem *> QGraphicsScene::items(const QPointF &pos,
Qt::ItemSelectionMode mode = Qt::IntersectsItemShape,
Qt::SortOrder order = Qt::DescendingOrder,
const QTransform &deviceTransform = QTransform()) const
items()参数2的选择模式有四种,如下表所示:
模式 | 说明 |
---|---|
Qt::ContainsItemBoundingRect | 被选择的图元的外边界矩形,必须完全在选择范围内,才会被选中 |
Qt::IntersectsItemBoundingRect | 与选择范围有交集,但并不完全包含的图元,也会被选中。 |
Qt::ContainsItemShape | 被选择的图元的外边界矩形,必须完全在选择范围内,才会被选中。此种模式下,边界计算更加精确,但是也更耗费计算能力。 |
Qt::IntersectsItemShape | 与Qt::IntersectsItemBoundingRect类似,但是边界计算精度更高。 |
在一些特殊的场景下,我们需要显示Scene的一部分区域,或者某个特定的图元,这时候视图View的焦点就聚焦到了Scene的一部分。我们可以通过控制滚动条来实现对应的功能,但是控制精度有限。图形视图框架提供了对应的接口,通过对应的接口我们可以很轻松的实现场景聚焦。对应的接口如下。
centerOn()可以通过滚动View中场景,使某个坐标或者某个Item显示在视图中央。如果某个Item离边界特别近,或者在边界之外,centerOn()只会让该元素可见,并不会将其移动到视图中央。
//聚焦到某个坐标点
QGraphicsView::centerOn(const QPointF &pos)
//聚焦到某个Item
QGraphicsView::centerOn(const QGraphicsItem *item)
//注意:点坐标是float类型,而scrollbar步长是int类型的,所以center是一个近似值
ensureVisible()功能和centerOn()功能类似,通过滚动View中的内容使某个Rect或者Item可见,同时还可以指定该范围与边界的最小margin。如果聚焦的范围超出了边界,那么View视图会定位到距离这个范围最近的位置。
//让某个矩形范围可见
QGraphicsView::ensureVisible(const QRectF &rect, int xmargin = 50, int ymargin = 50)
//让某个Item可见
QGraphicsView::ensureVisible(const QGraphicsItem *item, int xmargin = 50, int ymargin = 50)
fitInView()功能是通过对视图View进行缩放和移动,让某个固定范围或者Item,完全显示在View当中,使用fitInView()的时候要确保指定的范围必须包含在Scene中。fitInView()无法保证指定的范围完整的显示在View中。
通常在实现resizeEvent()的时候调用fitInView()来确保视图View尺寸变化的时候,对应的场景内容能自动的填充满对应的的范围。但有一点需要注意,如果在位移变换的过程中启动了滚动条的自动状态,在ResizeEvent()调用fitInView(),会出现递归调用,从而引发崩溃。可以将滚动条状态设置为Always On 或者 Always Off来避免该问题。
//聚焦某个范围
QGraphicsView::fitInView(const QRectF &rect, Qt::AspectRatioMode aspectRatioMode = Qt::IgnoreAspectRatio)
//聚焦某个控件
QGraphicsView::fitInView(const QGraphicsItem *item, Qt::AspectRatioMode aspectRatioMode = Qt::IgnoreAspectRatio)
下面以一个例子来说明一下几种场景聚焦的接口的用法,例子中通过按键事件来聚焦不同的图元:
//graphicsview.h
#ifndef GRAPHICSVIEW_H
#define GRAPHICSVIEW_H
#include
#include
namespace Ui {
class GraphicsView;
}
class GraphicsView : public QWidget
{
Q_OBJECT
public:
explicit GraphicsView(QWidget *parent = 0);
~GraphicsView();
public:
bool eventFilter(QObject* watched, QEvent* event) override;
private:
//场景
QGraphicsScene m_scene;
//矩形图元
QGraphicsRectItem* m_rect_item;
//图片图元
QGraphicsPixmapItem* m_pixmap_item;
//椭圆图元
QGraphicsEllipseItem* m_ellipse_item;
Ui::GraphicsView *ui;
};
#endif // GRAPHICSVIEW_H
//graphicsview.cpp
#include "graphicsview.h"
#include "ui_graphicsview.h"
#include
#include
#include
GraphicsView::GraphicsView(QWidget *parent) :
QWidget(parent),
ui(new Ui::GraphicsView)
{
ui->setupUi(this);
this->setWindowTitle("GraphicsView");
//在Scene中心添加一个十字坐标
m_scene.addLine(-1000, 0, 1000, 0);
m_scene.addLine(0, -1000, 0, 1000);
//添加矩形图元
m_rect_item = m_scene.addRect(-50,-50,150,150);
m_rect_item->setBrush(QBrush(QColor("#4D9CF8")));
m_rect_item->setFlag(QGraphicsItem::ItemIsSelectable,true);
m_rect_item->setFlag(QGraphicsItem::ItemIsMovable,true);
//添加椭圆图元
m_ellipse_item = m_scene.addEllipse(QRectF(QPointF(150,100),QPointF(250,50)),QPen("#0000FF"),QBrush(QColor("#4D00F8")));
m_ellipse_item->setZValue(10);
m_ellipse_item->setFlag(QGraphicsItem::ItemIsSelectable,true);
m_ellipse_item->setFlag(QGraphicsItem::ItemIsMovable,true);
//绘制图片
m_pixmap_item = m_scene.addPixmap(QPixmap(":/demo.png"));
m_pixmap_item->setPos(-300,-50);
m_pixmap_item->setZValue(20);
ui->graphicsView->setScene(&m_scene);
m_ellipse_item->setFlag(QGraphicsItem::ItemIsSelectable,true);
m_ellipse_item->setFlag(QGraphicsItem::ItemIsMovable,true);
//抗锯齿效果
ui->graphicsView->setRenderHint(QPainter::Antialiasing);
installEventFilter(this);
//添加fillMode
ui->fit_mode_combo->addItems(QStringList() << "IgnoreAspectRation" << "KeepAspectRatio" << "KeepAspectRationByExpanding");
ui->fit_mode_combo->setItemData(0,Qt::IgnoreAspectRatio);
ui->fit_mode_combo->setItemData(1,Qt::KeepAspectRatio);
ui->fit_mode_combo->setItemData(2,Qt::KeepAspectRatioByExpanding);
ui->fit_mode_combo->setFocusPolicy(Qt:: NoFocus);
}
GraphicsView::~GraphicsView()
{
delete ui;
}
bool GraphicsView::eventFilter(QObject *watched, QEvent *event)
{
if (event->type() == QEvent::KeyPress)
{
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
Qt::KeyboardModifiers modifier = keyEvent->modifiers();
int change_mode = ui->fit_mode_combo->currentData().toInt();
qDebug() << change_mode;
//center()聚焦图元
if(keyEvent->key() == Qt::Key_R && modifier == Qt::NoModifier)
{
ui->graphicsView->centerOn(m_rect_item);
}
else if(keyEvent->key() == Qt::Key_P && modifier == Qt::NoModifier)
{
ui->graphicsView->centerOn(m_pixmap_item);
}
else if(keyEvent->key() == Qt::Key_E && modifier == Qt::NoModifier)
{
ui->graphicsView->centerOn(m_ellipse_item);
}
//ensureVisible()聚焦图元
else if(keyEvent->key() == Qt::Key_R && modifier == Qt::ControlModifier)
{
ui->graphicsView->ensureVisible(m_rect_item,50,50);
}
else if(keyEvent->key() == Qt::Key_P && modifier == Qt::ControlModifier)
{
ui->graphicsView->ensureVisible(m_pixmap_item,100,100);
}
else if(keyEvent->key() == Qt::Key_E && modifier == Qt::ControlModifier)
{
ui->graphicsView->ensureVisible(m_ellipse_item,20,20);
}
//fitInView()聚焦图元
else if(keyEvent->key() == Qt::Key_S && modifier == Qt::NoModifier)
{
ui->graphicsView->fitInView(m_ellipse_item,Qt::AspectRatioMode(change_mode));
}
}
return QObject::eventFilter(watched, event);
}
使用CenterOn聚焦图元的效果如下所示:
使用ensureVisible()聚焦图元的效果如下所示:
fitInView()有四种缩放策略,分别如下:
模式 | 说明 |
---|---|
Qt::IgnoreAspectRatio | 自由缩放,不受长宽比限制,尽可能的填充View |
Qt::KeepAspectRatio | 在保持长宽比的情况下,在View范围内缩放,使View中显示的范围尽可能的大。 |
Qt::KeepAspectRatioByExpanding | 在保持长宽比的情况下,通过缩放让处于View范围之外的面积尽可能的小。 |