Qt - Graphics View(图形/视图)

  Qt提供了图形视图框架(Graphics View Framework)、动画框架(The AnimationFramework)和状态机框架(The State Machine Framework)来实现更加高级的图形和动画应用。使用这些框架可以快速设计出动态GUI应用程序和各种动画,游戏程序。

1、图形视图框架简介

  QT4.2开始引入了Graphics View框架用来取代QT3中的Canvas模块,并作出了改进,Graphics View框架实现了模型-视图结构的图形管理,能对大量图元进行管理,支持碰撞检测,坐标变换和图元组等多种方便的功能。

  GraphicsView框架结构主要包含三个主要的类QGraphicsScene(场景)、QGraphicsView(视图)、QGraphicsItem(图元)。QGraphicsScene本身不可见,是一个存储图元的容器,必须通过与之相连的QGraphicsView视图来显示及与外界进行交互,主要提供图元的操作接口、传递事件和管理各个图元状态,提供无变换的绘制功能(如打印);QGraphicsView提供一个可视的窗口,用于显示场景中的图元,一个场景中可以有多个视图。QGraphicsItem是场景中各个图元的基础类,QT提供了常用图形图元的标准类,如矩形(QGraphicsRectItem)、椭(QGraphicsEllipseItem)、文本(QGraphicsTextItem)。

  GraphicsView是一个基于图元的Model/View架构的框架,每一个组件都是一个独立的元素。QPainter采用面向过程的描述方式绘图;GraphicsView采用面向对象的描述方式绘图。GraphicsView绘图时首先创建一个场景,然后创建图元对象(如一个直线对象、一个多边形对象),再使用场景的add()函数,将图元对象添加到场景中,最后通过视图进行显示。对于复杂的图像来说,如果图像包含大量的直线、曲线、多边形等图元对象,管理图元对象比管理QPainter的绘制过程语句要容易,并且图元对象更符合面向对象的思想,图形的可复用性更好。

 

2、QGraphicsScene 场景

QGraphicsScene场景是QGraphicsItem对象的容器,主要功能如下:

  • 提供一个快速的接口,用于管理大量图元。

  • 向每个图元传递事件

  • 管理图元的状态,如:选中、焦点处理

  • 提供未进行坐标转换的渲染功能,主要用于打印

  通过函数QGraphicsScene::addItem()可以加入一个图元到场景中。图元可以通过多个函数进行检索:QGraphicsScene::items()及重载函数可以返回和点、矩形、多边形或向量路径相交的所有图元。QGraphicsScene::itemAt()返回指定点的最顶层图元。所有图元查找函数按照递减栈顺序返回图元,第一个返回的图元位置最顶层,最后一个返回的图元位于最底层。

  QGraphicsScene的事件传播体系将场景事件发送给图元,同时也管理图元之间的事件传播。如果场景收到了在某一点的鼠标单击事件,场景会把事件传给在这一点的最顶层图元。QGraphicsScene负责管理一些图元的状态,如图元选择和焦点。通过QGraphicsScene::setSeletionArea()函数选择多个图元,选择区域可以是任意的形状,使用 QPainterPath表示;要得到当前选择的图元列表可以使用 QGraphicsScene::selectedItems()函数;QGraphicsScene还管理图元的键盘输入焦点状态,可以通过QGraphicsScene::setFocusItem()函数或者QGraphicsItem::setFoucs()函数来设置图元的焦点;获得当前具有焦点的图元使用函数QGraphicsScene::foucsItem()。可以使用 QGraphicsScene::render()函数在绘图设备上绘制场景。

示例代码:

#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGraphicsRectItem>
#include <QDebug>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    //新建场景
    QGraphicsScene* secne = new QGraphicsScene;
    //创建图形项
    QGraphicsRectItem* rectItem = new QGraphicsRectItem(0,0,100,100);
    //将图形项添加到场景中
    secne->addItem(rectItem);
    //输出(50,50)点出的图形项
    qDebug()<<secne->itemAt(50,50,QTransform());

    return a.exec();
}

这里先创建了一个场景,然后创建了一个矩形图形项,并且将该图形项添加到了场景中。然后使用itemAt()函数返回指定坐标处最顶层的图形项,这里返回的就是刚才添加的矩形图形项。现在可以运行程序,不过因为还没有设置视图,所以不会出现任何图形界面。这时可以在应用程序输出栏中看到输出项目的信息,要关闭运行的程序,则可以按下应用程序输出栏上的红色按钮,然后强行关闭应用程序。

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

QGraphicsScene也用来管理图形项的状态,如图形项的选择和焦点等。可以通过向QGraphicsScene: :setSelectionArea()函数传递一个任意的形状来选择场景中指定的图形项。如果要获取当前选取的所有图形项的列表,则可以使用QGraphicsScene: :selectedItems()函数。另外可以调用QGraphicsScene: : setFocusItem()或者QGraphicsScene: : setFocus()函数来为一个图形项设置焦点,调用QGraphicsScene : : focus-Item()函数获取当前获得焦点的图形项。

QGraphicsScene也可以使用QGraphicsScene: : render()函数将场景中的一部分渲染到一个绘图设备上。这里讲到的这些函数会在后面的内容中看到它们的应用。

 

3、QGraphicsView 视图

  QGraphicsView是视图窗口部件,使场景内容可视化,可以连接多个视图到一个场景,也可以为相同数据源的数据集提供不同的视图。QGraphicsView是可滚动的窗口部件,可以提供滚动条来浏览大的场景。如果需要使用OpenGL,可以使用QGraphicsView::setViewport()将视图设置为QGLWidget组件。

  视图接收键盘和鼠标的输入事件,并把事件翻译为场景事件(将坐标转换为场景的坐标),再发送到场景。使用变换矩阵函数QGraphicsView::martix()可以变换场景的坐标系统,通过变换场景的坐标系统可以实现场景的缩放和旋转。为了方便,QGraphicsView提供了视图和场景的坐标转换函数:QGraphicsView::mapToScene()和QGraphicsView::mapFromScene()。

示例代码:

#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGraphicsRectItem>
#include <QDebug>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    //新建场景
    QGraphicsScene* secne = new QGraphicsScene;
    //创建图形项
    QGraphicsRectItem* rectItem = new QGraphicsRectItem(0,0,100,100);
    //将图形项添加到场景中
    secne->addItem(rectItem);

    //创建视图
    QGraphicsView view(secne);
    //view.setScene(secne);    //也可以这样设置场景
    //设置场景的前景色
    view.setForegroundBrush(QColor(255,255,0,100));
    //设置场景的背景色
    view.setBackgroundBrush(QPixmap("://images/background.png"));
    //设置场景与视图对齐
    view.setAlignment(Qt::AlignTop | Qt::AlignLeft);
    view.resize(640,480);
    view.show();

    return a.exec();
}

