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

news2024/9/20 17:34:08

文章目录

  • 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();
            else
                zoomOut();
            setInteractive(true);
        }
    } else {
        switch (event->modifiers()) {
            case Qt::ControlModifier:
                if (abs(delta) == 120) {
                    setInteractive(false);
                    if (delta > 0)
                        zoomIn();
                    else
                        zoomOut();
                    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 mm
    const QRect scrGeometry(QApplication::primaryScreen()->geometry()); // size in pix
    x = 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 mm

    QGraphicsView::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 mark
    double 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.coloradmin.cn/o/835159.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

leetcode 738. 单调递增的数字

2023.8.4 这题用暴力法会超时&#xff0c;我就没试了&#xff0c;采用了个挺巧的方法&#xff0c;为了方便需要先将整数n转换为字符串的形式&#xff0c;然后从后向前遍历&#xff0c;当两个数字非递增时&#xff0c;将前一个数字--&#xff0c;后一个数字的位置记录在index中&…

路由的hash和history模式的区别

目录 ✅ 路由模式概述 一. 路由的hash和history模式的区别 1. hash模式 2. history模式 3. 两种模式对比 二. 如何获取页面的hash变化 ✅ 路由模式概述 单页应用是在移动互联时代诞生的&#xff0c;它的目标是不刷新整体页面&#xff0c;通过地址栏中的变化来决定内容区…

Three.js 创建网格辅助线,坐标轴辅助线,模型骨骼辅助线

three.js中的辅助线使用 1.网格辅助线&#xff08;GridHelper&#xff09; 2.坐标轴辅助线&#xff08;AxesHelper&#xff09; 3.模型骨骼辅助线(SkeletonHelper) 在上一篇 Three.js加载外部glb,fbx,gltf,obj 模型文件 的文章基础上新加入一个创建 辅助线的函数 createHel…

原型链污染例题复现

一、什么是原型链 下面我们通过这个小例子来看看。 可以看到b在实例化为test对象以后&#xff0c;就可以输出test类中的属性a了。这是因为在于js中的一个重要的概念&#xff1a;继承。而继承的整个过程就称为该类的原型链。 在javascript中,每个对象的都有一个指向他的原型(p…

CRM系统哪些功能可以个性化定制?

不同的企业有着不同的业务流程和需求&#xff0c;因此在选型时就需要一款可以个性化定制的CRM系统。下面说说可以个性化定制的CRM系统的功能和优势。 如何实现个性化定制&#xff1f; Zoho CRM支持个性化定制&#xff0c;您可以创建自定义功能模块、字段以及业务流程&#xf…

还在人工管理?太傻了!建筑行业高手给你支一招!

在现代科技日益发展的时代&#xff0c;智慧工地成为建筑行业的新兴趋势。借助先进的技术和数字化解决方案&#xff0c;智慧工地为建筑项目带来了前所未有的效率、安全性和可持续性。 智慧工地不仅提高了建筑施工的效率&#xff0c;也为管理人员提供了更好的决策依据和风险预测能…

MQTT(EMQX) - SpringBoot 整合MQTT 连接池 Demo - 附源代码 + 在线客服聊天架构图

MQTT 概述 MQTT (Message Queue Telemetry Transport) 是一个轻量级传输协议&#xff0c;它被设计用于轻量级的发布/订阅式消息传输&#xff0c;MQTT协议针对低带宽网络&#xff0c;低计算能力的设备&#xff0c;做了特殊的优化。是一种简单、稳定、开放、轻量级易于实现的消息…

DLL的引入方式(DllImport的特殊引入方式)

Dll引入方式有四种&#xff1a; 1.就是普通的比如一个解决方案中有多个项目&#xff0c;将其他项目的引入到该项目中 2.就是软件自带的程序集的引入 3.就是使用dll的引入&#xff08;普通的本身就是VS的dll文件&#xff09; 4.就是使用dll的引入&#xff08;不是属于该语言的…

网络音频终端音频编码解码终端

网络对讲终端SV-7011V 网络对讲终端SV-7011V&#xff0c;采用了ARM音频DSP架构&#xff0c;集网络对讲、网络广播、监听等功能于一身&#xff0c;内置麦克风、配置line out、line in、Mic in功能输出接口&#xff0c;适用于学校&#xff0c;机场&#xff0c;广场等场所。 产品…

解决 MySQL 删除数据后,ID 自增不连续问题

修复前 除了部分数据&#xff0c;导致后续新增的数据&#xff0c;ID 自增不连续 解决方案 执行下方 SQL 语句即可修复此问题&#xff0c;mbs_order为需要修复的表名 SET i0; UPDATE mbs_order SET id(i:i1); ALTER TABLE mbs_order AUTO_INCREMENT0;

NetApp 入门级全闪存系统 AFF A250:小巧而强大

NetApp 入门级全闪存系统 AFF A250&#xff1a;小巧而强大 作为 AFF A 系列中的入门级全闪存系统&#xff0c;AFF A250 不但可以简化数据管理&#xff0c;还能为您的所有工作负载提供令人惊叹的强劲动力&#xff0c;价格也平易近人。 AFF A250&#xff1a;您的新 IT 专家 AFF…

指向字符串常量(字符串右值)的char指针,free的时候为什么会报错?

起因是如下代码 char *p "abc"; free(p);先说结果&#xff0c;这里会报错&#xff0c;会报一个错误如下 一开始还没反应过来&#xff0c;只知道 “test_content” 是一个右值&#xff0c;这是一个指向右值的指针。 但是free的时候为什么会报错呢&#xff1f; 首…

HarmonyOS元服务开发实践:桌面卡片字典

一、项目说明 1.DEMO创意为卡片字典。 2.不同卡片显示不同内容&#xff1a;微卡、小卡、中卡、大卡&#xff0c;根据不同卡片特征显示同一个字的不同内容&#xff0c;基于用户习惯可选择喜欢的卡片。 3.万能卡片刷新&#xff1a;用户点击卡片刷新按钮查看新内容&#xff0c;同时…

NVM保姆级安装配置

nvm安装配置 1、NVM简介2、NVM安装三、NVM使用四、NVM常用命令 1、NVM简介 在项目开发过程中&#xff0c;使用到vue框架技术&#xff0c;需要安装node下载项目依赖&#xff0c;但经常会遇到node版本不匹配而导致无法正常下载&#xff0c;重新安装node却又很麻烦。为解决以上问…

浏览器调式的时候刷新页面network的日志和请求不会消失的解决办法

把“Preserve log”勾选去掉&#xff0c;F5刷新页面时&#xff0c;就是最新的日志&#xff0c;新的请求 把“Preserve log”勾上&#xff0c;F5刷新浏览器页面时&#xff0c;就会有历史日志&#xff0c;而不会消失

基于VR技术的新型实验室教学模式——VR线上生物实验室

随着科技的发展&#xff0c;虚拟现实技术已经逐渐走进了我们的生活。在教育领域中&#xff0c;虚拟现实技术也被广泛应用于各种学科的教学中。其中&#xff0c;VR线上生物实验室是广州华锐互动开发的&#xff0c;一种基于VR技术的新型教学模式&#xff0c;它能够为学生提供更加…

开源免费用|Apache Doris 2.0 推出跨集群数据复制功能

随着企业业务的发展&#xff0c;系统架构趋于复杂、数据规模不断增大&#xff0c;数据分布存储在不同的地域、数据中心或云平台上的现象越发普遍&#xff0c;如何保证数据的可靠性和在线服务的连续性成为人们关注的重点。在此基础上&#xff0c;跨集群复制&#xff08;Cross-Cl…

镜头基础知识

本文介绍镜头基础知识。 1.焦距 焦距指透镜中心到光聚集之焦点的距离&#xff0c;如下图&#xff0c;通常用f表示。 焦距是正值&#xff0c;一束平行光将会聚集在一个点上&#xff0c;焦距是负值&#xff0c;一束平行光在通过透镜之后将会扩散开。 注意&#xff1a; 1)这里…

【Winform学习笔记(五)】引用自定义控件库(dll文件)

引用自定义控件库dll文件 前言正文1、生成dll文件2、选择工具箱项3、选择需要导入的dll文件4、确定需要导入的控件5、导入及使用 前言 在本文中主要介绍 如何引用自定义控件库(dll文件)。 正文 1、生成dll文件 通过生成解决方案 或 重新生成解决方案 生成 dll 文件 生成的…

MybatisPlus注意点

1、表id过长 默认生成的id过长&#xff0c;不是从1开始需要加如下注解&#xff0c;如果表已经建立&#xff0c;需要重新建表才生效 2、MybatisPlus表明字段非数据库字段 mybatis-plus 就可以用注解 TableField(exist false) 表明字段非数据库字段 TableName(value "o…