QT 图形视图框架(1)

       图形视图框架提供了一个基于图形项的模型视图编程方法,主要由场景(QGraphicsScene)、视图(QGraphicsView)和项(QGraphicsItem)三部分组成。多个视图可以查看一个场景,场景中包含各种各样几何形状的图形项。

场景

QGraphicsScene提供了图形视图框架中的场景,场景拥有以下功能:

  • 提供用于管理大量图形项的高速接口
  • 传播事件到每一个图形项
  • 管理图形项的状态,比如选择和处理焦点
  • 提供无变换的渲染功能,主要用于打印

       QGraphicsScene的事件传播构架可以将场景事件传递给图形项,也可以管理图形项之间事件的传递。例如,如果场景在一个特定的点接收到了一个鼠标按下事件,那么场景就会把这个事件传递给该点的图形项。

       一个场景分为3层:图形项层(ItemLayer)、前景层(ForegroundLayer)、背景层(BackgroundLayer)。场景的绘制总是从背景层开始,然后是图形项层,最后是前景层。前景层和背景层都可以使用QBrush进行填充,比如使用渐变和贴图等。

视图

       QGraphicsView提供了视图部件,它用来使场景中的内容可视化。可以连接多个视图到同一个场景来为相同的数据集提供多个视口。视图部件是一个可滚动的区域,它提供了一个滚动条来浏览大的场景。默认的QGraphicsView提供了一个QWidget作为视口部件,如果要使用OpenGL进行渲染,则可调用QGraphicsView::setViewport()设置QOpenGLWidget作为视口。QGraphicsView会获取视口部件的所有权。

       视图从键盘或者鼠标接收输入事件,然后会在发送这些事件到可视化场景前将他们转换为场景事件(将坐标转换为合适的场景坐标)。另外,使用视图的变换矩阵函数QGraphicsView::transform()时,可以通过视图来变换场景的坐标系统,这样便可以实现缩放和旋转等高级的导航功能。

视图的坐标系统有以下几种:

  • 项坐标
  • 场景坐标
  • 视图坐标
  • 坐标映射

项(图元)

       QGraphicsItem是场景中图形项的基类。典型的形状的标准图形项有矩形(QGraphicsRectItem)、椭圆(QGraphicsEllipseItem)和文本项(QGraphicsTextItem)等。但只有编写自定义的图形项才能发挥QGraphicsItem的强大功能。

