当前位置: 首页 > news >正文

QT图形视图系统 - 使用一个项目来学习QT的图形视图框架 -第一篇

文章目录

  • QT图形视图系统
    • 介绍
    • 开始搭建MainWindow框架
    • 设置scene的属性
    • 缩放功能的添加
    • 加上标尺

QT图形视图系统

介绍

详细的介绍可以看QT的官方助手,那里面介绍的详细且明白,需要一定的英语基础,我这里直接使用一个开源项目来介绍QGraphicsView、QGraphicsScene的使用。

先提供一个项目的图片

在这里插入图片描述

先来一个简单的例子,这个例子是介绍了一下QGraphicsView 和 QGraphicsScene的关系,并且如何在View中展示Scene

#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsView>int main(int argc, char **argv)
{QApplication app(argc, argv);QGraphicsScene scene;scene.addText("Hello, QGraphicsView");QGraphicsView view(&scene);view.show();return app.exec();
}

上面的是最基本的QGraphicsView 中显示QGraphicsScene, 并且打印Hello, QGraphicsView在界面上的例子。由此我们可以看到,scene对象需要被view对象管理之后再显示出来。

接下来,我们将重写QGraphicsView 来实现我们自己要的效果。

开始搭建MainWindow框架

使用mainwindowz作为整个项目的外部界面框架,并且将自己的view放在mainwindow中

mainwindow 之后的代码我会将头文件代码和cpp代码放在一个代码块中,请注意区分

// mainwindow.h
#ifndef GRAPHICSVIEWQ_MAINWINDOW_H
#define GRAPHICSVIEWQ_MAINWINDOW_H#include <QMainWindow>
class GraphicsView;
class MainWindow : public QMainWindow
{Q_OBJECT
public:explicit MainWindow(QWidget *parent = 0);~MainWindow() override;protected:private:GraphicsView *graphics_view_;
};#endif //GRAPHICSVIEWQ_MAINWINDOW_H
// mainwindow.cpp#include <QHBoxLayout>
#include "mainwindow.h"
#include "graphicsview.h"MainWindow::MainWindow(QWidget *parent): QMainWindow(parent)
{setMouseTracking(true);resize(1600, 1000);graphics_view_ = new GraphicsView(this);graphics_view_->setObjectName(QString::fromUtf8("graphicsView"));graphics_view_->setTransformationAnchor(QGraphicsView::AnchorUnderMouse);graphics_view_->setResizeAnchor(QGraphicsView::AnchorUnderMouse);QWidget *centralWidget = new QWidget(this);centralWidget->setObjectName(QString::fromUtf8("centralwidget"));QHBoxLayout *horizontalLayout= new QHBoxLayout(centralWidget);horizontalLayout->setSpacing(0);horizontalLayout->setObjectName(QString::fromUtf8("horizontalLayout"));horizontalLayout->setContentsMargins(3, 3, 3, 3);horizontalLayout->addWidget(graphics_view_);setCentralWidget(centralWidget);QGraphicsScene *scene = new QGraphicsScene();scene->addText("Hello, MainWindow");graphics_view_->setScene(scene);
}MainWindow::~MainWindow()
{}

graphicsview

// graphicsview.h
#ifndef GRAPHICSVIEWQ_GRAPHICSVIEW_H
#define GRAPHICSVIEWQ_GRAPHICSVIEW_H
#include <QGraphicsView>
#include <QWidget>class GraphicsView : public QGraphicsView
{Q_OBJECT
public:explicit GraphicsView(QWidget *parent = nullptr);explicit GraphicsView(QGraphicsScene *scene, QWidget *parent = nullptr);~GraphicsView() override;protected:private:
};
#endif //GRAPHICSVIEWQ_GRAPHICSVIEW_H
// graphicsview.cpp
#include "graphicsview.h"
GraphicsView::GraphicsView(QWidget *parent): QGraphicsView(parent)
{}GraphicsView::GraphicsView(QGraphicsScene *scene, QWidget *parent): QGraphicsView(scene, parent)
{}GraphicsView::~GraphicsView()
{}

