Qt开发 | Qt绘图技术 | 常见图像绘制 | Qt移动鼠标绘制任意形状 | Qt绘制带三角形箭头的窗口

一、基本绘图技术介绍

  Qt提供了绘图技术,程序员可以在界面上拖动鼠标,或者在代码里指定参数进行绘图。

Qt绘图技术介绍

  • QPainter

    基本要素

    • QPen:用于控制线条的颜色、宽度、线型等;

      • 宽度(Width):
        • 通过 setWidth() 方法设置画笔的宽度,这将决定绘制线条的粗细。
      • 颜色(Color):
        • 使用 setColor() 方法设置画笔的颜色。颜色可以是 Qt 命名的颜色常量,如 Qt::red,或者使用 QColor 对象定义的更具体的颜色。
      • 样式(Style):
        • setStyle() 方法定义了线条的样式。可以是实线(Qt::SolidLine)、虚线(Qt::DashLine)、点线(Qt::DotLine)等。
      • 端点样式(Cap Style):
        • setCapStyle() 方法设置线条端点的样式。常见的端点样式有方形(Qt::SquareCap)、圆形(Qt::RoundCap)和平面(Qt::FlatCap)。
      • 连接样式(Join Style):
        • setJoinStyle() 方法定义了线条连接处的样式。斜接(Qt::MiterJoin)是默认样式,适用于大多数情况。还可以选择圆角(Qt::RoundJoin)或斜切(Qt::BevelJoin)。
      • 线型(Dash Pattern):
        • 对于虚线或点线,可以通过 setDashPattern() 方法设置具体的虚线模式,例如定义点和间隔的长度。
      • 笔刷(Brush):
        • 虽然 QPen 主要用于线条,但它也有一个 setBrush() 方法,可以设置用于填充形状轮廓的画刷样式。
      • 使用 QPen:
        • 创建 QPen 对象后,可以通过上述方法设置其属性,然后通过 QPainter::setPen() 方法将其应用到 QPainter 对象上。之后,QPainter 将使用这个画笔的样式来绘制线条和形状的轮廓。

      示例:

      QPainter painter;
      Qpen pen;
      
      //设置画笔属性
      pen.setWidth(10);  		//设置画笔的宽度为10像素。
      pen.setColor(Qt::red);	//设置画笔的颜色为红色
      pen.setStyle(Qt::SolidLine);    //设置画笔的样式为实线
      pen.setCapStyle(Qt::SquareCap);	//设置画笔端点的样式为方形
      pen.setJoinStyle(Qt::MiterJoin); // 设置画笔线条连接处的样式为斜接,这是最常用的线条连接方式,适用于大多数情况。
      
      painter.setPen(pen);    //将设置好的QPen对象应用到QPainter对象上
      
    • QBrush:设置区域填充特性,可以设置填充颜色、填充方式、渐变特性等,还可以采用图片做纹理填充

      • 颜色设置:
        • QBrush 可以通过 setColor() 方法设置填充颜色。颜色可以使用 QColor 对象指定,提供广泛的颜色选择。
      • 填充样式:
        • setStyle()方法用于设置填充样式。Qt::BrushStyle枚举提供了多种预定义的填充样式,如:
          • Qt::SolidPattern:纯色填充。
          • Qt::HorizontalPatternQt::VerticalPattern:水平或垂直条纹填充。
          • Qt::CrossPatternQt::BDiagPattern:交叉或对角线条纹填充。
          • Qt::DenseNPattern:提供不同密度的条纹或点状图案。
      • 渐变填充:
        • QBrush 支持使用渐变作为填充模式。可以创建 QLinearGradientQRadialGradient 对象,并将其设置为 QBrush 的渐变属性。
      • 纹理填充:
        • 通过 setTexture() 方法,可以将 QPixmapQImage 对象设置为纹理,用于填充形状。
      • 透明度:
        • setOpacity() 方法允许设置填充的透明度,范围从 0(完全透明)到 1(完全不透明)。
      • 变换:
        • setTransform() 方法可以对 QBrush 应用变换,如旋转、缩放等,这会影响纹理或渐变在形状上的呈现方式。
      • 使用 QBrush:
        • 在绘制过程中,一旦 QBrush 对象被设置好,就可以通过QPainter::setBrush() 方法将其应用到 QPainter 对象上。随后,使用 QPainter 绘制的封闭形状将使用该画刷进行填充。

      示例:

      QPainter painter;
      QBrush brush;
      
      brush.setColor(Qt::yellow); // 设置为黄色
      brush.setStyle(Qt::SolidPattern);	//设置填充样式为纯色填充
      
      painter.setBrush(brush);
      
    • QFont:绘制文字时,设置文字的字体样式、大小等属性

      • 字体名称(Family):
        • 使用 setFamily() 方法设置字体的名称,如 “Arial”、“Times New Roman” 等。
      • 字体大小(Size):
        • setPointSize() 方法用于设置字体的大小,通常以点为单位。
      • 字体风格(Style):
        • setStyle() 方法可以设置字体的风格,如 QFont::StyleNormalQFont::StyleItalic 等。
      • 字体粗细(Weight):
        • 使用 setWeight() 方法设置字体的粗细,如 QFont::LightQFont::NormalQFont::Bold
      • 字体拉伸(Stretch):
        • setStretch() 方法用于设置字体的宽度拉伸,可以使得字体更宽或更窄。
      • 字体间距(Kerning):
        • setKerning() 方法可以启用或禁用字符间距调整,影响字符之间的空间。
      • 字体字距(Letter Spacing):
        • setLetterSpacing() 方法设置字符之间的水平间距。
      • 文本方向(Text Direction):
        • setStyleStrategy() 方法可以设置文本的方向,如从左到右或从右到左。
      • 固定宽度字体(Fixed Pitch):
        • setFixedPitch() 方法用于设置是否使用固定宽度字体。
      • 使用 QFont:
        • 创建 QFont 对象后,可以通过上述方法设置其属性,然后通过 QPainter::setFont() 方法将其应用到 QPainter 对象上。之后,使用 QPainter 绘制的文本将使用这个字体。

      示例:

      QFont font;
      font.setFamily("Helvetica");
      font.setPointSize(12);
      font.setBold(true); // 设置为粗体
      font.setStyleItalic(true); // 设置为斜体
      
      QPainter painter;
      painter.setFont(font);
      painter.drawText(10, 20, "Hello, World!"); // 使用字体绘制文本
      
    • 渐变填充

      • QLinearGradient线性渐变填充,颜色沿着直线从一点渐变到另一点。可以指定起点和终点,以及中间的颜色停止点来创建平滑的颜色过渡。

        • 起点和终点:
          • QLinearGradient 需要两个点来定义渐变的方向和范围:起始点和终点。渐变的颜色将从起始点开始,向终点过渡。
        • 颜色停止:
          • 使用 setColorAt() 方法可以设置颜色在渐变中的停止点。这个方法接受一个0到1之间的值,表示从起始点到终点的相对位置,以及一个 QColor 对象,表示该位置的颜色。
        • 渐变坐标系统:
          • 渐变的坐标系统可以是对象坐标系或绝对坐标系。通过 setCoordinateMode() 方法可以设置渐变的坐标模式。
        • 坐标模式:
          • QLinearGradient支持两种坐标模式:
            • ObjectBoundingMode:渐变是相对于使用渐变的对象的边界框。
            • StretchToDeviceMode:渐变是相对于整个设备(如画布或窗口)的尺寸。
        • 使用 QLinearGradient:
          • 创建 QLinearGradient 对象后,设置起始点、终点和颜色停止点,然后将它作为 QBrush 对象的样式来使用。

        示例:

        QLinearGradient gradient(0, 0, 100, 100); // 定义从左到右的渐变
        gradient.setColorAt(0.0, QColor("red"));    // 起始颜色为红色
        gradient.setColorAt(1.0, QColor("blue"));   // 结束颜色为蓝色
        
        QBrush brush(gradient); // 创建一个使用渐变的画刷
        QPainter painter;
        painter.setBrush(brush);
        painter.drawRect(10, 10, 100, 100); // 使用画刷填充矩形
        
      • QRadialGradient:径向渐变填充,颜色从中心点向外辐射,形成一个圆形或椭圆形的渐变效果。这种渐变通常用于创建按钮或图标的立体感。

      • QConicalGradient:圆锥形渐变填充,颜色从一个点向外辐射,但与径向渐变不同,圆锥形渐变的颜色过渡是沿着圆锥的侧面进行的,而不是沿着半径。这种渐变较少使用,但可以创造出独特的视觉效果。

  • QPaintDevice

    是一个可以使用QPainter进行绘图的抽象的二维界面

    常用的绘图设备:

    • QWidget
    • QPixmap、QImage:可用来绘制视频图片数据
  • QPaintEngine

    • 给QPainter提供在不同设备上绘图的接口
    • 应用程序一般无需与QPaintEngine打交道
    • 可以禁用QPaintEngine,使用DX或OPenGL自渲染
  • 绘图事件paintEvent()

    绘图事件,需要用户override

  • 坐标系:原点在左上角,x向左为正,y向下为正

  • 基本的绘图元素

    在Qt框架中,这些操作是通过QPainter类实现的。QPainter是一个低级的绘图类,提供了丰富的方法来绘制线条、形状、文本和图像。使用QPainter时,你通常会先创建一个QPainter对象,然后设置画笔、画刷等属性,最后调用相应的绘图函数来绘制内容。

      • drawPoint: 用于绘制单个点。
      • drawPoints: 用于绘制多个点。通常需要一个点的数组或列表作为参数。
    • 线
      • 直线: drawLine 用于绘制直线。通常需要两个点(起点和终点)作为参数。
      • 圆弧: drawArc 用于绘制圆或椭圆的一部分,即圆弧。需要指定圆心、半径、起始角度和结束角度。
    • 封闭的图形
      • 矩形
        • 普通矩形:drawRect: 用于绘制一个矩形的边框或填充整个矩形。
        • 圆角矩形:drawRoundedRect: 用于绘制带有圆角的矩形。
      • 弧弦:drawChord 用于绘制一个圆弧以及与圆弧两端点相连的直线,形成一个封闭的图形
      • 椭圆: drawEllipse 用于绘制椭圆的边框或填充整个椭圆
    • 任意路径绘制
      • drawPolygon:绘制一个多边形。传入的点列表中最后一个点会自动与第一个点相连,形成一个闭合的多边形。
      • drawPolyline:与drawPolygon类似,drawPolyline用于绘制一系列点连接成的折线,但最后一个点不会与第一个点相连,因此结果是开放的。
      • drawConvexPolygon:用于绘制任意凸多边形。凸多边形是指任意两个顶点之间的线段都不会与其他顶点相交的多边形。
      • drawLines:绘制一系列的线段。你需要传入一系列的起点和终点来定义这些线段。
      • drawPath:用于绘制更复杂的路径,可以包含直线段、曲线段等
      • drawPie:用于绘制扇形,即圆的一部分。你需要指定圆心、半径、起始角度和结束角度来定义扇形。
    • 图片绘制
      • drawPixmap: 用于在指定位置绘制QPixmap对象,QPixmap是Qt中用于存储图像数据的类。
      • drawImage: 类似于drawPixmap,但用于绘制QImage对象,QImage是另一个图像类,通常用于处理像素数据。
    • 绘制文本
      • drawText: 用于在指定位置绘制文本。你可以指定文本内容、位置、对齐方式等属性。
    • 其他操作
      • eraseRect: 用于擦除指定矩形区域内的内容,通常是用当前画笔的背景色来填充这个区域。
      • fillPath: 用于填充由QPainterPath定义的路径。如果设置了画笔颜色,路径内部会被填充,但不会绘制路径的轮廓线。
      • fillRect: 用于填充一个矩形区域,如果设置了画笔样式,矩形的边框也会被绘制。