运行效果:

这里新建了视图部件,并指定了要可视化的场景。然后为该视图设置了场景前景色和背景图片。一个场景分为3层:图形项层(ItemLayer)、前景层(ForegroundLayer)和背景层(BackgroundLayer)。场景的绘制总是从背景层开始,然后是图形项层,最后是前景层。前景层和背景层都可以使用QBrush进行填充,比如使用渐变和贴图等。

代码中使用了QGraphicsView类中的函数设置了场景中的背景和前景,其实也可以使用QGraphicsScene 中的同名函数来实现,不过它们的效果并不完全一样。如果使用QGraphicsScene对象设置了场景背景或者前景,那么对所有关联了该场景的视图都有效,而QGraphicsView对象设置的场景的背景或者前景只对它本身对应的视图有效。

 

4、QGraphicsItem 图元

QGraphicsItem是图元的基类。QGraphics View框架提供了多种标准的图元,例如:矩形(QGraphicsRectlem)、椭圆(QGraphicsEllipseItem)和文本项(QGraphicsTextItcm)等等。

标准图元:

  • QGraphicsEllipseItem 提供了一个椭圆图元

  • QGraphicsLineItem 提供了一个线段图元

  • QGraphicsPathItem 提供任意路径图元

  • QGraphicsPixmapItem 提供了一个像素图图元

  • QGraphicsPolygonItem 提供了一个多边形图元

  • QGraphicsRectItem 提供了一个矩形图元

  • QGraphicsSimpleTextItem 提供了一个简单的文本标签图元

  • QGraphicsTextItem 提供了一个高级的文本浏览图元

用户可以继承QGraphicsItem实现自定义的图元。QGraphicsItem图元主要特性如下:

  • 支持鼠标按下、移动、释放、双击、悬停、滚动和右键菜单事件。

  • 支持键盘输入焦点和按键事件

  • 支持拖拽事件

  • 支持分组,使用父子关系和QGraphicsItemGroup

  • 支持碰撞检测

图元存在于本地坐标系统上,场景提供了在图元和场景间、图元与图元间进行坐标变换的函数。QGraphicsItem::transform()函数可以使用矩阵转换坐标系统。这对于翻转和缩放图元是有用的。

  图元可以包含其他图元,父图元的变换会被其所有的子图元继承。无论一个图元本身有多少变换,图元的所有函数(QGraphicsItem::contains(), QGraphicsItem::boundingRect(), QGraphicsItem::collidesWith())仍旧执行在本地坐标系上。

  QGraphicsItem通过虚函数shape()和collideWith())来支持碰撞检测。从shape()返回图元的形状(以本地坐标QPainterPath表示),QGraphicsItem会处理所有的碰撞检测。如果要提供自己的碰撞检测,需要重新实现QGraphicsItem::collideWith()。

碰撞检测的方法:

a、重写shape()函数来返回图元的精准轮廓,依靠默认的collidesWithItem()来做外形交集。如果item轮廓和复杂时候,消耗是很大的。

b、重写collidesWithItem(),提供一个自己的图元和轮廓碰撞的算法

  Contains()函数可以调用,用来决定一个图元是否包含一个点。Contains函数可以重写,contains()函数默认的方法是通过调用shape()来完成的。

  图元中也可以包含其他的图元,也可以被别的图元包含,所有的图元可以有一个父类图元和多个子类图元,除非一个图元没有父类,否则图元的位置是在父类坐标中,子类图元将会继承父类图元的位置和转换。

  通过调用setVisible(),可以设置图元是否可见,隐藏一个图元同时也隐藏了其子类,通过调用 setEnabled()来是指图元是否可用。如果禁用了图元,那么其所有的子类都不可用。图元默认都是可见和可用的。

示例代码1:绘制矩形

#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGraphicsRectItem>
#include <QDebug>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    //新建场景
    QGraphicsScene* secne = new QGraphicsScene;
    //创建图元
    QGraphicsRectItem* RectItem = new QGraphicsRectItem(10,10,50,50);
    //将图元添加到场景中
    secne->addItem(RectItem);
    //设置图元可移动的
    RectItem->setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable | QGraphicsItem::ItemIsFocusable);


    //创建视图
    QGraphicsView view(secne);//设置场景
    //view.setScene(secne);   //也可以这样设置场景
    //设置场景的前景色
    //view.setForegroundBrush(QColor(0,0,0,100));
    //设置场景的背景色
    view.setBackgroundBrush(QColor(255,255,255));
    //view.setBackgroundBrush(QPixmap("://images/background.png"));

    //设置场景与视图对齐
    view.setAlignment(Qt::AlignTop | Qt::AlignLeft);
    //设置拖动模式
    //view.setDragMode(QGraphicsView::RubberBandDrag);

    view.resize(640,480);
    view.show();

    return a.exec();
}

以上代码创建了一个可以拖动的矩形

运行效果:

示例代码2:绘制图片

    //QGraphicsView框架显示图片
    QGraphicsScene *scene = new QGraphicsScene(this);//创建创建
    QGraphicsPixmapItem *item = new QGraphicsPixmapItem();//创建qt标准图元
    item->setPixmap(QPixmap::fromImage(QImage("./7days_prom_de.jpg")));//设置图片
    scene->addItem(item);//往场景里添加图元
    QGraphicsView *view = new QGraphicsView(scene);
    view->setScene(scene);//QGraphicsView设置场景

运行效果:

 

4.1 自定义图形(三角形)

新建类文件,在头文件中加入如下代码:创建一个绘制三角形的Item

graphicstriangleitem.h

#ifndef GRAPHICSTRIANGLEITEM_H
#define GRAPHICSTRIANGLEITEM_H

#include<QGraphicsItem>

class GraphicsTriangleItem:public QGraphicsItem
{
public:
    GraphicsTriangleItem(int w,int h);

    QRectF boundingRect()const override;
    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)override;
private:
    int m_w;
    int m_h;
};

#endif // GRAPHICSTRIANGLEITEM_H

再到.cpp文件添加实现

graphicstriangleitem.cpp

#include "graphicstriangleitem.h"
#include <QPainter>

GraphicsTriangleItem::GraphicsTriangleItem(int w,int h):m_w(w),m_h(h)
{

}

QRectF GraphicsTriangleItem::boundingRect() const
{
    return QRectF(0,0,m_w,m_h);
}

