详细解析
🔧 构造函数解析
python
def __init__(self, parent=None):
# 🏗️ 继承链调用:调用QGraphicsView的构造函数进行基础初始化
super(AnnotationView, self).__init__(parent)
# 🖱️ 鼠标跟踪设置:启用连续鼠标位置监听
# 默认情况下,只有按下鼠标时才触发移动事件
# 设置为True后,鼠标悬停移动也会触发事件
self.setMouseTracking(True)
# 📜 滚动条策略:强制显示滚动条
# ScrollBarAlwaysOn = 始终显示,不管内容是否超出视图
self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarPolicy.ScrollBarAlwaysOn)
self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarPolicy.ScrollBarAlwaysOn)
# 🖐️ 拖拽模式设置:启用手动拖拽滚动
# ScrollHandDrag = 鼠标变成手形,可以拖拽移动视图内容
self.setDragMode(QtWidgets.QGraphicsView.DragMode.ScrollHandDrag)
# 🔍 缩放参数:定义缩放步长
self.factor = 1.2 # 每次缩放20%
🎯 核心方法解析
🖱️ 滚轮事件处理
python
def wheelEvent(self, event: QtGui.QWheelEvent):
# 📐 获取滚轮滚动信息
angel = event.angleDelta() # 返回QPoint对象,包含x和y方向的角度增量
angelX, angelY = angel.x(), angel.y() # 分别提取水平和垂直滚动量
# 📍 获取鼠标当前位置(视图坐标系)
point = event.pos()
# 🔍 根据滚动方向进行缩放
if angelY > 0: # 向上滚动 = 放大
self.zoom(self.factor, point)
else: # 向下滚动 = 缩小
self.zoom(1 / self.factor, point)
继承调用链:
用户滚动鼠标 → Qt事件系统 → QGraphicsView.wheelEvent() → AnnotationView.wheelEvent()
🔍 缩放功能方法
python
def zoom_in(self):
# 🔍 放大:直接调用zoom方法,不指定鼠标位置
self.zoom(self.factor)
def zoom_out(self):
# 🔍 缩小:使用缩放因子的倒数
self.zoom(1 / self.factor)
def zoomfit(self):
# 📐 自适应缩放:调用QGraphicsView的内置方法
# fitInView继承自QGraphicsView,用于自动调整视图以完全显示指定区域
self.fitInView(
0, 0, # 起始坐标
self.scene().width(), self.scene().height(), # 场景的宽度和高度
QtCore.Qt.AspectRatioMode.KeepAspectRatio # 保持宽高比
)
🎨 核心缩放方法深度解析
def zoom(self, factor, point=None):
# 📍 坐标转换:将视图坐标转换为场景坐标
# mapToScene()是QGraphicsView的方法,用于坐标系转换
mouse_old = self.mapToScene(point) if point is not None else None
# 🧮 计算缩放后的像素比例
# transform()获取当前变换矩阵
# scale()创建新的缩放变换
# mapRect()应用变换到矩形
pix_widget = self.transform().scale(factor, factor).mapRect(QtCore.QRectF(0, 0, 1, 1)).width()
# 🚫 缩放限制检查
if pix_widget > 30 and factor > 1: # 防止过度放大
return
if pix_widget < 0.01 and factor < 1: # 防止过度缩小
return
# 🔍 执行缩放:调用QGraphicsView的scale方法
self.scale(factor, factor)
# 📐 以鼠标为中心的缩放调整
if point is not None:
mouse_now = self.mapToScene(point) # 缩放后的鼠标场景坐标
# 获取当前视图中心点的场景坐标
center_now = self.mapToScene(self.viewport().width() // 2, self.viewport().height() // 2)
# 🧮 计算新的视图中心:确保鼠标位置保持不变
center_new = mouse_old - mouse_now + center_now
# 🎯 调整视图中心:调用QGraphicsView的centerOn方法
self.centerOn(center_new)
🔄 方法调用关系图
用户操作
↓
┌─────────────────────┐
│ wheelEvent() │ ← Qt事件系统
│ zoom_in() │ ← 工具栏按钮
│ zoom_out() │ ← 工具栏按钮
│ zoomfit() │ ← 菜单选项
└─────────────────────┘
↓
┌─────────────┐
│ zoom() │ ← 核心缩放逻辑
└─────────────┘
↓
┌─────────────────────┐
│ QGraphicsView方法 │
│ • mapToScene() │ ← 坐标转换
│ • transform() │ ← 获取变换矩阵
│ • scale() │ ← 执行缩放
│ • centerOn() │ ← 调整视图中心
│ • fitInView() │ ← 自适应显示
└─────────────────────┘
🧠 设计思路分析
🎯 核心设计模式
- *� 模板方法模式:
-
- 重写
wheelEvent()
自定义滚轮行为 - 保留Qt的事件处理机制
- 重写
- *� 适配器模式:
-
- 封装复杂的Qt Graphics Framework
- 提供简洁的缩放接口
- *� 策略模式:
-
- 不同的缩放策略(以鼠标为中心 vs 以视图中心)
💡 关键技术点
# 🔄 坐标系转换
视图坐标 → mapToScene() → 场景坐标
# 🎯 变换矩阵操作
当前变换 → scale() → 新变换 → mapRect() → 像素计算
# 📐 几何计算
鼠标位置保持 = 旧鼠标坐标 - 新鼠标坐标 + 当前中心
🛡️ 错误处理机制
- 缩放限制:防止过度放大/缩小导致性能问题
- 坐标验证:确保变换矩阵计算正确
- 边界检查:避免视图超出合理范围
这个类完美地展示了Qt Graphics Framework的高级用法 和面向对象设计的继承机制!
源代码
python
class AnnotationView(QtWidgets.QGraphicsView):
def __init__(self, parent=None):
super(AnnotationView, self).__init__(parent)
self.setMouseTracking(True)
self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarPolicy.ScrollBarAlwaysOn)
self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarPolicy.ScrollBarAlwaysOn)
self.setDragMode(QtWidgets.QGraphicsView.DragMode.ScrollHandDrag)
self.factor = 1.2
def wheelEvent(self, event: QtGui.QWheelEvent):
angel = event.angleDelta()
angelX, angelY = angel.x(), angel.y()
point = event.pos()
if angelY > 0:
self.zoom(self.factor, point)
else:
self.zoom(1 / self.factor, point)
def zoom_in(self):
self.zoom(self.factor)
def zoom_out(self):
self.zoom(1 / self.factor)
def zoomfit(self):
self.fitInView(0, 0, self.scene().width(), self.scene().height(), QtCore.Qt.AspectRatioMode.KeepAspectRatio)
def zoom(self, factor, point=None):
mouse_old = self.mapToScene(point) if point is not None else None
pix_widget = self.transform().scale(factor, factor).mapRect(QtCore.QRectF(0, 0, 1, 1)).width()
if pix_widget > 30 and factor > 1: return
if pix_widget < 0.01 and factor < 1: return
self.scale(factor, factor)
if point is not None:
mouse_now = self.mapToScene(point)
center_now = self.mapToScene(self.viewport().width() // 2, self.viewport().height() // 2)
center_new = mouse_old - mouse_now + center_now
self.centerOn(center_new)