QGraphicsItem主要支持如下功能:

  • 鼠标按下(mousePressEvent)、移动(mouseMoveEvent)、释放(mouseReleaseEvent)、双击(mouseDoubleClickEvent)、悬停(hoverMoveEvent)、滚轮(wheelEvent)和右键菜单(contextMenuEvent)事件
  • 键盘输入焦点和键盘事件
  • 拖放事件
  • 碰撞检测(shape()

       除此之外,图形项还可以存储自定义的数据,可以使用setData()进行数据存储,然后使用data()获取其中的数据。

       要实现自定义的图形项,那么首先要创建一个QGraphicsItem的子类,然后重新实现它的两个纯虚公共函数:boundingRect()paint(),前者用来返回要绘制图形项的矩形区域,后者用来执行实际的绘图操作。其中boundingRect()函数将图形项的外部边界定义为一个矩形,所有的绘图操作都必须限制在图形项的边界矩形中。这个矩形对于剔除不可见图形项、确定绘制交叉项目时哪些区域需要重新构建、碰撞检测机制都很重要。一定要保证所有绘图都在boundingRect()的边界之中,特别是当QPainter使用了指定的QPen来渲染图形的边界轮廓时,绘制的图形的边界线的一般会在外面,一半会在里面(例如使用了宽度为两个单位的画笔,就必须在boundingRect()里绘制一个单位的边界线),这也是在boundingRect()中要包含半个画笔宽度的原因。
       注意:如果boundingRect()设置的不合适,会导致缩放时候在一定比例下出现无法显示的情况。

示例

项有关部分

头文件部分

#ifndef OBJECTITEM_H
#define OBJECTITEM_H

#include <qmath.h>
#include <QObject>
#include <QGraphicsItem>
#include <QStyle>
#include <QPainter>
#include <QMenu>
#include <QAction>
#include <QMouseEvent>

#include "objecttype.h"

class ObjectItem : public QObject, public QGraphicsItem
{
    Q_OBJECT
    Q_INTERFACES(QGraphicsItem)
public:
    enum{ Type = UserType + 1 };        // 注册类型,用来通过类型分辨是什么项
    ObjectItem(QObject *parent = nullptr);

    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
    void contextMenuEvent(QGraphicsSceneContextMenuEvent *event) override;
    void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
    void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;
    void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
    void wheelEvent(QGraphicsSceneWheelEvent *event) override;
    void hoverMoveEvent(QGraphicsSceneHoverEvent *event);

    void delItem();

    int type() const override
    {
        return Type;
    }

    QRectF boundingRect() const override;
    QPainterPath shape() const Q_DECL_OVERRIDE;

private:
    QPixmap m_pixMap;
    QPoint m_qPointfOrg;

    qreal m_qrealScaleValue; //缩放值
    double m_dDirection; //弧度制角度
    bool m_bRotating;
    int m_iSize;

    double m_dPixMapScale;
    
    void setPixMap();
    bool isInRotateArea(const QPointF &pos);
    qreal GetDegreeAngle(QVector2D vector2d) const;
    
signals:
    void sigdelTriggered();
    
public slots:
    void on_deleteItem(bool bFlag);
};

#endif // OBJECTITEM_H

.cpp部分

#include <QDebug>

#include "objectitem.h"

#define TRAFFICCONE_PIXMAP ":/new/res/mainwindow/trafficcone_verticalview.png"
#define PEDESTRIAN_PIXMAP ":/new/res/mainwindow/trafficsign_verticalview.png"
#define ZEBRACROSSING_PIXMAP ":/new/res/mainwindow/zebracrossing@2x.png"
#define STR_DELETE "删除"

const double dROAD_WIDTH = 7.0;
const double dEACH_WHEEL_DISTANCE = 600.0;
const double dROAD_FIFTY_PART = 50.0;
const double dROAD_TWENTY_PART = 20.0;
const double dROAD_TEN_PART = 10.0;
const double dDEFAULT_MIN = 0.1;
const double dDEFAULT_MAX = 1;
const double dZOOM_IN_BASE = 1.1;
const double dZOOM_OUT_BASE = 1 / 1.1;
const double dPIXMAP_SCALE = 1 / 2.0;
const double dBOUNDING_BOX_SIZE = 20;


ObjectItem::ObjectItem(QObject *parent)
    : QObject(parent)
{
    setFlag(QGraphicsItem::ItemIsMovable, true);      // 设置可移动
    setFlag(QGraphicsItem::ItemIsSelectable, true);   // 设置可选中
    setFlag(QGraphicsItem::ItemIsFocusable, true);
    setAcceptHoverEvents(true);
    setAcceptTouchEvents(true);
    
    setPixMap();
}

// 重绘事件
void ObjectItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
    painter->setRenderHint(QPainter::Antialiasing);
    painter->setRenderHint(QPainter::SmoothPixmapTransform);

    if (option->state & QStyle::State_Selected)
    {
        painter->setRenderHint(QPainter::Antialiasing, true);
        painter->setPen(QPen(Qt::red,0.1));
        painter->drawRect(QRectF(-m_dWidth/2, -m_dHeight/2, m_dWidth, m_dHeight));
        // rotate control point
        painter->setPen(QPen(Qt::blue, 0.1));
        painter->drawLine(0, 0, m_iSize / 2, 0);
        painter->setPen(QPen(Qt::blue, 1));
        painter->drawPoint(m_iSize / 2, 0);
    }

    painter->scale(4, 4);
    painter->drawPixmap(-m_pixMap.width() / 2, -m_pixMap.height() / 2, m_pixMap.width(), m_pixMap.height(), m_pixMap);
}

// 设置右键菜单
void ObjectItem::contextMenuEvent(QGraphicsSceneContextMenuEvent *event)
{
    QMenu menu;

    QAction *pActionDel=new QAction(STR_DELETE);
    menu.addAction(pActionDel);

    connect(pActionDel, SIGNAL(triggered(bool)), this, SLOT(on_deleteItem(bool)));
    menu.exec(event->screenPos());
}

// 按下事件
void ObjectItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
    if (event->button() == Qt::LeftButton)
    {
        m_qPointfOrg.setX(event->scenePos().x() - this->x());
        m_qPointfOrg.setY(event->scenePos().y() - this->y());
    }

    QGraphicsItem::mousePressEvent(event);
}

// 移动事件(包含两种:旋转和移动)
void ObjectItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
    if (m_bRotating)
    {
        // 旋转
        QPointF cursorPos = event->pos();
        QVector2D vectorStart = QVector2D(QPointF(m_iSize / 2, 0.0) - QPointF(0.0, 0.0));           // 起始向量
        QVector2D vectorEnd = QVector2D(cursorPos - QPointF(0.0, 0.0));                             // 结束向量

        // 计算起始向量和结束向量之间的角度
        qreal angle = 0.0;
        qreal angleEnd = GetDegreeAngle(vectorEnd);
        qreal angleStart = GetDegreeAngle(vectorStart);
        angle = angleEnd - angleStart + rotation();

        while(angle > 360.0)
        {
            angle -= 360.0;
        }

        while(angle < 0.0)
        {
            angle += 360.0;
        }

        setRotation(angle);
        m_dDirection = 2 * M_PI - angle / AnglePerPI;
    }
    else
    {
        // 移动
        this->setPos(event->scenePos().x() - m_qPointfOrg.x(), event->scenePos().y() - m_qPointfOrg.y());
        QGraphicsItem::mouseMoveEvent(event);
    }
}