// 修正第三个点的坐标为(m_w, m_h)
void GraphicsTriangleItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *)
{
    QPoint triangle[3] = {
        QPoint(m_w/2, 0),    // 顶点
        QPoint(0, m_h),      // 左下角
        QPoint(m_w, m_h)      // 右下角(原错误点)
    };

    painter->setPen(Qt::black);
    painter->setBrush(Qt::red);
    painter->drawPolygon(triangle, 3);

    // painter->drawLine(0,0,50,50); // 测试线可删除
}

要实现自定义的图形项,那么首先要创建一个QGraphicsItem的子类,然后重新实现它的两个纯虚公共函数: boundingRect()和 paint(),前者用来返回要绘制图形项的矩形区域,后者用来执行实际的绘图操作。其中, boundingRect()函数将图形项的外部边界定义为一个矩形,所有的绘图操作都必须限制在图形项的边界矩形之中。而且,QGraphicsView要使用这个矩形来剔除那些不可见的图形项,还要使用它来确定绘制交叉项目时哪些区域需要进行重新构建。另外,QGraphicsItem的碰撞检测机制也需要使用到这个边界矩形。如果图形绘制了一个轮廓,那么在边界矩形中包含一半画笔的宽度是很重要的,尽管对于抗锯齿绘图并不需要这些补偿。

boundingRect有两个参数,第一个是QPointF,第二个是QSizeF。其中第一个点坐标,设置的是,在item的边框矩阵内做一个坐标系,这个坐标系对应于边框矩阵左上角的点的坐标是什么。如果传(0, 0),则边界矩形左上角坐标就是(0, 0);如果传(- width / 2, -height / 2),边界矩形左上角坐标就是(- width / 2, -height / 2),从而(0, 0)点就被设置在了边界矩形的中心点。

使用自定义图形项:

#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGraphicsRectItem>
#include <QDebug>
#include "graphicstriangleitem.h"

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    //新建场景
    QGraphicsScene* secne = new QGraphicsScene;
    //创建图形项
    //QGraphicsRectItem* rectItem = new QGraphicsRectItem(0,0,100,100);
    GraphicsTriangleItem* TriangleItem = new GraphicsTriangleItem(200,200);
    //将图形项添加到场景中
    //secne->addItem(rectItem);
    secne->addItem(TriangleItem);

    //创建视图
    QGraphicsView view(secne);//设置场景
    //view.setScene(secne);   //也可以这样设置场景
    //设置场景的前景色
    view.setForegroundBrush(QColor(255,255,0,100));
    //设置场景的背景色
    view.setBackgroundBrush(QPixmap("://images/background.png"));
    //设置场景与视图对齐
    view.setAlignment(Qt::AlignTop | Qt::AlignLeft);

    view.setDragMode(QGraphicsView::RubberBandDrag);

    view.resize(640,480);
    view.show();

    return a.exec();
}

运行效果:

 

4.2 自定义图形(矩形)

mygraphicrectitem.h

#ifndef MYGRAPHICRECTITEM_H
#define MYGRAPHICRECTITEM_H
#include <QObject>
#include <QWidget>
#include <QMouseEvent>
#include <QGraphicsScene>
#include <QGraphicsRectItem>
#include <QGraphicsSceneMouseEvent>
#include <QRect>
#include <QPainter>
#include <QPolygon>
#include <QList>

enum STATE_FLAG{
    DEFAULT_FLAG=0,
    MOV_LEFT_LINE,//标记当前为用户按下矩形的左边界区域
    MOV_TOP_LINE,//标记当前为用户按下矩形的上边界区域
    MOV_RIGHT_LINE,//标记当前为用户按下矩形的右边界区域
    MOV_BOTTOM_LINE,//标记当前为用户按下矩形的下边界区域
    MOV_RIGHTBOTTOM_RECT,//标记当前为用户按下矩形的右下角
    MOV_RECT,//标记当前为鼠标拖动图片移动状态
    ROTATE//标记当前为旋转状态
};
class myGraphicRectItem:public QObject,public QGraphicsItem
{
    Q_OBJECT
public:
    myGraphicRectItem(QGraphicsItem *parent = nullptr);
    //myGraphicRectItem(QRectF m_OriginRect = QRectF(0,0,100,100));
    QRectF  boundingRect() const;
    ~myGraphicRectItem();
    void setRectSize(QRectF mrect,bool bResetRotateCenter = true);
    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
    void mousePressEvent(QGraphicsSceneMouseEvent *event);
    void mouseMoveEvent(QGraphicsSceneMouseEvent *event);
    void mouseReleaseEvent(QGraphicsSceneMouseEvent *event);
    void SetRotate(qreal RotateAngle,QPointF ptCenter=QPointF(-999,-999));
    QPointF getRotatePoint(QPointF ptCenter, QPointF ptIn, qreal angle);//获取旋转后的点
    QList<QPointF> getRotatePoints(QPointF ptCenter,QList<QPointF> ptIns,qreal angle);//获取多个旋转后的点
    QPolygonF getRotatePolygonFromRect(QPointF ptCenter,QRectF rectIn,qreal angle);//将矩形旋转之后返回多边形
    QRectF getCrtPosRectToSceen();
    QRectF m_SmallRotateRect;//矩形顶部用来表示旋转的标记的矩形
    QPolygonF m_SmallRotatePolygon;//矩形顶部用来表示旋转的标记的矩形旋转后形成的多边形
    QPointF getSmallRotateRectCenter(QPointF ptA,QPointF ptB);//获取旋转时候矩形正上方的旋转标记矩形
    QRectF  getSmallRotateRect(QPointF ptA,QPointF ptB);
    bool    m_bRotate;
    qreal   m_RotateAngle;
    QPointF m_RotateCenter;

private:
    QRectF  m_oldRect;
    QPolygonF m_oldRectPolygon;
    QRectF  m_RotateAreaRect;
    bool    m_bResize;
    QPolygonF m_insicedPolygon;
    QRectF  m_insicedRectf;
    QPolygonF m_leftPolygon;
    QRectF  m_leftRectf;
    QPolygonF m_topPolygon;
    QRectF  m_topRectf;
    QPolygonF m_rightPolygon;
    QRectF  m_rightRectf;
    QPolygonF m_bottomPolygon;
    QRectF  m_bottomRectf;
//    QPolygonF m_rbPolygon;
//    QRectF  m_rbRectf;
    QPointF m_startPos;
    STATE_FLAG m_StateFlag;
    QPointF *pPointFofSmallRotateRect;
protected:

};
#endif // MYGRAPHICRECTITEM_H

mygraphicrectitem.cpp

查看代码
 #include "mygraphicrectitem.h"
#include <QtMath>
#include <QDebug>

