Qt 图形视图框架 (一)

    如果要绘制成千上万的图形,并且对它们进行控制,比如拖动这些图形、检测它们的位置以及判断它们是否碰撞等,可以使用Qt提供的图形视图框架来进行设计。

    图形视图框架提供了一个基于图形项的模型视图编程方法,主要由场景视图图形项三部分组成,这三部分分别由QGraphicsSceneQGraphicsViewQGraphicsItem这三个类来表示。多个视图可以查看一个场景,场景中包含各种各样几何形状的图形项。

    图形视图框架可以管理数量庞大的自定义2D图形项,并且可以与它们进行交互。使用视图部件可以使这些图形项可视化,视图还支持缩放与旋转。框架中包含了一个事件传播架构,提供了和场景中的图形项进行精确的双精度交互的能力。图形视图框架使用一个BSP(Binary Space Partitioning)树来快速发现图形项。可通过Graphics View Framework关键字查看相关帮助。

 场景

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

1) 提供用于管理大量图形项的高速接口

2) 传播事件到每一个图形项

3) 管理图形项的状态,比如选择和处理焦点

4) 提供无变换的渲染功能,主要用于打印


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

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

扫描二维码关注公众号,回复: 2291022 查看本文章

视图

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

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

图形项

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

    QGraphicsItem主要支持如下功能:

1) 鼠标按下、移动、释放、双击、悬停、滚轮和右键菜单事件

2) 键盘输入焦点和键盘事件

3) 拖放事件

4) 碰撞检测

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

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

    下面是纯虚函数的实现示例:

QRectF MyItem::boundingRect() const
{
    qreal penWidth = 1; //画笔宽度
    return QRectF(0 - penWidth / 2, 0 - penWidth / 2,
                  20 + penWidth, 20 + penWidth);
}
void MyItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *)
{
    painter->setBrush(Qt::red);
    painter->drawRect(0, 0, 20, 20);
}

图形视图框架的坐标系统

    图形视图框架基于笛卡尔坐标系统。图形视图框架中有3个有效的坐标系统:图形项坐标、场景坐标和视图坐标。进行绘图时,场景坐标对应QPainter的逻辑坐标,视图坐标对应设备坐标。

图形项坐标

    图形项使用自己的本地坐标系统,坐标通常是以(0,0)为原点,而这也是所有变换的中心。当要创建一个自定义图形项时,只需要考虑图形项的坐标系统,而且一个图形项的边界矩形和图形形状都是在图形项坐标系统中的。

    图形项的位置是指图形项的原点在其父图形项或者场景中的位置。可以使用setPos()函数来指定图形项的位置,如果没有指定,默认出现在父图形项或者场景的原点处。

    子图形项的位置和坐标是相对于父图形项的,虽然父图形项的坐标变换会隐含地变换子图形项,但子图形项的坐标不会受到父图形项的变换的影响。例如,在没有坐标变换时,子图形项就在父图形项的(10,0)点,那么子图形项中的(0,10)点就对应了父图形项的(10,10)点。现在即使父图形项进行了旋转或者缩放,子图形项的(0,10)点仍对应着父图形项的(10,10)点。但是相对于场景,子图形项就会跟随父图形项的变换,例如,父图形项放大为(2x,2x),那么子图形项在场景中的位置就会变成(20,0),它的(10,0)点就会对应着场景中的(40,0)点。

    所有的图形项都会使用确定的顺序来进行绘制,这个顺序也决定了单击场景时哪个图形项会先获得鼠标输入。一个子图形项会堆叠在父图形项的上面,而兄弟图形项会以插入顺序进行堆叠。所有图形项都包含一个Z值来设置它们的层叠顺序,一个图形项的Z值默认为0,可以使用QGraphicsItem::setZValue()来改变一个图形项的Z值,从而使它堆叠到其兄弟图形项的上面(使用较大的Z值)或者下面(使用较小的Z值)。

场景坐标

    场景坐标是所有图形项的基础坐标系统。场景坐标的原点在场景的中心,x和y坐标分别向右和向下增大。

视图坐标

    视图坐标的每一个单位对应一个像素,原点(0,0)总在QGraphicsView视口的左上角,而右下角是(宽,高)。所有的鼠标事件和拖放事件最初都是使用视图坐标接收的。

坐标映射

    不仅可以在视图、场景和图形项之间使用坐标映射,还可以在子图形项、父图形项或者图形项、图形项之间进行坐标映射。所有的映射函数都可以映射点、矩形、多边形和路径。例如要获取在视图中的一个椭圆形中包含的图形项,则可以先传递一个QPainterPath对象作为参数给mapToScene()函数,然后传递映射后的路径给QGraphicsScene::items()函数。

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