这个时候我们展示mainwindow的时候是能正常看到 hello mainwindow的时候,我们离我们的目标又进一步了。

设置scene的属性

接下来给我们的view在构造的时候加一些属性,并且删除掉mainwindow中的scene

void GraphicsView::setBaseAttribute()
{// 设置场景QGraphicsScene *scene = new QGraphicsScene(this);scene->addText("Hello, MainWindow");setScene(scene);// 设置接收场景交互setInteractive(true);// 接收Drop事件setAcceptDrops(true);// 接收鼠标移动事件setMouseTracking(true);// CacheNone  所有的绘画都是直接在视窗上完成的.// 背景被缓存,这影响自定义背景和基于backgroundBrush属性的背景.当这个标志被启用,QGraphicsView将分配一个像素图与viewport的完整尺寸.setCacheMode(CacheBackground);// 渲染时,QGraphicsView在渲染背景或前景以及渲染每个项目时保护画家状态(参见QPainter::save())。这允许你让画工处于一个改变的状态(例如,你可以调用QPainter::setPen()或QPainter::setBrush(),而不需要在绘画后恢复状态)。但是,如果项目始终恢复状态,则应该启用此标志以防止QGraphicsView做同样的事情。setOptimizationFlag(DontSavePainterState);// 禁用QGraphicsView对曝光区域的抗锯齿自动调整。setOptimizationFlag(DontAdjustForAntialiasing);// QGraphicsView将通过分析需要重绘的区域来尝试找到最佳的更新模式。setViewportUpdateMode(SmartViewportUpdate);// 一个橡皮筋会出现。鼠标拖动将设置橡皮筋的几何形状,并选择橡皮筋覆盖的所有项目。非交互式视图禁用此模式。setDragMode(RubberBandDrag);// 设置支持鼠标右键弹出菜单setContextMenuPolicy(Qt::DefaultContextMenu);// 设置横向和纵向滚动条常开setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);// 设置黑色背景setStyleSheet("QGraphicsView { background: #000000 }");scene->setSceneRect(-1000, -1000, +2000, +2000);// 流出添加标尺的空间setViewportMargins(24, 0, 0, 24);
}

这个时候我们再运行的时候,可以看到整个背景就编程黑色的了。并且出现了滚动条

缩放功能的添加

接下来我们给界面添加缩放功能

首先我们需要注释掉黑色背景,方便我们查看文字的变化, 并且添加以下代码,以便放大缩小的时候更好的跟随鼠标

// 设置抗锯齿
setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);
// 设置放大缩小的时候跟随鼠标
setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
setResizeAnchor(QGraphicsView::AnchorUnderMouse);

接下来我们添加缩放函数,同时我们重写鼠标事件

