Deprecated: imwpcache\f884414bce24ee67f\f73723ec7b1919fa5::__construct(): Implicitly marking parameter $YECBGYFECGEAFWHA as nullable is deprecated, the explicit nullable type must be used instead in /www/wwwroot/www.chuangxiangniao.com/wp-content/plugins/imwpcache-dist/build/f884414bce24ee67ff73723ec7b1919fa5.php on line 2

Deprecated: imwpcache\f884414bce24ee67f\f73723ec7b1919fa5::__construct(): Implicitly marking parameter $BBWFDDBHHYHDXXAB as nullable is deprecated, the explicit nullable type must be used instead in /www/wwwroot/www.chuangxiangniao.com/wp-content/plugins/imwpcache-dist/build/f884414bce24ee67ff73723ec7b1919fa5.php on line 2
利用PyQt扩展QPdfView:实现交互式PDF矩形标注功能_创想鸟

利用PyQt扩展QPdfView:实现交互式PDF矩形标注功能

利用PyQt扩展QPdfView:实现交互式PDF矩形标注功能

本文详细介绍了如何通过继承QPdfView类,在PyQt应用程序中实现交互式矩形绘制功能,允许用户直接在PDF文档上拖动鼠标来创建和调整矩形标注。教程涵盖了自定义状态管理、鼠标事件处理以及关键的渲染刷新机制,特别强调了使用self.viewport().repaint()来解决绘制内容不立即显示的问题,从而提供流畅的用户体验。

1. 引言:扩展QPdfView以实现自定义绘制

qpdfview是qt框架中用于显示pdf文档的强大组件。然而,在某些应用场景下,我们可能需要在pdf内容之上添加自定义的交互式图形元素,例如绘制矩形标注。由于qpdfview本身不直接提供此类功能,最常见的做法是通过子类化qpdfview并重写其事件处理方法和绘图方法来实现。本教程将指导您完成这一过程,重点解决在pdf视图上进行实时绘制时可能遇到的渲染刷新问题。

2. 构建自定义QPdfView类

我们将创建一个名为customQPdfView的类,它继承自QPdfView。这个自定义类将包含用于管理绘图状态、存储矩形坐标以及处理鼠标事件的逻辑。

2.1 类结构与初始化

在customQPdfView的构造函数中,我们需要初始化一些关键变量来追踪矩形的起始和结束点,以及当前的绘图状态。

from PyQt5.QtWidgets import QMainWindow, QApplication, QPdfViewfrom PyQt5.QtPdf import QPdfDocumentfrom PyQt5.QtCore import QPoint, QRect, QUrlfrom PyQt5.QtGui import QPainter, QColor, QPenimport sys# 定义绘图状态常量FREE_STATE = 1        # 自由状态,未进行绘制或编辑BUILDING_SQUARE = 2   # 正在绘制新矩形BEGIN_SIDE_EDIT = 3   # 正在编辑矩形左侧边界END_SIDE_EDIT = 4     # 正在编辑矩形右侧边界class customQPdfView(QPdfView):    def __init__(self, parent=None):        super().__init__(parent)        # 设置初始几何尺寸,可根据需要调整        self.setGeometry(30, 30, 800, 600)         # 存储矩形的起始点和结束点        self.begin = QPoint()        self.end = QPoint()        # 初始化绘图状态为自由状态        self.state = FREE_STATE    # ... 其他方法 ...

2.2 绘制事件处理 (paintEvent)

paintEvent是Qt组件中用于执行自定义绘制的核心方法。在我们的customQPdfView中,我们需要确保在绘制自定义矩形之前,先调用父类的paintEvent来渲染PDF内容。然后,我们使用QPainter在QPdfView的viewport()上绘制矩形。

