根据多个 QComboBox 的文本启用或禁用 QDialogBu​​ttonBox

Posted

技术标签:

【中文标题】根据多个 QComboBox 的文本启用或禁用 QDialogBu​​ttonBox【英文标题】:enable or disable QDialogButtonBox based on the text of multiple QComboBox 【发布时间】:2019-12-04 17:44:56 【问题描述】:

我想根据两个 QComboBox(es) 的选定文本启用或禁用 QDialogBu​​ttonBox,或者最好只启用 QDialog 中的 OK 按钮。

我的例子如下。它目前不工作,并且在启用或禁用 QDialogBu​​ttonBox 时,两个 ComboBox 相互独立工作。

import sys

from PyQt5.QtCore import QSignalMapper, pyqtSlot
from PyQt5.QtWidgets import (QGroupBox, QFormLayout, QLabel, QComboBox,
                             QApplication, QDialog, QDialogButtonBox,
                             QVBoxLayout)


class SheetColumns(QDialog):
    def __init__(self, column_header):
        super().__init__()

        self.setMinimumWidth(300)
        self.setWindowTitle("Input Column Names")

        self.column_headers = column_header

        self.column_headers.insert(0, ' ')
        self.setWhatsThis('Please match columns in your data sheet names'
                          ' with the right side labels')

        col_names = ["Student Name:", "Student ID:", "Cohort:", "Gender:",
                     "College:", "Department:", "Major:", "Minor", "Email:",
                     "Adviser", "Adviser Email"]
        self.form_group_box = QGroupBox("Specify Column Names")
        self.layout = QFormLayout()
        for col_name in col_names:
            combo = QComboBox()
            combo.addItems(self.column_headers)
            self.layout.addRow(QLabel(col_name), combo)
        self.form_group_box.setLayout(self.layout)

        self.button_box = QDialogButtonBox(
            QDialogButtonBox.Ok | QDialogButtonBox.Cancel)

        self.button_box.setEnabled(False)

        self.layout.itemAt(0, 1).widget().currentTextChanged.connect(
            self.check_validity)
        self.layout.itemAt(1, 1).widget().currentTextChanged.connect(
            self.check_validity)

        self.button_box.accepted.connect(self.accept)
        self.button_box.rejected.connect(self.reject)

        main_layout = QVBoxLayout()
        main_layout.addWidget(self.form_group_box)
        main_layout.addWidget(self.button_box)
        self.setLayout(main_layout)

    def check_validity(self, text):
        print(text)
        if text == ' ':
            self.button_box.setEnabled(False)
        else:
            self.button_box.setEnabled(True)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    dialog = SheetColumns(['name student', 'id', 'cohort', 'test 1'])
    sys.exit(dialog.exec_())

我希望当两个 ComboBox 中的 currentText(s) 不是 ' ' 时启用 QDialogBu​​ttonBox,而当它们都是 ' ' 时它被禁用。

我尝试使用QSignalMapper

但是,我无法让它工作。