二、常见的18种图形、路径、文字、图片绘制

  利用QTreeView类的信号void clicked(const QModelIndex &index);得到要绘制的图形,触发槽函数,槽函数用于设置绘图样式并调用update()函数触发paintEvent()绘图事件函数,在该函数中绘制已选择的图形。

示例:

drawtype.h

#pragma once
//基本画图枚举
enum class DRAW_TYPE
{
    point,
    multipoints,
    line,
    arc,
    rect,
    roundrect,
    chord,
    ellipse,
    polygon,
    polyline,
    ConvexPloygon,
    lines,
    path,
    pie,
    image,
    pixmap,
    draw_text,
    draw_erase,
    draw_fillpath,
    draw_fillrect
};

CPaintWidget.h

#pragma once
#include <QWidget>
#include "drawtype.h"

class CPaintWidget : public QWidget
{
	Q_OBJECT

public:
	CPaintWidget(QWidget* p = nullptr);
	~CPaintWidget() {}

	void setDrawType(DRAW_TYPE type);   //设置绘图类型

private:
	void paintEvent(QPaintEvent* event) override; //绘图事件

private:
        /* 绘制基本图形 */
        void draw_point();
        void draw_multipoints();
        void draw_line();
        void draw_arc();
        void draw_rect();
        void draw_roundrect();
        void draw_chord();
        void draw_ellipse();
        void draw_polygon();
        void draw_polyline();
        void draw_ConvexPloygon();
        void draw_lines();
        void draw_path();
        void draw_pie();
        void draw_image();
        void draw_pixmap();
        void draw_text();
        void draw_erase();
        void draw_fillpath();
        void draw_fillrect();
    
private:
	DRAW_TYPE m_drawType;

