Qt 实现自定义截图工具

目录

  • Qt 实现自定义截图工具
    • 实现效果图
    • PrintScreen 类介绍
      • PrintScreen 类的主要特性
    • 逐步实现
      • 第一步:类定义
      • 第二步:初始化截图窗口
      • 第三步:处理鼠标事件
      • 第四步:计算截图区域
      • 第五步:捕获和保存图像
    • 完整代码
      • PrintScreen.h
      • PrintScreen.cpp
      • MainWindow.h
      • MainWindow.cpp
      • main.cpp

Qt 实现自定义截图工具

本文使用Qt框架从头开始创建一个简单的屏幕截图工具。

实现效果图

截图按钮:
截图按钮
选取截图初始状态:
选取截图初始状态
选取截图区域:
选取截图区域
截图保存界面:
截图保存界面

PrintScreen 类介绍

PrintScreen 类是一个自定义的 QWidget,允许用户捕捉屏幕上的任意区域。本教程将展示如何利用 Qt 的多功能库来实现这一功能。

PrintScreen 类的主要特性

  • 全屏覆盖,用于选择要捕捉的屏幕区域。
  • 通过鼠标互动选择定义捕捉区域。
  • 拖放调整选定区域。
  • 将捕获的区域保存为图片文件。

逐步实现

第一步:类定义

首先定义继承自 QWidgetPrintScreen 类。包含必要的 Qt 头文件,并声明我们的函数和成员变量。

#include <QWidget>
#include <QPainter>/*** @brief The PrintScreen class* @param 区域截屏功能*/
class PrintScreen : public QWidget
{Q_OBJECT
public:PrintScreen(QWidget *parent = nullptr);~PrintScreen();private:/*** @brief 初始化截图窗口的背景和尺寸*/void InitWindow();/*** @brief 根据起始点和终止点计算矩形区域* @param beginPoint 矩形区域的起始点* @param endPoint 矩形区域的终止点* @return 返回根据两点计算出的 QRect 对象*/QRect GetRect(const QPoint &beginPoint, const QPoint &endPoint);protected:// 事件处理方法void mousePressEvent(QMouseEvent *event);void mouseMoveEvent(QMouseEvent* event);void mouseReleaseEvent(QMouseEvent *event);void keyPressEvent(QKeyEvent *event);void paintEvent(QPaintEvent *event);private:// 成员变量bool m_isMousePress = false;     // 是否按下鼠标bool m_captureComplete = false;  // 截图是否完成bool m_isDragging = false;       // 是否正在拖动截图区域QPixmap m_loadPixmap;    // 加载的屏幕截图QPixmap m_capturePixmap; // 截取的屏幕区域int m_screenWidth;       // 屏幕宽度int m_screenHeight;      // 屏幕高度QPoint m_beginPoint;     // 截图开始点QPoint m_endPoint;       // 截图结束点QPoint m_originalBegin;  // 原始截图开始点QPoint m_originalEnd;    // 原始截图结束点QPoint m_dragPosition;   // 拖动时的鼠标位置QPainter m_painter;      // 绘图器对象
};

第二步:初始化截图窗口

InitWindow 方法设置窗口属性,如全屏模式、无边框窗口提示和鼠标跟踪。它还捕获整个屏幕并存储在 m_loadPixmap 中。

void PrintScreen::InitWindow()
{// 启用鼠标跟踪this->setMouseTracking(true);// 设置无边框窗口this->setWindowFlags(Qt::FramelessWindowHint);// 设置窗口为激活状态和全屏模式setWindowState(Qt::WindowActive | Qt::WindowFullScreen);// 确保关闭时自动删除setAttribute(Qt::WA_DeleteOnClose);// 获取主屏幕QScreen *screen = QApplication::primaryScreen();// 抓取整个屏幕内容m_loadPixmap = screen->grabWindow(QApplication::desktop()->winId());// 设置屏幕宽度、高度m_screenWidth = m_loadPixmap.width();m_screenHeight = m_loadPixmap.height();
}

第三步:处理鼠标事件

鼠标事件对于定义捕获区域至关重要。mousePressEventmouseMoveEventmouseReleaseEvent 处理捕获区域的开始、调整和最终确定。

