目录
一、功能需求
二、实现效果
三、实现方法
一、功能需求
3D测量软件中,需要在轮廓上进行二次编程,需要显示轮廓线,然后可以调节矩形框的范围的获取参数,如华汉的HyperShape3D软件,对轮廓的编程界面如下。
二、实现效果
2个矩形框 + 1个轮廓
1个矩形框 + 1个轮廓
三、实现方法
找到了一篇比较符合需求的博客,基于QChartView实现;
Qt Charts使用(重写QChartView,实现一些自定义功能)_讳疾忌医丶的博客-CSDN博客_qtcharts使用
该文中重写了paintEvent(QPaintEvent *event),绘制2个矩形框。然而当我需要添加更多的矩形框时,发现需要添加很多重复性的代码,扩展性极差。为此我在此基础上做了改进,QChartView是基于qt的Graphics/View框架实现的,因此我可以将矩形框封装成一个图元QGraphicsItem,然后在场景中添加图元。
关键代码如下
自定义QChartView
#ifndef MYCHARTS_H
#define MYCHARTS_H
#include <QWidget>
#include <QtCharts/QLineSeries>
#include <QtCharts/QValueAxis>
#include <QChart>
#include <QChartView>
#include "processpropertyeditor_global.h"
QT_CHARTS_USE_NAMESPACE
class GraphicsRangeRectItem;
class PROCESSPROPERTYEDITORSHARED_EXPORT ContourChartView : public QChartView
{
Q_OBJECT
public:
enum ItemTheme
{
LightPink = 0, /* 浅粉红 */
Violet, /* 紫罗兰 */
SkyBlue, /* 天蓝色 */
Cyan, /* 青色 */
SeaGreen, /* 海洋绿 */
Yellow, /* 纯黄 */
Gold, /* 金 */
LightGrey, /* 浅灰色 */
};
explicit ContourChartView(QWidget *parent = nullptr);
GraphicsRangeRectItem *addItem(ItemTheme theme, const QPointF &topleft);
void lineSeriesAppend(const QList<QPointF> &points);
protected:
void paintEvent(QPaintEvent *event) override;
void mousePressEvent(QMouseEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override;
void contextMenuEvent(QContextMenuEvent *event) override;
public:
QMap<ItemTheme, QColor > m_colors;
QLineSeries *m_pSeries;
QChart *m_pChart;
};
//
#include "ContourChartView.h"
#include "GraphicsRangeRectItem.h"
#include <QMenu>
#include <QDebug>
#include <QtMath>
ContourChartView::ContourChartView(QWidget *parent)
: QChartView(parent)
{
int a = 100;
m_colors.insert(ItemTheme::LightPink, QColor(255, 182, 193, a));
m_colors.insert(ItemTheme::Violet, QColor(238, 130, 238, a));
m_colors.insert(ItemTheme::SkyBlue, QColor(135, 206, 235, a));
m_colors.insert(ItemTheme::Cyan, QColor(0, 255, 255, a));
m_colors.insert(ItemTheme::SeaGreen, QColor(46, 139, 87, a));
m_colors.insert(ItemTheme::Yellow, QColor(255, 255, 0, a));
m_colors.insert(ItemTheme::Gold, QColor(255, 215, 0, a));
m_colors.insert(ItemTheme::LightGrey, QColor(211, 211, 211, a));
m_pSeries = new QLineSeries();
m_pSeries->setUseOpenGL(true);
m_pChart = new QChart();
m_pChart->setTheme(QChart::ChartThemeDark);
m_pChart->legend()->hide();
this->setChart(m_pChart);
this->setRenderHints(QPainter::Antialiasing);
}
GraphicsRangeRectItem *ContourChartView::addItem(ContourChartView::ItemTheme theme,
const QPointF &topleft)
{
GraphicsRangeRectItem *rectItem = new GraphicsRangeRectItem;
rectItem->setRect(QRectF(topleft, QSizeF(60, 700)));
rectItem->setPen(QPen(QColor(205, 104, 57, 100)));
rectItem->setBrush(m_colors.value(theme));
rectItem->setFlags(QGraphicsItem::ItemIsSelectable | QGraphicsItem::ItemIsMovable);
rectItem->setZValue(100);
this->scene()->addItem(rectItem);
return rectItem;
}
void ContourChartView::lineSeriesAppend(const QList<QPointF> &points)
{
QList<QAbstractSeries *>series = m_pChart->series();
if(series.contains(m_pSeries))
{
m_pChart->removeSeries(m_pSeries);
}
m_pSeries->clear();
m_pSeries->append(points);
m_pChart->addSeries(m_pSeries);
m_pChart->createDefaultAxes();
}
void ContourChartView::paintEvent(QPaintEvent *event)
{
QChartView::paintEvent(event);
}
void ContourChartView::mousePressEvent(QMouseEvent *event)
{
QChartView::mousePressEvent(event);
}
void ContourChartView::mouseMoveEvent(QMouseEvent *event)
{
QChartView::mouseMoveEvent(event);
}
void ContourChartView::mouseReleaseEvent(QMouseEvent *event)
{
QChartView::mouseReleaseEvent(event);
}
void ContourChartView::contextMenuEvent(QContextMenuEvent *event)
{
QMenu menu(this);
menu.addAction(QStringLiteral("放大"), [ = ]()
{
chart()->zoom(1.2);
});
menu.addAction(QStringLiteral("缩小"), [ = ]()
{
chart()->zoom(0.8);
});
menu.addAction(QStringLiteral("还原"), [ = ]()
{
chart()->zoomReset();
});
menu.exec(event->globalPos());
}
自定义矩形框图元
#ifndef GRAPHICSRANGERECTITEM_H
#define GRAPHICSRANGERECTITEM_H
#include <QGraphicsRectItem>
class GraphicsRangeRectItem: public QObject, public QGraphicsRectItem
{
Q_OBJECT
public:
enum E_HandleFlag : int
{
Default = 0x00,
AtLeft = 0x01,
AtRight = 0x02,
AtCenter = 0x03
};
explicit GraphicsRangeRectItem(QGraphicsItem *parent = Q_NULLPTR);
~GraphicsRangeRectItem();
signals:
void stateChanged();
protected:
virtual void hoverMoveEvent(QGraphicsSceneHoverEvent *event) override;
virtual void hoverLeaveEvent(QGraphicsSceneHoverEvent *event) override;
virtual void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
virtual void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;
virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
private:
E_HandleFlag handleAt(const QPointF &pos);
private:
QMap<E_HandleFlag, Qt::CursorShape> handleCursors;
E_HandleFlag handleSelected = Default;
const int MIN_BOX_WIDTH = 20;
};
#endif // GRAPHICSRANGERECTITEM_H
//
#include "GraphicsRangeRectItem.h"
#include <QCursor>
#include <QGraphicsSceneHoverEvent>
#include <QDebug>
#include <QGraphicsScene>
#include <QGraphicsView>
GraphicsRangeRectItem::GraphicsRangeRectItem(QGraphicsItem *parent)
: QGraphicsRectItem(parent)
{
handleCursors[Default] = Qt::ArrowCursor;
handleCursors[AtLeft] = Qt::SizeHorCursor;
handleCursors[AtRight] = Qt::SizeHorCursor;
this->setFlags(QGraphicsItem::ItemIsSelectable |
QGraphicsItem::ItemIsFocusable);
setAcceptHoverEvents(true);
}
GraphicsRangeRectItem::~GraphicsRangeRectItem()
{
}
void GraphicsRangeRectItem::hoverMoveEvent(QGraphicsSceneHoverEvent *event)
{
if(this->isSelected())
{
E_HandleFlag handle = this->handleAt(event->pos());
this->setCursor(QCursor(handleCursors.value(handle)));
}
QGraphicsRectItem::hoverMoveEvent(event);
}
void GraphicsRangeRectItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *event)
{
this->setCursor(QCursor(Qt::ArrowCursor));
QGraphicsRectItem::hoverLeaveEvent(event);
}
void GraphicsRangeRectItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
handleSelected = this->handleAt(event->pos());
QGraphicsRectItem::mousePressEvent(event);
}
void GraphicsRangeRectItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
switch (handleSelected)
{
case E_HandleFlag::AtLeft:
{
QRectF oldRect = this->rect();
/* 最小宽度限制 */
if(oldRect.right() - event->pos().x() < MIN_BOX_WIDTH)
{
return;
}
QRectF newRect = oldRect;
newRect.setLeft(event->pos().x());
this->setRect(newRect);
update();
}
break;
case E_HandleFlag::AtRight:
{
QRectF oldRect = this->rect();
/* 最小宽度限制 */
if(event->pos().x() - oldRect.left() < MIN_BOX_WIDTH)
{
return;
}
QRectF newRect = oldRect;
newRect.setRight(event->pos().x());
this->setRect(newRect);
update();
}
break;
case E_HandleFlag::AtCenter:
{
QRectF oldRect = this->rect();
/* 防止超出左右边界 */
int leftLimit = 0;
int rightLimit = this->scene()->views().at(0)->size().width();
if(event->pos().x() <= leftLimit || event->pos().x() >= rightLimit)
{
return;
}
QRectF newRect = oldRect;
newRect.moveCenter(QPointF(event->pos().x(), oldRect.center().y()));
this->setRect(newRect);
update();
}
break;
default:
break;
}
}
void GraphicsRangeRectItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
QGraphicsItem::mouseReleaseEvent(event);
handleSelected = Default;
if(event->button() == Qt::LeftButton)
{
emit stateChanged();
}
this->update();
}
GraphicsRangeRectItem::E_HandleFlag GraphicsRangeRectItem::handleAt(const QPointF &pos)
{
QRectF rect = this->rect();
static const int check_radius = 3;
if (std::abs(pos.x() - rect.right()) < check_radius)
{
return E_HandleFlag::AtRight;
}
else if (std::abs(pos.x() - rect.left()) < check_radius)
{
return E_HandleFlag::AtLeft;
}
else if(rect.contains(pos))
{
return E_HandleFlag::AtCenter;
}
return E_HandleFlag::Default;
}
使用
创建窗口
ContourChartView *graphicsView = new ContourChartView();
添加轮廓
graphicsView->lineSeriesAppend(const QList<QPointF> &points);
添加矩形框
GraphicsRangeRectItem *rectItem1 = chartView()->addItem(ContourChartView::ItemTheme::LightPink, QPointF(100, 10));
rectItem1->setToolTip(QStringLiteral("基准对象"));
GraphicsRangeRectItem *rectItem2 = chartView()->addItem(ContourChartView::ItemTheme::Violet, QPointF(400, 10));
rectItem2->setToolTip(QStringLiteral("测量对象"));
坐标映射,需要将场景中的坐标转换到chart图表上的坐标值
connect(rectItem1, &GraphicsRangeRectItem::stateChanged, this, [ = ]()
{
double xl = chartView()->chart()->mapToValue(QPointF(rectItem1->rect().left(), 0)).x();
double xr = chartView()->chart()->mapToValue(QPointF(rectItem1->rect().right(), 0)).x();
});