重要提示:绘制操作必须在viewport()上进行,因为QPdfView的实际内容(PDF页面)显示在viewport中。

    def paintEvent(self, event):        super().paintEvent(event) # 首先调用父类方法绘制PDF内容        # 创建一个QPainter,目标是QPdfView的viewport        painter = QPainter(self.viewport())        # 设置画笔颜色和宽度        painter.setPen(QPen(QColor(255, 0, 0), 2)) # 红色,2像素宽        # 绘制矩形,如果begin和end点有效        if not self.begin.isNull() and not self.end.isNull():            painter.drawRect(QRect(self.begin, self.end).normalized()) # 使用normalized确保矩形有效

normalized()方法用于确保矩形的宽度和高度都是正值,无论begin和end点的相对位置如何。

2.3 鼠标事件处理:实现交互式绘制与编辑

为了实现交互式绘制和编辑,我们需要重写mousePressEvent、mouseMoveEvent和mouseReleaseEvent。

2.3.1 mousePressEvent:起始点与状态切换

当鼠标按下时,我们首先判断当前是否有已绘制的矩形,并检查鼠标点击位置是否靠近矩形的左右边缘,以决定是开始编辑现有矩形还是绘制新矩形。

    def mousePressEvent(self, event):        print('Mouse Press')        # 如果当前有矩形,检查是否点击到边缘进行编辑        if not self.begin.isNull() and not self.end.isNull():            p = event.pos()            # 获取矩形纵坐标范围,用于判断是否在矩形高度内            y1, y2 = sorted([self.begin.y(), self.end.y()])            if y1 <= p.y() <= y2:                # 检查是否接近左侧边缘(3像素容差)                if abs(self.begin.x() - p.x()) <= 3:                    self.state = BEGIN_SIDE_EDIT                    return                # 检查是否接近右侧边缘(3像素容差)                elif abs(self.end.x() - p.x()) <= 3:                    self.state = END_SIDE_EDIT                    return        # 如果不是编辑现有矩形,则开始绘制新矩形        self.state = BUILDING_SQUARE        self.begin = event.pos()        self.end = event.pos()        # 注意:这里不再调用update(),因为moveEvent和releaseEvent会处理刷新
2.3.2 apply_event:更新矩形坐标

为了避免代码重复,我们创建一个辅助方法apply_event来根据当前状态更新矩形的begin或end坐标。

    def apply_event(self, event):        if self.state == BUILDING_SQUARE:            self.end = event.pos() # 绘制时更新结束点        elif self.state == BEGIN_SIDE_EDIT:            self.begin.setX(event.x()) # 编辑左侧时更新起始点的X坐标        elif self.state == END_SIDE_EDIT:            self.end.setX(event.x())   # 编辑右侧时更新结束点的X坐标
2.3.3 mouseMoveEvent:实时更新与刷新

当鼠标拖动时,我们调用apply_event来更新矩形坐标,并关键地使用self.viewport().repaint()来强制QPdfView的视口立即重绘。

重要修复点:原始代码中使用self.update()可能不会立即触发QPdfView的视口重绘,导致矩形在拖动时无法实时显示。self.viewport().repaint()则会强制立即重绘视口区域,确保绘制的矩形能够实时跟随鼠标移动。

    def mouseMoveEvent(self, event):        print('Mouse Move')        if self.state != FREE_STATE: # 仅在绘制或编辑状态下响应移动            self.apply_event(event)            self.viewport().repaint() # 强制立即重绘viewport,解决不刷新问题
2.3.4 mouseReleaseEvent:结束操作

当鼠标释放时,我们调用apply_event进行最后一次坐标更新,并将状态重置为FREE_STATE。

    def mouseReleaseEvent(self, event):        print('Mouse Release')        self.apply_event(event)        self.state = FREE_STATE        self.viewport().repaint() # 确保最终状态被绘制

3. 完整示例代码

将以上所有部分整合,并添加一个简单的QMainWindow来加载PDF文档和显示customQPdfView。