class SheetColumns(QDialog):
    def __init__(self, column_header):
        super().__init__()

        self.setMinimumWidth(300)
        self.setWindowTitle("Input Column Names")

        self.column_headers = column_header

        self.column_headers.insert(0, ' ')
        self.setWhatsThis('Please match columns in your data sheet names'
                          ' with the right side labels')

        col_names = ["Student Name:", "Student ID:", "Cohort:", "Gender:",
                     "College:", "Department:", "Major:", "Minor", "Email:",
                     "Adviser", "Adviser Email"]
        self.form_group_box = QGroupBox("Specify Column Names")
        self.layout = QFormLayout()
        for col_name in col_names:
            combo = QComboBox()
            combo.addItems(self.column_headers)
            self.layout.addRow(QLabel(col_name), combo)
        self.form_group_box.setLayout(self.layout)

        self.button_box = QDialogButtonBox(
            QDialogButtonBox.Ok | QDialogButtonBox.Cancel)

        self.button_box.setEnabled(False)

        self.mapper = QSignalMapper(self)

        comb_bx1 = self.layout.itemAt(0, 1).widget()
        comb_bx2 = self.layout.itemAt(1, 1).widget()

        comb_bx1.currentTextChanged.connect(self.mapper.map)
        comb_bx2.currentTextChanged.connect(self.mapper.map)

        self.mapper.setMapping(comb_bx1, comb_bx1.currentText())
        self.mapper.setMapping(comb_bx2, comb_bx2.currentText())

        self.mapper.mapped.connect(self.check_validity)

        self.button_box.accepted.connect(self.accept)
        self.button_box.rejected.connect(self.reject)

        main_layout = QVBoxLayout()
        main_layout.addWidget(self.form_group_box)
        main_layout.addWidget(self.button_box)
        self.setLayout(main_layout)

    def check_validity(self, text):
        print(text)
        if text == ' ':
            self.button_box.setEnabled(False)
        else:
            self.button_box.setEnabled(True)

谁能告诉我我做错了什么,或者有更好的方法吗?

提前致谢

【问题讨论】:

【参考方案1】:

使用 QSignalMapper 超出了您的要求,在您的情况下,您只需遍历 QComboBox 并验证它们没有适当的选项,并根据该选项启用按钮,如下所示:

class SheetColumns(QDialog):
    def __init__(self, column_header, parent=None):
        super().__init__(parent)

        self.setMinimumWidth(300)
        self.setWindowTitle("Input Column Names")

        self.column_headers = column_header

        self.setWhatsThis(
            "Please match columns in your data sheet names"
            " with the right side labels"
        )

        col_names = [
            "Student Name:",
            "Student ID:",
            "Cohort:",
            "Gender:",
            "College:",
            "Department:",
            "Major:",
            "Minor",
            "Email:",
            "Adviser",
            "Adviser Email",
        ]

        self.combos = []

        flay = QFormLayout()
        for i, col_name in enumerate(col_names):
            combo = QComboBox()
            combo.addItems([""] + self.column_headers)
            flay.addRow(col_name, combo)
            if i in (0, 1):
                combo.currentIndexChanged.connect(self.check_validity)
                self.combos.append(combo)

        self.form_group_box = QGroupBox("Specify Column Names")
        self.form_group_box.setLayout(flay)

        self.button_box = QDialogButtonBox(
            QDialogButtonBox.Ok | QDialogButtonBox.Cancel
        )
        self.button_box.accepted.connect(self.accept)
        self.button_box.rejected.connect(self.reject)

        main_layout = QVBoxLayout(self)
        main_layout.addWidget(self.form_group_box)
        main_layout.addWidget(self.button_box)

    @pyqtSlot()
    def check_validity(self):
        is_enabled = True
        for combo in self.combos:
            if not combo.currentText():
                is_enabled = False
                break
        button = self.button_box.button(QDialogButtonBox.Ok)
        button.setEnabled(is_enabled)

@TheKewlStore 在他的回答中指出了一些不正确的地方:QObjects 的所有权不是由 Python 而是由 C++ 部分处理的。 python中QObject的所有权具有创建它的类,如果它是一个属性,在OP的示例中不满足,或者具有另一个QObject,它是在创建它或使用setParent时设置的父对象() 方法。对于 QWidget,当您将其添加到布局时,它会将其管理的小部件设置为父级。在您的情况下,QComboBoxes 的所有权是 QGroupBox,因此 GC 没有问题。

【讨论】:

【参考方案2】:

就个人而言,我发现信号映射器非常令人困惑,并像避免瘟疫一样避免它。这是我将如何做到这一点(将使用一个虚拟类让生活更轻松):

