Vispy与PyQt5全屏Resize事件适配问题求助
PyQt5集成Vispy SceneCanvas全屏后无法同步缩放问题
基于PyQt5开发线条查看器,将Vispy场景作为QGraphicsView的视口,已实现缩放和平移功能。手动点击右上角全屏按钮后,QGraphicsView主部件可正常缩放,但Vispy的SceneCanvas无法同步缩放,始终保持原有尺寸。
状态说明
- 全屏前:画布定位异常,平移或缩放时右侧存在边界限制;
- 全屏后:画布移至左下角,且未随窗口缩放。
曾尝试将QGraphicsView的resize事件信号传递给Vispy画布,但未解决问题。原代码如下:
import sys from PyQt5.QtGui import QPainter, QPaintEvent from PyQt5 import QtCore from PyQt5.QtCore import Qt from PyQt5.QtWidgets import QGraphicsView, QApplication import vispy.scene from vispy.scene import visuals import numpy as np def dummy_generator(num_points, max_points): from scipy.spatial import Voronoi # 定义尺寸 city_width = 8000 city_height = 6000 points = np.random.rand(num_points, 2) * np.array([city_width, city_height]) # 计算Voronoi图生成地块边界 vor = Voronoi(points) # 创建代表地块边界的折线坐标列表 polylines = [] pos_arr = [] for ridge in vor.ridge_vertices: if ridge[0] >= 0 and ridge[1] >= 0: x1, y1 = vor.vertices[ridge[0]] x2, y2 = vor.vertices[ridge[1]] if x1 < 0 or x1 > city_width or y1 < 0 or y1 > city_height: continue if x2 < 0 or x2 > city_width or y2 < 0 or y2 > city_height: continue # 生成折线上的中间点使线条更平滑 val = np.random.randint(3, max_points) xs = np.linspace(x1, x2, num=val) ys = np.linspace(y1, y2, num=val) polyline = [(xs[i], ys[i]) for i in range(val)] points = np.array(polyline).T polylines.append(points) pos_arr.append(max(points.shape)) # 计算每个轮廓的起止索引 pos_arr = np.array(pos_arr) - 1 fill_arr = np.ones(np.sum(pos_arr)).astype(int) zero_arr = np.zeros(len(pos_arr)).astype(int) c = np.insert(fill_arr, np.cumsum(pos_arr), zero_arr) connect = np.where(c == 1, True, False) coords = np.concatenate(polylines, axis=1) return coords.T, connect class VispyViewport(QGraphicsView): def __init__(self, parent=None): super().__init__(parent) self.scale_factor = 1.5 self.setRenderHint(QPainter.Antialiasing) self.setInteractive(True) # 创建Vispy画布并添加到QGraphicsView self.canvas = canvas = vispy.scene.SceneCanvas(app='pyqt5', show=True) vispy_widget = canvas.native vispy_widget.setParent(self) # 将Vispy部件设置为QGraphicsView的视口 self.setViewport(vispy_widget) self.setGeometry(QtCore.QRect(0,0, 2100,600)) # 创建网格布局并添加到画布 grid = canvas.central_widget.add_grid() # 创建ViewBox并添加到网格布局 self.view_vispy = grid.add_view(row=0, col=0, bgcolor='#c0c0c0') self.grid = self.canvas.central_widget.add_grid() line_data, connect = dummy_generator(5000, 50) self.line = visuals.Line(line_data, parent=self.view_vispy.scene, connect=connect, color=(0.50196, 0.50196, 0.50196, 1)) self.view_vispy.camera = vispy.scene.PanZoomCamera() self.view_vispy.camera.set_range() self.view_vispy.camera.aspect = 1.0 # 获取变换矩阵 self.tform = self.view_vispy.scene.transform def wheelEvent(self, event): # 获取视口中心的场景坐标 pos = event.pos() # 确定缩放因子 if event.angleDelta().y() > 0: zoom_factor = 1 / self.scale_factor else: zoom_factor = self.scale_factor # 映射到Vispy坐标 center = self.tform.imap((pos.x(), pos.y(), 0)) # 以中心为锚点应用缩放 self.view_vispy.camera.zoom(zoom_factor, center=center) def mousePressEvent(self, event): if event.button() == Qt.MiddleButton: self.setDragMode(QGraphicsView.ScrollHandDrag) self.setInteractive(True) self.mouse_press_pos = event.pos() self.mouse_press_center = self.view_vispy.camera.center[:2] else: super().mousePressEvent(event) def mouseReleaseEvent(self, event): if event.button() == Qt.MiddleButton: self.setDragMode(QGraphicsView.NoDrag) self.setInteractive(False) else: super().mouseReleaseEvent(event) def mouseMoveEvent(self, event): if self.dragMode() == QGraphicsView.ScrollHandDrag: # 获取鼠标位置差值 diff = event.pos() - self.mouse_press_pos # 获取场景坐标中的移动向量 move_vec = self.tform.imap((diff.x(), diff.y())) - self.tform.imap((0, 0)) # 应用平移并设置中心 self.view_vispy.camera.center = (self.mouse_press_center[0] - move_vec[0], self.mouse_press_center[1] - move_vec[1]) else: super().mouseMoveEvent(event) def paintEvent(self, event: QPaintEvent) -> None: # 强制触发绘制事件 self.canvas.native.paintEvent(event) return super().paintEvent(event) if __name__ == '__main__': app = QApplication(sys.argv) view = VispyViewport() view.show() # 启动Qt事件循环 sys.exit(app.exec_())
解决方案
问题核心在于Vispy画布未随QGraphicsView尺寸变化更新,且布局逻辑存在冗余。以下是具体修改:
- 移除固定尺寸限制:删除
self.setGeometry(QtCore.QRect(0,0, 2100,600)),让窗口自适应系统尺寸。 - 添加resizeEvent同步画布:在VispyViewport类中添加resize事件处理,同步Vispy画布尺寸并更新相机范围:
def resizeEvent(self, event): super().resizeEvent(event) # 同步Vispy画布与视口尺寸 self.canvas.size = self.viewport().size() # 更新相机范围,确保内容适配新窗口 self.view_vispy.camera.set_range()
- 删除冗余布局代码:移除
self.grid = self.canvas.central_widget.add_grid(),避免布局冲突。 - 调整相机宽高比:将
self.view_vispy.camera.aspect = 1.0改为self.view_vispy.camera.aspect = None,让相机自适应窗口比例(若需固定1:1可保留原设置)。
修改后的完整代码
import sys from PyQt5.QtGui import QPainter, QPaintEvent from PyQt5 import QtCore from PyQt5.QtCore import Qt from PyQt5.QtWidgets import QGraphicsView, QApplication import vispy.scene from vispy.scene import visuals import numpy as np def dummy_generator(num_points, max_points): from scipy.spatial import Voronoi # 定义尺寸 city_width = 8000 city_height = 6000 points = np.random.rand(num_points, 2) * np.array([city_width, city_height]) # 计算Voronoi图生成地块边界 vor = Voronoi(points) # 创建代表地块边界的折线坐标列表 polylines = [] pos_arr = [] for ridge in vor.ridge_vertices: if ridge[0] >= 0 and ridge[1] >= 0: x1, y1 = vor.vertices[ridge[0]] x2, y2 = vor.vertices[ridge[1]] if x1 < 0 or x1 > city_width or y1 < 0 or y1 > city_height: continue if x2 < 0 or x2 > city_width or y2 < 0 or y2 > city_height: continue # 生成折线上的中间点使线条更平滑 val = np.random.randint(3, max_points) xs = np.linspace(x1, x2, num=val) ys = np.linspace(y1, y2, num=val) polyline = [(xs[i], ys[i]) for i in range(val)] points = np.array(polyline).T polylines.append(points) pos_arr.append(max(points.shape)) # 计算每个轮廓的起止索引 pos_arr = np.array(pos_arr) - 1 fill_arr = np.ones(np.sum(pos_arr)).astype(int) zero_arr = np.zeros(len(pos_arr)).astype(int) c = np.insert(fill_arr, np.cumsum(pos_arr), zero_arr) connect = np.where(c == 1, True, False) coords = np.concatenate(polylines, axis=1) return coords.T, connect class VispyViewport(QGraphicsView): def __init__(self, parent=None): super().__init__(parent) self.scale_factor = 1.5 self.setRenderHint(QPainter.Antialiasing) self.setInteractive(True) # 创建Vispy画布并添加到QGraphicsView self.canvas = canvas = vispy.scene.SceneCanvas(app='pyqt5', show=True) vispy_widget = canvas.native vispy_widget.setParent(self) # 将Vispy部件设置为QGraphicsView的视口 self.setViewport(vispy_widget) # 创建网格布局并添加到画布 grid = canvas.central_widget.add_grid() # 创建ViewBox并添加到网格布局 self.view_vispy = grid.add_view(row=0, col=0, bgcolor='#c0c0c0') line_data, connect = dummy_generator(5000, 50) self.line = visuals.Line(line_data, parent=self.view_vispy.scene, connect=connect, color=(0.50196, 0.50196, 0.50196, 1)) self.view_vispy.camera = vispy.scene.PanZoomCamera() self.view_vispy.camera.set_range() self.view_vispy.camera.aspect = None # 自适应窗口比例 # 获取变换矩阵 self.tform = self.view_vispy.scene.transform def wheelEvent(self, event): # 获取视口中心的场景坐标 pos = event.pos() # 确定缩放因子 if event.angleDelta().y() > 0: zoom_factor = 1 / self.scale_factor else: zoom_factor = self.scale_factor # 映射到Vispy坐标 center = self.tform.imap((pos.x(), pos.y(), 0)) # 以中心为锚点应用缩放 self.view_vispy.camera.zoom(zoom_factor, center=center) def mousePressEvent(self, event): if event.button() == Qt.MiddleButton: self.setDragMode(QGraphicsView.ScrollHandDrag) self.setInteractive(True) self.mouse_press_pos = event.pos() self.mouse_press_center = self.view_vispy.camera.center[:2] else: super().mousePressEvent(event) def mouseReleaseEvent(self, event): if event.button() == Qt.MiddleButton: self.setDragMode(QGraphicsView.NoDrag) self.setInteractive(False) else: super().mouseReleaseEvent(event) def mouseMoveEvent(self, event): if self.dragMode() == QGraphicsView.ScrollHandDrag: # 获取鼠标位置差值 diff = event.pos() - self.mouse_press_pos # 获取场景坐标中的移动向量 move_vec = self.tform.imap((diff.x(), diff.y())) - self.tform.imap((0, 0)) # 应用平移并设置中心 self.view_vispy.camera.center = (self.mouse_press_center[0] - move_vec[0], self.mouse_press_center[1] - move_vec[1]) else: super().mouseMoveEvent(event) def paintEvent(self, event: QPaintEvent) -> None: # 强制触发绘制事件 self.canvas.native.paintEvent(event) return super().paintEvent(event) def resizeEvent(self, event): super().resizeEvent(event) # 同步Vispy画布与视口尺寸 self.canvas.size = self.viewport().size() # 更新相机范围,确保内容适配新窗口 self.view_vispy.camera.set_range() if __name__ == '__main__': app = QApplication(sys.argv) view = VispyViewport() view.show() # 启动Qt事件循环 sys.exit(app.exec_())
内容的提问来源于stack exchange,提问作者MBV
相关产品推荐
相关产品推荐