from PyQt5.QtWidgets import QMainWindow, QApplication, QPdfViewfrom PyQt5.QtPdf import QPdfDocumentfrom PyQt5.QtCore import QPoint, QRect, QUrlfrom PyQt5.QtGui import QPainter, QColor, QPenimport sys# 定义绘图状态常量FREE_STATE = 1BUILDING_SQUARE = 2BEGIN_SIDE_EDIT = 3END_SIDE_EDIT = 4class customQPdfView(QPdfView):    def __init__(self, parent=None):        super().__init__(parent)        self.setGeometry(30, 30, 800, 600)         self.begin = QPoint()        self.end = QPoint()        self.state = FREE_STATE    def paintEvent(self, event):        super().paintEvent(event)        painter = QPainter(self.viewport())        painter.setPen(QPen(QColor(255, 0, 0), 2)) # 红色,2像素宽        if not self.begin.isNull() and not self.end.isNull():            painter.drawRect(QRect(self.begin, self.end).normalized())    def mousePressEvent(self, event):        print('Mouse Press')        if not self.begin.isNull() and not self.end.isNull():            p = event.pos()            y1, y2 = sorted([self.begin.y(), self.end.y()])            if y1 <= p.y() <= y2:                if abs(self.begin.x() - p.x()) <= 3:                    self.state = BEGIN_SIDE_EDIT                    return                elif abs(self.end.x() - p.x()) <= 3:                    self.state = END_SIDE_EDIT                    return        self.state = BUILDING_SQUARE        self.begin = event.pos()        self.end = event.pos()    def apply_event(self, event):        if self.state == BUILDING_SQUARE:            self.end = event.pos()        elif self.state == BEGIN_SIDE_EDIT:            self.begin.setX(event.x())        elif self.state == END_SIDE_EDIT:            self.end.setX(event.x())    def mouseMoveEvent(self, event):        print('Mouse Move')        if self.state != FREE_STATE:            self.apply_event(event)            self.viewport().repaint() # 关键:强制立即重绘viewport    def mouseReleaseEvent(self, event):        print('Mouse Release')        self.apply_event(event)        self.state = FREE_STATE        self.viewport().repaint() # 确保最终状态被绘制class MainWindow(QMainWindow):    def __init__(self):        super().__init__()        self.setWindowTitle("PDF Annotator")        self.setGeometry(100, 100, 1000, 800)        self.pdf_view = customQPdfView(self)        self.setCentralWidget(self.pdf_view)        # 加载一个PDF文档 (请替换为您的PDF文件路径)        pdf_document = QPdfDocument()        # 请确保'sample.pdf'文件存在于与脚本相同的目录下,或者提供完整路径        if pdf_document.load(QUrl.fromLocalFile("sample.pdf")):             self.pdf_view.setDocument(pdf_document)        else:            print("Failed to load PDF document.")if __name__ == '__main__':    app = QApplication(sys.argv)    main_win = MainWindow()    main_win.show()    sys.exit(app.exec_())

使用前请注意:

将代码中的”sample.pdf”替换为您实际的PDF文件路径。确保您的环境中已安装PyQt5和PyQt5.QtPdf模块。

4. 注意事项与进一步优化

多矩形支持:当前代码只支持绘制和编辑一个矩形。若要支持多个矩形,您需要维护一个矩形对象的列表,并在paintEvent中遍历绘制它们。鼠标事件处理也需要更复杂的逻辑来识别和选择要操作的矩形。矩形数据管理:绘制的矩形通常需要保存起来。您可以将矩形的坐标、颜色等信息封装成一个自定义对象,并在应用程序关闭时将其序列化到文件,在打开PDF时加载。坐标转换:QPdfView的viewport坐标是基于像素的,而PDF文档内部可能有其自己的坐标系统。如果需要将绘制的矩形与PDF文档内容进行精确关联(例如,保存标注到PDF的特定位置),您可能需要使用QPdfView提供的坐标转换方法,如pageAt()、mapToPage()、mapFromPage()等。性能:对于非常复杂的绘制或大量矩形,频繁调用repaint()可能会影响性能。可以考虑优化绘制区域,只重绘受影响的部分,或者在mouseMoveEvent中引入节流(throttling)机制。用户体验:可以添加更多的视觉反馈,例如在鼠标悬停在矩形边缘时改变光标样式,或者在选中矩形时显示调整手柄。

5. 总结

