QGraphicsView、QGraphicsScene、QGraphicsItem是QT图形框架三个重要元素,通过QGraphicsItem创建图元,但是实际应用中,通常使用继承QGraphicsItem而来的QGraphicsObject。图元添加到QGraphicsScene中可以显示出来或者进行用户交互操作。QGraphicsView的作用是,将Scene中的部分或者全部显示出来,可以拖动或放大缩小,Scene和View的关系如下图所示。
基本使用流程为:首先创建一个继承QWidget的类,用于盛放和显示View,包括成员:QGridLayout、QGraphicsScene、QGraphicsView。MyGraphicsView是继承自QGraphicsView的自定义View,用于替代QGraphicsView。
#include
#include
#include
#include
#include
#include
#include
#include
#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 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
void DxfWnd::LoadXYZ(const QString &t_strFile){
// 读取数据
QVector 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; ishow();
}
else{
m_listPositionItem.at(i)->hide();
}
}
}
else{
for(int i=0; ishow();
}
}
}
在实际使用中,通常使用继承QGraphicsView的自定义View,可以进行鼠标操作等用户交互操作的管理。
#ifndef MYGRAPHICSVIEW_H
#define MYGRAPHICSVIEW_H
#include
#include
#include
#include
#include
#include
#include
#include
#include
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 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};
来表示当前鼠标是什么状态。