myGraphicRectItem::myGraphicRectItem(QGraphicsItem *parent):
    m_bResize(false),
    m_oldRect(0,0,100,100),
    m_bRotate(false),
    m_RotateAngle(0),
    m_StateFlag(DEFAULT_FLAG)
{
    //setParent(parent);
    setRectSize(m_oldRect);
    setToolTip("Click and drag me!");  //提示
    setCursor(Qt::ArrowCursor);   //改变光标形状,手的形状
    setFlag(QGraphicsItem::ItemIsMovable);
    //    setAcceptDrops(true);
    pPointFofSmallRotateRect = new QPointF[4];
    SetRotate(0);
    setFlag(QGraphicsItem::ItemIsSelectable);//
}

QRectF myGraphicRectItem::boundingRect() const
{
    //return m_oldRectPolygon.boundingRect();
    QRectF boundingRectF = m_oldRectPolygon.boundingRect();
    return QRectF(boundingRectF.x()-40,boundingRectF.y()-40,boundingRectF.width()+80,boundingRectF.height()+80);
}

myGraphicRectItem::~myGraphicRectItem()
{
    delete []pPointFofSmallRotateRect;
    pPointFofSmallRotateRect = nullptr;
}

void myGraphicRectItem::setRectSize(QRectF mrect, bool bResetRotateCenter)
{
    m_oldRect = mrect;
    if(bResetRotateCenter)
    {
        m_RotateCenter.setX(m_oldRect.x()+m_oldRect.width()/2);
        m_RotateCenter.setY(m_oldRect.y()+m_oldRect.height()/2);
    }
    m_oldRectPolygon = getRotatePolygonFromRect(m_RotateCenter,m_oldRect,m_RotateAngle);

    m_insicedRectf = QRectF(m_oldRect.x()+8,m_oldRect.y()+8,m_oldRect.width()-16,m_oldRect.height()-16);
    m_insicedPolygon =getRotatePolygonFromRect(m_RotateCenter,m_insicedRectf,m_RotateAngle);

    m_leftRectf = QRectF(m_oldRect.x(),m_oldRect.y(),8,m_oldRect.height()-8);
    m_leftPolygon = getRotatePolygonFromRect(m_RotateCenter,m_leftRectf,m_RotateAngle);

    m_topRectf = QRectF(m_oldRect.x()+8,m_oldRect.y(),m_oldRect.width()-8,8);
    m_topPolygon = getRotatePolygonFromRect(m_RotateCenter,m_topRectf,m_RotateAngle);

    m_rightRectf = QRectF(m_oldRect.right()-8,m_oldRect.y()+8,8,m_oldRect.height()-16);
    m_rightPolygon = getRotatePolygonFromRect(m_RotateCenter,m_rightRectf,m_RotateAngle);

    m_bottomRectf = QRectF(m_oldRect.x(),m_oldRect.bottom()-8,m_oldRect.width()-8,8);
    m_bottomPolygon = getRotatePolygonFromRect(m_RotateCenter,m_bottomRectf,m_RotateAngle);

//    m_rbRectf = QRectF(m_oldRect.right()-8,m_oldRect.bottom()-8,8,8);
//    m_rbPolygon = getRotatePolygonFromRect(m_RotateCenter,m_rbRectf,m_RotateAngle);

    m_SmallRotateRect = getSmallRotateRect(mrect.topLeft(),mrect.topRight());//矩形正上方的旋转标记矩形
    m_SmallRotatePolygon = getRotatePolygonFromRect(m_RotateCenter,m_SmallRotateRect,m_RotateAngle);
}

void myGraphicRectItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
    QPen mPen = QPen(Qt::yellow);
    painter->setPen(mPen);
    //绘制旋转后的矩形
    painter->drawPolygon(m_oldRectPolygon);
    //绘制旋转圆形
    mPen.setWidth(2);
    mPen.setColor(Qt::green);
    painter->setPen(mPen);
    QPointF pf = getSmallRotateRectCenter(m_oldRectPolygon[0],m_oldRectPolygon[1]);
    QRectF rect = QRectF(pf.x()-10,pf.y()-10,20,20);
    painter->drawEllipse(rect);//绘制圆形
    painter->drawPoint(pf);//绘制点
}

void myGraphicRectItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
    if(event->button()== Qt::LeftButton)
    {
        m_startPos = event->pos();//鼠标左击时,获取当前鼠标在图片中的坐标,
        if(m_SmallRotatePolygon.containsPoint(m_startPos,Qt::WindingFill))//旋转矩形
        {
            setCursor(Qt::PointingHandCursor);
            m_StateFlag = ROTATE;
        }
        else if(m_insicedPolygon.containsPoint(m_startPos,Qt::WindingFill))//在矩形内框区域时按下鼠标,则可拖动图片
        {
            setCursor(Qt::ClosedHandCursor);   //改变光标形状,手的形状
            m_StateFlag = MOV_RECT;//标记当前为鼠标拖动图片移动状态
        }
        else if(m_leftPolygon.containsPoint(m_startPos,Qt::WindingFill))
        {
            setCursor(Qt::SizeHorCursor);
            m_StateFlag = MOV_LEFT_LINE;//标记当前为用户按下矩形的左边界区域
        }
        else if(m_rightPolygon.containsPoint(m_startPos,Qt::WindingFill))
        {
            setCursor(Qt::SizeHorCursor);
            m_StateFlag = MOV_RIGHT_LINE;//标记当前为用户按下矩形的右边界区域
        }
        else if(m_topPolygon.containsPoint(m_startPos,Qt::WindingFill))
        {
            setCursor(Qt::SizeVerCursor);
            m_StateFlag = MOV_TOP_LINE;//标记当前为用户按下矩形的上边界区域
        }
        else if(m_bottomPolygon.containsPoint(m_startPos,Qt::WindingFill))
        {
            setCursor(Qt::SizeVerCursor);
            m_StateFlag = MOV_BOTTOM_LINE;//标记当前为用户按下矩形的下边界区域
        }
//        else if(m_rbPolygon.containsPoint(m_startPos,Qt::WindingFill))
//        {
//            setCursor(Qt::SizeFDiagCursor);
//            m_StateFlag = MOV_RIGHTBOTTOM_RECT;//标记当前为用户按下矩形的右下角
//        }
        else
        {
            m_StateFlag = DEFAULT_FLAG;
        }
    }
    else
    {
        QGraphicsItem::mousePressEvent(event);
    }
}

void myGraphicRectItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
    if(m_StateFlag == ROTATE)
    {
       int nRotateAngle = atan2((event->pos().x()-m_RotateCenter.x()),(event->pos().y()-m_RotateCenter.y()))*180/M_PI;
       SetRotate(180-nRotateAngle);
       setRectSize(m_oldRect);
       //qDebug()<<nRotateAngle;
    }
    else if(m_StateFlag == MOV_RECT)
    {
        QPointF point = (event->pos() - m_startPos);
        moveBy(point.x(), point.y());
        setRectSize(m_oldRect);
        scene()->update();
    }
    else if(m_StateFlag == MOV_LEFT_LINE)
    {
        QPointF pf = QPointF((m_oldRectPolygon.at(1).x()+m_oldRectPolygon.at(2).x())/2,((m_oldRectPolygon.at(1).y()+m_oldRectPolygon.at(2).y())/2));
        //计算到右侧边中点的距离
        qreal dis = sqrt((event->pos().x()-pf.x())*(event->pos().x()-pf.x()) +(event->pos().y()-pf.y())*(event->pos().y()-pf.y()));
        qreal dis2LT = sqrt((event->pos().x()-m_oldRectPolygon.at(0).x())*(event->pos().x()-m_oldRectPolygon.at(0).x()) +(event->pos().y()-m_oldRectPolygon.at(0).y())*(event->pos().y()-m_oldRectPolygon.at(0).y()));
        qreal dis2RT = sqrt((event->pos().x()-m_oldRectPolygon.at(1).x())*(event->pos().x()-m_oldRectPolygon.at(1).x()) +(event->pos().y()-m_oldRectPolygon.at(1).y())*(event->pos().y()-m_oldRectPolygon.at(1).y()));
        if(dis<16||dis2LT>dis2RT)
        {
            return;
        }
        else
        {
            QRectF newRect(m_oldRect);
            newRect.setLeft(m_oldRect.right()-dis);
            newRect.setRight(m_oldRect.right());
            setRectSize(newRect,false);
            m_RotateCenter=QPointF((m_oldRectPolygon.at(0).x()+m_oldRectPolygon.at(2).x())/2,(m_oldRectPolygon.at(0).y()+m_oldRectPolygon.at(2).y())/2);
            m_oldRect.moveCenter(m_RotateCenter);
            setRectSize(m_oldRect);
            scene()->update();//必须要用scene()->update(),不能用update();否则会出现重影
        }
    }
    else if(m_StateFlag == MOV_TOP_LINE)
    {
        //底边中点
        QPointF pf = QPointF((m_oldRectPolygon.at(2).x()+m_oldRectPolygon.at(3).x())/2,((m_oldRectPolygon.at(2).y()+m_oldRectPolygon.at(3).y())/2));
        //计算到底边中点的距离
        qreal dis = sqrt((event->pos().x()-pf.x())*(event->pos().x()-pf.x()) +(event->pos().y()-pf.y())*(event->pos().y()-pf.y()));
        qreal dis2LT = sqrt((event->pos().x()-m_oldRectPolygon.at(0).x())*(event->pos().x()-m_oldRectPolygon.at(0).x()) +(event->pos().y()-m_oldRectPolygon.at(0).y())*(event->pos().y()-m_oldRectPolygon.at(0).y()));
        qreal dis2LB = sqrt((event->pos().x()-m_oldRectPolygon.at(3).x())*(event->pos().x()-m_oldRectPolygon.at(3).x()) +(event->pos().y()-m_oldRectPolygon.at(3).y())*(event->pos().y()-m_oldRectPolygon.at(3).y()));
        if(dis<16||dis2LT>dis2LB)
        {
            return;
        }
        else
        {
            QRectF newRect(m_oldRect);
            newRect.setTop(m_oldRect.bottom()-dis);
            newRect.setBottom(m_oldRect.bottom());
            setRectSize(newRect,false);
            m_RotateCenter=QPointF((m_oldRectPolygon.at(0).x()+m_oldRectPolygon.at(2).x())/2,(m_oldRectPolygon.at(0).y()+m_oldRectPolygon.at(2).y())/2);
            m_oldRect.moveCenter(m_RotateCenter);
            setRectSize(m_oldRect);
            scene()->update();//必须要用scene()->update(),不能用update();否则会出现重影
        }
    }
    else if(m_StateFlag == MOV_RIGHT_LINE)
    {
        QPointF pf = QPointF((m_oldRectPolygon.at(0).x()+m_oldRectPolygon.at(3).x())/2,((m_oldRectPolygon.at(0).y()+m_oldRectPolygon.at(3).y())/2));
        //计算到左侧边中点的距离
        qreal dis = sqrt((event->pos().x()-pf.x())*(event->pos().x()-pf.x()) +(event->pos().y()-pf.y())*(event->pos().y()-pf.y()));
        qreal dis2LT = sqrt((event->pos().x()-m_oldRectPolygon.at(0).x())*(event->pos().x()-m_oldRectPolygon.at(0).x()) +(event->pos().y()-m_oldRectPolygon.at(0).y())*(event->pos().y()-m_oldRectPolygon.at(0).y()));
        qreal dis2RT = sqrt((event->pos().x()-m_oldRectPolygon.at(1).x())*(event->pos().x()-m_oldRectPolygon.at(1).x()) +(event->pos().y()-m_oldRectPolygon.at(1).y())*(event->pos().y()-m_oldRectPolygon.at(1).y()));
        if(dis<16||dis2LT<dis2RT)
        {
            return;
        }
        else
        {
            QRectF newRect(m_oldRect);
            newRect.setLeft(m_oldRect.left());
            newRect.setRight(m_oldRect.left()+dis);
            setRectSize(newRect,false);
            m_RotateCenter=QPointF((m_oldRectPolygon.at(0).x()+m_oldRectPolygon.at(2).x())/2,(m_oldRectPolygon.at(0).y()+m_oldRectPolygon.at(2).y())/2);
            m_oldRect.moveCenter(m_RotateCenter);
            setRectSize(m_oldRect);
            scene()->update();//必须要用scene()->update(),不能用update();否则会出现重影
        }
    }
    else if(m_StateFlag == MOV_BOTTOM_LINE)
    {
        //顶边中点
        QPointF pf = QPointF((m_oldRectPolygon.at(0).x()+m_oldRectPolygon.at(1).x())/2,((m_oldRectPolygon.at(0).y()+m_oldRectPolygon.at(1).y())/2));
        //计算到底边中点的距离
        qreal dis = sqrt((event->pos().x()-pf.x())*(event->pos().x()-pf.x()) +(event->pos().y()-pf.y())*(event->pos().y()-pf.y()));
        qreal dis2LT = sqrt((event->pos().x()-m_oldRectPolygon.at(0).x())*(event->pos().x()-m_oldRectPolygon.at(0).x()) +(event->pos().y()-m_oldRectPolygon.at(0).y())*(event->pos().y()-m_oldRectPolygon.at(0).y()));
        qreal dis2LB = sqrt((event->pos().x()-m_oldRectPolygon.at(3).x())*(event->pos().x()-m_oldRectPolygon.at(3).x()) +(event->pos().y()-m_oldRectPolygon.at(3).y())*(event->pos().y()-m_oldRectPolygon.at(3).y()));
        if(dis<16||dis2LT<dis2LB)
        {
            return;
        }
        else
        {
            QRectF newRect(m_oldRect);
            newRect.setTop(m_oldRect.top());
            newRect.setBottom(m_oldRect.top()+dis);
            setRectSize(newRect,false);
            m_RotateCenter=QPointF((m_oldRectPolygon.at(0).x()+m_oldRectPolygon.at(2).x())/2,(m_oldRectPolygon.at(0).y()+m_oldRectPolygon.at(2).y())/2);
            m_oldRect.moveCenter(m_RotateCenter);
            setRectSize(m_oldRect);
            scene()->update();//必须要用scene()->update(),不能用update();否则会出现重影
        }
    }
}

void myGraphicRectItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
    setCursor(Qt::ArrowCursor);
    if(m_StateFlag == MOV_RECT)
    {
        m_StateFlag = DEFAULT_FLAG;
    }
    else {
        QGraphicsItem::mouseReleaseEvent(event);
    }
}

void myGraphicRectItem::SetRotate(qreal RotateAngle, QPointF ptCenter)
{
    m_bRotate = true;
    if(ptCenter.x()==-999 && ptCenter.y()==-999)
    {
        m_RotateCenter = QPointF(m_oldRect.x()+m_oldRect.width()/2,m_oldRect.y()+m_oldRect.height()/2);
    }
    else
    {
        m_RotateCenter = ptCenter;
    }
    m_RotateAngle = RotateAngle;
    this->update();
}

QPointF myGraphicRectItem::getRotatePoint(QPointF ptCenter, QPointF ptIn, qreal angle)
{
    double dx = ptCenter.x();
    double dy = ptCenter.y();
    double x = ptIn.x();
    double y = ptIn.y();
    double xx,yy;
    xx = (x-dx)*cos(angle*M_PI/180)-(y-dy)*sin(angle*M_PI/180)+dx;
    yy = (x-dx)*sin(angle*M_PI/180)+(y-dy)*cos(angle*M_PI/180)+dy;

    return QPointF(xx,yy);
}

QList<QPointF> myGraphicRectItem::getRotatePoints(QPointF ptCenter, QList<QPointF> ptIns, qreal angle)
{
    QList<QPointF> lstPt;
    for(int i = 0;i<ptIns.count();i++)
    {
        lstPt.append(getRotatePoint(ptCenter,ptIns.at(i),angle));
    }
    return lstPt;
}

QPolygonF myGraphicRectItem::getRotatePolygonFromRect(QPointF ptCenter, QRectF rectIn, qreal angle)
{
    QVector<QPointF> vpt;
    QPointF pf = getRotatePoint(ptCenter,rectIn.topLeft(),angle);
    vpt.append(pf);
    pf = getRotatePoint(ptCenter,rectIn.topRight(),angle);
    vpt.append(pf);
    pf = getRotatePoint(ptCenter,rectIn.bottomRight(),angle);
    vpt.append(pf);
    pf = getRotatePoint(ptCenter,rectIn.bottomLeft(),angle);
    vpt.append(pf);
    pf = getRotatePoint(ptCenter,rectIn.topLeft(),angle);
    vpt.append(pf);
    return QPolygonF(vpt);
}

QRectF myGraphicRectItem::getCrtPosRectToSceen()
{
    QRectF retRect = QRectF(m_oldRect.x()+pos().x(),m_oldRect.y()+pos().y(),m_oldRect.width(),m_oldRect.height());
    return retRect;
}
QRectF myGraphicRectItem::getSmallRotateRect(QPointF ptA,QPointF ptB)
{
    QPointF pt = getSmallRotateRectCenter(ptA,ptB);
    return QRectF(pt.x()-10,pt.y()-10,20,20);
}
QPointF myGraphicRectItem::getSmallRotateRectCenter(QPointF ptA,QPointF ptB)
{
    QPointF ptCenter = QPointF((ptA.x()+ptB.x())/2,(ptA.y()+ptB.y())/2);//A,B点的中点C
    //中垂线方程式为 y=x*k + b;
    qreal x,y;//旋转图标矩形的中心
    if(abs(ptB.y()-ptA.y())<0.1)
    {
        if(ptA.x()<ptB.x())//矩形左上角在上方
        {
            x = ptCenter.x();
            y = ptCenter.y()-20;
        }
        else//矩形左上角在下方
        {
            x = ptCenter.x();
            y = ptCenter.y()+20;
        }
    }
    else if(ptB.y()>ptA.y())//顺时针旋转0-180
    {
        qreal k = (ptA.x()-ptB.x())/(ptB.y()-ptA.y());//中垂线斜率
        qreal b = (ptA.y()+ptB.y())/2-k*(ptA.x()+ptB.x())/2;
        //求AB线中垂线上离AB中点20个像素的点C的坐标
        x = 20*cos(atan(k))+ptCenter.x();
        y = k*x+b;
    }
    else if(ptB.y()<ptA.y())//顺时针旋转180-360
    {
        qreal k = (ptA.x()-ptB.x())/(ptB.y()-ptA.y());//中垂线斜率
        qreal b = (ptA.y()+ptB.y())/2-k*(ptA.x()+ptB.x())/2;
        //求AB线中垂线上离AB中点20个像素的点C的坐标
        x = -20*cos(atan(k))+ptCenter.x();
        y = k*x+b;
    }
    return QPointF(x,y);
}

 使用自定义的矩形有两种方式如下

方式一:直接在main函数中使用

main.cpp

#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGraphicsRectItem>
#include <QDebug>
#include "mygraphicrectitem.h"

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    //新建场景
    QGraphicsScene* secne = new QGraphicsScene;
    //创建图形项
    myGraphicRectItem* RectItem = new myGraphicRectItem();
    //将图形项添加到场景中
    secne->addItem(RectItem);

    //创建视图
    QGraphicsView view(secne);//设置场景
    //view.setScene(secne);   //也可以这样设置场景
    //设置场景的前景色
    //view.setForegroundBrush(QColor(0,0,0,100));
    //设置场景的背景色
    view.setBackgroundBrush(QColor(55,55,55));
    //view.setBackgroundBrush(QPixmap("://images/background.png"));

    //设置场景与视图对齐
    view.setAlignment(Qt::AlignTop | Qt::AlignLeft);
    //设置拖动模式
    //view.setDragMode(QGraphicsView::RubberBandDrag);

    view.resize(640,480);
    view.show();

    return a.exec();
}