	int W = 0;
	int H = 0;
};

CPaintWidget.cpp

#include "CPaintWidget.h"
#include <QPainter>
#include <QPainterPath>

CPaintWidget::CPaintWidget(QWidget* p)
	:QWidget(p)
{
	this->setMinimumSize(800, 600);

	m_drawType = DRAW_TYPE::polygon;
}

void CPaintWidget::paintEvent(QPaintEvent* event)
{
	W = this->width();
	H = this->height();

	switch (m_drawType)
	{
	case DRAW_TYPE::point:
		draw_point();
		break;

	case DRAW_TYPE::multipoints:
		draw_multipoints();
		break;

	case DRAW_TYPE::line:
		draw_line();
		break;

	case DRAW_TYPE::arc:
		draw_arc();
		break;

	case DRAW_TYPE::rect:
		draw_rect();
		break;

	case DRAW_TYPE::roundrect:
		draw_roundrect();
		break;

	case DRAW_TYPE::chord:
		draw_chord();
		break;

	case DRAW_TYPE::ellipse:
		draw_ellipse();
		break;

	case DRAW_TYPE::polygon:
		draw_polygon();
		break;

	case DRAW_TYPE::polyline:
		draw_polyline();
		break;

	case DRAW_TYPE::ConvexPloygon:
		draw_ConvexPloygon();
		break;

	case DRAW_TYPE::lines:
		draw_lines();
		break;

	case DRAW_TYPE::path:
		draw_path();
		break;

	case DRAW_TYPE::pie:
		draw_pie();
		break;

	case DRAW_TYPE::image:
		draw_image();
		break;

	case DRAW_TYPE::pixmap:
		draw_pixmap();
		break;

	case DRAW_TYPE::draw_text:
		draw_text();
		break;

	case DRAW_TYPE::draw_erase:
		draw_erase();
		break;

	case DRAW_TYPE::draw_fillpath:
		draw_fillpath();
		break;

	case DRAW_TYPE::draw_fillrect:
		draw_fillrect();
		break;

	default:
		break;
	}
}

void CPaintWidget::draw_point()
{
	QPainter painter(this);
	QPen pen;
	pen.setWidth(10);
	pen.setColor(Qt::red);
	pen.setStyle(Qt::SolidLine);
	painter.setPen(pen);
	painter.drawPoint(QPoint(W / 2, H / 2));
}

void CPaintWidget::draw_multipoints()
{
	QPainter painter(this);
	QPen pen;
	pen.setWidth(10);
	pen.setColor(Qt::blue);
	pen.setStyle(Qt::SolidLine);
	painter.setPen(pen);

	// 画很多点
	QPoint points[] = {
	QPoint(5 * W / 12,H / 4),
	QPoint(3 * W / 4, 5 * H / 12),
	QPoint(2 * W / 4, 5 * H / 12) };
	painter.drawPoints(points, 3);
}

void CPaintWidget::draw_line()
{
	QPainter painter(this);

	//QLine Line(W / 4, H / 4, W / 2, H / 2);
	QLine Line(W / 4, H / 4, W / 2, H / 4);
	painter.drawLine(Line);
}

void CPaintWidget::draw_arc()
{
	QPainter painter(this);
	QRect rect(W / 4, H / 4, W / 2, H / 2);
	int startAngle = 90 * 16;
	int spanAngle = 90 * 16;       //旋转 90°
	painter.drawArc(rect, startAngle, spanAngle);
}

void CPaintWidget::draw_rect()
{
	QPainter painter(this);

	// 画矩形
	QRect rect(W / 4, H / 4, W / 2, H / 2);
	painter.drawRect(rect);
}

void CPaintWidget::draw_roundrect()
{
	QPainter painter(this);

	// 画圆角矩形
	QRect rect(W / 4, H / 4, W / 2, H / 2);
	painter.drawRoundedRect(rect, 20, 20);
}

void CPaintWidget::draw_chord()
{
	QPainter painter(this);
	QRect rect(W / 4, H / 4, W / 2, H / 2);
	int startAngle = 90 * 16;
	int spanAngle = 90 * 16;
	painter.drawChord(rect, startAngle, spanAngle);
}

void CPaintWidget::draw_ellipse()
{
	QPainter painter(this);

	QRect rect(W / 4, H / 4, W / 2, H / 2);
	painter.drawEllipse(rect);
}

void CPaintWidget::draw_polygon()
{
	QPainter painter(this);
	painter.setRenderHint(QPainter::Antialiasing);

	QPen pen;
	pen.setWidth(10);
	pen.setColor(Qt::red);
	pen.setStyle(Qt::SolidLine);
	pen.setCapStyle(Qt::SquareCap);
	pen.setJoinStyle(Qt::MiterJoin);  //画笔断点的样式
	painter.setPen(pen);

	QBrush brush;
	brush.setColor(Qt::yellow);
	brush.setStyle(Qt::SolidPattern);
	painter.setBrush(brush);

	// 画多边形,最后一个点会和第一个点闭合
	QPoint points[] = {
	QPoint(5 * W / 12,H / 4),
	QPoint(3 * W / 4,5 * H / 12),
	QPoint(5 * W / 12,3 * H / 4),
	QPoint(2 * W / 4,5 * H / 12) };
	painter.drawPolygon(points, 4);
}

