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

Qt源码分析:窗体绘制与响应

作为一套开源跨平台的UI代码库,窗体绘制与响应自然是最为基本的功能。在前面的博文中,已就Qt中的元对象系统(反射机制)、事件循环等基础内容进行了分析,并捎带阐述了窗体响应相关的内容。因此,本文着重分析Qt中窗体绘制相关的内容。

在本文最后,通过FreeCAD SheetTableView单元格缩放功能的实现,来对研究分析予以检验与测试。

注1:限于研究水平,分析难免不当,欢迎批评指正。

注2:文章内容会不定期更新。

一、坐标系统

在Qt中,每个窗口(准确说是派生于QPaintDevice的C++类)均有一个以像素为单位的二维窗体坐标系,默认情况下,坐标原点位于窗体左上角,x轴水平向右,y轴竖直向下。

Ref. from QPaintDevice 

A paint device is an abstraction of a two-dimensional space that can be drawn on using a QPainter. Its default coordinate system has its origin located at the top-left position. X increases to the right and Y increases downwards. The unit is one pixel.

The drawing capabilities of QPaintDevice are currently implemented by the QWidget, QImage, QPixmap, QGLPixelBuffer, QPicture, and QPrinter subclasses.

 整体上,世界坐标(也称作逻辑坐标)需要首先转换成窗体坐标,然后再转换为设备坐标(也称作物理坐标)。

Ref. from Qt Coordinate System

将上述坐标变换写成矩阵变换的形式,如下

\left ( x_{device}^{'},y_{device}^{'}, w\right )=\left ( x_{world},y_{world}, 1\right )\cdot \mathbf{W}\cdot \mathbf{V}