方式二:在widget的ui文件中添加一个graphicsView组件

在创建的Qwidget类构造函数中添加以下代码

#include "widget.h"
#include "ui_widget.h"

#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGraphicsRectItem>
#include <QDebug>
#include "mygraphicrectitem.h"


Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{
    ui->setupUi(this);

    ui->setupUi(this);
    ui->graphicsView->setScene(new QGraphicsScene);
    ui->graphicsView->setBackgroundBrush(QColor(55,55,55));
    myGraphicRectItem *item = new myGraphicRectItem;
    ui->graphicsView->scene()->addItem(item);
    item->setFlags(myGraphicRectItem ::ItemIsMovable | myGraphicRectItem ::ItemIsSelectable);
}

Widget::~Widget()
{
    delete ui;
}

本例程通过重写了一个类,继承自QGraphicItem,来实现了在QgraphicsScene上绘制、拖动、缩放、旋转矩形。
效果如下:

其实要实现绘制、拖动、缩放矩形都不难,难的是在旋转之后还要支持缩放。

我的思路是:

1.实现绘制矩形:只要定义一个全局变量QRectF m_oldRect,在外面矩形大小传进来,然后在paint函数里面绘制这个矩形就行

2.实现拖动矩形:重写mousePressEvent,mouseMoveEvent,mouseReleaseEvent,记录鼠标按下的起始点和移动时候的点,并用moveBy()函数来移动矩形即可

3.实现缩放:在矩形内部靠近4条边的地方定义4个矩形,当鼠标按下的时候在这4个矩形方框内,则将矩形往4个方向拉伸

4.实现旋转:

我给之前定义的矩形全部配一个QPolygonF,因为我只能通过绘制多边形的方式来画出旋转之后的矩形。

矩形正上方的圆圈我是通过三角函数+直线方程来计算,让其始终绘制在矩形左右两个顶点的中垂线上方。

当鼠标落在圆形内部的时候可以控制矩形旋转。

在矩形旋转之后,再进行拉伸的时候,我是通过的计算鼠标的点离对面的边的距离来重新计算矩形的大小,然后计算对应的QPolygonF的大小。

 

5、GraphicsView坐标系统

  Graphics View坐标系基于笛卡尔坐标系,图元的场景中的位置和几何形状通过x坐标和y坐标表示。当使用没有变换的视图观察场景时,场景中的一个单位对应屏幕上的一个像素。

  Graphics View架构中有三个有效的坐标系统,图元坐标、场景坐标和视图坐标。Graphics View提供了三个坐标系统之间的转换函数。在绘制图形时,QGraphics View的场景坐标对应QPainter的逻辑坐标,QGraphics View的视图坐标对应QPainter的设备坐标。

5.1 图元坐标

图元存在于自己的本地坐标上,图元的坐标系统通常以图元中心为原点,图元中心也是所有坐标变换的原点,图元坐标方向是x轴正方向向右,y轴正方向向下。创建自定义图元时,只需要注意图元的坐标,QGraphicsScene和QGraphicsView会完成所有的变换。 例如,如果接受到一个鼠标按下或拖入事件,所给的事件位置是基于图元坐标系的。如果某个点位于图元内部,使用图元上的点作为QGraphicsItem::contains()虚函数的参数,函数会返回true。类似,图元的边界矩形和形状也是基于图元坐标系。

  图元的位置是图元的中心点在其父图元坐标系统的坐标。按这种说法,场景是所有无父图元的图元的父图元。顶层图元的位置是场景坐标。

子图元的坐标与父图元的坐标相关。如果子图元无变换,子图元坐标和父图元坐标之间的区别与他们的父图元的坐标相同。例如,如果一个无变换的子图元精确的位于父图元的中心点,父子图元的坐标系统是相同的。如果子图元的位置是(10,0),子图元上的点(0,10)就是父图元上的点(10,10)。

  由于图元的位置和变换与父图元相关,但子图元的坐标并不会被父图元的变换影响,虽然父图元的变换会隐式地变换子图元。在上例中,即使父图元被翻转和缩放,子图元上的点(0,10)仍旧是父图元上的点(10,10)。

  如果调用QGraphicsItem类的paint()函数重绘图元时,则以图元坐标系为基准。

5.2 场景坐标

场景坐标是所有图元的基础坐标系统。场景坐标系统描述了顶层图元的位置,并且构成从视图传播到场景的所有场景事件的基础。每个图元在场景上都有场景坐标和边界矩形。场景坐标的原点在场景中心,坐标原点是X轴正方向向右,Y轴正方向向下。

5.3 视图坐标

视图坐标是窗口部件的坐标,视图坐标的单位是像素,QGraphicsView的左上角是(0,0)。所有鼠标事件、拖拽事件最开始都使用视图坐标,为了和图元交互,需要转换坐标为场景坐标。

5.4 坐标变换

在Graphics View框架中,经常需要将多种坐标变换,从场景到图元,从图元到图元,从视图到场景 。QGraphics View框架坐标变换函数如下:

映射函数 描述
QGraphicsView::mapToScene() 从视图坐标系统映射到场景坐标系统
QGraphicsView::mapFromScene() 从场景坐标系统映射到视图坐标系统
QGraphicsItem::mapToScene() 从图形项的坐标系统映射到场景的坐标系统
QGraphicsItem::mapFromScene() 从场景的坐标系统映射到图形项的坐标系统
QGraphicsItem::mapToParent() 从本图形的坐标系统映射到其父图形的坐标系统
QGraphicsItem::mapFromParent() 从父图形项的坐标系统映射到本图形项的坐标系统
QGraphicsItem::mapToItem() 从本图形项的坐标系统映射到另一个图形项的坐标系统
QGraphicsItem::mapFromScene() 从另一个图形项的坐标系统映射到本图形项的坐标系统

在场景中处理图元时,从场景到图元、从图元到图元、从视图到场景进行坐标和图形变换是有用的。当在QGraphicsView的视口中点击鼠标时,应该通过调用QGraphicsView::mapToScence()与QGraphicsScene::itemAt()来获知光标下是场景中的哪个图元;如果想获知一个图元位于视口中的位置,应该先在图元上调用QGraphicsItem::mapToScene(),然后调用QGraphicsView::mapFromScene();如果想获知在一个视图椭圆中有哪些图元,应该把QPainterPath传递到mapToScene(),然后再把映射后的路径传递到QGraphicsScene::items()。 可以调用QGraphicsItem::mapToScene()与QGraphicsItem::mapFromScene()在图元与场景之间进行坐标与形状的映射,也可以在子图元与其父图元之间通过QGraphicsItem::mapToParent()与QGraphicsItem::mapFromItem()进行映射。所有映射函数可以包括点、矩形、多边形、路径。视图与场景之间的映射也与此类似。对于视图与图元之间的映射,应该先从视图映射到场景,然后再从场景图映射到图元。

 