void CPaintWidget::draw_polyline()
{
	QPainter painter(this);

	// 画多点连接的线,最后一个点不会和第一个点连接
	QPoint points[] = {
	QPoint(5 * W / 12, H / 4),
	QPoint(3 * W / 4, 5 * H / 12),
	QPoint(5 * W / 12, 3 * H / 4),
	QPoint(2 * W / 4, 5 * H / 12)};
	painter.drawPolyline(points, 4);
}

void CPaintWidget::draw_ConvexPloygon()
{
	QPainter painter(this);

	QPoint points[4] = {
		QPoint(5 * W / 12, H / 4),
		QPoint(3 * W / 4, 5 * H / 12),
		QPoint(5 * W / 12, 3 * H / 4),
		QPoint(W / 4, 5 * H / 12)};

	painter.drawConvexPolygon(points, 4);
}

void CPaintWidget::draw_lines()
{
	QPainter painter(this);

	// 画一批直线
	QRect rect(W / 4, H / 4, W / 2, H / 2);
	QVector<QLine> Lines;
	Lines.append(QLine(rect.topLeft(), rect.bottomRight()));
	Lines.append(QLine(rect.topRight(), rect.bottomLeft()));
	Lines.append(QLine(rect.topLeft(), rect.bottomLeft()));
	Lines.append(QLine(rect.topRight(), rect.bottomRight()));
	painter.drawLines(Lines);
}

void CPaintWidget::draw_path()
{
	QPainter painter(this);
	// 绘制由QPainterPath对象定义的路线
	QRect rect(W / 4, H / 4, W / 2, H / 2);
	QPainterPath path;
	path.addEllipse(rect);
	path.addRect(rect);
	painter.drawPath(path);
}

void CPaintWidget::draw_pie()
{
	QPainter painter(this);
	// 绘制扇形
	QRect    rect(W / 4, H / 4, W / 2, H / 2);
	int startAngle = 40 * 16; //起始 40。
	int spanAngle = 120 * 16; //旋转 120。 
	painter.drawPie(rect, startAngle, spanAngle);
}

void CPaintWidget::draw_image()
{
	QPainter painter(this);

	// 在指定的矩形区域内绘制图片
	QRect rect(W / 4, H / 4, W / 2, H / 2);
	QImage image(":/resources/tupian.png");
	painter.drawImage(rect, image);
}

void CPaintWidget::draw_pixmap()
{
	QPainter painter(this);
	// 绘制Pixmap图片
	QRect    rect(W / 4, H / 4, W / 2, H / 2);
	QPixmap    pixmap(":/resources/tupix.png");
	painter.drawPixmap(rect, pixmap);
}

void CPaintWidget::draw_text()
{
	QPainter painter(this);

	// 绘制文本,只能绘制单行文字,字体的大小等属性由QPainter::font()决定。
	QRect rect(W / 4, H / 4, W / 2, H / 2);
	QFont font;
	font.setPointSize(30);
	font.setBold(true);
	painter.setFont(font);
	painter.drawText(rect, "Hello,Qt");
}

void CPaintWidget::draw_erase()
{
	QPainter painter(this);

	// 擦除某个矩形区域,等效于用背景色填充该区域
	QRect rect(W / 4, H / 4, W / 2, H / 2);
	painter.eraseRect(rect);
}

void CPaintWidget::draw_fillpath()
{
	QPainter painter(this);

	// 填充某个QPainterPath定义的绘图路径,但是轮廓线不显示
	QRect rect(W / 4, H / 4, W / 2, H / 2);
	QPainterPath path;
	path.addEllipse(rect);
	path.addRect(rect);
	painter.fillPath(path, Qt::red);
}

void CPaintWidget::draw_fillrect()
{
	QPainter painter(this);
	// 填充一个矩形,无边框线
	QRect rect(W / 4, H / 4, W / 2, H / 2);
	painter.fillRect(rect, Qt::green);
}

void CPaintWidget::setDrawType(DRAW_TYPE type)
{
	m_drawType = type;
}

widget.h

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <memory>
#include <QTreeView>
#include "CPaintWidget.h"

using namespace std;


class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

private:
    void treeView();

private slots:
    void treeViewExpand(const QModelIndex &index);

private:
    QTreeView* m_pLeftTree = nullptr;
    CPaintWidget* m_pPaintWidget = nullptr;
};

#endif // WIDGET_H

widget.cpp

#pragma execution_character_set("utf-8")

#include "widget.h"
#include <QStandardItemModel>
#include <QHBoxLayout>
#include "drawtype.h"

Widget::Widget(QWidget* parent)
    : QWidget(parent)
{
    setWindowTitle(u8"draw all");

    QHBoxLayout* pHLay = new QHBoxLayout(this);
    m_pLeftTree = new QTreeView(this);
    m_pLeftTree->setEditTriggers(QAbstractItemView::NoEditTriggers);  //设置不可编辑
    m_pLeftTree->setFixedWidth(300);

    m_pPaintWidget = new CPaintWidget(this);
    pHLay->addWidget(m_pLeftTree);
    pHLay->addWidget(m_pPaintWidget);

    treeView();
}

Widget::~Widget()
{
   
}