通过子类化QPdfView并精心设计鼠标事件处理逻辑,我们成功地为PDF视图添加了交互式矩形绘制和编辑功能。解决关键的渲染刷新问题(即使用self.viewport().repaint()而非self.update())是实现流畅用户体验的关键。这一技术为在PyQt应用程序中创建更丰富的PDF交互工具奠定了基础。

以上就是利用PyQt扩展QPdfView:实现交互式PDF矩形标注功能的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/1373797.html

赞 (0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
Flask应用调试模式的配置与实践
上一篇 2025年12月14日 13:34:55
Python中将2D列向量转换为1D向量以计算Pearson相关系数
下一篇 2025年12月14日 13:35:07

相关推荐

  • Figma中AI插件生成的图片如何导出?快速导出的详细操作指南

    AI插件生成的图片在Figma中以普通图层形式存在,需选中后通过右侧导出面板设置格式(PNG/JPG)、尺寸倍数(1x/2x/3x)并点击导出;支持多选图层或使用切片工具批量导出,结合命名规范与质量权衡可高效管理大量AI图像资产。 ☞☞☞AI 智能聊天, 问答助手, AI 智能搜索, 免费无限量使用…

    2026年9月21日
    500
  • windows10关机后鼠标键盘灯还亮_windows10关机外设灯异常解决方法

    windows10关机后鼠标键盘灯还亮_windows10关机外设灯异常解决方法windows10关机后鼠标键盘灯还亮_windows10关机外设灯异常解决方法windows10关机后鼠标键盘灯还亮_windows10关机外设灯异常解决方法windows10关机后鼠标键盘灯还亮_windows10关机外设灯异常解决方法

    电脑关机后鼠标键盘灯仍亮,可依次关闭Windows快速启动、调整BIOS中USB关机供电设置、卸载异常USB驱动,并执行电源重置以彻底切断外设供电。 如果您在使用Windows 10系统时发现电脑已关机,但连接的鼠标和键盘指示灯仍未熄灭,这可能是由于系统或主板电源管理设置导致外设在关机后仍保持供电。…

    2026年9月21日 • 用户投稿
    100
  • 使用EventBus实现Android实时速度显示与后台保存教程

    本教程详细介绍了如何在Android应用中实现实时速度的显示与后台保存功能。通过利用前台服务(Foreground Service)获取位置数据,并结合EventBus库实现服务与UI界面(MainActivity)之间的实时数据通信,确保即使应用处于后台或屏幕关闭时,速度数据也能持续更新并显示在用…

    2026年9月21日
    000
  • 提高蝴蝶号无人直播留存率的6个实用技巧和策略

    提高蝴蝶号无人直播留存率的6个实用技巧和策略提高蝴蝶号无人直播留存率的6个实用技巧和策略提高蝴蝶号无人直播留存率的6个实用技巧和策略提高蝴蝶号无人直播留存率的6个实用技巧和策略

    提高蝴蝶号无人直播留存率的核心在于让用户觉得直播间“有东西”,具体措施包括:1.内容为王,垂直深耕某一领域并提供专业知识;2.互动是魂,利用弹幕、投票、抽奖引导用户参与;3.利益驱动,通过抽奖、红包提升用户积极性;4.氛围营造,打造独特风格和专属互动方式;5.数据分析,持续优化直播策略;6.活动预告…

    2026年9月21日 • 用户投稿
    100
  • 佳能EOS R1对决索尼A1:奥运年旗舰微单的速度与画质对决,谁能代表微单技术的最高峰?

    佳能EOS R1凭借AI驱动的智能对焦、20张预连拍、机内神经网络降噪和6K RAW视频,结合深度学习技术与专业生态整合,在体育与新闻摄影领域展现出更前瞻的技术高度。 在专业体育与新闻摄影领域,佳能EOS R1和索尼A1是两款代表品牌顶尖技术的旗舰微单。它们都在追求速度、对焦与画质的极致平衡,但实现…

    2026年9月21日
    100
  • laravel如何进行安全的SQL查询以防止注入_Laravel安全SQL查询防注入方法

    使用Eloquent和Query Builder并配合参数绑定可有效防止SQL注入。Laravel通过PDO预处理机制自动转义参数,确保安全;应避免拼接用户输入,尤其在whereRaw等原生语句中需使用?占位符绑定变量;所有用户输入均需验证,对ID类字段强制类型转换,并禁止将用户输入直接用于表名、字…

    2026年9月21日
    000
  • 在Java中如何分析异常堆栈性能开销

    异常堆栈在高并发场景下开销显著,因JVM需遍历调用栈、创建对象、字符串拼接及同步操作,频繁使用将增加GC压力与CPU消耗;可通过JMH测试量化影响,发现填充堆栈耗时可达清空的10倍以上;建议避免在热点代码抛异常、禁用非必要堆栈填充、按需打印日志、使用异步日志框架,并借助JFR、Profiler和GC…

    2026年9月21日
    000
  • PHP/MySQL:高效合并订单商品并按日期分组显示

    本教程将指导如何在PHP/MySQL应用中,将同一日期的订单商品合并显示在同一行,以提高数据展示的清晰度。核心解决方案是利用MySQL的GROUP_CONCAT函数在数据库层面进行高效聚合,避免复杂的PHP逻辑处理,从而简化代码并优化性能。 订单数据展示的常见挑战 在开发在线购物平台时,通常需要向用…

    2026年9月21日
    100
  • google浏览器CPU占用率过高怎么解决_google浏览器CPU占用过高解决方法

    Chrome CPU占用过高可通过清除缓存、禁用高耗能扩展、结束高占用进程、更新浏览器、关闭硬件加速及禁用Software Reporter Tool解决。 如果您在使用Google Chrome浏览器时发现电脑运行缓慢或风扇狂转,很可能是由于Chrome的CPU占用率过高导致系统资源被大量消耗。以…

    2026年9月21日
    000
  • win11任务管理器打不开怎么办_win11任务管理器无法打开修复方法

    1、使用SFC和DISM命令修复系统文件后重启;2、通过gpedit.msc检查并禁用“删除任务管理器”策略;3、在注册表中将DisableTaskMgr值设为0;4、创建新用户账户测试是否解决任务管理器无法打开问题。 如果您尝试打开任务管理器时没有响应或无法启动,可能是由于系统文件损坏、组策略设置…

    2026年9月21日
    000
  • VSCode报错怎么显示中文_VSCode错误信息本地化与中文显示教程

    安装中文语言包可将VSCode界面和错误提示转为中文,提升使用便捷性;但外部工具如编译器、解释器生成的报错仍为英文,因VSCode仅显示其原始输出,无法翻译。 在VSCode中让报错信息显示中文,核心在于安装并启用官方的中文(简体)语言包。这不仅仅是针对错误信息,而是将整个VSCode的用户界面本地…

    2026年9月21日
    000
  • 如何在MindSpore中训练AI大模型?华为AI框架的训练教程

    如何在MindSpore中训练AI大模型?华为AI框架的训练教程如何在MindSpore中训练AI大模型?华为AI框架的训练教程如何在MindSpore中训练AI大模型?华为AI框架的训练教程如何在MindSpore中训练AI大模型?华为AI框架的训练教程

    答案:MindSpore通过自动并行、混合精度、优化器状态分片等技术,结合Profiler工具调试性能瓶颈,实现大模型高效分布式训练。 ☞☞☞AI 智能聊天, 问答助手, AI 智能搜索, 免费无限量使用 DeepSeek R1 模型☜☜☜ 在MindSpore中训练AI大模型,核心在于巧妙地利用其…

    2026年9月21日 • 用户投稿
    300
  • Java ConcurrentSkipListMap在并发场景下应用

    ConcurrentSkipListMap是基于跳跃表实现的线程安全有序映射,支持高并发读写与高效范围查询,适用于需排序的并发场景,如排行榜系统;相比ConcurrentHashMap,它提供有序性与导航操作,但插入查找为O(log n),内存开销较大,适合读多写少或需区间扫描的业务。 在高并发场景…

    2026年9月21日
    100
  • MySQL数据库如何支持多租户业务_设计策略与实现?

    MySQL数据库如何支持多租户业务_设计策略与实现?MySQL数据库如何支持多租户业务_设计策略与实现?MySQL数据库如何支持多租户业务_设计策略与实现?MySQL数据库如何支持多租户业务_设计策略与实现?

    mysql 支持多租户架构的关键在于选择合适的数据隔离策略,并兼顾性能与运维管理。1. 常见方式包括共享数据库共享表(资源利用率高但隔离性差)、共享数据库独立表(平衡隔离性与维护成本)和独立数据库(隔离性强但管理复杂)。2. 租户识别需在请求前确定租户id,并自动附加到sql查询中,可通过视图或中间…

    2026年9月21日 • 用户投稿
    000
  • VSCode怎么启动Layui项目_VSCode运行Layui前端框架项目教程

    必须使用本地服务器运行Layui项目,因为直接打开HTML文件通过file://协议会受浏览器安全限制,导致AJAX、跨域等功能异常,Layui组件无法正常加载;推荐安装Node.js后使用npm全局安装http-server,通过命令行启动服务,或在VSCode中安装Live Server插件,右…

    2026年9月21日
    000
  • 俄罗斯Яндекс账号登录入口 Yandex电脑版官方网站登录

    答案是https://www.yandex.com/。该网站提供搜索、地图、新闻、翻译等服务,界面简洁,支持个性化设置与账户同步,并拥有邮箱、云存储及丰富的应用生态。 1、立即进入“☞☞☞☞点击俄罗斯yandex搜索引擎入口☜☜☜☜”; 2、立即进入“☞☞☞☞点击快速获取Yandex免登录官网链接☜…

    2026年9月21日
    000
  • Steam游戏平台下载缓存怎么清理_Steam清理下载缓存的方法

    清理Steam下载缓存可解决下载慢、中断或安装失败问题。首先可通过客户端设置中的“清除下载缓存”功能操作,随后重新登录账户;若无效,可手动删除Steam安装目录下的appcache和depotcache文件夹;此外,重置网络配置并执行netsh winsock reset与ipconfig /flu…

    2026年9月21日
    000
  • 蝴蝶号直播掉帧、断流怎么办?技术实用建议

    蝴蝶号直播掉帧、断流怎么办?技术实用建议蝴蝶号直播掉帧、断流怎么办?技术实用建议蝴蝶号直播掉帧、断流怎么办?技术实用建议蝴蝶号直播掉帧、断流怎么办?技术实用建议

    解决蝴蝶号直播掉帧、断流问题需从硬件、软件、网络三方面入手。1. 硬件方面:检查cpu和gpu压力,必要时升级硬件或降低分辨率、帧率;确保摄像头、采集卡、内存正常工作。2. 软件方面:调整分辨率、帧率、码率至合适水平;使用h.265或硬件编码减轻cpu负担;设置关键帧间隔为2秒;关闭后台程序并检查平…

    2026年9月21日 • 用户投稿
    300
  • 如何使用Ribbet的AI功能裁剪图片?快速实现精准图像裁剪

    答案:Ribbet的AI裁剪功能可快速智能识别主体并推荐裁剪方案,支持手动微调与多种比例选择,结合亮度、色彩等编辑工具优化效果,适用于制作符合社交媒体尺寸要求的封面图,操作简便且大部分功能免费,适合追求效率的普通用户。 ☞☞☞AI 智能聊天, 问答助手, AI 智能搜索, 免费无限量使用 DeepS…

    2026年9月21日
    400
  • SystemTap

    SystemTap 简介 systemtap 是一款用于诊断 linux 系统性能或功能问题的开源工具。它使得对运行中的 linux 系统进行诊断和调试变得更加便捷和高效。有了 systemtap,开发者和调试人员无需重新编译内核、安装新内核或重启系统等繁琐步骤。为了解决系统问题或提升性能,开发者只…

    2026年9月21日
    100

发表回复

登录后才能评论
关注微信