6、GraphicsView框架特性

1、缩放与旋转

  QGraphicsView通过QGraphicsView::setMatrix()支持同QPainter一样的坐标变换,通过对一个视图应用变换,可以很容易地支持普通的导航特性如缩放与旋转。

2、打印

  图形视图架构通过渲染函数QGraphicsScene::render()和QGraphicsView::render()支持单行打印

  场景和视图的渲染函数的不同在于QGraphicsScene::render()使用场景坐标,QGraphicsView::render()使用视图坐标。QGraphicsScene::render()经常用于打印未变换场景中的整块,例如一块图形数据或是打印一个文本文档。 QGraphicsView::render()适合用于截屏,默认会使用绘图设备精确渲染视口的内容。

QGraphicsScene scene;

scene.addRect(QRectF(0, 0, 100, 200), QPen(Qt::black), QBrush(Qt::green));

QPixmap pixmap;

QPainter painter(&pixmap);

painter.setRenderHint(QPainter::Antialiasing);

scene.render(&painter);

painter.end();

pixmap.save("scene.png");

当源和目标区尺寸不匹配时,源的内容会比例缩放适合目标区。

3、拖拽

  由于QGraphicsView继承自QWidget,GraphicsView同样提供了拖拽功能。此外,为了方便,GraphicsView框架也为场景、图元提供拖拽支持。当视图接收到拖拽事件,GraphicsView框架会将拖拽事件翻译为QGraphicsSceneDragDropEvent事件,再发送到场景,场景接管事件,再把事件发送到光标下接受拖拽的第一个图元。

  为了开启图元拖拽,创建一个QDrag对象,传递启动拖拽的QWidget的指针。图元可以同时被多个视图观察,但只有一个视图可以拖拽图元。通常,拖拽是从按下鼠标或是移动鼠标开始的,在mousePressEvent()或mouseMoveEvent()中,可以从事件中得到原始的QWidget指针。

void CustomItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
    QMimeData *data = new QMimeData;
    data->setColor(Qt::green);
    QDrag *drag = new QDrag(event->widget());
    drag->setMimeData(data);
    drag->start();
}

要在场景中取拖拽事件,需要重新实现QGraphicsScene::dragEnterEvent()和QGraphicsItem子类里特定场景需要的事件处理器。

  图元也可以通过调用QGraphicsItem::setAcceptDrops()获得拖拽支持,为了处理将要进行的拖拽,需要重新实现QGraphicsItem的dragEnterEvent()、dragMoveEvent()、dropEvent()、dragLeaveEvent() 。

4、光标与工具提示

  QGraphicsItem支持光标(QgraphicsItem::setCursor)与工具提示(QGraphicsItem::setToolTip())。当光标进入到图元的区域,光标与工具提示被QGraphicsView激活(通过调用QGraphicsItem::contains()检测),也可以直接在视图上设置一个缺省光标(QGraphicsView::setCursor)。

5、动画

  GraphicsView框架支持多种层次的动画。使用动画框架可以很容易制作出动画。

GraphicsView框架支持的动画实现种类如下:

A、图元需要继承自QGraphicsObject,并且需要联结QPropertyAnimation属性。

B、创建继承自QObject和QGraphicsItem的图元,图元可以设置自己的定时器,在QObject::timeEvent()中增加步进的方式来控制动画。

C、通过调用QGraphicsScene::advance()来推进场景,依次调用QGraphicsItem::advance()。

6、OpenGL渲染

为了使用OpenGL渲染,需要设置一个新的QGLWidget作为QGraphicsView的视口:

QGraphicsView::setViewPort()

如果需要OpenGL提供反锯齿功能,则需要OpenGL采样缓冲支持。

1 QGraphicsView view(&scene);
2 view.setViewport(new QGLWidget(QGLFormat(QGL::SampleBuffers)));

7、图元组

  通过把一个图元做为另一个图元的孩子,可以得到图元组的大多数本质特性:所有图元会一起移动,所有变换会从父到子传递。

  另外,QGraphicsItemGroup是一个特殊的图元。为了增加和删除图元,它使用一个有用接口合并了子图元的事件处理。把一个图元加到QGraphicsItemGroup仍会保留图元的原始位置与变换,而给一个图元重新指定父图元则会让图元根据其新的父亲重新定位。可以用QGraphicsScene::createItemGroup()创建图元组。

8、图形组件和布局

  QT4.4通过QGraphicsWidget支持图形和图元布局。QGraphicsWidget类似于QWidget,但QGraphicsWidget并不从QPaintDevice继承,而是继承自QGraphicsItem。QGraphicsWidget支持事件、信号与槽、大小和策略。通过QGraphicsLinearLayout、QGraphicsGridLayout可以对图形组件进行布局管理。

  QGraphicsWidget继承了QWidget和QGraphicsItem的优点,如QWidget的样式、字体、调色板、布局方向以及QGraphicsItem的图形、独立精度和变换支持。

  QGraphicsLayout是专为QGraphicsWidget特殊设计的第二代布局框架。QGraphicsLayout的API类似于QLayout。通过QGraphicsLinearLayout和QGraphicsGridLayout可以管理组件与子布局。

9、嵌入组件

  图形视图框架为嵌入任何组件到场景提供了无缝支持。可以嵌入简单的组件,如QLineEdit、QPushButton,或是复杂的组件如QTableWidget,甚至是主窗口。

  要嵌入组件到场景,只需要调用QGraphicsScene::addWidget(),或是创建一个QGraphicsProxyWidget实例,手动嵌入组件。

  通过QGraphicsProxyWidget,图形视图框架可以深度整合客户组件特性,如光标、工具提示、鼠标、平板和键盘事件、子组件、动画、弹拉框、组件输入焦点和激活。QGraphicsProxyWidget甚至整合了嵌入组件的tab顺序,可以通过tab选择嵌入的组件。甚至可以嵌入一个QGraphicsView到场景。

  当变换和嵌入组件时,图形视图框架会确保组件会被独立变换。

 

========================================

原文链接:https://www.cnblogs.com/ybqjymy/p/14931414.html

 

posted @ 2022-07-17 09:13  [BORUTO]  阅读(6996)  评论(0)    收藏  举报