网络知识 娱乐 qt QGraphicsView 绘制多种图形

qt QGraphicsView 绘制多种图形

入门

先看一个简单的例子

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include 
#include 
MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    init();
}

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


void MainWindow::init()
{
    QGraphicsScene *pScene = new QGraphicsScene();
    pScene->addText("Hello, world!");
    QGraphicsView *pView = new QGraphicsView(pScene, this);

}

效果:
在这里插入图片描述

  • QGraphicsView
  • QCustomQGraphicsScene
  • QGraphicsItem

实时绘制

在这里插入图片描述
需求:至少有两种图形需要绘制

画一个图元的逻辑

在界面上(LabelGraphicsView类)需要有一个成员变量(m_graphicsBaseItem )负责当前图元的绘画。这个成员变量可以画多种类型的图元。

设计一个基类 GraphicsBaseItem (继承自QGraphicsItem),再扩展两个子类:

  1. MyPolylineItem (继承自GraphicsBaseItem ): 绘制多边形,自定义paint函数
  2. MyRectangleItem(继承自GraphicsBaseItem ):绘制长方形,自定义paint函数. (调用update后,会调用paint函数)

需要画矩形时,就把MyRectangleItem 赋值到GraphicsBaseItem ,调用paint函数绘制。需要画多边形时,使用MyRectangleItem
每个图元类自己内部维护一个画图状态(m_drawFinished)。如画图未完成,则可以继续加点,可以更新实时动态等。

画多个图元的逻辑

成员变量(m_graphicsBaseItem )负责当前图元的绘画,画图完成之后,更新画图状态(m_drawFinished),并重新new一个相同的图元,添加当场景中。清空成员变量(m_graphicsBaseItem )的值,等待下一个图元的绘制

GraphicsBaseItem 类

至少需要考虑一下场景:

  1. 画矩形框:鼠标拖动实时显示矩形的形状。那么就涉及到:
    (1)矩形框的起始位置
    (2)当前鼠标位置
    (3)矩形最终位置
    所以鼠标按下时,添加第一个点,拖动时更新鼠标当前位置m_mousePt,并绘图。鼠标松开时,加入最后一个点。所以还需要判断当前是否在绘图(bool m_drawFinished;)
  2. 画多边形:
    (1)点是否有重复
    (2)需要一个闭合函数:比如最后一个点自动连接到第一个点( int GetNearestVertex(const QPointF& pt);)
  3. 图行点击时高亮:QColor m_color; QColor m_lineHighlightColor;
#ifndef GRAPHICSBASEITEM_H
#define GRAPHICSBASEITEM_H
#include
#include "globaldefine.h"
class GraphicsBaseItem : public QGraphicsItem
{
public:
    GraphicsBaseItem(const QPolygonF& pts,
                     bool drawFinished = false,
                     QGraphicsItem *parent = Q_NULLPTR);
    // 查看图形类型,比如矩形,多边形
    virtual ShapeType GetShapeType() = 0;
    // 查看是否包含这个点
    virtual bool Contains(const QPointF& pt) = 0;
    //是否完成了绘图(鼠标拖动场景下使用,或者多边形绘制)
    virtual void FinishDrawing();
	//添加点,获取点
    void AddPoint(const QPointF& point);
    int GetPointCounts();
    bool GetPoint(int id, QPointF& pt);
    QPolygonF GetPoints();
    bool RemoveLastPoint();
    int GetNearestVertex(const QPointF& pt);
   //设置当前鼠标位置
   void SetMousePt(const QPointF& mousePt);
   //重置
   void Reset();
   //是否被选中
   void SetSelected(bool isSelected);
   virtual QRectF boundingRect() const override;

protected:
    QPolygonF m_pts;//闭合曲线首尾点无需重复存储
    QPointF m_mousePt;//画图时鼠标当前位置点
    bool    m_selected;//是否被选中
    bool    m_drawFinished;//是否完成绘图
};

#endif // GRAPHICSBASEITEM_H

MyRectangleItem类

主要实现该类的绘图功能 paint

