如何在QtDesigner+PySide6的自定义QtWidget中实现Matplotlib图动态缩放
问题描述
项目结构如下:
¬project ¬ uis (包含所有GUI和控件) ¬ python脚本
使用QtDesigner设计GUI并通过QWidget嵌入Matplotlib绘图时,遇到两个问题:
- 提升自定义
MplWidget(位于uis/mplwidget.py)时,UI转Python代码生成的导入语句为from mplwidget import MplWidget,但正确路径应为from project.uis.mplwidget import MplWidget,开发期间无法手动修改自动生成的UI文件。 - 在主脚本中将GUI中的QWidget替换为
MplWidget后,绘图控件尺寸固定,无法随父控件动态调整大小。
主脚本代码
import sys import numpy as np from project.uis.ui_QTplotStrokeProfileLayout import Ui_StrokeProfilePlot from project.uis.mplwidget import MplWidget,MplCanvas from PySide6.QtWidgets import (QApplication,QMainWindow,QWidget) class QTplotStrokeProfileLayout(QMainWindow): def __init__(self): super(QTplotStrokeProfileLayout, self).__init__() self.ui = Ui_StrokeProfilePlot() self.ui.setupUi(self) # replace QWidet to MplWidget self.ui.plotStrokeProfileWidget = MplWidget(self.ui.frame) # plot some data x=range(0, 10) y=range(0, 20, 2) self.ui.plotStrokeProfileWidget.canvas.ax.plot(x,y) self.ui.plotStrokeProfileWidget.canvas.draw() # bind push button self.ui.pushButton.clicked.connect(self.plot_data) def plot_data(self): x=range(0, 10) y=range(0, 20, 2) y = np.random.randint(10, size=10) self.ui.plotStrokeProfileWidget.canvas.ax.clear() self._line = self.ui.plotStrokeProfileWidget.canvas.ax.plot(x,y) # setup title and axis labels self.ui.plotStrokeProfileWidget.canvas.ax.set_title("title") self.ui.plotStrokeProfileWidget.canvas.ax.set_xlabel("x label") self.ui.plotStrokeProfileWidget.canvas.ax.set_ylabel("y label") # display canvas self.ui.plotStrokeProfileWidget.canvas.draw() if __name__ == "__main__": app = QApplication(sys.argv) window = QTplotStrokeProfileLayout() window.show() sys.exit(app.exec())
MplWidget定义代码
from PySide6 import QtWidgets from PySide6.QtWidgets import QWidget from matplotlib.backends.backend_qtagg import ( FigureCanvas, NavigationToolbar2QT as NavigationToolbar,FigureCanvasQTAgg) from matplotlib.figure import Figure import matplotlib.pyplot as plt class MplCanvas(FigureCanvasQTAgg): def __init__(self, parent=None, width=5, height=4, dpi=100): self.fig, self.ax = plt.subplots(1,1,figsize=(width, height),dpi=dpi) super(MplCanvas, self).__init__(self.fig) self.setParent(parent) self.ax.set_title("title") self.ax.set_xlabel("xlabel") self.ax.set_ylabel("ylabel") # Matplotlib widget class MplWidget(QWidget): def __init__(self, parent=None): QWidget.__init__(self, parent) # Inherit from QWidget self.canvas = MplCanvas() # Create canvas object self.vbl = QtWidgets.QVBoxLayout() # Set box for plotting self.vbl.addWidget(self.canvas) self.vbl.addWidget(NavigationToolbar(self.canvas, self)) # add a toolbar self.setLayout(self.vbl) self.resize(1000,300) # resize needed to have a visible plot self.vbl.setSizeConstraint
编译后GUI代码
# -*- coding: utf-8 -*- ################################################################################ ## Form generated from reading UI file 'QTplotStrokeProfileLayout.ui' ## ## Created by: Qt User Interface Compiler version 6.3.2 ## ## WARNING! All changes made in this file will be lost when recompiling UI file! ################################################################################ from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale, QMetaObject, QObject, QPoint, QRect, QSize, QTime, QUrl, Qt) from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor, QFont, QFontDatabase, QGradient, QIcon, QImage, QKeySequence, QLinearGradient, QPainter, QPalette, QPixmap, QRadialGradient, QTransform) from PySide6.QtWidgets import (QApplication, QFrame, QHBoxLayout, QHeaderView, QMainWindow, QMenuBar, QPushButton, QSizePolicy, QStatusBar, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget) class Ui_StrokeProfilePlot(object): def setupUi(self, StrokeProfilePlot): if not StrokeProfilePlot.objectName(): StrokeProfilePlot.setObjectName(u"StrokeProfilePlot") StrokeProfilePlot.resize(1235, 912) self.centralwidget = QWidget(StrokeProfilePlot) self.centralwidget.setObjectName(u"centralwidget") self.horizontalLayout = QHBoxLayout(self.centralwidget) self.horizontalLayout.setObjectName(u"horizontalLayout") self.treeWidget = QTreeWidget(self.centralwidget) self.treeWidget.setObjectName(u"treeWidget") self.treeWidget.setMaximumSize(QSize(300, 16777215)) self.horizontalLayout.addWidget(self.treeWidget) self.verticalLayout = QVBoxLayout() self.verticalLayout.setObjectName(u"verticalLayout") self.frame = QFrame(self.centralwidget) self.frame.setObjectName(u"frame") self.frame.setFrameShape(QFrame.StyledPanel) self.frame.setFrameShadow(QFrame.Raised) self.verticalLayout_4 = QVBoxLayout(self.frame) self.verticalLayout_4.setObjectName(u"verticalLayout_4") self.verticalLayout_3 = QVBoxLayout() self.verticalLayout_3.setObjectName(u"verticalLayout_3") self.plotStrokeProfileWidget = QWidget(self.frame) self.plotStrokeProfileWidget.setObjectName(u"plotStrokeProfileWidget") self.plotStrokeProfileWidget.setMinimumSize(QSize(30, 30)) self.plotStrokeProfileWidget.setStyleSheet(u"background-color: rgb(55, 255, 128);") self.verticalLayout_3.addWidget(self.plotStrokeProfileWidget) self.widget = QWidget(self.frame) self.widget.setObjectName(u"widget") self.widget.setStyleSheet(u"background-color: rgb(140, 140, 255);") self.verticalLayout_3.addWidget(self.widget) self.verticalLayout_4.addLayout(self.verticalLayout_3) self.verticalLayout.addWidget(self.frame) self.pushButton = QPushButton(self.centralwidget) self.pushButton.setObjectName(u"pushButton") self.verticalLayout.addWidget(self.pushButton) self.horizontalLayout.addLayout(self.verticalLayout) StrokeProfilePlot.setCentralWidget(self.centralwidget) self.menubar = QMenuBar(StrokeProfilePlot) self.menubar.setObjectName(u"menubar") self.menubar.setGeometry(QRect(0, 0, 1235, 22)) StrokeProfilePlot.setMenuBar(self.menubar) self.statusbar = QStatusBar(StrokeProfilePlot) self.statusbar.setObjectName(u"statusbar") StrokeProfilePlot.setStatusBar(self.statusbar) self.retranslateUi(StrokeProfilePlot) QMetaObject.connectSlotsByName(StrokeProfilePlot) # setupUi def retranslateUi(self, StrokeProfilePlot): StrokeProfilePlot.setWindowTitle(QCoreApplication.translate("StrokeProfilePlot", u"MainWindow", None)) ___qtreewidgetitem = self.treeWidget.headerItem() ___qtreewidgetitem.setText(2, QCoreApplication.translate("StrokeProfilePlot", u"New Column", None)); ___qtreewidgetitem.setText(1, QCoreApplication.translate("StrokeProfilePlot", u"test", None)); ___qtreewidgetitem.setText(0, QCoreApplication.translate("StrokeProfilePlot", u"1", None)); self.pushButton.setText(QCoreApplication.translate("StrokeProfilePlot", u"PushButton", None)) # retranslateUi
解决方案
一、修正UI导入路径(优化提升控件流程)
在QtDesigner中提升控件时直接指定完整导入路径:
- 右键目标QWidget → 选择「提升为...」
- 提升类名称填写
MplWidget - 头文件填写
project.uis.mplwidget - 勾选「全局包含」,后续生成UI代码时会自动生成正确的导入语句,无需手动修改。
二、实现动态调整大小
当前替换控件仅重新赋值变量,未将新控件加入布局,同时MplWidget自身设置也需调整,具体修改如下:
1. 主脚本:修正控件替换逻辑
移除原QWidget并将新MplWidget加入布局,同时设置尺寸策略:
def __init__(self): super(QTplotStrokeProfileLayout, self).__init__() self.ui = Ui_StrokeProfilePlot() self.ui.setupUi(self) # 移除原QWidget控件 old_widget = self.ui.plotStrokeProfileWidget self.ui.verticalLayout_3.removeWidget(old_widget) old_widget.deleteLater() # 创建MplWidget并设置尺寸策略 self.ui.plotStrokeProfileWidget = MplWidget(self.ui.frame) self.ui.plotStrokeProfileWidget.setSizePolicy( QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding ) # 将新控件添加到原布局 self.ui.verticalLayout_3.addWidget(self.ui.plotStrokeProfileWidget) # 后续绘图代码不变 x=range(0, 10) y=range(0, 20, 2) self.ui.plotStrokeProfileWidget.canvas.ax.plot(x,y) self.ui.plotStrokeProfileWidget.canvas.draw() self.ui.pushButton.clicked.connect(self.plot_data)
2. 修改MplWidget实现
移除固定resize,优化布局设置:
class MplWidget(QWidget): def __init__(self, parent=None): QWidget.__init__(self, parent) # 将parent传入MplCanvas,确保画布跟随控件 self.canvas = MplCanvas(parent=self) self.vbl = QtWidgets.QVBoxLayout() self.vbl.addWidget(self.canvas) self.vbl.addWidget(NavigationToolbar(self.canvas, self)) # 移除布局边距避免空白 self.vbl.setContentsMargins(0,0,0,0) # 设置布局尺寸约束为默认(自动适配父控件) self.vbl.setSizeConstraint(QtWidgets.QLayout.SetDefaultConstraint) self.setLayout(self.vbl) # 删除固定resize语句 # self.resize(1000,300)
3. 修改MplCanvas实现,支持自适应
重写resizeEvent,让画布随控件大小调整时自动适配:
class MplCanvas(FigureCanvasQTAgg): def __init__(self, parent=None, width=5, height=4, dpi=100): # 直接创建Figure,避免使用plt.subplots产生全局对象干扰 self.fig = Figure(figsize=(width, height), dpi=dpi) self.ax = self.fig.add_subplot(111) super(MplCanvas, self).__init__(self.fig) self.setParent(parent) self.ax.set_title("title") self.ax.set_xlabel("xlabel") self.ax.set_ylabel("ylabel") # 设置画布尺寸策略为可拉伸 self.setSizePolicy( QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding ) self.updateGeometry() def resizeEvent(self, event): # 控件大小变化时自动调整子图布局 super().resizeEvent(event) self.fig.tight_layout()
完成以上修改后,MplWidget会跟随父布局动态调整大小,Matplotlib画布也会自适应控件尺寸。
内容的提问来源于stack exchange,提问作者sylvanoMTL
相关产品推荐
相关产品推荐