// 悬停事件
void ObjectItem::hoverMoveEvent(QGraphicsSceneHoverEvent *event)
{
    if ((isInRotateArea(event->pos()) && isSelected()))
    {
        setCursor(Qt::SizeVerCursor);
        m_bRotating = true;
    }
    else
    {
        setCursor(Qt::ArrowCursor);
        m_bRotating = false;
    }

    QGraphicsItem::hoverMoveEvent(event);
}

// 鼠标释放事件
void ObjectItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
    if (event->button() == Qt::LeftButton && m_bRotating)
    {
        m_bRotating = false;
    }
    else
    {
        this->setPos(event->scenePos().x() - m_qPointfOrg.x(), event->scenePos().y() - m_qPointfOrg.y());
    }

    QGraphicsItem::mouseReleaseEvent(event);
}

// 滚轮事件,主要是放大和缩小
void ObjectItem::wheelEvent(QGraphicsSceneWheelEvent *event)
{
    if (QStyle::State_Selected)
    {
        int iScaleValue = m_qrealScaleValue;
        if(event->delta() > 0) //delta()为正,滚轮向上滚
        {
            iScaleValue++;
        }
        else
        {
            iScaleValue--;
        }

        if(iScaleValue > ZOOM_IN_TIMES || iScaleValue < ZOOM_OUT_TIMES)
        {
            return;
        }

        m_qrealScaleValue = iScaleValue;

        qreal scale;
        if(m_qrealScaleValue > 0)
        {
            // 向上滚
            scale = pow(dZOOM_IN_BASE, m_qrealScaleValue); //放大 计算x的y方次 参数都是double类型
        }
        else
        {
            // 向下滚
            scale = pow(dZOOM_OUT_BASE, -m_qrealScaleValue); //缩小
        }

        setScale(scale);
        setTransformOriginPoint(event->pos().x(), event->pos().y());
    }

    QGraphicsItem::wheelEvent(event);
}

void ObjectItem::setPixMap()
{
    // 初始化项(加载图片,设置方向等)
    m_pixMap.load(TRAFFICCONE_PIXMAP);
    m_dWidth = m_pixMap.width() * dOBSTACLE_SCALE;
    m_dHeight = m_pixMap.height() * dOBSTACLE_SCALE;

    m_dPixMapScale = m_dWidth / m_pixMap.width();

    m_object.width = m_dWidth;
    m_object.length = m_dHeight;
    m_qrealScaleValue = 0.0;
    m_dDirection = 0.0 ;
    m_bRotating = false;
    m_iSize = dBOUNDING_BOX_SIZE;
    setRotation(m_dDirection);
}

void ObjectItem::on_deleteItem(bool /*bFlag*/)
{
    emit sigdelTriggered();
}

QRectF ObjectItem::boundingRect() const
{
    return QRectF(-m_iSize / 2 - 1, -m_iSize / 2, m_iSize + 2, m_iSize);
}

QPainterPath ObjectItem::shape() const
{
    QPainterPath path;
    if(this->isSelected())
    {
        path.addRect(-m_dWidth / 2, -m_dHeight / 2, m_dWidth, m_dHeight);
        path.addEllipse(QPoint(m_iSize / 2, 0), 0.5, 0.5);
    }
    else
    {
        path.addRect(-m_dWidth / 2, -m_dHeight / 2, m_dWidth, m_dHeight);
    }

    return path;
}

bool ObjectItem::isInRotateArea(const QPointF &pos)
{
    if (
       m_iSize / 2 -0.5 <= pos.x()  &&
       pos.x() <= m_iSize / 2 + 0.5 &&
       -0.5 <= pos.y()             &&
       pos.y() <= 0.5
       )
    {
        return true;
    }
    else
    {
        return false;
    }
}

qreal ObjectItem::GetDegreeAngle(QVector2D vector2d) const
{
    return fmod((atan2((qreal)vector2d.y(), (qreal)vector2d.x()) * AnglePerPI + 360.0), 360.0 );
}

视图有关部分

QGraphicsView*pQWGraphicsView = new QGraphicsView();
m_pScene = new QGraphicsScene(-4, -4, 4, 4); 		// 创建QGraphicsScene
pQWGraphicsView->setScene(m_pScene); 				// 与view关联

// 场景滚动条部分设置样式
pQWGraphicsView ->verticalScrollBar()->setStyleSheet(STR_VERICAL_SCROLLBAR_STYLE);
pQWGraphicsView ->horizontalScrollBar()->setStyleSheet(STR_HORIZONTAL_SCROLLBAR_STYLE);

//暂时根据文件大小调整缩放度
pQWGraphicsView->resetTransform();
pQWGraphicsView->scale(4, 4); // 由于物体、交通灯等元素可能导致加载后有问题
pQWGraphicsView->centerOn(0, 0);

场景有关部分

m_pScene->addItem(m_pItem);  	 // 添加项
m_pScene->clear();  			 // 清空项
m_pScene->removeItem(m_pItem);   // 移除项

QGraphicsItem *item = m_pScene->itemAt(pointf, pQWGraphicsView->transform()); 	// 获取光标下的绘图项
QList<QGraphicsItem*> qlstpItem = m_pScene->selectedItems();  					// 获取选中的项
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值