QGraphicsView图形视图框架使用(三)位移变换和图元定位

文章目录

    • 位移变换
    • 图片渲染
    • 图元定位
    • 场景聚焦

当视图框架中的图元比较多且位置比较散乱的时候,为了操作某个特定的图元,我们需要对图元进行位移变换和定位,而从更加方便的操作管理图元。这里就介绍一下图元的位移变换和定位。

位移变换

在图形视图框架中我们可以通过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();
}

显示效果如下:
QGraphicsView图形视图框架使用(三)位移变换和图元定位_第1张图片
绘制位置保持不变的文字
这里以一个两端是圆形的哑铃状图元为例进行说明,在默认状态下所有视图中的图元都会受位移变换的影响,对应的示例如下:

#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();
}

显示效果如下:
QGraphicsView图形视图框架使用(三)位移变换和图元定位_第2张图片
为了查看方便,我们并不希望图形中的文字跟随着视图的位移变化发生变化,我们可以通过下面的配置让图形中的文字保持不变。

text->setFlag(QGraphicsItem::ItemIgnoresTransformations);

显示效果如下:
QGraphicsView图形视图框架使用(三)位移变换和图元定位_第3张图片
虽然图形中的文字没有发生位移变化,但也出现了一个非常诡异的问题。那就是文字不再显示在图形中央了。这是因为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);

显示效果如下:
QGraphicsView图形视图框架使用(三)位移变换和图元定位_第4张图片
解决方案二
方案二是通过添加一个辅助的矩形图元来进行定位的。矩形图元和文本图元的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;
}

显示效果如下:
QGraphicsView图形视图框架使用(三)位移变换和图元定位_第5张图片
辅助矩形图元,在实际使用的时候是不需要显示的,我们可以过下面的配置隐藏矩形外边框的显示。

//隐藏矩形边框
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聚焦图元的效果如下所示:
QGraphicsView图形视图框架使用(三)位移变换和图元定位_第6张图片
使用ensureVisible()聚焦图元的效果如下所示:
QGraphicsView图形视图框架使用(三)位移变换和图元定位_第7张图片
fitInView()有四种缩放策略,分别如下:

模式 说明
Qt::IgnoreAspectRatio 自由缩放,不受长宽比限制,尽可能的填充View
Qt::KeepAspectRatio 在保持长宽比的情况下,在View范围内缩放,使View中显示的范围尽可能的大。
Qt::KeepAspectRatioByExpanding 在保持长宽比的情况下,通过缩放让处于View范围之外的面积尽可能的小。

官方文档中各种缩放模式的示例图如下所示:
QGraphicsView图形视图框架使用(三)位移变换和图元定位_第8张图片
在例子中对应的效果如下所示:
QGraphicsView图形视图框架使用(三)位移变换和图元定位_第9张图片

你可能感兴趣的:(QT,qt,开发语言,图形视图框架,UI,2D游戏)