#ifndef MYRECTANGLEITEM_H
#define MYRECTANGLEITEM_H
#include "globaldefine.h"
#include"mygraphicsbaseitem.h"
class MyRectangleItem : public GraphicsBaseItem
{
public:
    MyRectangleItem(const QPolygonF& pts,bool drawFinished = false,GraphicsBaseItem *parent = Q_NULLPTR);


    virtual ShapeType GetShapeType();
    //绘图 在updata后调用 MyPolylineItem
    virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
    virtual bool Contains(const QPointF& pt);
    virtual void FinishDrawing();//完成绘制并检查点的位置是否是左上点和右下点,如果不是就更新
};

#endif // MYRECTANGLEITEM_H

具体实现

#include "myrectangleitem.h"
#include
#include 
namespace
{
    bool IsGreaterThan(double d1, double d2)//比较d1是否大于d2
    {
        double eps = 1.0e-6;
        double delta = d1 - d2;
        if (delta > eps)
        {
            return true;
        }
        else
        {
            return false;
        }
    }

    QRectF makeRect(const QPointF& pt1, const QPointF& pt2)
    {
        double topLeftX = IsGreaterThan(pt1.x(), pt2.x()) ? pt2.x() : pt1.x();
        double topLeftY = IsGreaterThan(pt1.y(), pt2.y()) ? pt2.y() : pt1.y();
        double width = qAbs(pt1.x() - pt2.x());
        double height = qAbs(pt1.y() - pt2.y());
        return QRectF(topLeftX, topLeftY, width, height);
    }
}


MyRectangleItem::MyRectangleItem(const QPolygonF& pts,bool drawFinished ,GraphicsBaseItem *parent )
    :GraphicsBaseItem(pts, drawFinished, parent)
{

}



ShapeType MyRectangleItem::GetShapeType()
{
    return RECTANGLE;
}

void MyRectangleItem::paint(QPainter * painter, const QStyleOptionGraphicsItem * option, QWidget * widget)
{
    if (m_pts.isEmpty())
    {
        return;
    }

    painter->setPen(Qt::blue);
    painter->setBrush(Qt::NoBrush);
    painter->setOpacity(1.0);

    if (m_drawFinished)
    {
        if (m_pts.size() == 2)
        {
            QRectF rect = makeRect(m_pts[0], m_pts[1]);
            painter->drawRect(rect);
            painter->setBrush(Qt::blue);
			qDebug() <<"finish "<< m_pts[0] <<" , "<< m_pts[1];
        }
    }
    else
    {
        QRectF rect = makeRect(m_pts[0], m_mousePt);//m_mousePt用于追踪鼠标绘制
        painter->drawRect(rect);
		qDebug() << "unfinish " << m_pts[0] << " , " << m_mousePt;
    }
}

bool MyRectangleItem::Contains(const QPointF & pt)
{
    if (m_pts.count() != 2)
    {
        return false;
    }

    QRectF rect(m_pts[0],m_pts[1]);
    bool ret = rect.contains(pt);
    return ret;
}

void MyRectangleItem::FinishDrawing()
{
	GraphicsBaseItem::FinishDrawing();

    if (m_pts.count() == 2)
    {
        QRectF rect = makeRect(m_pts[0], m_pts[1]);
        QPointF topLeft = rect.topLeft();
        QPointF bottomRight = rect.bottomRight();
        if (topLeft != m_pts[0] || bottomRight != m_pts[1])
        {
            QPolygonF pts;
            pts.push_back(topLeft);
            pts.push_back(bottomRight);
            m_pts.swap(pts);
        }
    }
}

MyPolylineItem 类

多边形类
MyPolylineItem
多边形由一系列点组成,起始点和终止点比较重要
需要设置终止方案。

#ifndef MYPOLYLINEITEM_H
#define MYPOLYLINEITEM_H
#include"mygraphicsbaseitem.h"

class MyPolylineItem : public GraphicsBaseItem
{
public:
    MyPolylineItem(const QPolygonF& pts,bool drawFinished = false,GraphicsBaseItem *parent = Q_NULLPTR);
    virtual ShapeType GetShapeType();
    virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
    virtual QPainterPath shape() const override;
    virtual bool Contains(const QPointF& pt);
};