void Widget::treeView()
{
    m_pLeftTree->setFrameShape(QFrame::NoFrame);

    QStandardItemModel* model = new QStandardItemModel(m_pLeftTree);
    model->setHorizontalHeaderLabels(QStringList() << "draw all");

    QStandardItem* pParentItem = NULL;
    QStandardItem* pChildItem = NULL;

    // 点
    pParentItem = new QStandardItem(QIcon(":/resources/point.png"), "draw ponit");
    model->appendRow(pParentItem);

    pChildItem = new QStandardItem(QIcon(":/resources/point.png"), "point");
    pParentItem->appendRow(pChildItem);

    pChildItem = new QStandardItem(QIcon(":/resources/multipoints.png"), "multipoints");
    pParentItem->appendRow(pChildItem);

    // 线
    pParentItem = new QStandardItem(QIcon(":/resources/line.png"), "draw line");
    model->appendRow(pParentItem);

    pChildItem = new QStandardItem(QIcon(":/resources/line.png"), "line");
    pParentItem->appendRow(pChildItem);

    pChildItem = new QStandardItem(QIcon(":/resources/arc.png"), "arc");
    pParentItem->appendRow(pChildItem);

    // 封闭的图形
    pParentItem = new QStandardItem(QIcon(":/resources/rect.png"), "draw rect");
    model->appendRow(pParentItem);

    pChildItem = new QStandardItem(QIcon(":/resources/rect.png"), "rect");
    pParentItem->appendRow(pChildItem);

    pChildItem = new QStandardItem(QIcon(":/resources/roundrect.png"), "roundrect");
    pParentItem->appendRow(pChildItem);

    pChildItem = new QStandardItem(QIcon(":/resources/chord.png"), "chord");
    pParentItem->appendRow(pChildItem);

    pChildItem = new QStandardItem(QIcon(":/resources/ellipse.png"), "ellipse");
    pParentItem->appendRow(pChildItem);

    // 任意路径绘制
    pParentItem = new QStandardItem(QIcon(":/resources/polygon.png"), "draw polygon");
    model->appendRow(pParentItem);

    pChildItem = new QStandardItem(QIcon(":/resources/polygon.png"), "polygon");
    pParentItem->appendRow(pChildItem);

    pChildItem = new QStandardItem(QIcon(":/resources/polyline.png"), "polyline");
    pParentItem->appendRow(pChildItem);

    pChildItem = new QStandardItem(QIcon(":/resources/ConvexPloygon.png"), "ConvexPloygon");
    pParentItem->appendRow(pChildItem);

    pChildItem = new QStandardItem(QIcon(":/resources/lines.png"), "lines");
    pParentItem->appendRow(pChildItem);

    pChildItem = new QStandardItem(QIcon(":/resources/path.png"), "path");
    pParentItem->appendRow(pChildItem);

    pChildItem = new QStandardItem(QIcon(":/resources/pie.png"), "pie");
    pParentItem->appendRow(pChildItem);

    // 图片绘制
    pParentItem = new QStandardItem(QIcon(":/resources/image.png"), "draw image");
    model->appendRow(pParentItem);

    pChildItem = new QStandardItem(QIcon(":/resources/image.png"), "image");
    pParentItem->appendRow(pChildItem);

    pChildItem = new QStandardItem(QIcon(":/resources/pixmap.png"), "pixmap");
    pParentItem->appendRow(pChildItem);

    // 文本绘制
    pParentItem = new QStandardItem(QIcon(":/resources/text.png"), "draw text");
    model->appendRow(pParentItem);

    // 擦除
    pParentItem = new QStandardItem(QIcon(":/resources/erase.png"), "draw erase");
    model->appendRow(pParentItem);

    // 路径填充
    pParentItem = new QStandardItem(QIcon(":/resources/fillpath.png"), "draw fillpath");
    model->appendRow(pParentItem);

    // 矩形填充
    pParentItem = new QStandardItem(QIcon(":/resources/fillrect.png"), "draw fillrect");
    model->appendRow(pParentItem);

    m_pLeftTree->setModel(model);   //设置要展示的模型

    connect(m_pLeftTree, &QAbstractItemView::clicked, this, &Widget::treeViewExpand);
}

void Widget::treeViewExpand(const QModelIndex& index)
{
    QString text = index.data().toString();

    if (text.compare("point") == 0)
    {
        m_pPaintWidget->setDrawType(DRAW_TYPE::point);
        /*调用QWidget的update()函数触发重写 paintEvent() 函数*/
        m_pPaintWidget->update();  //不更新就不会立即显示
    }
    else if (text.compare("multipoints") == 0)
    {
        m_pPaintWidget->setDrawType(DRAW_TYPE::multipoints);
        m_pPaintWidget->update();  
    }
    else if (text.compare("line") == 0)
    {
        m_pPaintWidget->setDrawType(DRAW_TYPE::line);
        m_pPaintWidget->update();
    }
    else if (text.compare("arc") == 0)
    {
        m_pPaintWidget->setDrawType(DRAW_TYPE::arc);
        m_pPaintWidget->update();
    }
    else if (text.compare("rect") == 0)
    {
        m_pPaintWidget->setDrawType(DRAW_TYPE::rect);
        m_pPaintWidget->update();
    }
    else if (text.compare("roundrect") == 0)
    {
        m_pPaintWidget->setDrawType(DRAW_TYPE::roundrect);
        m_pPaintWidget->update();
    }
    else if (text.compare("chord") == 0)
    {
        m_pPaintWidget->setDrawType(DRAW_TYPE::chord);
        m_pPaintWidget->update();
    }
    else if (text.compare("ellipse") == 0)
    {
        m_pPaintWidget->setDrawType(DRAW_TYPE::ellipse);
        m_pPaintWidget->update();
    }
    else if (text.compare("polygon") == 0)
    {
        m_pPaintWidget->setDrawType(DRAW_TYPE::polygon);
        m_pPaintWidget->update();
    }
    else if (text.compare("polyline") == 0)
    {
        m_pPaintWidget->setDrawType(DRAW_TYPE::polyline);
        m_pPaintWidget->update();
    }
    else if (text.compare("ConvexPloygon") == 0)
    {
        m_pPaintWidget->setDrawType(DRAW_TYPE::ConvexPloygon);
        m_pPaintWidget->update();
    }
    else if (text.compare("lines") == 0)
    {
        m_pPaintWidget->setDrawType(DRAW_TYPE::lines);
        m_pPaintWidget->update();  
    }
    else if (text.compare("path") == 0)
    {
        m_pPaintWidget->setDrawType(DRAW_TYPE::path);
        m_pPaintWidget->update();
    }
    else if (text.compare("pie") == 0)
    {
        m_pPaintWidget->setDrawType(DRAW_TYPE::pie);
        m_pPaintWidget->update();
    }
    else if (text.compare("image") == 0)
    {
        m_pPaintWidget->setDrawType(DRAW_TYPE::image);
        m_pPaintWidget->update();
    }
    else if (text.compare("pixmap") == 0)
    {
        m_pPaintWidget->setDrawType(DRAW_TYPE::pixmap);
        m_pPaintWidget->update();
    }
    else if (text.compare("draw text") == 0)
    {
        m_pPaintWidget->setDrawType(DRAW_TYPE::draw_text);
        m_pPaintWidget->update();
    }
    else if (text.compare("draw erase") == 0)
    {
        m_pPaintWidget->setDrawType(DRAW_TYPE::draw_erase);
        m_pPaintWidget->update();
    }
    else if (text.compare("draw fillpath") == 0)
    {
        m_pPaintWidget->setDrawType(DRAW_TYPE::draw_fillpath);
        m_pPaintWidget->update();
    }
    else if (text.compare("draw fillrect") == 0)
    {
        m_pPaintWidget->setDrawType(DRAW_TYPE::draw_fillrect);
        m_pPaintWidget->update();
    }
}

