Python 更新:如何减少PyQt代码中的QWidget嵌套? 更新问题

Python 更新:如何减少PyQt代码中的QWidget嵌套? 更新问题,python,pyqt5,Python,Pyqt5,我认为我最初的困惑可能是我的PyQt应用程序的结构造成的。我创建GUI的方法是将较大的小部件分成较小的部分,每个部分都有自己的类,直到部分足够简单为止。正因为如此,我最终得到了大量的嵌套,因为一个大的小部件包含了一些小部件的实例,而那些小部件则包含了它们自己的小部件。这使得在应用程序中导航数据变得困难 PyQt应用程序应该如何构造,使其在代码中易于理解,但其结构包含很少的嵌套?我还没有找到很多这样的例子,所以我有点被卡住了。我原始问题中的代码示例展示了我当前使用的结构的一个非常好的示例,它有大量

我认为我最初的困惑可能是我的PyQt应用程序的结构造成的。我创建GUI的方法是将较大的小部件分成较小的部分,每个部分都有自己的类,直到部分足够简单为止。正因为如此,我最终得到了大量的嵌套,因为一个大的小部件包含了一些小部件的实例,而那些小部件则包含了它们自己的小部件。这使得在应用程序中导航数据变得困难

PyQt应用程序应该如何构造,使其在代码中易于理解,但其结构包含很少的嵌套?我还没有找到很多这样的例子,所以我有点被卡住了。我原始问题中的代码示例展示了我当前使用的结构的一个非常好的示例,它有大量嵌套

节目信息 GUI用于创建一组用于运行测试的参数。每个设置中的选项都应该对应一个二进制数,并且每个选项集所指示的所有二进制数都将被收集,形成一个二进制数序列,并传递下去。对设置的更改不必在会话之间进行,因为每个新会话很可能对应一个新的测试(以及一组新的设置选项)

应用程序的基本流程应该是,打开应用程序后,所有可用设置(总共约20个)都设置为默认值。用户可以查看并更改他们想要的任何设置,完成后,他们可以按“生成”按钮收集与设置对应的所有二进制数并创建命令。这将是非常有帮助的,有一个实时预览个别位的更新设置被改变,这就是为什么更新必须立即

某些设置依赖于其他设置;例如,设置A有4个选项,如果选择了选项3,则应使设置B可见,否则不可见

原始问题 我绝对是PyQt的初学者,所以我不知道我的问题是否用词正确,但现在开始。我有一个GUI,在其中我尝试进行一系列不同的设置,跟踪从每个设置中选择的数字,然后将数字传递给一个对象,该对象跟踪所有设置中的所有数字。问题是,可以说,我不知道将所有单个设置值放到我的类树中的最佳方法。以下是迄今为止我的GUI的结构:

  • 底部:单个定制QWidget,每个都负责一个设置。每一个都有一个信号,只要返回的值发生变化,就会触发该信号

  • 中间:一个QWidget,每个QWidget包含约7-10个单独的设置。这些设置将设置收集到相关组中

  • Top:将设置组的每个实例放置到单个选项卡中的QTabWidget。此小部件还包含一个对象,理想情况下,该对象应将各个组中的所有设置收集到其中

我的问题是如何从底层信号到顶层小部件获取值?我唯一的想法是把所有的信号从这些小设置部件连接到中间层的信号,并将中间层信号连接到顶层中的某个东西。不过,这种链锁似乎很疯狂

我正在运行PyQt5和python3.7

这里有一些精简的代码,希望能显示我想做什么

类选项卡窗口(QTabWidget):
定义初始化(自):
super()。\uuuu init\uuuuu()
self.tabs=[设置组1、设置组2、设置组3]
self.setting_storage={#dictionary是我想存储所有设置值的地方
#“设置名称”:设置值
}
对于self.tabs中的选项卡:
self.addTab(选项卡“示例”)
类设置组(QWidget):
定义初始化(自):
super()。\uuuu init\uuuuu()
#未显示:为小部件创建的布局
self.settings=[]
def将_添加到_组(自我、新_设置):
self.settings.append(新设置)
#未显示:将设置添加到布局
类别设置组1(设置组):
定义初始化(自):
super()。\uuuu init\uuuuu()
self.add_到_组([设置1,设置2,设置3])
类设置组2(设置组):。。。
类设置组3(设置组):。。。
类设置(QWidget):
val_signal=pyqtSignal([int],name='valChanged')
定义初始化(self,name):
self.val=无
self.name=名称
def设置值(自身、新值):
self.val=新值
self.val_signal.emit(self.val)#setingsgroup1、2、3),因为每个子类都有自己独特的函数和内部依赖项。例如,对于每个设置子类,都有不同的用户界面


谢谢你的帮助

编辑:与此同时,问题已经更新,我在答案底部添加了一个更具体的解决方案

我觉得这个问题有点“基于意见”,但既然我也遇到过类似的情况,我想提出我的建议。在这种情况下,重要的是要明白,做事情没有一个好方法,但有很多方法是错误的

原始答案 一个想法是为每个“级别”创建一个通用的信号接口,该接口将获取该信号,并通过添加自己的名称来跟踪设置“路径”,将其发送回其父级;最顶端的小部件将相应地评估更改