#endif // MYPOLYLINEITEM_H

绘图的逻辑

思路

纵向是三个鼠标事件

  1. virtual void mousePressEvent(QMouseEvent *event) override; virtual

  2. void mouseReleaseEvent(QMouseEvent *event) override; virtual void

  3. mouseMoveEvent(QMouseEvent *event) override;

横向是两种图元

  1. 矩形
  2. 多边形

矩形画法

按下鼠标左键,不放开拖动到目标位置,放开。于是按照时间顺序依次触发

  1. mousePressEvent 添加第一个点

  2. mouseMoveEvent 界面实时更新鼠标位置 画矩形

  3. mouseReleaseEvent 如果释放的点不是第一个点的位置,则认为是一个完整的矩形,添加最后一个点完成绘制。否则清空所有点

多边形画法

问题:

  1. 什么事件加点?mousePressEvent or mouseReleaseEvent ? 考虑到有人会在按下鼠标时后悔,再拖动鼠标到正确位置释放,所以选择在mouseReleaseEvent 事件中加点
  2. 什么时候标记第一个点:判断当前没有图形绘制时,也没有其他操作时,且触发了mouseReleaseEvent
  3. 什么情况完成绘图:最后一个点和第一个点重合时,结束绘画

所以在界面类中,我们需要标记状态

  1. 绘图
  2. 移动
  3. 默认

界面LabelGraphicsView

#include "LabelGraphicsView.h"
#include "myrectangleitem.h"
#include
 
LabelGraphicsView::LabelGraphicsView()
{
	init();
	QVector<QPointF> pts;
	m_GraphicsBaseItem = new MyRectangleItem(pts, false);
}
LabelGraphicsView::~LabelGraphicsView()
{
}
void LabelGraphicsView::init()
{
	setBackgroundBrush(QColor(255, 255, 255));
	setScene(&m_graphicsScene);
	setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);
	setOptimizationFlags(QGraphicsView::DontSavePainterState);
	setViewportUpdateMode(QGraphicsView::SmartViewportUpdate);
	setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
	m_graphicsScene.setBackgroundBrush(Qt::darkGray);
}

void LabelGraphicsView::mousePressEvent(QMouseEvent *event)
{
	//TODO:鼠标左键,点击绘制图形;
	if (event->button() == Qt::LeftButton)
	{
		QPoint pos = event->pos();
		QPointF leftButtonPressedPos = mapToScene(pos);
		m_GraphicsBaseItem->AddPoint(leftButtonPressedPos);
		viewport()->update();
	}
	QGraphicsView::mousePressEvent(event);
}
void LabelGraphicsView::mouseReleaseEvent(QMouseEvent *event)
{
	//TODO:鼠标释放之后操作
	QPoint pos = event->pos();
	QPointF ReleasePos = mapToScene(pos);
	m_GraphicsBaseItem->AddPoint(ReleasePos);
	m_GraphicsBaseItem->FinishDrawing();
	viewport()->update();
	ShapeType shapeType = m_GraphicsBaseItem->GetShapeType();
	QPolygonF pts = m_GraphicsBaseItem->GetPoints();
	auto newItem = new MyRectangleItem(pts, true);
	m_graphicsScene.addItem(newItem);
	m_GraphicsBaseItem->Reset();
}

//实时获取鼠标最新位置并绘图 
void LabelGraphicsView::mouseMoveEvent(QMouseEvent *event)
{
	//TODO:鼠标移动时,如果存在有效图形类型,进行图形绘制
	if (m_GraphicsBaseItem->GetShapeType() == RECTANGLE)
	{
		QPoint pos = event->pos();
		QPointF movePos = mapToScene(pos);
		m_GraphicsBaseItem->SetMousePt(movePos);
		viewport()->update();
	}
	QGraphicsView::mouseMoveEvent(event);
}

未完待续

参考文献

Qt中QGraphicsView架构下实时鼠标绘制图形