main.cpp

#include "widget.h"

#include <QApplication>


int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    Widget w;
    w.show();
    return a.exec();
}

运行结果

image-20240709161702260

三、Qt移动鼠标绘制任意形状

  在Qt中,实现移动鼠标绘制任意形状通常涉及到鼠标事件的处理,如 mousePressEventmouseMoveEventmouseReleaseEventmouseDoubleClickEventpaintEvent

void paintEvent(QPaintEvent *) override;
void mousePressEvent(QMouseEvent *e) override;       //按下
void mouseMoveEvent(QMouseEvent *e) override;        //移动
void mouseReleaseEvent(QMouseEvent *e) override;     //松开
void mouseDoubleClickEvent(QMouseEvent *event) override;     //双击
  • 鼠标按下事件 (mousePressEvent):

    当用户按下鼠标按钮时触发。标志开始绘制

    // 按下
    void MyPainterWidget::mousePressEvent(QMouseEvent *e)
    {
        if (e->button() == Qt::LeftButton)
        {
            if(!m_bStartDraw)
            {
                pointList.clear();
                m_bStartDraw = true;
            }
        }   
    }
    
  • 鼠标移动事件 (mouseMoveEvent):

    当鼠标移动时触发,如果按下了鼠标按钮,则可以在绘图事件中进行绘制,并标志处于绘制时的鼠标移动状态

    // 移动
    void MyPainterWidget::mouseMoveEvent(QMouseEvent *e)
    {
        if(m_bStartDraw)
        {
            movePoint = e->pos();
            
            this->update();
    
            // 先刷新再设为true, 防止第一点和(0,0)连在一块
            bMove = true;
        }
    }
    
  • 鼠标释放事件 (mouseReleaseEvent):

    当用户释放鼠标按钮时触发,将鼠标松开后的点需要添加到路径中

    // 松开
    void MyPainterWidget::mouseReleaseEvent(QMouseEvent *e)
    {
        if (e->button() == Qt::LeftButton)
        {
            if (m_bStartDraw)
            {
                // 鼠标松开后的点需要添加到路径中
                pointList.push_back(QPointF(e->x(), e->y()));
                bMove = false;
                this->update();
            }
        }
    }
    
  • 鼠标双击事件(mouseDoubleClickEvent):

    当用户双击鼠标时,结束绘制

    // 双击结束绘制
    void MyPainterWidget::mouseDoubleClickEvent(QMouseEvent *event)
    {
        endDraw();
    }
    
    void MyPainterWidget::endDraw()
    {
        m_bStartDraw = false;
    
        //需要把第一个点连起来
        pointList.push_back(pointList[0]);
        this->update();
    }
    
  • 绘图事件(paintEvent):

    QWidget 的派生类中重写 paintEvent 来实现自定义绘制逻辑

    void MyPainterWidget::paintEvent(QPaintEvent *)
    {
        QPainter painter(this);
        painter.setPen(QColor(255,0,0));
    
        QVector<QLineF> lines;
    
        for(int i = 0; i < pointList.size()-1; i++)
        {
            QLineF line(QPointF(pointList[i].x(), pointList[i].y()), 
                QPointF(pointList[i+1].x(), pointList[i+1].y()));
    
            lines.push_back(line);
        }
    	
        //绘制鼠标移动状态
        if (m_bStartDraw)
        {
            int size = pointList.size();
    
            if (bMove && size > 0)
            {
                QLineF line(QPointF(pointList[pointList.size() - 1].x(), pointList[pointList.size() - 1].y()),
                    movePoint);
    
                lines.push_back(line);
            }
        }
    
        painter.drawLines(lines);	//画多条线
    }
    

示例:

需要维护布尔变量来跟踪是否处于绘制模式与移动模式

MyPainterWidget.h

#ifndef GRAPHICSPAINTER_H
#define GRAPHICSPAINTER_H

#include <QWidget>

class MyPainterWidget : public QWidget
{
    Q_OBJECT

public:
    explicit MyPainterWidget(QWidget *parent = nullptr);

    void endDraw();     //结束绘制
    void clearPath();   //清除路径

protected:
    void paintEvent(QPaintEvent *) override;
    void mousePressEvent(QMouseEvent *e) override;       //按下
    void mouseMoveEvent(QMouseEvent *e) override;        //移动
    void mouseReleaseEvent(QMouseEvent *e) override;     //松开
    void mouseDoubleClickEvent(QMouseEvent *event) override;     //双击

    bool m_bStartDraw = false;    //是否已经开始左键点击,同时标识是否开始进行绘制
    bool bMove = false;           //是否处于绘制时的鼠标移动状态

    QVector<QPointF> pointList;
    QPointF movePoint;
};

#endif // GRAPHICSPAINTER_H

MyPainterWidget.cpp

#include "MyPainterWidget.h"
#include <QPainter>
#include <QMouseEvent>

MyPainterWidget::MyPainterWidget(QWidget *parent) : QWidget(parent)
{
    setMouseTracking(true);  //窗口启用鼠标捕获

    pointList.clear();
}

void MyPainterWidget::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
    painter.setPen(QColor(255,0,0));

    QVector<QLineF> lines;

    for(int i = 0; i < pointList.size()-1; i++)
    {
        QLineF line(QPointF(pointList[i].x(), pointList[i].y()), 
            QPointF(pointList[i+1].x(), pointList[i+1].y()));

        lines.push_back(line);
    }

    if (m_bStartDraw)
    {
        int size = pointList.size();

        if (bMove && size > 0)
        {
            QLineF line(QPointF(pointList[pointList.size() - 1].x(), pointList[pointList.size() - 1].y()),
                movePoint);

            lines.push_back(line);
        }
    }

    painter.drawLines(lines);
}

// 按下
void MyPainterWidget::mousePressEvent(QMouseEvent *e)
{
    if (e->button() == Qt::LeftButton)
    {
        if(!m_bStartDraw)
        {
            pointList.clear();
            m_bStartDraw = true;
        }
    }   
}