在本例中,每个选项卡“组”都有自己的
值更改
信号,其中包括组名称、设置名称和值;源信号从“源”(本例中为自旋框)触发,然后跟随其父信号,父信号依次“添加”其名称。
请记住,您也可以对e使用广义的
pyqtSignal(object)
import sys
from PyQt5 import QtCore, QtWidgets

defaultValues = '0010101', '1001010', '000111'
# set bit lengths for each setting; be careful in ensuring that each
# setting group has the full default value bit length!
groups = [
    ['Group 1', [1, 3, 2, 1]], 
    ['Group 2', [1, 2, 2, 1, 1]], 
    ['Group 1', [2, 1, 2, 1]], 
]

class BinaryWidget(QtWidgets.QFrame):
    changed = QtCore.pyqtSignal()
    def __init__(self, name, index, defaults='0'):
        QtWidgets.QFrame.__init__(self)
        self.setFrameShape(self.StyledPanel|self.Sunken)
        layout = QtWidgets.QGridLayout()
        self.setLayout(layout)
        self.index = index
        self.defaults = defaults
        self.buttons = []
        # use the "defaults" length to create buttons
        for i in range(len(defaults)):
            value = int(defaults[i], 2) & 1
            # I used QToolButtons as they're usually smaller than QPushButtons
            btn = QtWidgets.QToolButton()
            btn.setText(str(value))
            layout.addWidget(btn, 1, i)
            btn.setCheckable(True)
            btn.setChecked(value)
            btn.toggled.connect(self.changed)
            # show the binary value on change, just for conveniency
            btn.toggled.connect(lambda v, btn=btn: btn.setText(str(int(v))))
            self.buttons.append(btn)
        layout.addWidget(QtWidgets.QLabel(name), 0, 0, 1, layout.columnCount())

    def value(self):
        # return the correct value of all widget's buttons; they're reversed
        # because of how bit shifting works
        v = 0
        for i, btn in enumerate(reversed(self.buttons)):
            v += btn.isChecked() << i
        # bit shift again, according to the actual "setting" bit index
        return v << self.index

    def resetValues(self):
        oldValue = self.value()
        self.blockSignals(True)
        for i, value in enumerate(self.defaults):
            self.buttons[i].setChecked(int(self.defaults[i], 2) & 1)
        self.blockSignals(False)
        newValue = self.value()
        # emit the changed signal only once, and only if values actually changed
        if oldValue != newValue:
            self.changed.emit()

class Group(QtWidgets.QWidget):
    changed = QtCore.pyqtSignal()
    def __init__(self, name, defaults=None, lenghts=None):
        QtWidgets.QWidget.__init__(self)
        layout = QtWidgets.QHBoxLayout()
        self.setLayout(layout)
        self.name = name
        self.bitLength = 0
        self.widgets = []
        if defaults is not None:
            self.addOptions(defaults, lenghts)

    def value(self):
        v = 0
        for widget in self.widgets:
            v += widget.value()
        return v

    def addOption(self, name, index, default='0'):
        widget = BinaryWidget(name, index, default)
        self.layout().addWidget(widget)
        self.widgets.append(widget)
        widget.changed.connect(self.changed)
        self.bitLength += len(default)

    def addOptions(self, defaults, lenghts = None):
        if lenghts is None:
            lenghts = [1] * len(defaults)
        # reverse bit order for per-setting indexing
        defaultsIndex = 0
        bitIndex = len(defaults)
        for i, l in enumerate(lenghts):
            self.addOption(
                'Setting {}'.format(i + 1), 
                bitIndex - l, 
                defaults[defaultsIndex:defaultsIndex + l])
            bitIndex -= l
            defaultsIndex += l

    def resetValues(self):
        for widget in self.widgets:
            widget.resetValues()

class Tester(QtWidgets.QWidget):
    def __init__(self):
        QtWidgets.QWidget.__init__(self)
        layout = QtWidgets.QGridLayout()
        self.setLayout(layout)

        self.tabWidget = QtWidgets.QTabWidget()
        layout.addWidget(self.tabWidget)

        resultLayout = QtWidgets.QHBoxLayout()
        layout.addLayout(resultLayout, layout.rowCount(), 0, 1, layout.columnCount())

        self.tabs = []
        self.labels = []

        for (group, lenghts), defaults in zip(groups, defaultValues):
            tab = Group(group, defaults, lenghts)
            self.tabWidget.addTab(tab, group)
            tab.changed.connect(self.updateResults)
            self.tabs.append(tab)
            tabLabel = QtWidgets.QLabel()
            self.labels.append(tabLabel)
            resultLayout.addWidget(tabLabel)

        self.resetButton = QtWidgets.QPushButton('Reset values')
        layout.addWidget(self.resetButton)
        self.resetButton.clicked.connect(lambda: [tab.resetValues() for tab in self.tabs])

        self.updateResults()

    def values(self):
        return [tab.value() for tab in self.tabs]

    def updateResults(self):
        for value, tab, label in zip(self.values(), self.tabs, self.labels):
            label.setText('''
                {0}: <span style="font-family:monospace;">{1} <b>{1:0{2}b}</b></span>
                '''.format(tab.name, value, tab.bitLength))

if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    w = Tester()
    w.show()
    sys.exit(app.exec_())