void GraphicsView::zoomIn()
{if(transform().m11() > 1000.0) return;scale(zoomFactor, zoomFactor);
}void GraphicsView::zoomOut()
{if(transform().m11() < 1.0) return;scale(1.0 / zoomFactor, 1.0 / zoomFactor);
}void GraphicsView::wheelEvent(QWheelEvent *event)
{const auto delta = event->angleDelta().y();const auto pos = event->position().toPoint();static auto sbUpdate = [&delta, this, scale = 3](QScrollBar* sb) {// @TODO 如果是多个view的话 会不会出问题sb->setValue(sb->value() - delta);};if (event->buttons() & Qt::RightButton) {if (abs(delta) == 120) {setInteractive(false);if (delta > 0)zoomIn();elsezoomOut();setInteractive(true);}} else {switch (event->modifiers()) {case Qt::ControlModifier:if (abs(delta) == 120) {setInteractive(false);if (delta > 0)zoomIn();elsezoomOut();setInteractive(true);}break;case Qt::ShiftModifier:if (!event->angleDelta().x())sbUpdate(QAbstractScrollArea::horizontalScrollBar());break;case Qt::NoModifier:if (!event->angleDelta().x())sbUpdate(QAbstractScrollArea::verticalScrollBar());break;default:break;}}emit sig_mouseMove(mapToScene(pos));// QGraphicsView::wheelEvent(event);
}

通过鼠标,我们可以看到对应的变化,我这里添加了混合按钮操作,ctrl是缩放,shift是移动横轴,我这里就不贴效果图了,你们按照此步骤加函数即可,自己去尝试效果去吧。

我们还需要回到最初始的大小,这个时候我们需要添加回到100%比例的函数。并且添加一个键盘事件,按下空格的时候则回到100%的状态。这里可以在初始化的时候直接给设置成百分百

QSizeF GraphicsView::getRealSize() 
{static QSizeF size;if (!size.isEmpty())return size;if (size.isEmpty()) FIXME 当前界面的物理尺寸size = QGuiApplication::screens()[0]->physicalSize();return size;
}void GraphicsView::zoomTo100()
{
 根据物理尺寸设置大小, 因为后面我们会引入尺子,因此这里设置为根据物理尺寸设置double x = 1.0, y = 1.0;const double m11 = QGraphicsView::transform().m11(), m22 = QGraphicsView::transform().m22();const double dx = QGraphicsView::transform().dx(), dy = QGraphicsView::transform().dy();const QSizeF size(getRealSize());                                      // size in mmconst QRect scrGeometry(QApplication::primaryScreen()->geometry()); // size in pixx = qAbs(1.0 / m11 / (size.height() / scrGeometry.height()));y = qAbs(1.0 / m22 / (size.width() / scrGeometry.width()));std::cout << dx << " " << dy << std::endl;scale(x, y); 恢复到初始状态(位移状态未记录)
//    QMatrix q;
//    q.setMatrix(1,this->matrix().m12(),this->matrix().m21(),1,this->matrix().dx(),this->matrix().dy());
//    this->setMatrix(q,false);
}void GraphicsView::keyPressEvent(QKeyEvent *event)
{switch (event->key()) {case Qt::Key_Space:zoomTo100();break;case Qt::Key_F:zoomFit();break;default:break;}QGraphicsView::keyPressEvent(event);
}void GraphicsView::zoomFit()
{fitInView(scene()->itemsBoundingRect(), false);
}void GraphicsView::fitInView(QRectF dstRect, bool withBorders)
{if (dstRect.isNull())return;if (withBorders)dstRect += QMarginsF(dstRect.width() / 5, dstRect.height() / 5, dstRect.width() / 5, dstRect.height() / 5); // 5 mmQGraphicsView::fitInView(dstRect, Qt::KeepAspectRatio);
}

加上标尺

接下来我们来给我们的视图加上左边和下面的标尺

先上一张图片

在这里插入图片描述

ruler

#ifndef GRAPHICSVIEWLEARN_RULER_H
#define GRAPHICSVIEWLEARN_RULER_H#include <QWidget>
#include <QPen>class Ruler final : public QWidget
{Q_OBJECT
public:enum { Width = 24};explicit Ruler(Qt::Orientation rulerType, QWidget* parent);void drawAScaleMeter(QPainter* painter, QRectF rulerRect, double scaleMeter, double startPosition);// 绘制刻度线void drawFromOriginTo(QPainter* painter, QRectF rect, double startMark, double endMark, int startTickNo, double step, double startPosition);protected:void paintEvent(QPaintEvent* event) override;void drawMousePosTick(QPainter* painter);private:Qt::Orientation orientation_;double grid_step_ {1.0};double origin_ {};double ruler_unit_ {1.0};double ruler_zoom_ {1.0};double tick_koef_ {1.0};QPoint cursor_pos_;QPen meter_pen_;bool draw_text_ {};
};#endif //GRAPHICSVIEWLEARN_RULER_H#include "ruler.h"
#include <QPainter>Ruler::Ruler(Qt::Orientation rulerType, QWidget *parent): QWidget(parent), orientation_ { rulerType }
{setMouseTracking(true);setStyleSheet("QWidget{ background:black; }");
}void Ruler::paintEvent(QPaintEvent *event)
{Q_UNUSED(event)QPainter painter(this);painter.setRenderHints(QPainter::TextAntialiasing);painter.setPen(QPen(Qt::darkGray, 0.0)); // 零宽度笔是装饰笔QRectF rulerRect(rect()); // 需要QRectF// 首先填充矩形painter.fillRect(rulerRect, QColor().rgb());if (qFuzzyIsNull(ruler_zoom_))return;// fixme 这个地方需要修改成带单位转换的grid_step_ = pow(10.0, ceil(log10(8.0 / ruler_zoom_)));// ViewSettings::instance().gridStep(rulerZoom_);// 绘制小刻度if ((grid_step_ * ruler_zoom_) > 35) {tick_koef_ = 0.1;draw_text_ = true;}meter_pen_ = QPen(Qt::darkGray, 0.0);drawAScaleMeter(&painter, rulerRect, grid_step_ * 1, static_cast<double>(Ruler::Width) * 0.6);draw_text_ = false;// 绘制中间刻度if ((grid_step_ * ruler_zoom_) <= 35) {tick_koef_ = 0.5;draw_text_ = true;}meter_pen_ = QPen(Qt::green, 0.0);drawAScaleMeter(&painter, rulerRect, grid_step_ * 5, static_cast<double>(Ruler::Width) * 0.3);draw_text_ = false;// 绘制整刻度线meter_pen_ = QPen(Qt::red, 0.0);drawAScaleMeter(&painter, rulerRect, grid_step_ * 10, static_cast<double>(Ruler::Width) * 0);// 绘制当前鼠标位置十字线drawMousePosTick(&painter);// 在视图和标尺之间分割线 红色的线(看是否需要)if ((1)) {QPointF starPt((Qt::Horizontal == orientation_) ? rulerRect.topLeft() : rulerRect.topRight());QPointF endPt((Qt::Horizontal == orientation_) ? rulerRect.topRight() : rulerRect.bottomRight()); // FIXME same branches!!!!!!painter.setPen(QPen(Qt::red, 2));painter.drawLine(starPt, endPt);}QWidget::paintEvent(event);
}void Ruler::drawAScaleMeter(QPainter* painter, QRectF rulerRect, double scaleMeter, double startPosition)
{bool isHorzRuler = Qt::Horizontal == orientation_;scaleMeter = scaleMeter * ruler_unit_ * ruler_zoom_;double rulerStartMark = isHorzRuler ? rulerRect.left() : rulerRect.top();// Ruler rectangle ending markdouble rulerEndMark = isHorzRuler ? rulerRect.right() : rulerRect.bottom();if (origin_ >= rulerStartMark && origin_ <= rulerEndMark) {drawFromOriginTo(painter, rulerRect, origin_, rulerEndMark, 0, scaleMeter, startPosition);drawFromOriginTo(painter, rulerRect, origin_, rulerStartMark, 0, -scaleMeter, startPosition);} else if (origin_ < rulerStartMark) {int tickNo = int((rulerStartMark - origin_) / scaleMeter);drawFromOriginTo(painter, rulerRect, origin_ + scaleMeter * tickNo,rulerEndMark, tickNo, scaleMeter, startPosition);} else if (origin_ > rulerEndMark) {int tickNo = int((origin_ - rulerEndMark) / scaleMeter);drawFromOriginTo(painter, rulerRect, origin_ - scaleMeter * tickNo,rulerStartMark, tickNo, -scaleMeter, startPosition);}
}void Ruler::drawFromOriginTo(QPainter* painter, QRectF rect, double startMark, double endMark, int startTickNo, double step, double startPosition)
{const auto isHorzRuler = (Qt::Horizontal == orientation_);// fixme 这个地方要修改成单位转换的const auto K = grid_step_ * tick_koef_ * 1.0;QColor color(0xFFFFFFFF - QColor(Qt::black).rgb());painter->setPen(QPen(color, 0.0));painter->setFont(font());QVector<QLineF> lines;lines.reserve(abs(ceil((endMark - startMark) / step)));constexpr double padding = 3;for (double current = startMark; (step < 0 ? current >= endMark : current <= endMark); current += step) {double x1, y1;lines.push_back(QLineF(x1 = isHorzRuler ? current : rect.left() + startPosition,y1 = isHorzRuler ? rect.top() : current,/*x2*/ isHorzRuler ? current : rect.right(),/*y2*/ isHorzRuler ? rect.bottom() - startPosition : current));if (draw_text_) {painter->save();auto number { QString::number(startTickNo * K) };if (startTickNo)number = ((isHorzRuler ^ (step > 0.0)) ? "-" : "+") + number;QRectF textRect(QFontMetricsF(font()).boundingRect(number));textRect.setWidth(textRect.width() + 1);if (isHorzRuler) {painter->translate(x1 + padding, textRect.height());painter->drawText(textRect, Qt::AlignCenter, number);} else {painter->translate(textRect.height() - padding, y1 - padding);painter->rotate(-90);painter->drawText(textRect, number);}painter->restore();}++startTickNo;}painter->setPen(meter_pen_);painter->drawLines(lines.data(), lines.size());
}void Ruler::drawMousePosTick(QPainter* painter)
{QPoint starPt = cursor_pos_;QPoint endPt;if (Qt::Horizontal == orientation_) {starPt.setY(this->rect().top());endPt.setX(starPt.x());endPt.setY(this->rect().bottom());} else {starPt.setX(this->rect().left());endPt.setX(this->rect().right());endPt.setY(starPt.y());}painter->drawLine(starPt, endPt);
}

好了,本篇先介绍到这里,接下来我会写下一篇,让我们一起去实现后续的效果。

http://www.lryc.cn/news/107121.html

相关文章:

  • Cat.1如何成为物联网业务加速器?
  • Qt应用开发(基础篇)——布局管理 Layout Management
  • Python web实战之 Django 的 ORM 框架详解
  • pycharm制作柱状图
  • 静态资源导入探究
  • 安全狗V3.512048版本绕过
  • prometheus监控k8s kube-proxy target down
  • SPSS数据分析--假设检验的两种原假设取舍决定方式
  • Python实现猫狗分类
  • pjsip、pjsua2+bcg729 windows下编译java版本
  • 尝试多数据表 sqlite
  • Keil出现Flash Timeout.Reset the Target and try it again.我有一种解决方法
  • 纯粹即刻,畅享音乐搜索的轻松体验
  • 动态规划之树形DP
  • 嵌入式_GD32使用宏开关进行Debug串口打印调试
  • 使用 GitHub Copilot 进行 Prompt Engineering 的初学者指南(译)
  • c++开发模式,享元模式
  • LLM大模型——langchain相关知识总结
  • 【Python】数据可视化利器PyCharts在测试工作中的应用
  • AOP的实战(统一功能处理模块)
  • 时间复杂度为O(n2)的三种简单排序算法
  • LeetCode 热题 100 JavaScript --226. 翻转二叉树
  • hive所有窗口函数详情总结
  • Talk | 新加坡国立大学博士生施宇钧:DragDiffusion-基于扩散模型的关键点拖拽图片编辑
  • 22 | 贝叶斯分类算法
  • java.sql.SQLSyntaxErrorException: ORA-00909: 参数个数无效
  • 数据结构8-哈希表
  • vue3引用Font-Awesome字体图标库
  • Python: Django 服务部署可能遇到的一些问题
  • Python爬虫时遇到连接超时解决方案