// 移动
void MyPainterWidget::mouseMoveEvent(QMouseEvent *e)
{
    if(m_bStartDraw)
    {
        movePoint = e->pos();
        
        this->update();

        // 先刷新再设为true, 防止第一点和(0,0)连在一块
        bMove = true;
    }
}

// 松开
void MyPainterWidget::mouseReleaseEvent(QMouseEvent *e)
{
    if (e->button() == Qt::LeftButton)
    {
        if (m_bStartDraw)
        {
            // 鼠标松开后的点需要添加到路径中
            pointList.push_back(QPointF(e->x(), e->y()));
            bMove = false;
            this->update();
        }
    }
}

// 双击结束绘制
void MyPainterWidget::mouseDoubleClickEvent(QMouseEvent *event)
{
    endDraw();
}

void MyPainterWidget::endDraw()
{
    m_bStartDraw = false;

    //需要把第一个点连起来
    pointList.push_back(pointList[0]);
    this->update();
}

void MyPainterWidget::clearPath()
{
    pointList.clear();
    this->update();
}

mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QMenu>

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

protected:
    // 右键菜单需要重写的类
    //处理窗口的右键点击事件,以弹出一个菜单
    void contextMenuEvent(QContextMenuEvent* event) override;

private:
    Ui::MainWindow *ui;

    QMenu* m_pMenu;
};
#endif // MAINWINDOW_H

mainwindow.cpp

/*
使用方式
左键点击,移动鼠标开始绘制,双击左键结束绘制,或者右键点击结束绘制
*/

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QMenu>
#include <QAction>


MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

	m_pMenu = new QMenu(this);

	QAction* pAc1 = new QAction(QString::fromLocal8Bit("结束绘制"), this);
	pAc1->setShortcut(QKeySequence("Ctrl+E"));
	QAction* pAc2 = new QAction(QString::fromLocal8Bit("清除"), this);
	pAc2->setShortcut(QKeySequence("Ctrl+D"));

	// 这是个假动作,为了让菜单消失,且不影响绘制路径
	QAction* pAc3 = new QAction(QString::fromLocal8Bit("退出菜单"), this);

	m_pMenu->addAction(pAc1);
	m_pMenu->addAction(pAc2);
	m_pMenu->addAction(pAc3);

	m_pMenu->setStyleSheet("QMenu{font:18px;}");

	connect(pAc1, &QAction::triggered, [=] {
		ui->graphicsPainter->endDraw();
		});

	connect(pAc2, &QAction::triggered, [=] {
		ui->graphicsPainter->clearPath();
		});
}

MainWindow::~MainWindow()
{
    delete ui;
}

// 这段代码的作用是当用户在 MainWindow 中通过鼠标右键触发上下文菜单时,将一个已经创建好的菜单 m_pMenu 移动到鼠标当前的位置,并显示出来。
void MainWindow::contextMenuEvent(QContextMenuEvent* event)
{
	m_pMenu->move(cursor().pos());  //将菜单的位置设置为当前鼠标光标的位置
	m_pMenu->show();  //显示菜单
}

main.cpp

#include "mainwindow.h"

#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();
    return a.exec();
}

运行结果

image-20240709192615096

四、Qt绘制带三角形箭头的窗口

  本案例:在点击按钮时,弹出带三角形箭头的窗口(含窗口阴影)。

  • QPainterPath组合绘制

    // 小三角区域,这里是等腰三角形
    QPolygon trianglePolygon;
    trianglePolygon << QPoint(m_startX, m_triangleHeight + SHADOW_WIDTH);
    trianglePolygon << QPoint(m_startX + m_triangleWidth / 2, SHADOW_WIDTH);
    trianglePolygon << QPoint(m_startX + m_triangleWidth, m_triangleHeight + SHADOW_WIDTH);
    
    QPainterPath drawPath;
    drawPath.addRoundedRect(QRect(SHADOW_WIDTH, SHADOW_WIDTH + m_triangleHeight,
                                  this->width() - SHADOW_WIDTH * 2, this->height() - SHADOW_WIDTH * 2 - 								m_triangleHeight),
                    		BORDER_RADIUS, BORDER_RADIUS);
    // 矩形路径加上三角形
    drawPath.addPolygon(trianglePolygon);
    
    // 绘制路径
    painter.drawPath(drawPath);
    

示例:

  首先,绘制顶层窗口

userinfowidget.h

#ifndef USERINFOWIDGET_H
#define USERINFOWIDGET_H

#include <QWidget>

namespace Ui {
class UserInfoWidget;
}

class UserInfoWidget : public QWidget
{
    Q_OBJECT

public:
    explicit UserInfoWidget(QWidget *parent = nullptr);
    ~UserInfoWidget();

private:
    Ui::UserInfoWidget *ui;
};

#endif // USERINFOWIDGET_H

userinfowidget.cpp

#include "userinfowidget.h"
#include "ui_userinfowidget.h"

UserInfoWidget::UserInfoWidget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::UserInfoWidget)
{
    ui->setupUi(this);

    setFixedSize(this->width(), this->height());

    setAttribute(Qt::WA_StyledBackground);
    QString qss = "QLabel{font-family:Microsoft YaHei; font-size:18px; color:#ffffff; background-color:#363636;}";

    ui->label_UserImage->setText("");
    QPixmap pmp1(":/resources/user_image.png");
    pmp1.scaled(ui->label_UserImage->size(), Qt::KeepAspectRatio);
    ui->label_UserImage->setScaledContents(true);
    ui->label_UserImage->setPixmap(pmp1);

    ui->label_UserName->setText(u8"没有做完的梦最痛");
    ui->label_UserName->setStyleSheet(qss);

    ui->label_VipInfo->setText("");
    QPixmap pmp2(":/resources/vipinfo.png");
    pmp2.scaled(ui->label_VipInfo->size(), Qt::KeepAspectRatio);
    ui->label_VipInfo->setScaledContents(true);
    ui->label_VipInfo->setPixmap(pmp2);
}

UserInfoWidget::~UserInfoWidget()
{
    delete ui;
}

  接着,绘制带三角的窗口(含窗口阴影)

triangledialog.h

/*

带三角形箭头的对话框

*/

#ifndef TRIANGLEDIALOG_H
#define TRIANGLEDIALOG_H

