多标签页下QPushButton无法传递QDoubleSpinBox值至类内函数
背景
我想要开发一个电源电压控制界面,用PyQt5设计了包含电压输入框、应用/检查电压按钮的多标签页界面。界面使用QDoubleSpinBox作为电压输入控件,QPushButton作为操作按钮,期望点击按钮时,把自旋框中的值传入实际执行电压控制的函数(实际依赖nidaqmx库,这里用print()代替)。程序里所有函数都放在类中,包含UI构建函数和应用、检查等功能函数。
问题
点击按钮时,无法将自旋框的值传递到对应的电压应用/检查函数。参考过Qt的按钮点击示例和标签对话框示例,但问题依然存在。
示例程序
以下是最小可复现代码,包含自旋框、两个标签页及CHECK按钮:
import sys from PyQt5.QtCore import * from PyQt5.QtGui import * from PyQt5 import QtWidgets # 各标签页通用功能类 class Commands: def __init__(self): self.AuxVolt = [0]*16 self.BufferVolt = [0]*16 def check(self): print(f"DC01 = {self.DC01.value():.3f} V") def apply(self): print(f"Applying voltages") # 实际nidaqmx仪器控制逻辑写在这里 # 主窗口类 class Ui_MainWindow(QtWidgets.QWidget,Commands): def __init__(self, parent: QtWidgets.QWidget = None): super().__init__(parent) self.setFixedSize(500, 400) tabs = QtWidgets.QTabWidget() tabs.addTab(tab1UI(),"Tab 1") tabs.addTab(tab2UI(),"Tab 2") RESET = QtWidgets.QPushButton('RESET') RESET.setObjectName("RESET") RESET.clicked.connect(self.reset) main_layout = QtWidgets.QVBoxLayout() main_layout.addWidget(tabs) main_layout.addWidget(RESET) self.setLayout(main_layout) self.setWindowTitle("Voltage control") # 标签页1 class tab1UI(QtWidgets.QWidget,Commands): def __init__(self, parent: QtWidgets.QWidget): super().__init__(parent) main_layout = QtWidgets.QGridLayout() DC01 = QtWidgets.QDoubleSpinBox() DC01.setObjectName("DC01") DC02 = QtWidgets.QDoubleSpinBox() DC02.setObjectName("DC02") main_layout.addWidget(DC01,0,1) main_layout.addWidget(DC02,1,1) CHECK = QtWidgets.QPushButton('CHECK') CHECK.setObjectName("CHECK") main_layout.addWidget(CHECK,10,0) CHECK.clicked.connect(self.check) self.setLayout(main_layout) # 标签页2 class tab2UI(QtWidgets.QWidget,Commands): def __init__(self, parent: QtWidgets.QWidget): super().__init__(parent) main_layout = QtWidgets.QGridLayout() DC01 = QtWidgets.QDoubleSpinBox() DC01.setObjectName("DC01") DC02 = QtWidgets.QDoubleSpinBox() DC02.setObjectName("DC02") main_layout.addWidget(DC01,0,1) main_layout.addWidget(DC02,1,1) CHECK = QtWidgets.QPushButton('CHECK') CHECK.setObjectName("CHECK") main_layout.addWidget(CHECK,10,0) CHECK.clicked.connect(self.check) self.setLayout(main_layout) # 运行程序 def main(): app = QtWidgets.QApplication(sys.argv) ex = Ui_MainWindow() ex.show() sys.exit(app.exec_()) if __name__ == '__main__': main()
报错信息
点击CHECK按钮时出现如下错误:
print(f"DC01 = {self.DC01.value():.3f} V") ^^^^^^^^^^^^^^^ AttributeError: 'NoneType' object has no attribute 'value'
点击任意按钮都会出现类似错误,仿佛QDoubleSpinBox的值未正确传递至函数,即使已使用self。
解决方案
问题根源
- 控件未绑定为实例属性:在
tab1UI和tab2UI中,创建的DC01、DC02是局部变量,没有通过self.xxx绑定为类实例的属性,导致self.DC01不存在,调用时返回None。 - 继承逻辑冗余:主窗口和标签页都继承了
Commands类,造成功能逻辑分散,且主窗口类并不需要这些功能。
修改步骤
- 将控件设为实例属性:创建自旋框时用
self.DC01代替局部变量DC01,确保实例能访问到控件。 - 复用标签页代码:创建基类统一实现标签页的UI和功能绑定,避免重复编写相同逻辑。
- 调整继承关系:主窗口不再继承
Commands,仅标签页继承该功能类。
修改后的代码
import sys from PyQt5.QtCore import * from PyQt5.QtGui import * from PyQt5 import QtWidgets # 通用功能类 class Commands: def __init__(self): self.AuxVolt = [0]*16 self.BufferVolt = [0]*16 def check(self): print(f"DC01 = {self.DC01.value():.3f} V") print(f"DC02 = {self.DC02.value():.3f} V") def apply(self): print(f"Applying voltages") # 实际nidaqmx仪器控制逻辑写在这里 # 标签页基类,统一实现UI和功能绑定 class BaseTab(QtWidgets.QWidget, Commands): def __init__(self, parent: QtWidgets.QWidget = None): super().__init__(parent) main_layout = QtWidgets.QGridLayout() # 将控件绑定为实例属性 self.DC01 = QtWidgets.QDoubleSpinBox() self.DC01.setObjectName("DC01") self.DC02 = QtWidgets.QDoubleSpinBox() self.DC02.setObjectName("DC02") main_layout.addWidget(self.DC01, 0, 1) main_layout.addWidget(self.DC02, 1, 1) CHECK = QtWidgets.QPushButton('CHECK') CHECK.setObjectName("CHECK") main_layout.addWidget(CHECK, 10, 0) CHECK.clicked.connect(self.check) # 添加APPLY按钮 APPLY = QtWidgets.QPushButton('APPLY') APPLY.setObjectName("APPLY") main_layout.addWidget(APPLY, 10, 1) APPLY.clicked.connect(self.apply) self.setLayout(main_layout) # 标签页1,直接继承基类 class tab1UI(BaseTab): def __init__(self, parent: QtWidgets.QWidget = None): super().__init__(parent) # 标签页2,直接继承基类 class tab2UI(BaseTab): def __init__(self, parent: QtWidgets.QWidget = None): super().__init__(parent) # 主窗口类 class Ui_MainWindow(QtWidgets.QWidget): def __init__(self, parent: QtWidgets.QWidget = None): super().__init__(parent) self.setFixedSize(500, 400) tabs = QtWidgets.QTabWidget() tabs.addTab(tab1UI(),"Tab 1") tabs.addTab(tab2UI(),"Tab 2") RESET = QtWidgets.QPushButton('RESET') RESET.setObjectName("RESET") # 可补充reset功能逻辑 # RESET.clicked.connect(self.reset) main_layout = QtWidgets.QVBoxLayout() main_layout.addWidget(tabs) main_layout.addWidget(RESET) self.setLayout(main_layout) self.setWindowTitle("Voltage control") # 运行程序 def main(): app = QtWidgets.QApplication(sys.argv) ex = Ui_MainWindow() ex.show() sys.exit(app.exec_()) if __name__ == '__main__': main()
说明
- 修改后,每个标签页的自旋框都绑定为实例属性,
Commands类的check、apply方法可以正常获取控件值。 - 通过
BaseTab基类复用了标签页的UI代码,减少冗余,后续新增标签页只需继承基类即可。 - 主窗口类专注于整体布局,功能逻辑由标签页类承担,职责更清晰。
内容的提问来源于stack exchange,提问作者Aldehyde
相关产品推荐
相关产品推荐