void PrintScreen::mousePressEvent(QMouseEvent *event)
{// 按下右键 关闭截图窗口if (event->button() == Qt::RightButton){close();}// 按下左键else if (event->button() == Qt::LeftButton){if (m_captureComplete && QRect(m_beginPoint, m_endPoint).contains(event->pos())){m_isDragging = true;                          // 开始拖动m_dragPosition = event->pos() - m_beginPoint; // 计算开始拖动位置}else{m_isMousePress = true;       // 鼠标被按下m_isDragging = false;m_beginPoint = event->pos(); // 记录开始点m_originalBegin = m_beginPoint;}}
}void PrintScreen::mouseMoveEvent(QMouseEvent *event)
{// 获取屏幕尺寸QRect screenRect = QGuiApplication::primaryScreen()->geometry();// 鼠标按下且截图未完成if (m_isMousePress && !m_captureComplete){// 确保终点坐标不超过屏幕范围int x = qBound(screenRect.left(), event->pos().x(), screenRect.right());int y = qBound(screenRect.top(), event->pos().y(), screenRect.bottom());m_endPoint = QPoint(x, y);}// 正在拖动else if (m_isDragging){QPoint newTopLeft = event->pos() - m_dragPosition;// 确保新的顶点坐标不超过屏幕范围int x = qBound(screenRect.left(), newTopLeft.x(), screenRect.right() - m_dragPosition.x());int y = qBound(screenRect.top(), newTopLeft.y(), screenRect.bottom() - m_dragPosition.y());newTopLeft = QPoint(x, y);QPoint offset = newTopLeft - m_beginPoint;m_beginPoint += offset;m_endPoint += offset;}update();return QWidget::mouseMoveEvent(event);
}void PrintScreen::mouseReleaseEvent(QMouseEvent *event)
{// 鼠标释放且截图未完成if (m_isMousePress && !m_captureComplete){m_endPoint = event->pos();  // 设置结束点m_isMousePress = false;     // 重置鼠标按下状态m_captureComplete = true;   // 标记截图完成update();}// 释放时正在拖动else if (m_isDragging){m_isDragging = false;}update();
}

第四步:计算截图区域

GetRect 方法接收两个参数:beginPointendPoint,这两个点是用户通过鼠标操作定义的截图区域的开始和结束位置。此方法用于计算并返回一个 QRect 对象,该对象表示屏幕上要截取的矩形区域。

QRect PrintScreen::GetRect(const QPoint &beginPoint, const QPoint &endPoint)
{int x = std::min(beginPoint.x(), endPoint.x());int y = std::min(beginPoint.y(), endPoint.y());int width = std::abs(beginPoint.x() - endPoint.x());int height = std::abs(beginPoint.y() - endPoint.y());if (width == 0) width = 1;   // 确保宽度至少为1像素if (height == 0) height = 1; // 确保高度至少为1像素return QRect(x, y, width, height);
}

解释:

  • 计算 x 和 y 坐标:使用 std::min 函数确定矩形的左上角 x 和 y 坐标,这保证了无论用户如何拖动鼠标(从左到右或从右到左),都能正确计算出矩形的位置。
  • 计算宽度和高度:使用 std::abs 函数计算宽度和高度,确保值总是正数。如果计算结果为0(即起点和终点在同一直线上),则将宽度或高度设为1像素,确保矩形至少有最小的可见尺寸。

第五步:捕获和保存图像

paintEvent 方法在屏幕上绘制捕获的区域。keyPressEvent 监听回车键以触发保存捕获的图像。