#include <QDialog>

class TriangleDialog : public QDialog
{
    Q_OBJECT
public:
    explicit TriangleDialog(int fixedW, int fixedH, QWidget *parent = nullptr);

    // 设置小三角行的起始位置、宽、高
    void setTriangleInfo(int startX, int width, int height);

protected:
    void paintEvent(QPaintEvent *e);

private:
    // 小三角形的起始位置
    int m_startX;

    // 小三角形的宽度
    int m_triangleWidth;

    // 小三角形的高度
    int m_triangleHeight;
};

#endif // TRIANGLEDIALOG_H

triangledialog.cpp

#include "triangledialog.h"
#include <QVBoxLayout>
#include <QPainter>
#include <QGraphicsDropShadowEffect>
#include <QPainterPath>
#include "userinfowidget.h"

#define SHADOW_WIDTH    15                 // 窗口阴影宽度
#define TRIANGLE_WIDTH  15                 // 小三角行的宽度
#define TRIANGLE_HEIGHT 10                 // 小三角行的高度
#define BORDER_RADIUS   5                  // 窗口圆角


TriangleDialog::TriangleDialog(int fixedW, int fixedH, QWidget *parent) : QDialog(parent)
  ,m_startX(50)
  ,m_triangleWidth(TRIANGLE_WIDTH)
  ,m_triangleHeight(TRIANGLE_HEIGHT)
{
    setWindowFlags(Qt::FramelessWindowHint | Qt::Popup | Qt::NoDropShadowWindowHint);
    setAttribute(Qt::WA_TranslucentBackground);  // 设置透明窗口, 为窗口阴影和圆角做准备

    UserInfoWidget* pUserinfoWidget = new UserInfoWidget(this);

    QVBoxLayout* pVlay = new QVBoxLayout(this);
    pVlay->addWidget(pUserinfoWidget);

    //设置布局的四周边距
    pVlay->setContentsMargins(SHADOW_WIDTH, SHADOW_WIDTH + TRIANGLE_HEIGHT, SHADOW_WIDTH, SHADOW_WIDTH);

    // 设置阴影边框
    auto shadowEffect = new QGraphicsDropShadowEffect(this);
    shadowEffect->setOffset(0, 0);
    shadowEffect->setColor(Qt::red);
    shadowEffect->setBlurRadius(SHADOW_WIDTH);
    this->setGraphicsEffect(shadowEffect);

    //设置窗口固定大小
    setFixedSize(fixedW, fixedH);
}

void TriangleDialog::setTriangleInfo(int startX, int width, int height)
{
    m_startX = startX;
    m_triangleWidth = width;
    m_triangleHeight = height;
}

void TriangleDialog::paintEvent(QPaintEvent *e)
{
    QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing, true);
    painter.setPen(Qt::NoPen);
    painter.setBrush(QColor(55, 55, 55));

    // 小三角区域,这里是等腰三角形
    QPolygon trianglePolygon;
    trianglePolygon << QPoint(m_startX, m_triangleHeight + SHADOW_WIDTH);
    trianglePolygon << QPoint(m_startX + m_triangleWidth / 2, SHADOW_WIDTH);
    trianglePolygon << QPoint(m_startX + m_triangleWidth, m_triangleHeight + SHADOW_WIDTH);

    QPainterPath drawPath;
    drawPath.addRoundedRect(QRect(SHADOW_WIDTH, SHADOW_WIDTH + m_triangleHeight,
                                  this->width() - SHADOW_WIDTH * 2, this->height() - SHADOW_WIDTH * 2 - m_triangleHeight),
                            BORDER_RADIUS, BORDER_RADIUS);
    // 矩形路径加上三角形
    drawPath.addPolygon(trianglePolygon);

    // 绘制路径
    painter.drawPath(drawPath);
}

  然后,调用 show() 方法会触发 paintEvent 来绘制窗口。

widget.h

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>

QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

public slots:
    void on_pushButton_clicked();

private:
    Ui::Widget *ui;
};
#endif // WIDGET_H

widget.cpp

#include "widget.h"
#include "ui_widget.h"
#include "triangledialog.h"
#include <QLabel>
#include "userinfowidget.h"
#include <QPropertyAnimation>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
}

Widget::~Widget()
{
    delete ui;
}

void Widget::on_pushButton_clicked()
{
    int triangle_start_x = 60;

    TriangleDialog *pDialog = new TriangleDialog(343, 320, this);
    pDialog->setTriangleInfo(triangle_start_x, 20, 12);

    // 设置三角窗口的弹出位置, 有Qt::Popup属性
    QPoint p1 = ui->pushButton->mapToGlobal(QPoint(0, 0));  //按钮左上角相对于桌面的绝对位置
    QRect rect1 = ui->pushButton->rect();

    int x = p1.x() + rect1.width() / 2 - triangle_start_x - 20 / 2;
    int y = p1.y() + rect1.height() + 1 - 15;
    pDialog->move(x, y);

    pDialog->show();
}

main.cpp

#include "widget.h"

#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Widget w;
    w.show();
    return a.exec();
}

运行结果

image-20240709203859903

相关推荐

  1. Qt+opencv】基础图像绘制

    2024-07-10 05:28:03       4 阅读
  2. QT绘制同心扇形

    2024-07-10 05:28:03       38 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-10 05:28:03       3 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-10 05:28:03       4 阅读
  3. 在Django里面运行非项目文件

    2024-07-10 05:28:03       3 阅读
  4. Python语言-面向对象

    2024-07-10 05:28:03       2 阅读

热门阅读

  1. 白骑士的C++教学基础篇 1.3 控制流

    2024-07-10 05:28:03       9 阅读
  2. Istio实战教程:Service Mesh部署与流量管理

    2024-07-10 05:28:03       8 阅读
  3. DHCP&IP、Lan IP&Lan Static IP

    2024-07-10 05:28:03       7 阅读
  4. 在Ubuntu 16.04上安装和配置VNC的方法

    2024-07-10 05:28:03       13 阅读
  5. Flink推测机制

    2024-07-10 05:28:03       8 阅读
  6. Vue3框架搭建2:axios+typescript封装

    2024-07-10 05:28:03       8 阅读
  7. 江苏高防服务器都有哪些优势?

    2024-07-10 05:28:03       9 阅读