事件处理与传播

    图形视图框架中的事件都是先由视图进行接收,然后传递给场景,再由场景传递给相应的图形项。而对于键盘事件,它会传递给获得焦点的图形项,可以使用QGraphicsScene类的setFocusItem()函数或者图形项自身调用setFocus()函数来设置焦点图形项。默认的,如果场景没有获得焦点,那么所有的键盘事件都会被丢弃。场景中的图形项获得了焦点,场景也会自动获得焦点。

下面是一个应用实例:

main.cpp
#include <QApplication>
#include "myitem.h"
#include "myview.h"
#include <QTime>

int main(int argc, char* argv[ ])
{
    QApplication app(argc, argv);
    qsrand(QTime(0, 0, 0).secsTo(QTime::currentTime()));
    QGraphicsScene scene;
    scene.setSceneRect(-200, -150, 400, 300);

    for (int i = 0; i < 5; ++i) {
        MyItem *item = new MyItem;
        item->setColor(QColor(qrand() % 256, qrand() % 256, qrand() % 256));
        item->setPos(i * 50 - 90, -50);
        scene.addItem(item);
    }

    MyView view;
    view.setScene(&scene);
    view.setBackgroundBrush(QPixmap("../myView/background.png"));
    view.show();
    return app.exec();
}
myitem.h
#include <QGraphicsItem>

class MyItem : public QGraphicsItem
{
public:
    MyItem();
    QRectF boundingRect() const;
    void paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
               QWidget *widget);

    void setColor(const QColor &color) { brushColor = color; }

private:
    QColor brushColor;

protected:
    void keyPressEvent(QKeyEvent *event);
    void mousePressEvent(QGraphicsSceneMouseEvent *event);
    void hoverEnterEvent(QGraphicsSceneHoverEvent *event);
    void contextMenuEvent(QGraphicsSceneContextMenuEvent *event);

};
myitem.cpp
#include "myitem.h"
#include <QPainter>
#include <QCursor>
#include <QKeyEvent>
#include <QGraphicsSceneHoverEvent>
#include <QGraphicsSceneContextMenuEvent>
#include <QMenu>

MyItem::MyItem()
{
    brushColor = Qt::red;

    setFlag(QGraphicsItem::ItemIsFocusable);
    setFlag(QGraphicsItem::ItemIsMovable);
    setAcceptHoverEvents(true);

}

QRectF MyItem::boundingRect() const
{
    qreal adjust = 0.5;
    return QRectF(-10 - adjust, -10 - adjust,
                  20 + adjust, 20 + adjust);
}

void MyItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *,
                   QWidget *)
{
    if (hasFocus()) {
        painter->setPen(QPen(QColor(255, 255, 255, 200)));
    } else {
        painter->setPen(QPen(QColor(100, 100, 100, 100)));
    }
    painter->setBrush(brushColor);
    painter->drawRect(-10, -10, 20, 20);
}

// 鼠标按下事件处理函数,设置被点击的图形项获得焦点,并改变光标外观
void MyItem::mousePressEvent(QGraphicsSceneMouseEvent *)
{
    setFocus();
    setCursor(Qt::ClosedHandCursor); //设置光标为手握下的形状
}

// 键盘按下事件处理函数,判断是否是向下方向键,如果是,则向下移动图形项
void MyItem::keyPressEvent(QKeyEvent *event)
{
    if (event->key() == Qt::Key_Down)
        moveBy(0, 10);
}
// 悬停事件处理函数,设置光标外观和提示
void MyItem::hoverEnterEvent(QGraphicsSceneHoverEvent *)
{
    setCursor(Qt::OpenHandCursor); //设置光标为手张开的形状
    setToolTip("I am item");
}
// 右键菜单事件处理函数,为图形项添加一个右键菜单
void MyItem::contextMenuEvent(QGraphicsSceneContextMenuEvent *event)
{
    QMenu menu;
    QAction *moveAction = menu.addAction("move back");
    QAction *selectedAction = menu.exec(event->screenPos());
    if (selectedAction == moveAction) {
        setPos(0, 0);
    }
}

myview.h
#include <QGraphicsView>

class MyView : public QGraphicsView
{
    Q_OBJECT
public:
    explicit MyView(QWidget *parent = 0);
protected:
    void keyPressEvent(QKeyEvent *event);

};

myview.cpp
#include "myview.h"
#include <QKeyEvent>

MyView::MyView(QWidget *parent) :
    QGraphicsView(parent)
{
}

void MyView::keyPressEvent(QKeyEvent *event)
{
    switch (event->key())
    {
    case Qt::Key_Plus :
        scale(1.2, 1.2);
        break;
    case Qt::Key_Minus :
        scale(1 / 1.2, 1 / 1.2);
        break;
    case Qt::Key_Right :
        rotate(30);
        break;
    }
    QGraphicsView::keyPressEvent(event);//一定要加上这个,否则在场景和图形项中就无法再接收到该事件了
}

鼠标拖动item后:


键盘按下+ - 进行缩放后:



猜你喜欢

转载自blog.csdn.net/qq_25800311/article/details/81002822
今日推荐