void PrintScreen::paintEvent(QPaintEvent *event)
{Q_UNUSED(event)m_painter.begin(this);                                           // 开始绘制QColor shadowColor(0, 0, 0, 100);                                // 半透明遮罩颜色m_painter.setPen(QPen(Qt::blue, 1, Qt::SolidLine, Qt::FlatCap)); // 设置画笔m_painter.drawPixmap(0, 0, m_loadPixmap);                        // 绘制加载的屏幕截图m_painter.fillRect(m_loadPixmap.rect(), shadowColor);            // 绘制半透明遮罩QRect selectedRect = GetRect(m_beginPoint, m_endPoint);          // 获取选择区域m_capturePixmap = m_loadPixmap.copy(selectedRect);               // 截取选择区域的屏幕截图m_painter.drawPixmap(selectedRect.topLeft(), m_capturePixmap);   // 绘制截取的区域m_painter.drawRect(selectedRect);                                // 绘制选择区域的边框m_painter.end();                                                 // 结束绘制
}
void PrintScreen::keyPressEvent(QKeyEvent *event)
{// 按下回车键if (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return){// 保存图片QString filePath = QFileDialog::getSaveFileName(nullptr, "保存图片",QString(),"Images (*.png *.jpg)");if(!filePath.isEmpty()){m_capturePixmap.save(filePath); // 保存截图到文件}close();}
}

完整代码

PrintScreen.h

#ifndef PRINTSCREEN_H
#define PRINTSCREEN_H#include <QWidget>
#include <QPainter>/*** @brief The PrintScreen class* @param 区域截屏功能*/
class PrintScreen : public QWidget
{Q_OBJECT
public:PrintScreen(QWidget *parent = nullptr);~PrintScreen();private:/*** @brief 初始化截图窗口的背景和尺寸*/void InitWindow();/*** @brief 根据起始点和终止点计算矩形区域* @param beginPoint 矩形区域的起始点* @param endPoint 矩形区域的终止点* @return 返回根据两点计算出的 QRect 对象*/QRect GetRect(const QPoint &beginPoint, const QPoint &endPoint);protected:// 事件处理方法void mousePressEvent(QMouseEvent *event);void mouseMoveEvent(QMouseEvent* event);void mouseReleaseEvent(QMouseEvent *event);void keyPressEvent(QKeyEvent *event);void paintEvent(QPaintEvent *event);private:// 成员变量bool m_isMousePress = false;     // 是否按下鼠标bool m_captureComplete = false;  // 截图是否完成bool m_isDragging = false;       // 是否正在拖动截图区域QPixmap m_loadPixmap;    // 加载的屏幕截图QPixmap m_capturePixmap; // 截取的屏幕区域int m_screenWidth;       // 屏幕宽度int m_screenHeight;      // 屏幕高度QPoint m_beginPoint;     // 截图开始点QPoint m_endPoint;       // 截图结束点QPoint m_originalBegin;  // 原始截图开始点QPoint m_originalEnd;    // 原始截图结束点QPoint m_dragPosition;   // 拖动时的鼠标位置QPainter m_painter;      // 绘图器对象
};#endif // PRINTSCREEN_H

PrintScreen.cpp

