- 位移变换
- 图片渲染
- 图元定位
- 场景聚焦
当视图框架中的图元比较多且位置比较散乱的时候,为了操作某个特定的图元,我们需要对图元进行位移变换和定位,而从更加方便的操作管理图元。这里就介绍一下图元的位移变换和定位。 位移变换
在图形视图框架中我们可以通过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的图元的时候,需要提供该矩阵。 QListQGraphicsScene::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 (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范围之外的面积尽可能的小。 |
官方文档中各种缩放模式的示例图如下所示:
在例子中对应的效果如下所示:



