使用QT QGraphicsView图形框架实现画图功能

QGraphicsView、QGraphicsScene、QGraphicsItem是QT图形框架三个重要元素,通过QGraphicsItem创建图元,但是实际应用中,通常使用继承QGraphicsItem而来的QGraphicsObject。图元添加到QGraphicsScene中可以显示出来或者进行用户交互操作。QGraphicsView的作用是,将Scene中的部分或者全部显示出来,可以拖动或放大缩小,Scene和View的关系如下图所示。

 基本使用流程为:首先创建一个继承QWidget的类,用于盛放和显示View,包括成员:QGridLayout、QGraphicsScene、QGraphicsView。MyGraphicsView是继承自QGraphicsView的自定义View,用于替代QGraphicsView。

#include <QWidget>
#include <QGridLayout>
#include <QGraphicsScene>
#include <QGraphicsRectItem>
#include <QTextStream>
#include <QVector3D>
#include <QString>
#include <QGraphicsItem>
#include "mygraphicsview.h"
#include "mygraphicsobject.h"

class DxfWnd : public QWidget
{
    Q_OBJECT
public:
    explicit DxfWnd(QWidget *parent = nullptr);

    void Demo();
    void LoadXYZ(const QString &t_strFile);

    MyGraphicsView *m_view;

private:
    QGridLayout *layout;
    QGraphicsScene *m_scene;

    QList<MyGraphicsObject*> m_listPositionItem;

private slots:
    void RecvRatioFromGraphicsView(double, double, double, double);

};

#endif // DXFWND_H

构造函数

DxfWnd::DxfWnd(QWidget *parent) : QWidget(parent)
{
    layout = new QGridLayout();
    this->setLayout(layout);
    m_view = new MyGraphicsView(); // 定义一个视图

    connect(m_view, &MyGraphicsView::SendRatio2DxfWnd, this, &DxfWnd::RecvRatioFromGraphicsView);

    Demo();
}

在类中定义成员函数,创建图元,创建Scene,创建View,讲图元添加到Scene,将Scene添加到View,将View添加到QLayout中,实现图形的显示。

void DxfWnd::Demo(){
    m_scene = new QGraphicsScene();   // 定义一个场景,设置背景色为白色
    m_scene->setBackgroundBrush(Qt::white);
    QPen pen;   // 定义一个画笔,设置画笔颜色和宽度
    pen.setColor(QColor(0, 160, 230));
    pen.setWidth(1);

    QGraphicsRectItem *rectItem = new QGraphicsRectItem();   // 定义一个矩形图元
    rectItem->setRect(476415,3888005, 80, 80);
    rectItem->setPen(pen);
    rectItem->setBrush(QBrush(QColor(255, 0, 255)));
    rectItem->setFlag(QGraphicsItem::ItemIsMovable);

    m_scene->addItem(rectItem);
    LoadXYZ("D:/QT_temp/layout_test/data/0.xyz");

//    MyGraphicsObject *gi = new MyGraphicsObject();
//    gi->setPos(476415,3888005);
//    gi->SetValue(12.34);
//    m_scene->addItem(gi);


    m_view->setFixedSize(400, 300);
    m_view->setScene(m_scene);
    m_view->setDragMode(QGraphicsView::RubberBandDrag); //设置view橡皮筋框选区域

    layout->addWidget(m_view);
}

对于大量图元的管理,可以创建一个QVector<MyGraphicsObject *>,MyGraphicsObject是继承自QGraphicsObject的自定义图元(见上一篇博客)。通过QVector对图元进行显示或隐藏的设置,提升View的刷新速度。

void DxfWnd::LoadXYZ(const QString &t_strFile){
    // 读取数据
    QVector<QVector3D> vectorPoint;
    QFile file(t_strFile);
    if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
        QTextStream in(&file);
        while (!in.atEnd()) {

            // 读取一行
            QString line = in.readLine();
            QStringList strList = line.split(',');

            // to QVector3D
            if(strList.size() == 3) {
                QVector3D point;
                point.setX(strList.at(0).toDouble());
                point.setY(strList.at(1).toDouble());
                point.setZ(strList.at(2).toDouble());
                vectorPoint.append(point);
            }
        }
    }
    // Scene中加载
    for(auto const point : vectorPoint) {
        auto pPositionItem = new MyGraphicsObject();
        pPositionItem->setPos(point.x(), point.y());
        pPositionItem->SetValue(point.z());
        m_scene->addItem(pPositionItem);
        m_listPositionItem.append(pPositionItem);
    }
}

void DxfWnd::RecvRatioFromGraphicsView(double p0x, double p0y, double p2x, double p2y){
    if(p2x - p0x > 1000 || p2y - p0y > 1000){
        for(int i=0; i<m_listPositionItem.size(); i++) {
            if(i % 10 == 0) {
                m_listPositionItem.at(i)->show();
            }
            else{
                m_listPositionItem.at(i)->hide();
            }
        }
    }
    else{
        for(int i=0; i<m_listPositionItem.size(); i++) {
            m_listPositionItem.at(i)->show();
        }
    }
}

在实际使用中,通常使用继承QGraphicsView的自定义View,可以进行鼠标操作等用户交互操作的管理。

#ifndef MYGRAPHICSVIEW_H
#define MYGRAPHICSVIEW_H

#include <QObject>
#include <QWidget>
#include <QGraphicsView>
#include <QMouseEvent>
#include <QMenu>
#include <QString>
#include <QList>
#include <QGraphicsItem>
#include <math.h>

enum MODE{selectMode, drawMode, deleteMode};

class MyGraphicsView : public QGraphicsView
{
    Q_OBJECT
public:
    MyGraphicsView();

private:
    QPointF centerAnchor;
    QPointF posAnchor;
    bool viewMove = false;
    double m_scaleValue = 1;
    MODE mode = selectMode;

    QGraphicsItem *selectedItem = nullptr;

    void GetScale();

    QVector<QPointF> point2LineCache;

public slots:
    void slot_rotateLeft();// { rotate(-30); }
    void slot_rotateRight();// { rotate(30); }
    void slot_reset();
    void ActiveDrawLine();
    void CancelDrawLine();
    void DeleteItem();
    void StopDeleteMode();

protected:
    virtual void mousePressEvent(QMouseEvent *event);
    virtual void mouseReleaseEvent(QMouseEvent *event);
    virtual void wheelEvent(QWheelEvent *event);
    virtual void mouseMoveEvent(QMouseEvent *event);
    void view_zoomIn();//  { scale(1.2, 1.2); }
    void veiw_zoomOut();// { scale(1/1.2, 1/1.2); }

signals:
    void SendRatio2DxfWnd(double, double, double, double);
};

#endif // MYGRAPHICSVIEW_H

mousePressEvent定义了鼠标点击操作

void MyGraphicsView::mousePressEvent(QMouseEvent *event)
{
    if( event->button() == Qt::RightButton)
    {
        QMenu *mouseLeftMenu = new QMenu(this);
        QAction* rotateLeft = new QAction(tr("rotateLeft"), this);
        QAction* rotateRight = new QAction(tr("rotateRight"), this);
        QAction* draw = new QAction(tr("drawLine"), this);
        QAction* cancel = new QAction(tr("stopDrawLine"), this);
        QAction* zoomReset = new QAction(tr("zoomReset"), this);
        QAction* deleteItem = new QAction(tr("delete"), this);
        QAction* cancelDeleteMode = new QAction(tr("stopDelete"), this);

        mouseLeftMenu->addAction(zoomReset);
//        mouseLeftMenu->addAction(rotateLeft);
//        mouseLeftMenu->addAction(rotateRight);
        if (mode == selectMode){
            mouseLeftMenu->addAction(draw);
            mouseLeftMenu->addAction(deleteItem);
        }
        else if(mode == drawMode){
            mouseLeftMenu->addAction(cancel);
        }
//        mouseLeftMenu->addAction(zoomOut);
        else if (mode == deleteMode){
            mouseLeftMenu->addAction(cancelDeleteMode);
        }

        mouseLeftMenu->move(cursor().pos());
        mouseLeftMenu->show();

        connect(rotateLeft, SIGNAL(triggered()), this, SLOT(slot_rotateLeft()));
        connect(rotateRight, SIGNAL(triggered()), this, SLOT(slot_rotateRight()));
        connect(draw, SIGNAL(triggered()), this, SLOT(ActiveDrawLine()));
        connect(cancel, SIGNAL(triggered()), this, SLOT(CancelDrawLine()));
        connect(zoomReset, SIGNAL(triggered()), this, SLOT(slot_reset()));
        connect(deleteItem, SIGNAL(triggered()), this, SLOT(DeleteItem()));
        connect(cancelDeleteMode, SIGNAL(triggered()), this, SLOT(StopDeleteMode()));
    }
    else if(event->button() == Qt::MiddleButton)
    {
        mode = selectMode;
        viewMove = true;
        GetScale();
//        centerAnchor = mapToScene(event->pos()) - event->pos() + QPointF(width() / 2, height() / 2);
        centerAnchor = mapToScene(QPoint(width() / 2, height() / 2));
        posAnchor = event->pos();
    }
    else if(event->button() == Qt::LeftButton){
        if(mode == drawMode){
            point2LineCache.append(mapToScene(event->pos()));
            if (point2LineCache.size() == 2){
                QLineF *line = new QLineF(point2LineCache[0], point2LineCache[1]);
                scene()->addLine(*line);
                point2LineCache.pop_front();

                scene()->update();
            }
        }
        else{
            QGraphicsItem *selectedItem = this->itemAt(event->pos());
            if(selectedItem){
                scene()->removeItem(selectedItem);
            }
        }
    }
}

点击右键,会弹出菜单

点击左键,进行画线、删除等操作。

点击滚轮,进行View显示范围的移动(配合mouseMoveEvent实现View拖动)。

 

centerAnchor = mapToScene(QPoint(width() / 2, height() / 2));

该语句功能是得到画面中心在Scene中的坐标。

posAnchor = event->pos();

是获取鼠标在View中的坐标(注意存在Scene坐标系和View坐标系两个坐标系,View坐标系是像素距离坐标系,Scene坐标系是图元所在的实际坐标系)。

void MyGraphicsView::mouseMoveEvent(QMouseEvent *event){
    if(viewMove){
        QPointF offsetPos = event->pos() - posAnchor;
//        setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
        GetScale();
        centerOn(centerAnchor - offsetPos * m_scaleValue);
    }
}

在mouseMoveEvent中,得到鼠标移动后的位置,计算鼠标移动了多少距离,乘画面比例(即1像素距离等于多少实际距离),得到View的中心应该移动多少实际距离。

centerOn(centerAnchor - offsetPos * m_scaleValue);

该函数是将View的中心放置在一个新的Scene点上,即实现了可视范围的拖动。

wheelEvent实现放大缩小

void MyGraphicsView::wheelEvent(QWheelEvent *event){
    auto test = this->mapToScene( this->viewport()->geometry() );
    double p0x = test[0].x();
    double p0y = test[0].y();
    double p2x = test[2].x();
    double p2y = test[2].y();

    if(event->delta() > 0){                    // 当滚轮远离使用者时
        view_zoomIn();                // 进行放大
    }else{                                     // 当滚轮向使用者方向旋转时
        veiw_zoomOut();               // 进行缩小
    }

    emit SendRatio2DxfWnd(p0x, p0y, p2x, p2y);
}

void MyGraphicsView::view_zoomIn()
{
    this->scale(1.2, 1.2);
}
void MyGraphicsView::veiw_zoomOut()
{
    this->scale(1/1.2, 1/1.2);
}

计算1像素距离代表多少实际距离,通过如下函数实现。

void MyGraphicsView::GetScale(){
    auto test = this->mapToScene( this->viewport()->geometry() );
    double p0x = test[0].x();
    double p2x = test[2].x();

    m_scaleValue = (p2x - p0x) / this->width();
}

mapToScene函数得到View四个角点在Scene坐标系中的实际坐标,width()函数获取View的像素宽度。

自定义View全部成员函数如下

#include "mygraphicsview.h"

MyGraphicsView::MyGraphicsView()
{
    setMouseTracking(true);
}

void MyGraphicsView::mousePressEvent(QMouseEvent *event)
{
    if( event->button() == Qt::RightButton)
    {
        QMenu *mouseLeftMenu = new QMenu(this);
        QAction* rotateLeft = new QAction(tr("rotateLeft"), this);
        QAction* rotateRight = new QAction(tr("rotateRight"), this);
        QAction* draw = new QAction(tr("drawLine"), this);
        QAction* cancel = new QAction(tr("stopDrawLine"), this);
        QAction* zoomReset = new QAction(tr("zoomReset"), this);
        QAction* deleteItem = new QAction(tr("delete"), this);
        QAction* cancelDeleteMode = new QAction(tr("stopDelete"), this);

        mouseLeftMenu->addAction(zoomReset);
//        mouseLeftMenu->addAction(rotateLeft);
//        mouseLeftMenu->addAction(rotateRight);
        if (mode == selectMode){
            mouseLeftMenu->addAction(draw);
            mouseLeftMenu->addAction(deleteItem);
        }
        else if(mode == drawMode){
            mouseLeftMenu->addAction(cancel);
        }
//        mouseLeftMenu->addAction(zoomOut);
        else if (mode == deleteMode){
            mouseLeftMenu->addAction(cancelDeleteMode);
        }

        mouseLeftMenu->move(cursor().pos());
        mouseLeftMenu->show();

        connect(rotateLeft, SIGNAL(triggered()), this, SLOT(slot_rotateLeft()));
        connect(rotateRight, SIGNAL(triggered()), this, SLOT(slot_rotateRight()));
        connect(draw, SIGNAL(triggered()), this, SLOT(ActiveDrawLine()));
        connect(cancel, SIGNAL(triggered()), this, SLOT(CancelDrawLine()));
        connect(zoomReset, SIGNAL(triggered()), this, SLOT(slot_reset()));
        connect(deleteItem, SIGNAL(triggered()), this, SLOT(DeleteItem()));
        connect(cancelDeleteMode, SIGNAL(triggered()), this, SLOT(StopDeleteMode()));
    }
    else if(event->button() == Qt::MiddleButton)
    {
        mode = selectMode;
        viewMove = true;
        GetScale();
//        centerAnchor = mapToScene(event->pos()) - event->pos() + QPointF(width() / 2, height() / 2);
        centerAnchor = mapToScene(QPoint(width() / 2, height() / 2));
        posAnchor = event->pos();
    }
    else if(event->button() == Qt::LeftButton){
        if(mode == drawMode){
            point2LineCache.append(mapToScene(event->pos()));
            if (point2LineCache.size() == 2){
                QLineF *line = new QLineF(point2LineCache[0], point2LineCache[1]);
                scene()->addLine(*line);
                point2LineCache.pop_front();

                scene()->update();
            }
        }
        else{
            QGraphicsItem *selectedItem = this->itemAt(event->pos());
            if(selectedItem){
                scene()->removeItem(selectedItem);
            }
        }
    }
}

void MyGraphicsView::mouseMoveEvent(QMouseEvent *event){
    if(viewMove){
        QPointF offsetPos = event->pos() - posAnchor;
//        setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
        GetScale();
        centerOn(centerAnchor - offsetPos * m_scaleValue);
    }
}

void MyGraphicsView::mouseReleaseEvent(QMouseEvent *event){
    viewMove = false;
}

void MyGraphicsView::wheelEvent(QWheelEvent *event){
    auto test = this->mapToScene( this->viewport()->geometry() );
    double p0x = test[0].x();
    double p0y = test[0].y();
    double p2x = test[2].x();
    double p2y = test[2].y();

    if(event->delta() > 0){                    // 当滚轮远离使用者时
        view_zoomIn();                // 进行放大
    }else{                                     // 当滚轮向使用者方向旋转时
        veiw_zoomOut();               // 进行缩小
    }

    emit SendRatio2DxfWnd(p0x, p0y, p2x, p2y);
}

void MyGraphicsView::GetScale(){
    auto test = this->mapToScene( this->viewport()->geometry() );
    double p0x = test[0].x();
    double p2x = test[2].x();

    m_scaleValue = (p2x - p0x) / this->width();
}

void MyGraphicsView::view_zoomIn()
{
    this->scale(1.2, 1.2);
}
void MyGraphicsView::veiw_zoomOut()
{
    this->scale(1/1.2, 1/1.2);
}
void MyGraphicsView::slot_rotateLeft()
{
    this->rotate(-30);
}
void MyGraphicsView::slot_rotateRight()
{
    this->rotate(30);
}
void MyGraphicsView::slot_reset()
{
    QRectF rectItem = scene()->itemsBoundingRect();
    QRectF rectView = this->rect();
    qreal ratioView = rectView.height() / rectView.width();
    qreal ratioItem = rectItem.height() / rectItem.width();
    if (ratioView > ratioItem)
    {
        rectItem.moveTop(rectItem.width()*ratioView - rectItem.height());
        rectItem.setHeight(rectItem.width()*ratioView);

        rectItem.setWidth(rectItem.width() * 1.2);
        rectItem.setHeight(rectItem.height() * 1.2);
    }
    else
    {
        rectItem.moveLeft(rectItem.height()/ratioView - rectItem.width());
        rectItem.setWidth(rectItem.height()/ratioView);

        rectItem.setWidth(rectItem.width() * 1.2);
        rectItem.setHeight(rectItem.height() * 1.2);
    }

    this->fitInView(rectItem, Qt::KeepAspectRatio);
}
void MyGraphicsView::ActiveDrawLine(){
    mode = drawMode;
}
void MyGraphicsView::CancelDrawLine(){
    mode = selectMode;
    point2LineCache.clear();
}
void MyGraphicsView::DeleteItem(){
    mode = deleteMode;
}
void MyGraphicsView::StopDeleteMode(){
    mode = selectMode;
}

利用一个结构体

enum MODE{selectMode, drawMode, deleteMode};

来表示当前鼠标是什么状态。

  • 1
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值