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


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

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

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include "mygraphicsview.h"
    10. #include "mygraphicsobject.h"
    11. class DxfWnd : public QWidget
    12. {
    13. Q_OBJECT
    14. public:
    15. explicit DxfWnd(QWidget *parent = nullptr);
    16. void Demo();
    17. void LoadXYZ(const QString &t_strFile);
    18. MyGraphicsView *m_view;
    19. private:
    20. QGridLayout *layout;
    21. QGraphicsScene *m_scene;
    22. QList m_listPositionItem;
    23. private slots:
    24. void RecvRatioFromGraphicsView(double, double, double, double);
    25. };
    26. #endif // DXFWND_H

    构造函数

    1. DxfWnd::DxfWnd(QWidget *parent) : QWidget(parent)
    2. {
    3. layout = new QGridLayout();
    4. this->setLayout(layout);
    5. m_view = new MyGraphicsView(); // 定义一个视图
    6. connect(m_view, &MyGraphicsView::SendRatio2DxfWnd, this, &DxfWnd::RecvRatioFromGraphicsView);
    7. Demo();
    8. }

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

    1. void DxfWnd::Demo(){
    2. m_scene = new QGraphicsScene(); // 定义一个场景,设置背景色为白色
    3. m_scene->setBackgroundBrush(Qt::white);
    4. QPen pen; // 定义一个画笔,设置画笔颜色和宽度
    5. pen.setColor(QColor(0, 160, 230));
    6. pen.setWidth(1);
    7. QGraphicsRectItem *rectItem = new QGraphicsRectItem(); // 定义一个矩形图元
    8. rectItem->setRect(476415,3888005, 80, 80);
    9. rectItem->setPen(pen);
    10. rectItem->setBrush(QBrush(QColor(255, 0, 255)));
    11. rectItem->setFlag(QGraphicsItem::ItemIsMovable);
    12. m_scene->addItem(rectItem);
    13. LoadXYZ("D:/QT_temp/layout_test/data/0.xyz");
    14. // MyGraphicsObject *gi = new MyGraphicsObject();
    15. // gi->setPos(476415,3888005);
    16. // gi->SetValue(12.34);
    17. // m_scene->addItem(gi);
    18. m_view->setFixedSize(400, 300);
    19. m_view->setScene(m_scene);
    20. m_view->setDragMode(QGraphicsView::RubberBandDrag); //设置view橡皮筋框选区域
    21. layout->addWidget(m_view);
    22. }

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

    1. void DxfWnd::LoadXYZ(const QString &t_strFile){
    2. // 读取数据
    3. QVector vectorPoint;
    4. QFile file(t_strFile);
    5. if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
    6. QTextStream in(&file);
    7. while (!in.atEnd()) {
    8. // 读取一行
    9. QString line = in.readLine();
    10. QStringList strList = line.split(',');
    11. // to QVector3D
    12. if(strList.size() == 3) {
    13. QVector3D point;
    14. point.setX(strList.at(0).toDouble());
    15. point.setY(strList.at(1).toDouble());
    16. point.setZ(strList.at(2).toDouble());
    17. vectorPoint.append(point);
    18. }
    19. }
    20. }
    21. // Scene中加载
    22. for(auto const point : vectorPoint) {
    23. auto pPositionItem = new MyGraphicsObject();
    24. pPositionItem->setPos(point.x(), point.y());
    25. pPositionItem->SetValue(point.z());
    26. m_scene->addItem(pPositionItem);
    27. m_listPositionItem.append(pPositionItem);
    28. }
    29. }
    30. void DxfWnd::RecvRatioFromGraphicsView(double p0x, double p0y, double p2x, double p2y){
    31. if(p2x - p0x > 1000 || p2y - p0y > 1000){
    32. for(int i=0; isize(); i++) {
    33. if(i % 10 == 0) {
    34. m_listPositionItem.at(i)->show();
    35. }
    36. else{
    37. m_listPositionItem.at(i)->hide();
    38. }
    39. }
    40. }
    41. else{
    42. for(int i=0; isize(); i++) {
    43. m_listPositionItem.at(i)->show();
    44. }
    45. }
    46. }

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

    1. #ifndef MYGRAPHICSVIEW_H
    2. #define MYGRAPHICSVIEW_H
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include
    10. #include
    11. #include
    12. enum MODE{selectMode, drawMode, deleteMode};
    13. class MyGraphicsView : public QGraphicsView
    14. {
    15. Q_OBJECT
    16. public:
    17. MyGraphicsView();
    18. private:
    19. QPointF centerAnchor;
    20. QPointF posAnchor;
    21. bool viewMove = false;
    22. double m_scaleValue = 1;
    23. MODE mode = selectMode;
    24. QGraphicsItem *selectedItem = nullptr;
    25. void GetScale();
    26. QVector point2LineCache;
    27. public slots:
    28. void slot_rotateLeft();// { rotate(-30); }
    29. void slot_rotateRight();// { rotate(30); }
    30. void slot_reset();
    31. void ActiveDrawLine();
    32. void CancelDrawLine();
    33. void DeleteItem();
    34. void StopDeleteMode();
    35. protected:
    36. virtual void mousePressEvent(QMouseEvent *event);
    37. virtual void mouseReleaseEvent(QMouseEvent *event);
    38. virtual void wheelEvent(QWheelEvent *event);
    39. virtual void mouseMoveEvent(QMouseEvent *event);
    40. void view_zoomIn();// { scale(1.2, 1.2); }
    41. void veiw_zoomOut();// { scale(1/1.2, 1/1.2); }
    42. signals:
    43. void SendRatio2DxfWnd(double, double, double, double);
    44. };
    45. #endif // MYGRAPHICSVIEW_H

    mousePressEvent定义了鼠标点击操作

    1. void MyGraphicsView::mousePressEvent(QMouseEvent *event)
    2. {
    3. if( event->button() == Qt::RightButton)
    4. {
    5. QMenu *mouseLeftMenu = new QMenu(this);
    6. QAction* rotateLeft = new QAction(tr("rotateLeft"), this);
    7. QAction* rotateRight = new QAction(tr("rotateRight"), this);
    8. QAction* draw = new QAction(tr("drawLine"), this);
    9. QAction* cancel = new QAction(tr("stopDrawLine"), this);
    10. QAction* zoomReset = new QAction(tr("zoomReset"), this);
    11. QAction* deleteItem = new QAction(tr("delete"), this);
    12. QAction* cancelDeleteMode = new QAction(tr("stopDelete"), this);
    13. mouseLeftMenu->addAction(zoomReset);
    14. // mouseLeftMenu->addAction(rotateLeft);
    15. // mouseLeftMenu->addAction(rotateRight);
    16. if (mode == selectMode){
    17. mouseLeftMenu->addAction(draw);
    18. mouseLeftMenu->addAction(deleteItem);
    19. }
    20. else if(mode == drawMode){
    21. mouseLeftMenu->addAction(cancel);
    22. }
    23. // mouseLeftMenu->addAction(zoomOut);
    24. else if (mode == deleteMode){
    25. mouseLeftMenu->addAction(cancelDeleteMode);
    26. }
    27. mouseLeftMenu->move(cursor().pos());
    28. mouseLeftMenu->show();
    29. connect(rotateLeft, SIGNAL(triggered()), this, SLOT(slot_rotateLeft()));
    30. connect(rotateRight, SIGNAL(triggered()), this, SLOT(slot_rotateRight()));
    31. connect(draw, SIGNAL(triggered()), this, SLOT(ActiveDrawLine()));
    32. connect(cancel, SIGNAL(triggered()), this, SLOT(CancelDrawLine()));
    33. connect(zoomReset, SIGNAL(triggered()), this, SLOT(slot_reset()));
    34. connect(deleteItem, SIGNAL(triggered()), this, SLOT(DeleteItem()));
    35. connect(cancelDeleteMode, SIGNAL(triggered()), this, SLOT(StopDeleteMode()));
    36. }
    37. else if(event->button() == Qt::MiddleButton)
    38. {
    39. mode = selectMode;
    40. viewMove = true;
    41. GetScale();
    42. // centerAnchor = mapToScene(event->pos()) - event->pos() + QPointF(width() / 2, height() / 2);
    43. centerAnchor = mapToScene(QPoint(width() / 2, height() / 2));
    44. posAnchor = event->pos();
    45. }
    46. else if(event->button() == Qt::LeftButton){
    47. if(mode == drawMode){
    48. point2LineCache.append(mapToScene(event->pos()));
    49. if (point2LineCache.size() == 2){
    50. QLineF *line = new QLineF(point2LineCache[0], point2LineCache[1]);
    51. scene()->addLine(*line);
    52. point2LineCache.pop_front();
    53. scene()->update();
    54. }
    55. }
    56. else{
    57. QGraphicsItem *selectedItem = this->itemAt(event->pos());
    58. if(selectedItem){
    59. scene()->removeItem(selectedItem);
    60. }
    61. }
    62. }
    63. }

    点击右键,会弹出菜单

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

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

     

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

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

    posAnchor = event->pos();

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

    1. void MyGraphicsView::mouseMoveEvent(QMouseEvent *event){
    2. if(viewMove){
    3. QPointF offsetPos = event->pos() - posAnchor;
    4. // setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
    5. GetScale();
    6. centerOn(centerAnchor - offsetPos * m_scaleValue);
    7. }
    8. }

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

    centerOn(centerAnchor - offsetPos * m_scaleValue);

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

    wheelEvent实现放大缩小

    1. void MyGraphicsView::wheelEvent(QWheelEvent *event){
    2. auto test = this->mapToScene( this->viewport()->geometry() );
    3. double p0x = test[0].x();
    4. double p0y = test[0].y();
    5. double p2x = test[2].x();
    6. double p2y = test[2].y();
    7. if(event->delta() > 0){ // 当滚轮远离使用者时
    8. view_zoomIn(); // 进行放大
    9. }else{ // 当滚轮向使用者方向旋转时
    10. veiw_zoomOut(); // 进行缩小
    11. }
    12. emit SendRatio2DxfWnd(p0x, p0y, p2x, p2y);
    13. }
    14. void MyGraphicsView::view_zoomIn()
    15. {
    16. this->scale(1.2, 1.2);
    17. }
    18. void MyGraphicsView::veiw_zoomOut()
    19. {
    20. this->scale(1/1.2, 1/1.2);
    21. }

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

    1. void MyGraphicsView::GetScale(){
    2. auto test = this->mapToScene( this->viewport()->geometry() );
    3. double p0x = test[0].x();
    4. double p2x = test[2].x();
    5. m_scaleValue = (p2x - p0x) / this->width();
    6. }

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

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

    1. #include "mygraphicsview.h"
    2. MyGraphicsView::MyGraphicsView()
    3. {
    4. setMouseTracking(true);
    5. }
    6. void MyGraphicsView::mousePressEvent(QMouseEvent *event)
    7. {
    8. if( event->button() == Qt::RightButton)
    9. {
    10. QMenu *mouseLeftMenu = new QMenu(this);
    11. QAction* rotateLeft = new QAction(tr("rotateLeft"), this);
    12. QAction* rotateRight = new QAction(tr("rotateRight"), this);
    13. QAction* draw = new QAction(tr("drawLine"), this);
    14. QAction* cancel = new QAction(tr("stopDrawLine"), this);
    15. QAction* zoomReset = new QAction(tr("zoomReset"), this);
    16. QAction* deleteItem = new QAction(tr("delete"), this);
    17. QAction* cancelDeleteMode = new QAction(tr("stopDelete"), this);
    18. mouseLeftMenu->addAction(zoomReset);
    19. // mouseLeftMenu->addAction(rotateLeft);
    20. // mouseLeftMenu->addAction(rotateRight);
    21. if (mode == selectMode){
    22. mouseLeftMenu->addAction(draw);
    23. mouseLeftMenu->addAction(deleteItem);
    24. }
    25. else if(mode == drawMode){
    26. mouseLeftMenu->addAction(cancel);
    27. }
    28. // mouseLeftMenu->addAction(zoomOut);
    29. else if (mode == deleteMode){
    30. mouseLeftMenu->addAction(cancelDeleteMode);
    31. }
    32. mouseLeftMenu->move(cursor().pos());
    33. mouseLeftMenu->show();
    34. connect(rotateLeft, SIGNAL(triggered()), this, SLOT(slot_rotateLeft()));
    35. connect(rotateRight, SIGNAL(triggered()), this, SLOT(slot_rotateRight()));
    36. connect(draw, SIGNAL(triggered()), this, SLOT(ActiveDrawLine()));
    37. connect(cancel, SIGNAL(triggered()), this, SLOT(CancelDrawLine()));
    38. connect(zoomReset, SIGNAL(triggered()), this, SLOT(slot_reset()));
    39. connect(deleteItem, SIGNAL(triggered()), this, SLOT(DeleteItem()));
    40. connect(cancelDeleteMode, SIGNAL(triggered()), this, SLOT(StopDeleteMode()));
    41. }
    42. else if(event->button() == Qt::MiddleButton)
    43. {
    44. mode = selectMode;
    45. viewMove = true;
    46. GetScale();
    47. // centerAnchor = mapToScene(event->pos()) - event->pos() + QPointF(width() / 2, height() / 2);
    48. centerAnchor = mapToScene(QPoint(width() / 2, height() / 2));
    49. posAnchor = event->pos();
    50. }
    51. else if(event->button() == Qt::LeftButton){
    52. if(mode == drawMode){
    53. point2LineCache.append(mapToScene(event->pos()));
    54. if (point2LineCache.size() == 2){
    55. QLineF *line = new QLineF(point2LineCache[0], point2LineCache[1]);
    56. scene()->addLine(*line);
    57. point2LineCache.pop_front();
    58. scene()->update();
    59. }
    60. }
    61. else{
    62. QGraphicsItem *selectedItem = this->itemAt(event->pos());
    63. if(selectedItem){
    64. scene()->removeItem(selectedItem);
    65. }
    66. }
    67. }
    68. }
    69. void MyGraphicsView::mouseMoveEvent(QMouseEvent *event){
    70. if(viewMove){
    71. QPointF offsetPos = event->pos() - posAnchor;
    72. // setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
    73. GetScale();
    74. centerOn(centerAnchor - offsetPos * m_scaleValue);
    75. }
    76. }
    77. void MyGraphicsView::mouseReleaseEvent(QMouseEvent *event){
    78. viewMove = false;
    79. }
    80. void MyGraphicsView::wheelEvent(QWheelEvent *event){
    81. auto test = this->mapToScene( this->viewport()->geometry() );
    82. double p0x = test[0].x();
    83. double p0y = test[0].y();
    84. double p2x = test[2].x();
    85. double p2y = test[2].y();
    86. if(event->delta() > 0){ // 当滚轮远离使用者时
    87. view_zoomIn(); // 进行放大
    88. }else{ // 当滚轮向使用者方向旋转时
    89. veiw_zoomOut(); // 进行缩小
    90. }
    91. emit SendRatio2DxfWnd(p0x, p0y, p2x, p2y);
    92. }
    93. void MyGraphicsView::GetScale(){
    94. auto test = this->mapToScene( this->viewport()->geometry() );
    95. double p0x = test[0].x();
    96. double p2x = test[2].x();
    97. m_scaleValue = (p2x - p0x) / this->width();
    98. }
    99. void MyGraphicsView::view_zoomIn()
    100. {
    101. this->scale(1.2, 1.2);
    102. }
    103. void MyGraphicsView::veiw_zoomOut()
    104. {
    105. this->scale(1/1.2, 1/1.2);
    106. }
    107. void MyGraphicsView::slot_rotateLeft()
    108. {
    109. this->rotate(-30);
    110. }
    111. void MyGraphicsView::slot_rotateRight()
    112. {
    113. this->rotate(30);
    114. }
    115. void MyGraphicsView::slot_reset()
    116. {
    117. QRectF rectItem = scene()->itemsBoundingRect();
    118. QRectF rectView = this->rect();
    119. qreal ratioView = rectView.height() / rectView.width();
    120. qreal ratioItem = rectItem.height() / rectItem.width();
    121. if (ratioView > ratioItem)
    122. {
    123. rectItem.moveTop(rectItem.width()*ratioView - rectItem.height());
    124. rectItem.setHeight(rectItem.width()*ratioView);
    125. rectItem.setWidth(rectItem.width() * 1.2);
    126. rectItem.setHeight(rectItem.height() * 1.2);
    127. }
    128. else
    129. {
    130. rectItem.moveLeft(rectItem.height()/ratioView - rectItem.width());
    131. rectItem.setWidth(rectItem.height()/ratioView);
    132. rectItem.setWidth(rectItem.width() * 1.2);
    133. rectItem.setHeight(rectItem.height() * 1.2);
    134. }
    135. this->fitInView(rectItem, Qt::KeepAspectRatio);
    136. }
    137. void MyGraphicsView::ActiveDrawLine(){
    138. mode = drawMode;
    139. }
    140. void MyGraphicsView::CancelDrawLine(){
    141. mode = selectMode;
    142. point2LineCache.clear();
    143. }
    144. void MyGraphicsView::DeleteItem(){
    145. mode = deleteMode;
    146. }
    147. void MyGraphicsView::StopDeleteMode(){
    148. mode = selectMode;
    149. }

    利用一个结构体

    enum MODE{selectMode, drawMode, deleteMode};

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

  • 相关阅读:
    npm安装vuecli出错的处理方法
    git push报错解决
    说说 React 性能优化的手段有哪些?
    机器学习(三)多项式回归
    条码二维码读取设备在医疗设备自助服务的重要性
    分析 java.util.LinkedHashMap
    【动手学深度学习】学习笔记
    一文深入springboot,springboot的实战实践文档(PDF)
    Openssl数据安全传输平台019:外联接口类的封装以及动态库的制作 - Bug未解决,感觉不是代码的问题
    Spring事务源码解读
  • 原文地址:https://blog.csdn.net/qq_41816368/article/details/127118311