x_{device}=\frac{x_{device}^{'}}{w}

y_{device}=\frac{y_{device}^{'}}{w}

其中,

默认值
\mathbf{W}=\begin{bmatrix} w_{11} & w_{12} & w_{13}\\ w_{21} & w_{22} & w_{23}\\ w_{31}& w_{32} & w_{33} \end{bmatrix}\begin{bmatrix} 1 & 0 & 0\\ 0& 1& 0\\ 0& 0 & 1 \end{bmatrix}
\mathbf{V}=\begin{bmatrix} v_{11} & v_{12} & v_{13}\\ v_{21} & v_{22} & v_{23}\\ v_{31}& v_{32} & v_{33} \end{bmatrix}\begin{bmatrix} \frac{vw}{ww} & 0 &0 \\ 0& \frac{vh}{wh} & 0\\ vx-wx\frac{vw}{ww} & vy-wy\frac{vh}{wh} &1 \end{bmatrix}

wx:窗体左边界坐标

wy:窗体上边界坐标

ww: 窗体宽度

wh: 窗体高度

vx:设备左边界坐标

vy:设备上边界坐标

vw: 设备宽度

vh: 设备高度

从中看可以看出,默认情况下,世界坐标与窗体坐标是重合的;但设备坐标则由窗体尺寸、设备尺寸等决定。上述分析,也可以通过QPainter的代码分析印证。

// src/gui/painting/qpainter.cppvoid QPainter::setViewport(const QRect &r)
{
#ifdef QT_DEBUG_DRAWif (qt_show_painter_debug_output)printf("QPainter::setViewport(), [%d,%d,%d,%d]\n", r.x(), r.y(), r.width(), r.height());
#endifQ_D(QPainter);if (!d->engine) {qWarning("QPainter::setViewport: Painter not active");return;}d->state->vx = r.x();d->state->vy = r.y();d->state->vw = r.width();d->state->vh = r.height();d->state->VxF = true;d->updateMatrix();
}QTransform QPainterPrivate::viewTransform() const
{if (state->VxF) {qreal scaleW = qreal(state->vw)/qreal(state->ww);qreal scaleH = qreal(state->vh)/qreal(state->wh);return QTransform(scaleW, 0, 0, scaleH,state->vx - state->wx*scaleW, state->vy - state->wy*scaleH);}return QTransform();
}

二、窗体绘制

2.1 整体流程

QWidget::update()
QWidget::repaint()

从上述流程分析,可以得到以下结论,

  • QWidget update()、repaint()绘制流程几乎相似,最终均会路由到QWidget::paintEvent函数。不同点在于update通过QCoreApplication::postEvent触发了QEvent::UpdateLater事件;而repaint()通过QCoreApplication::sendEvent触发了QEvent::UpdateRequest事件。

Ref. from QWidget::update() 

Updates the widget unless updates are disabled or the widget is hidden.

This function does not cause an immediate repaint; instead it schedules a paint event for processing when Qt returns to the main event loop. This permits Qt to optimize for more speed and less flicker than a call to repaint() does.

Calling update() several times normally results in just one paintEvent() call.

Ref. from QWidget::repaint() 

Repaints the widget directly by calling paintEvent() immediately, unless updates are disabled or the widget is hidden.

We suggest only using repaint() if you need an immediate repaint, for example during animation. In almost all circumstances update() is better, as it permits Qt to optimize for speed and minimize flicker.

  • 对于QPaintEvent事件,相关的绘制区域实际上是在窗口坐标系下描述的,这可通过  QWidgetRepaintManager::paintAndFlush()看出。
void QWidgetRepaintManager::paintAndFlush()
{qCInfo(lcWidgetPainting) << "Painting and flushing dirty"<< "top level" << dirty << "and dirty widgets" << dirtyWidgets;const bool updatesDisabled = !tlw->updatesEnabled();bool repaintAllWidgets = false;const bool inTopLevelResize = tlw->d_func()->maybeTopData()->inTopLevelResize;const QRect tlwRect = tlw->data->crect;const QRect surfaceGeometry(tlwRect.topLeft(), store->size());if ((inTopLevelResize || surfaceGeometry.size() != tlwRect.size()) && !updatesDisabled) {if (hasStaticContents() && !store->size().isEmpty() ) {// Repaint existing dirty area and newly visible area.const QRect clipRect(0, 0, surfaceGeometry.width(), surfaceGeometry.height());const QRegion staticRegion(staticContents(0, clipRect));QRegion newVisible(0, 0, tlwRect.width(), tlwRect.height());newVisible -= staticRegion;dirty += newVisible;store->setStaticContents(staticRegion);} else {// Repaint everything.dirty = QRegion(0, 0, tlwRect.width(), tlwRect.height());for (int i = 0; i < dirtyWidgets.size(); ++i)resetWidget(dirtyWidgets.at(i));dirtyWidgets.clear();repaintAllWidgets = true;}}// ... ...
}

三、分析演练:FreeCAD SheetTableView鼠标滚动缩放

学以致用”,作为前面分析研究的验证与测试,本文抛出下面一个小功能的实现,以期将上述原理串联起来。

在FreeCAD中,通过引用Sheet内的单元格数据,可以方便的实现几何参数化建模,同时FreeCAD SpreadsheetGui模块也提供了SheetTableView来显示/编辑电子表格。

当参数数据较多时,希望滚动缩放电子表格从而可以在屏幕内完整的显示整个电子表格,也就是说,鼠标滚动缩放时要求单元格尺寸单元格内容同等比例缩放。但SheetTableView目前并不支持此功能。

SheetTableView继承自QTableView,由horizontal header、vertical header、view port、horizontal scrollbar、vertical scrollbar、corner widget等组成。而且horizontal header、vertical header、QTableView共享了相同的model。

QTableView portrait

QTableView实际上使用水平/竖直QHeaderView来定位(表格)单元格,也就是说,QTableView利用水平/竖直QHeaderView将窗体坐标转换成单元格索引,这一点可由QTableView::paintEvent(QPaintEvent *event)看出。

void QTableView::paintEvent(QPaintEvent *event)
{// ... ...for (QRect dirtyArea : region) {dirtyArea.setBottom(qMin(dirtyArea.bottom(), int(y)));if (rightToLeft) {dirtyArea.setLeft(qMax(dirtyArea.left(), d->viewport->width() - int(x)));} else {dirtyArea.setRight(qMin(dirtyArea.right(), int(x)));}// dirtyArea may be invalid when the horizontal header is not stretchedif (!dirtyArea.isValid())continue;// get the horizontal start and end visual sectionsint left = horizontalHeader->visualIndexAt(dirtyArea.left());int right = horizontalHeader->visualIndexAt(dirtyArea.right());if (rightToLeft)qSwap(left, right);if (left == -1) left = 0;if (right == -1) right = horizontalHeader->count() - 1;// get the vertical start and end visual sections and if alternate colorint bottom = verticalHeader->visualIndexAt(dirtyArea.bottom());if (bottom == -1) bottom = verticalHeader->count() - 1;int top = 0;bool alternateBase = false;if (alternate && verticalHeader->sectionsHidden()) {const int verticalOffset = verticalHeader->offset();int row = verticalHeader->logicalIndex(top);for (int y = 0;((y += verticalHeader->sectionSize(top)) <= verticalOffset) && (top < bottom);++top) {row = verticalHeader->logicalIndex(top);if (alternate && !verticalHeader->isSectionHidden(row))alternateBase = !alternateBase;}} else {top = verticalHeader->visualIndexAt(dirtyArea.top());alternateBase = (top & 1) && alternate;}if (top == -1 || top > bottom)continue;// Paint each row itemfor (int visualRowIndex = top; visualRowIndex <= bottom; ++visualRowIndex) {int row = verticalHeader->logicalIndex(visualRowIndex);if (verticalHeader->isSectionHidden(row))continue;int rowY = rowViewportPosition(row);rowY += offset.y();int rowh = rowHeight(row) - gridSize;// Paint each column itemfor (int visualColumnIndex = left; visualColumnIndex <= right; ++visualColumnIndex) {int currentBit = (visualRowIndex - firstVisualRow) * (lastVisualColumn - firstVisualColumn + 1)+ visualColumnIndex - firstVisualColumn;if (currentBit < 0 || currentBit >= drawn.size() || drawn.testBit(currentBit))continue;drawn.setBit(currentBit);int col = horizontalHeader->logicalIndex(visualColumnIndex);if (horizontalHeader->isSectionHidden(col))continue;int colp = columnViewportPosition(col);colp += offset.x();int colw = columnWidth(col) - gridSize;const QModelIndex index = d->model->index(row, col, d->root);if (index.isValid()) {option.rect = QRect(colp + (showGrid && rightToLeft ? 1 : 0), rowY, colw, rowh);if (alternate) {if (alternateBase)option.features |= QStyleOptionViewItem::Alternate;elseoption.features &= ~QStyleOptionViewItem::Alternate;}d->drawCell(&painter, option, index);}}alternateBase = !alternateBase && alternate;}if (showGrid) {// Find the bottom right (the last rows/columns might be hidden)while (verticalHeader->isSectionHidden(verticalHeader->logicalIndex(bottom))) --bottom;QPen old = painter.pen();painter.setPen(gridPen);// Paint each rowfor (int visualIndex = top; visualIndex <= bottom; ++visualIndex) {int row = verticalHeader->logicalIndex(visualIndex);if (verticalHeader->isSectionHidden(row))continue;int rowY = rowViewportPosition(row);rowY += offset.y();int rowh = rowHeight(row) - gridSize;painter.drawLine(dirtyArea.left(), rowY + rowh, dirtyArea.right(), rowY + rowh);}// Paint each columnfor (int h = left; h <= right; ++h) {int col = horizontalHeader->logicalIndex(h);if (horizontalHeader->isSectionHidden(col))continue;int colp = columnViewportPosition(col);colp += offset.x();if (!rightToLeft)colp +=  columnWidth(col) - gridSize;painter.drawLine(colp, dirtyArea.top(), colp, dirtyArea.bottom());}painter.setPen(old);}}// ... ...
}

因此,要实现QTableView支持鼠标滚动缩放,就需要使QTableView在x方向缩放与水平QHeaderView保持一致,而在y方向伸缩与竖直QHeaderView保持一致。

以水平QHeaderView为例,设坐标变换表示为\boldsymbol{h}_{d}=\boldsymbol{h}\cdot \boldsymbol{ W}_{1}\cdot \boldsymbol{V}_{1};对于QTableView,设坐标变换为\boldsymbol{x}_{d}=\boldsymbol{x}\cdot \boldsymbol{ W}_{2}\cdot \boldsymbol{V}_{2}。则有,\boldsymbol{x}_{d}=\boldsymbol{x}\cdot \left (\boldsymbol{ W}_{2}\cdot \boldsymbol{ W}_{1}^{-1} \right )\cdot \boldsymbol{ W}_{1}\cdot \boldsymbol{V}_{2}

另一方面,从代码实现可以看出,QTableView::paintEvent(QPaintEvent *event)绘制函数采用了默认的变换矩阵\boldsymbol{W}=\boldsymbol{I}

/*!Paints the table on receipt of the given paint event \a event.
*/
void QTableView::paintEvent(QPaintEvent *event)
{// ... ...QPainter painter(d->viewport);// if there's nothing to do, clear the area and returnif (horizontalHeader->count() == 0 || verticalHeader->count() == 0 || !d->itemDelegate)return;const int x = horizontalHeader->length() - horizontalHeader->offset() - (rightToLeft ? 0 : 1);const int y = verticalHeader->length() - verticalHeader->offset() - 1;// ... ...
}

因此,一种实现方法,就是通过重写QTableView::paintEvent(QPaintEvent *event),指定合适的变换矩阵\boldsymbol{W}来实现QTableView滚动缩放。

具体来说,首先重写wheelEvent(QWheelEvent* event)以将鼠标滚动输入转化成缩放比例,

// following the zoom strategy in SALOME GraphicsView_Viewer 
// see SALOME gui/src/GraphicsView/GraphicsView_Viewer.cpp
void SheetTableView::wheelEvent(QWheelEvent* event)
{if (QApplication::keyboardModifiers() & Qt::ControlModifier) {const double d = 1.05;double q = pow(d, -event->delta() / 120.0);this->scale(q, q);event->accept();return;}return QTableView::wheelEvent(event);
}
void SheetTableView::scale(qreal sx, qreal sy)
{//Q_D(QGraphicsView);QTransform matrix = myMatrix;matrix.scale(sx, sy);setTransform(matrix);for (int i = 0; i < horizontalHeader()->count(); ++i) {int s = horizontalHeader()->sectionSize(i);horizontalHeader()->resizeSection(i, s * sx);}for (int i = 0; i < verticalHeader()->count(); ++i) {int s = verticalHeader()->sectionSize(i);verticalHeader()->resizeSection(i, s * sy);}this->update();
}

 然后重写paintEvent(QPaintEvent* event),依据缩放比例将单元格内容进行缩放。需要注意的是,由于水平/竖直 QHeaderView已经进行了缩放,而QTableView是依据水平/竖直QHeaderView计算单元格坐标,因此,在绘制窗体时,需要使用传入的窗体坐标;但为了缩放单元格内容,为QPainter指定了变换矩阵\boldsymbol{W},所以单元格坐标要施加矩阵变化\boldsymbol{W}^{-1}

void SheetTableView::paintEvent(QPaintEvent* event)
{// ... ...auto matrix = viewportTransform();auto inv_matrix = matrix.inverted();QPainter painter(viewport());painter.setWorldTransform(matrix);// ...for (QRect dirtyArea : region) {// ... ...// Paint each row itemfor (int visualRowIndex = top; visualRowIndex <= bottom; ++visualRowIndex) {int row = verticalHeader->logicalIndex(visualRowIndex);if (verticalHeader->isSectionHidden(row))continue;int rowY = rowViewportPosition(row);rowY += offset.y();int rowh = rowHeight(row) - gridSize;// Paint each column itemfor (int visualColumnIndex = left; visualColumnIndex <= right; ++visualColumnIndex) {int currentBit =(visualRowIndex - firstVisualRow) * (lastVisualColumn - firstVisualColumn + 1)+ visualColumnIndex - firstVisualColumn;if (currentBit < 0 || currentBit >= drawn.size() || drawn.testBit(currentBit))continue;drawn.setBit(currentBit);int col = horizontalHeader->logicalIndex(visualColumnIndex);if (horizontalHeader->isSectionHidden(col))continue;int colp = columnViewportPosition(col);colp += offset.x();int colw = columnWidth(col) - gridSize;const QModelIndex index = model()->index(row, col, rootIndex());if (index.isValid()) {//option.rect = QRect(colp + (showGrid && rightToLeft ? 1 : 0), rowY, colw, rowh);option.rect = inv_matrix.mapRect(QRect(colp + (showGrid && rightToLeft ? 1 : 0), rowY, colw, rowh));if (alternate) {if (alternateBase)option.features |= QStyleOptionViewItem::Alternate;elseoption.features &= ~QStyleOptionViewItem::Alternate;}this->drawCell(&painter, option, index);}}alternateBase = !alternateBase && alternate;}if (showGrid) {// Find the bottom right (the last rows/columns might be hidden)while (verticalHeader->isSectionHidden(verticalHeader->logicalIndex(bottom)))--bottom;QPen old = painter.pen();painter.setPen(gridPen);// Paint each rowfor (int visualIndex = top; visualIndex <= bottom; ++visualIndex) {int row = verticalHeader->logicalIndex(visualIndex);if (verticalHeader->isSectionHidden(row))continue;int rowY = rowViewportPosition(row);rowY += offset.y();int rowh = rowHeight(row) - gridSize;//painter.drawLine(dirtyArea.left(), rowY + rowh, dirtyArea.right(), rowY + rowh);QPoint p1(dirtyArea.left(), rowY + rowh), p2(dirtyArea.right(), rowY + rowh);painter.drawLine(inv_matrix.map(p1), inv_matrix.map(p2));}// Paint each columnfor (int h = left; h <= right; ++h) {int col = horizontalHeader->logicalIndex(h);if (horizontalHeader->isSectionHidden(col))continue;int colp = columnViewportPosition(col);colp += offset.x();if (!rightToLeft)colp += columnWidth(col) - gridSize;//painter.drawLine(colp, dirtyArea.top(), colp, dirtyArea.bottom());QPoint p1(colp, dirtyArea.top()), p2(colp, dirtyArea.bottom());painter.drawLine(inv_matrix.map(p1), inv_matrix.map(p2));}painter.setPen(old);}}//#if QT_CONFIG(draganddrop)
//    // Paint the dropIndicator
//    d->paintDropIndicator(&painter);
//#endif
}

依据上述方案,的确可以实现QTableView单元格尺寸、单元格内容的滚动缩放,但是存在以下问题:

  • 性能问题

QHeaderView由一串连续的section组成,每个section对应一个列/行字段,在section移动过程中,visualIndex会发生变化,但logicalIndex不变。

Ref. from  QHeaderView 

Each header has an orientation() and a number of sections, given by the count() function. A section refers to a part of the header - either a row or a column, depending on the orientation.

Sections can be moved and resized using moveSection() and resizeSection(); they can also be hidden and shown with hideSection() and showSection().

Each section of a header is described by a section ID, specified by its section(), and can be located at a particular visualIndex() in the header. 

You can identify a section using the logicalIndex() and logicalIndexAt() functions, or by its index position, using the visualIndex() and visualIndexAt() functions. The visual index will change if a section is moved, but the logical index will not change.

虽然QHeaderView::resizeSection(int logical, int size)可以调整单元格大小,但如果section数较多,逐个调整section尺寸比较卡。

  • 缩放QHeaderView

在QHeaderView::paintEvent(QPaintEvent *e)中,所使用QPainter的没有施加缩放变换矩阵。因此,无法对QHeaderView section内容进行缩放。

网络资料

Qt源码分析:QMetaObject实现原理icon-default.png?t=N7T8https://blog.csdn.net/qq_26221775/article/details/137023709?spm=1001.2014.3001.5502

Qt源码分析: QEventLoop实现原理icon-default.png?t=N7T8https://blog.csdn.net/qq_26221775/article/details/136776793?spm=1001.2014.3001.5502

QWidgeticon-default.png?t=N7T8https://doc.qt.io/qt-5/qwidget.html
QPaintericon-default.png?t=N7T8https://doc.qt.io/qt-5/qpainter.html
QPaintDeviceicon-default.png?t=N7T8https://doc.qt.io/qt-5/qpaintdevice.html
QPaintEngineicon-default.png?t=N7T8https://doc.qt.io/qt-5/qpaintengine.html
Coordinate Systemicon-default.png?t=N7T8https://doc.qt.io/qt-5/coordsys.html

相关文章:

Qt源码分析:窗体绘制与响应

作为一套开源跨平台的UI代码库&#xff0c;窗体绘制与响应自然是最为基本的功能。在前面的博文中&#xff0c;已就Qt中的元对象系统(反射机制)、事件循环等基础内容进行了分析&#xff0c;并捎带阐述了窗体响应相关的内容。因此&#xff0c;本文着重分析Qt中窗体绘制相关的内容…...

docker 安装 禅道

docker pull hub.zentao.net/app/zentao:20.1.1 sudo docker network create --subnet172.172.172.0/24 zentaonet 使用 8087端口号访问 使用禅道mysql 映射到3307 sudo docker run \ --name zentao2 \ -p 8087:80 \ -p 3307:3306 \ --networkzentaonet \ --ip 172.172.172.…...

【简要说说】make 增量编译的原理

make 增量编译的原理 make是一个工具&#xff0c;它可以根据依赖关系和时间戳来自动执行编译命令。 当您修改了源代码文件后&#xff0c;make会检查它们的修改时间是否比目标文件&#xff08;如可执行文件或目标文件&#xff09;新&#xff0c;如果是&#xff0c;就会重新编译…...

DETRs Beat YOLOs on Real-time Object Detection论文翻译

cvpr 2024 论文名称 DETRs在实时目标检测上击败YOLO 地址 https://arxiv.longhoe.net/abs/2304.08069 代码 https://github.com/lyuwenyu/RT-DETR 目录 摘要 1介绍 2.相关工作 2.1实时目标探测器 2.2.端到端物体探测器 3.检测器的端到端速度 3.1.NMS分析 3.2.端到端速度…...

SpringBoot 多数据源配置

目录 一. 引入maven依赖包 二. 配置yml 三、创建 xml 分组文件 四、切换数据源 一. 引入maven依赖包 <dependency><groupId>com.baomidou</groupId><artifactId>dynamic-datasource-spring-boot-starter</artifactId><version>3.6.1&…...

RK3568驱动指南|第十六篇 SPI-第192章 mcp2515驱动编写:完善write和read函数

瑞芯微RK3568芯片是一款定位中高端的通用型SOC&#xff0c;采用22nm制程工艺&#xff0c;搭载一颗四核Cortex-A55处理器和Mali G52 2EE 图形处理器。RK3568 支持4K 解码和 1080P 编码&#xff0c;支持SATA/PCIE/USB3.0 外围接口。RK3568内置独立NPU&#xff0c;可用于轻量级人工…...

#BI建模与数仓建模有什么区别?指标体系由谁来搭建?

问题1&#xff1a; 指标体系是我们数仓来搭建还是分析师来做&#xff0c;如何去推动&#xff1f; 问题2&#xff1a;BI建模与数仓建模有什么区别&#xff1f; 指标体系要想做好&#xff0c;其实是分两块内容的&#xff0c;一块是顶层设计阶段&#xff0c;业务指标体系的搭建&am…...

如何用Python实现三维可视化?

Python拥有很多优秀的三维图像可视化工具&#xff0c;主要基于图形处理库WebGL、OpenGL或者VTK。 这些工具主要用于大规模空间标量数据、向量场数据、张量场数据等等的可视化&#xff0c;实际运用场景主要在海洋大气建模、飞机模型设计、桥梁设计、电磁场分析等等。 本文简单…...

chrome.storage.local.set 未生效

之前chrome.storage.local.set 和 get 一直不起作用 使用以下代码运行成功。 chrome.storage.local.set({ pageState: "main" }).then(() > {console.log("Value is set");});chrome.storage.local.get(["pageState"]).then((result) > …...

泛微开发修炼之旅--30 linux-Ecology服务器运维脚本

文章链接&#xff1a;30 linux-ecology服务器运维脚本...

LeetCode 全排列

思路&#xff1a;这是一道暴力搜索问题&#xff0c;我们需要列出答案的所有可能组合。 题目给我们一个数组&#xff0c;我们很容易想到的做法是将数组中的元素进行排列&#xff0c;如何区分已选中和未选中的元素&#xff0c;容易想到的是建立一个标记数组&#xff0c;已经选中的…...

python实现支付宝异步回调验签

说明 python实现支付宝异步回调验签&#xff0c;示例中使用Django框架。 此方案使用了支付宝的pythonSDK&#xff0c;请一定装最新版本的&#xff0c;支付宝官网文档不知道多久没更新了&#xff0c;之前的版本pip安装会报一些c库不存在的错误&#xff1b; pip install alipay-…...

注意!Vue.js 或 Nuxt.js 中请停止使用.value

大家好,我是CodeQi! 一位热衷于技术分享的码仔。 当您在代码中使用.value时,必须每次都检查变量是否存在并且是引用。 这可能很麻烦,因为在运行时使用.value可能会导致错误。然而,有一个简单的解决方法,即使用unref()而不是.value。 unref()会检查变量是否是引用,并自…...

Java:JDK、JRE和JVM 三者关系

文章目录 一、JDK是什么二、JRE是什么三、JDK、JRE和JVM的关系 一、JDK是什么 JDK&#xff08;Java Development Kit&#xff09;&#xff1a;Java开发工具包 JRE&#xff1a;Java运行时环境开发工具&#xff1a;javac&#xff08;编译工具&#xff09;、java&#xff08;运行…...

Radio专业术语笔记

在收音机的 RDS (Radio Data System) 功能中&#xff0c;CT 代表 “Clock Time”。RDS 是一种数字广播标准&#xff0c;用于在调频广播中传输辅助数据&#xff0c;如电台名称、节目类型、交通信息等。CT 功能是其中的一部分&#xff0c;用于同步和显示广播电台发送的当前时间。…...

cocosCreator找出未用到的图片

最近整理项目的时候发现有些资源文件夹有点轮乱(一些历史原因导致的),而且有很多图片都是没用了的,但是没有被删除掉,还一直放在项目中,导致项目的资源文件夹比较大,而且还冗余。于是今天想着整理一下。 公开免费链接 找出未使用的图片 有好几种方法可以找出未使用的图片…...

一览 Anoma 上的有趣应用概念

撰文&#xff1a;Tia&#xff0c;Techub News 本文来源香港Web3媒体&#xff1a;Techub News Anoma 的目标是为应用提供通用的意图机器接口&#xff0c;这意味着使用 Anoma&#xff0c;开发人员可以根据意图和分布式意图机编写应用&#xff0c;而不是根据事务和特定状态机进行…...

Spring Boot集成fastjson2快速入门Demo

1.什么是fastjson2&#xff1f; fastjson2是阿里巴巴开发的一个高性能的Java JSON处理库&#xff0c;它支持将Java对象转换成JSON格式&#xff0c;同时也支持将JSON字符串解析成Java对象。本文将介绍fastjson2的常见用法&#xff0c;包括JSON对象、JSON数组的创建、取值、遍历…...

Three.js机器人与星系动态场景(二):强化三维空间认识

在上篇博客中介绍了如何快速利用react搭建three.js平台&#xff0c;并实现3D模型的可视化。本文将在上一篇的基础上强化坐标系的概念。引入AxesHelper辅助工具及文本绘制工具&#xff0c;带你快速理解camer、坐标系、position、可视区域。 Three.js机器人与星系动态场景&#x…...

java顺序查找

其中有一个常用的编程思想&#xff1a; 由于是遍历查找&#xff0c;不能用if-else来输出没有找到&#xff0c;而应该设置一个索引index&#xff0c;如果找到就将index的值设置成下标的值&#xff0c;如果遍历结束后index仍为初始值&#xff0c;才是没有找到 //2024.07.03impor…...

提升学生职务执行力的智慧校园学工管理策略

智慧校园的学工管理系统匠心独运地融入了“学生职务”这一创新模块&#xff0c;它紧密贴合学生的实际需求&#xff0c;致力于在校期间的实践经验积累和个人能力的全面提升。这个模块化身为一个便捷的综合平台&#xff0c;让学生们能够轻松发掘并参与到丰富多彩的校内职务中去&a…...

系统运维面试总结(shell编程)

SYNDDOS攻击&#xff0c;需要判断这个访问是正常访问还是信包攻击&#xff0c;当前这个信包发起的访问数量是多少&#xff0c;例如看到30个信包同时再访问时设置监控报警。 一般选用/dev/urandom生成&#xff0c;但其生成的随机数带有二进制乱码&#xff0c;所以需要tr命令…...

在数据库中,什么是主码、候选码、主属性、非主属性?

在数据库中&#xff0c;主码、候选码、主属性和非主属性是几个重要的概念&#xff0c;它们对于理解数据库的结构和数据的完整性至关重要。以下是对这些概念的详细解释&#xff1a; 一、主码&#xff08;Primary Key&#xff09; 定义&#xff1a;主码&#xff0c;也被称为主键…...

Linux-笔记 udev机制介绍

目录 前言 概念 规则文件 规则文件的命名 规则文件的语法 匹配条件 赋值指令 例子 前言 由于之前利用udev机制实现了一个自动配置某功能的项目&#xff0c;所以这里做一下笔记总结&#xff0c;什么是udev&#xff1f;怎么用&#xff1f; 概念 udev其实是linux系统中一…...

深度学习基准模型Mamba

深度学习基准模型Mamba Mamba(英文直译&#xff1a;眼镜蛇)具有选择性状态空间的线性时间序列建模&#xff0c;是一种先进的状态空间模型 (SSM)&#xff0c;专为高效处理复杂的数据密集型序列而设计。 Mamba是一种深度学习基准模型&#xff0c;专为处理长序列数据而设计&…...

面试专区|【40道移动端测试高频题整理(附答案背诵版)】

iOS应用和Android应用测试有什么侧重点&#xff1f; iOS应用和Android应用测试的侧重点略有不同&#xff0c;主要表现在以下几个方面&#xff1a; 分辨率和屏幕尺寸&#xff1a;Android设备的分辨率和屏幕尺寸多种多样&#xff0c;因此&#xff0c;需要测试更多的分辨率和屏幕…...

vb6多线程异步,VB.NET 全用API实现:CreateThread创建多线程,等待线程完成任务

在VB.NET中&#xff0c;你可以使用API函数来创建多线程并等待线程完成任务。以下是一个示例代码&#xff0c;展示如何使用API函数来实现这个功能&#xff1a; Imports System.Runtime.InteropServices Imports System.ThreadingPublic Class Form1Private Delegate Sub ThreadC…...

Python中计算一个序列中特点值出现的数量,比如 [0,0,0,1,1,0,0,]中1的数量

要计算列表 [0, 0, 0, 1, 1, 0, 0] 中 1 的数量&#xff0c;可以使用 Python 中的 count 方法。这是一个简洁而有效的方法。下面是示例代码&#xff1a; # 定义列表 lst [0, 0, 0, 1, 1, 0, 0]# 计算列表中1的数量 num_ones lst.count(1)# 输出结果 print("Number of 1…...

gitignore

.gitignore 是一个在 Git 版本控制系统中使用的文件&#xff0c;它用于指定在 Git 仓库中哪些文件或目录应该被忽略&#xff0c;即不会被 Git 追踪或记录。这对于一些自动生成的、用户特定的或敏感的文件特别有用&#xff0c;比如编译后的目标文件、日志文件、配置文件中的密码…...

Adobe Premiere 视频编辑软件下载安装,pr全系列分享 轻松编辑视频

Adobe Premiere&#xff0c;自其诞生之日起&#xff0c;便以其卓越的性能和出色的表现&#xff0c;稳坐视频编辑领域的王者宝座&#xff0c;赢得了无数专业编辑人员与广大爱好者的青睐。这款强大的视频编辑软件&#xff0c;凭借其丰富的功能和灵活的操作性&#xff0c;为用户提…...