#include "PrintScreen.h"#include <QScreen>
#include <QFileDialog>
#include <QMouseEvent>
#include <QApplication>
#include <QDesktopWidget>PrintScreen::PrintScreen(QWidget *parent): QWidget{parent}
{InitWindow();
}PrintScreen::~PrintScreen(){}void PrintScreen::InitWindow()
{// 启用鼠标跟踪this->setMouseTracking(true);// 设置无边框窗口this->setWindowFlags(Qt::FramelessWindowHint);// 设置窗口为激活状态和全屏模式setWindowState(Qt::WindowActive | Qt::WindowFullScreen);// 确保关闭时自动删除setAttribute(Qt::WA_DeleteOnClose);// 获取主屏幕QScreen *screen = QApplication::primaryScreen();// 抓取整个屏幕内容m_loadPixmap = screen->grabWindow(QApplication::desktop()->winId());// 设置屏幕宽度、高度m_screenWidth = m_loadPixmap.width();m_screenHeight = m_loadPixmap.height();
}QRect PrintScreen::GetRect(const QPoint &beginPoint, const QPoint &endPoint)
{int x = std::min(beginPoint.x(), endPoint.x());int y = std::min(beginPoint.y(), endPoint.y());int width = std::abs(beginPoint.x() - endPoint.x());int height = std::abs(beginPoint.y() - endPoint.y());if (width == 0) width = 1;   // 确保宽度至少为1像素if (height == 0) height = 1; // 确保高度至少为1像素return QRect(x, y, width, height);
}void PrintScreen::mousePressEvent(QMouseEvent *event)
{// 按下右键 关闭截图窗口if (event->button() == Qt::RightButton){close();}// 按下左键else if (event->button() == Qt::LeftButton){if (m_captureComplete && QRect(m_beginPoint, m_endPoint).contains(event->pos())){m_isDragging = true;                          // 开始拖动m_dragPosition = event->pos() - m_beginPoint; // 计算开始拖动位置}else{m_isMousePress = true;       // 鼠标被按下m_isDragging = false;m_beginPoint = event->pos(); // 记录开始点m_originalBegin = m_beginPoint;}}
}void PrintScreen::mouseMoveEvent(QMouseEvent *event)
{// 获取屏幕尺寸QRect screenRect = QGuiApplication::primaryScreen()->geometry();// 鼠标按下且截图未完成if (m_isMousePress && !m_captureComplete){// 确保终点坐标不超过屏幕范围int x = qBound(screenRect.left(), event->pos().x(), screenRect.right());int y = qBound(screenRect.top(), event->pos().y(), screenRect.bottom());m_endPoint = QPoint(x, y);}// 正在拖动else if (m_isDragging){QPoint newTopLeft = event->pos() - m_dragPosition;// 确保新的顶点坐标不超过屏幕范围int x = qBound(screenRect.left(), newTopLeft.x(), screenRect.right() - m_dragPosition.x());int y = qBound(screenRect.top(), newTopLeft.y(), screenRect.bottom() - m_dragPosition.y());newTopLeft = QPoint(x, y);QPoint offset = newTopLeft - m_beginPoint;m_beginPoint += offset;m_endPoint += offset;}update();return QWidget::mouseMoveEvent(event);
}void PrintScreen::mouseReleaseEvent(QMouseEvent *event)
{// 鼠标释放且截图未完成if (m_isMousePress && !m_captureComplete){m_endPoint = event->pos();  // 设置结束点m_isMousePress = false;     // 重置鼠标按下状态m_captureComplete = true;   // 标记截图完成update();}// 释放时正在拖动else if (m_isDragging){m_isDragging = false;}update();
}void PrintScreen::keyPressEvent(QKeyEvent *event)
{// 按下回车键if (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return){// 保存图片QString filePath = QFileDialog::getSaveFileName(nullptr, "保存图片",QString(),"Images (*.png *.jpg)");if(!filePath.isEmpty()){m_capturePixmap.save(filePath); // 保存截图到文件}close();}
}void PrintScreen::paintEvent(QPaintEvent *event)
{Q_UNUSED(event)m_painter.begin(this);                                           // 开始绘制QColor shadowColor(0, 0, 0, 100);                                // 半透明遮罩颜色m_painter.setPen(QPen(Qt::blue, 1, Qt::SolidLine, Qt::FlatCap)); // 设置画笔m_painter.drawPixmap(0, 0, m_loadPixmap);                        // 绘制加载的屏幕截图m_painter.fillRect(m_loadPixmap.rect(), shadowColor);            // 绘制半透明遮罩QRect selectedRect = GetRect(m_beginPoint, m_endPoint);          // 获取选择区域m_capturePixmap = m_loadPixmap.copy(selectedRect);               // 截取选择区域的屏幕截图m_painter.drawPixmap(selectedRect.topLeft(), m_capturePixmap);   // 绘制截取的区域m_painter.drawRect(selectedRect);                                // 绘制选择区域的边框m_painter.end();                                                 // 结束绘制
}

MainWindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H#include <QMainWindow>
#include <QPushButton>#include "PrintScreen.h"class MainWindow : public QMainWindow
{Q_OBJECTpublic:MainWindow(QWidget *parent = nullptr);~MainWindow();private:QPushButton *m_screenBtn;PrintScreen *m_printScree;// QObject interface
public:bool eventFilter(QObject *watched, QEvent *event);
};
#endif // MAINWINDOW_H

MainWindow.cpp

