PyQt6/Matplotlib:多FigureCanvasQTAgg共享单个Figure时如何冻结画布
问题解决思路与代码修复
代码问题分析
你当前的eventFilter逻辑存在错误:在Qt的事件过滤器中,返回True才会拦截事件,而你返回False相当于允许事件继续传播,所以根本没起到阻止Canvas重绘的作用。
临时修复:拦截Paint事件实现"冻结"
修改你的Canvas类,调整事件过滤器的返回值,同时确保Canvas完成初始绘制后再冻结:
import sys import random import matplotlib matplotlib.use('Qt5Agg') from PyQt6 import QtCore, QtWidgets from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas from matplotlib.figure import Figure class Canvas(FigureCanvas): def __init__(self, fig): self.paint_enabled = True super().__init__(fig) self.installEventFilter(self) def eventFilter(self, obj, event): # 返回True拦截Paint事件,阻止Canvas更新 if obj == self and event.type() == QtCore.QEvent.Type.Paint and not self.paint_enabled: return True return super().eventFilter(obj, event) app = QtWidgets.QApplication(sys.argv) fig = Figure(figsize=(5, 4), dpi=100) ax = fig.add_subplot(111) xdata = list(range(50)) ydata = [random.randint(0, 10) for i in range(50)] ax.plot(xdata, ydata) c1 = Canvas(fig) c1.move(100,100) c1.draw() c1.show() # 必须等draw完成后再冻结,确保Canvas缓存了初始图像 c1.paint_enabled = False def second_plot(): ydata = [random.randint(0, 10) for i in range(50)] ax.clear() ax.plot(xdata, ydata) c2 = Canvas(fig) c2.move(600,600) c2.draw() c2.show() timer = QtCore.QTimer() timer.setSingleShot(True) timer.timeout.connect(second_plot) timer.start(1000) app.exec()
更可靠的解决方案
共享单个Figure的方案本身存在状态耦合风险,更推荐以下两种方式:
方案1:将Canvas转为静态图像显示
直接把Matplotlib绘制的内容转成QPixmap,用QLabel显示,彻底和原Figure解耦:
import sys import random import matplotlib matplotlib.use('Qt5Agg') from PyQt6 import QtCore, QtWidgets, QtGui from matplotlib.figure import Figure def get_plot_pixmap(): fig = Figure(figsize=(5, 4), dpi=100) ax = fig.add_subplot(111) xdata = list(range(50)) ydata = [random.randint(0, 10) for _ in range(50)] ax.plot(xdata, ydata) # 将Figure转为QPixmap fig.canvas.draw() buf = fig.canvas.buffer_rgba() pixmap = QtGui.QPixmap.fromImage(QtGui.QImage(buf, buf.shape[1], buf.shape[0], QtGui.QImage.Format.Format_RGBA8888)) return pixmap app = QtWidgets.QApplication(sys.argv) # 第一个窗口 label1 = QtWidgets.QLabel() label1.setPixmap(get_plot_pixmap()) label1.move(100,100) label1.show() def show_second_plot(): # 第二个窗口 label2 = QtWidgets.QLabel() label2.setPixmap(get_plot_pixmap()) label2.move(600,600) label2.show() timer = QtCore.QTimer() timer.setSingleShot(True) timer.timeout.connect(show_second_plot) timer.start(1000) app.exec()
方案2:使用独立Figure并修复内存释放问题
你最初的多Figure方案内存释放问题,大概率是因为没有正确销毁对象引用。可以通过以下方式解决:
- 为每个窗口创建独立的
Figure和Canvas - 窗口关闭时手动清理引用,触发垃圾回收
- 避免全局引用持有这些对象
示例代码:
import sys import random import matplotlib matplotlib.use('Qt5Agg') from PyQt6 import QtCore, QtWidgets from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas from matplotlib.figure import Figure class PlotWindow(QtWidgets.QMainWindow): def __init__(self): super().__init__() # 每个窗口独立创建Figure和Canvas self.fig = Figure(figsize=(5, 4), dpi=100) self.ax = self.fig.add_subplot(111) self.canvas = FigureCanvas(self.fig) self.setCentralWidget(self.canvas) self.draw_plot() def draw_plot(self): xdata = list(range(50)) ydata = [random.randint(0, 10) for _ in range(50)] self.ax.plot(xdata, ydata) self.canvas.draw() def closeEvent(self, event): # 窗口关闭时清理引用,帮助内存释放 self.ax.clear() self.canvas.close() self.fig.clear() del self.ax, self.canvas, self.fig super().closeEvent(event) app = QtWidgets.QApplication(sys.argv) # 第一个窗口 w1 = PlotWindow() w1.move(100,100) w1.show() def open_second_window(): # 第二个窗口 w2 = PlotWindow() w2.move(600,600) w2.show() timer = QtCore.QTimer() timer.setSingleShot(True) timer.timeout.connect(open_second_window) timer.start(1000) app.exec()
内容的提问来源于stack exchange,提问作者Bearify
相关产品推荐
相关产品推荐