class DummyDialog(QDialog):
    def __init__(self):
        self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
        self.combo_box_1 = QComboBox()
        self.combo_box_2 = QComboBox()
        self.combo_1_text = ""  # This could also be self.combo_box_1.text()
        self.combo_2_text = ""
        self.combo_box_1.currentTextChanged.connect(self.combo_one_changed)
        self.combo_box_2.currentTextChanged.connect(self.combo_two_changed)

    def combo_one_changed(self, text):
        self.combo_1_text = text

    def combo_two_changed(self, text):
        self.combo_2_text = text

    def check_validity(self):
        if self.combo_1_text and self.combo_2_text:
            self.button_box.setEnabled(True)
        else:
            self.button_box.setEnabled(False)

如果您对此有任何问题,请告诉我,我只是很快想到了这个。

编辑:我还注意到您在 init 方法中将组合框定义为本地人,由于 pyqt 的所有权,这也可能是一个问题。如果您不存储对对象的引用,则不能保证它会继续存在,并且 python 可能会垃圾收集它,这意味着您可能会完全失去信号连接。不能说这是正在发生的事情,但一般来说,通过将它们设置为实例变量,在 init 方法中为它们保留一个实例会更安全。

【讨论】:

您的“编辑”不正确,因为当您将小部件添加到布局时,小部件的所有权将由设置小部件的小部件取得,在您的情况下,QComboBox 的所有权将是由 QGroupBox 采取,因此 GC 没有问题。 啊,没错,我没有注意到它们是布局的一部分。【参考方案3】:

感谢@eyllanesc 他的回答,效果很好。

下面的代码更适合我的用例,和@eyllanesc按照他的概念给出的答案非常相似。

class SheetColumns(QDialog):
    def __init__(self, column_header, parent=None):
        super().__init__(parent)

        self.setMinimumWidth(300)
        self.setWindowTitle("Input Column Names")

        self.column_headers = column_header

        self.setWhatsThis(
            "Please match columns in your datasheet names"
            " with the right side labels"
        )

        col_names = [
            "Student Name:",
            "Student ID:",
            "Cohort:",
            "Gender:",
            "College:",
            "Department:",
            "Major:",
            "Minor",
            "Email:",
            "Adviser",
            "Adviser Email",
        ]

        self.combos = []

        flay = QFormLayout()
        for i, col_name in enumerate(col_names):
            combo = QComboBox()
            combo.addItems([""] + self.column_headers)
            flay.addRow(col_name, combo)
            if i in (0, 1):
                combo.currentIndexChanged.connect(self.check_validity)
                self.combos.append(combo)

        self.form_group_box = QGroupBox("Specify Column Names")
        self.form_group_box.setLayout(flay)

        self.button_box = QDialogButtonBox(
            QDialogButtonBox.Ok | QDialogButtonBox.Cancel
        )
        self.button = self.button_box.button(QDialogButtonBox.Ok)
        self.button.setEnabled(False)
        self.button_box.accepted.connect(self.accept)
        self.button_box.rejected.connect(self.reject)

        self.label = QLabel()
        self.label.setText('Student Name and ID cannot be empty')

        main_layout = QVBoxLayout(self)
        main_layout.addWidget(self.form_group_box)
        main_layout.addWidget(self.label)
        main_layout.addWidget(self.button_box)

    @pyqtSlot()
    def check_validity(self):
        if all([combo.currentText() for combo in self.combos]):
            self.button.setEnabled(True)
            self.label.setText('')
        else:
            self.button.setEnabled(False)
            self.label.setText('Student Name and ID cannot be empty')

【讨论】:

self.label.setText('')改成self.label.clear(),可读性更强。

以上是关于根据多个 QComboBox 的文本启用或禁用 QDialogBu​​ttonBox的主要内容,如果未能解决你的问题,请参考以下文章

根据禁用/启用状态替换按钮文本

根据文本控件值标记和启用/禁用连续表单上的按钮

Oracle Apex 根据 LOV 选择的值动态启用/禁用文本字段

根据WPF中的TextBox Text属性启用/禁用按钮?

如何根据 b-form-select 中的选定值禁用/启用文本字段?

如何设置 QComboBox 按钮的背景颜色?