#include "MainWindow.h"
#include <QLayout>
#include <QMouseEvent>MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), m_screenBtn(new QPushButton("截图", this))
{// 设置中心窗口setCentralWidget(m_screenBtn);// 安装事件过滤器m_screenBtn->installEventFilter(this);// 隐藏标题栏setWindowFlags(Qt::FramelessWindowHint);resize(150, 50);
}MainWindow::~MainWindow() {}bool MainWindow::eventFilter(QObject *watched, QEvent *event)
{if (watched == m_screenBtn && event->type() == QEvent::MouseButtonPress){QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);if (mouseEvent->button() == Qt::RightButton){close();return true;}else if (mouseEvent->button() == Qt::LeftButton){m_printScree = new PrintScreen();m_printScree->show();return true;}}return QMainWindow::eventFilter(watched, event);
}

main.cpp

#include "MainWindow.h"#include <QApplication>int main(int argc, char *argv[])
{QApplication a(argc, argv);MainWindow w;w.show();return a.exec();
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/53810.shtml

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

相关文章

《程序猿之设计模式实战 · 池化思想》

&#x1f4e2; 大家好&#xff0c;我是 【战神刘玉栋】&#xff0c;有10多年的研发经验&#xff0c;致力于前后端技术栈的知识沉淀和传播。 &#x1f497; &#x1f33b; CSDN入驻不久&#xff0c;希望大家多多支持&#xff0c;后续会继续提升文章质量&#xff0c;绝不滥竽充数…

“xi” 和 “dbscan” 在OPTICS聚类中是什么意思

在 OPTICS&#xff08;Ordering Points To Identify the Clustering Structure&#xff09; 聚类算法中&#xff0c;xi 和 dbscan 是两种不同的聚类提取方法&#xff0c;它们用于从OPTICS算法生成的排序数据中提取最终的聚类结构。具体解释如下&#xff1a; dbscan 方法: 该方法…

LSS如何做深度和语义预测

get_cam_feats() 先来看看代码: def get_cam_feats(self, x):"""Return B x N x D x H/downsample x W/downsample x C"""B, N

PHP函数如何传递数组参数

php 函数可以使用数组参数传递大量数据。语法&#xff1a;参数类型前加上方括号 ([])。例如&#xff1a;myfunction(array $arr)。实战案例&#xff1a;计算数组元素平均值。注意&#xff1a;数组参数默认为引用传递&#xff0c;类型提示可提高代码可读性&#xff0c;数组解构可…

解锁编程潜力,从掌握GitHub开始

目录&#xff1a; 一、搜索开源项目 1、什么是Git 2、Github常用词含义 3、一个完整的项目界面 4、使用Github搜索项目 1&#xff09;in关键词 2&#xff09;star或fork数量去查找 3&#xff09;awesome加强搜索 二、访问速度慢的解决 1、使用网易UU加速器 2、使用…

OpenSSL工具验证RSA证书

openssl x509 是一个用于处理 X.509 证书的命令行工具。常用的 openssl x509 命令&#xff1a; -in <file>&#xff1a;指定输入文件。-out <file>&#xff1a;指定输出文件。-noout&#xff1a;不输出证书信息。-text&#xff1a;以文本格式输出证书信息。-pubke…

基于SSM的大学新生报到系统+LW参考示例

系列文章目录 1.基于SSM的洗衣房管理系统原生微信小程序LW参考示例 2.基于SpringBoot的宠物摄影网站管理系统LW参考示例 3.基于SpringBootVue的企业人事管理系统LW参考示例 4.基于SSM的高校实验室管理系统LW参考示例 5.基于SpringBoot的二手数码回收系统原生微信小程序LW参考示…

关于RabbitMQ消息丢失的解决方案

RabbitMQ如何保证消息的可靠性传输 一、消息丢失的原因 1. 生产者端 网络问题&#xff1a; 原因&#xff1a;生产者与RabbitMQ服务器之间的网络连接不稳定或中断&#xff0c;导致消息在传输过程中丢失。解决方案&#xff1a;确保网络连接稳定&#xff0c;监控网络状态&#x…

springboot后端开发-常见注解及其用途

文章目录 1. 组件注解2. 依赖注入注解3. 配置类注解4. 测试注解5. 控制器注解6. 安全和认证注解7. 切面相关注解8. API文档相关注解(需引入swagger)9. 其他注解 在Spring Boot框架中&#xff0c;有许多常用的注解用来简化开发过程中的依赖注入、组件扫描、配置、安全控制等方面…

VSCode创建C++项目和编译多文件

前言 在刚安装好VSCode后&#xff0c;我简单尝试了仅main.cpp单文件编译代码&#xff0c;没有问题&#xff0c;但是当我尝试多文件编译时&#xff0c;就出现了无法识别cpp文件。 内容 创建项目 首先点击左上角“文件”&#xff1b;在菜单中选择“打开文件夹”&#xff1b;在…

软件测试工程师面试整理-数据库与SQL

在软件测试过程中,数据库和SQL的知识是非常重要的,尤其是在涉及数据密集型应用或需要验证数据准确性的场景中。测试人员需要掌握SQL语句,以便查询、插入、更新和删除数据,并验证数据库操作的正确性。 1. 数据库基础知识 ● 关系型数据库:大多数应用使用关系型数据库(如My…

Qt什么时候触发paintEvent事件

‌paintEvent事件可以在以下几种情况下被触发‌&#xff1a; ‌窗口初始化和显示‌&#xff1a;当窗口首次被创建、显示&#xff0c;或者窗口被覆盖、最小化后再恢复时&#xff0c;paintEvent会被触发以绘制窗口的内容。‌部件大小或位置变化‌&#xff1a;如果窗口或部件的大…

【Elasticsearch系列二】安装 Kibana

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

ClickHouse 24.8 LTS 版本发布说明

本文字数&#xff1a;13885&#xff1b;估计阅读时间&#xff1a;35 分钟 作者&#xff1a;ClickHouse Team 本文在公众号【ClickHouseInc】首发 时间飞逝&#xff0c;又到了新版本发布的时刻&#xff01; 发布概要 本次ClickHouse 24.8 版本包含了19个新功能&#x1f381;、18…

基于51单片机的16X16点阵显示屏proteus仿真

地址&#xff1a; https://pan.baidu.com/s/1JQ225NSKweqf1Zlad_f1Mw 提取码&#xff1a;1234 仿真图&#xff1a; 芯片/模块的特点&#xff1a; AT89C52/AT89C51简介&#xff1a; AT89C52/AT89C51是一款经典的8位单片机&#xff0c;是意法半导体&#xff08;STMicroelectro…

管家婆云辉煌手机端怎么连接蓝牙打印机?

管家婆云辉煌手机端可以连接蓝牙打印机&#xff0c;这样手机可以发送打印任务到蓝牙打印机&#xff0c;完成打印任务。具体的设置步骤如下&#xff1a; 一、首先完成手机和蓝牙打印机配对&#xff0c;打开蓝牙打印机后。手机开启蓝牙和定位服务 点击手机设置&#xff0c;进入手…

分类预测|基于差分优化DE-支持向量机数据分类预测完整Matlab程序 DE-SVM

分类预测|基于差分优化DE-支持向量机数据分类预测完整Matlab程序 DE-SVM 文章目录 一、基本原理DE-SVM 分类预测原理和流程总结 二、实验结果三、核心代码四、代码获取五、总结 一、基本原理 DE-SVM 分类预测原理和流程 1. 差分进化优化算法&#xff08;DE&#xff09; 原理…

【深度学习】【图像分类】【OnnxRuntime】【Python】VggNet模型部署

【深度学习】【图像分类】【OnnxRuntime】【Python】VggNet模型部署 提示:博主取舍了很多大佬的博文并亲测有效,分享笔记邀大家共同学习讨论 文章目录 【深度学习】【图像分类】【OnnxRuntime】【Python】VggNet模型部署前言Windows平台搭建依赖环境模型转换--pytorch转onnxONN…

走进低代码表单开发(一):可视化表单数据源设计

在前文&#xff0c;我们已对勤研低代码平台的报表功能做了详细介绍。接下来&#xff0c;让我们深入探究低代码开发中最为常用的表单设计功能。一个完整的应用是由众多表单组合而成的&#xff0c;所以高效的表单设计在开发过程中起着至关重要的作用。让我们一同了解勤研低代码开…