Как проверить заполнение вкладок QTabWidget циклом без визуального переключения вкладок

Ситуация следующая: в виджете QTabWidget динамически создаются вкладки, содержащие несколько полей QLineEdit. Вкладок может быть n-ое количество. На каждой вкладке имеется одно поле QLineEdit обязательное для заполнения (self.tabWidget.currentWidget().lineEditType.text()), если это поле не заполнить, то кнопка self.buttonAdd будет недоступна для нажатия. Проверка заполнения полей во всех вкладках происходит циклом по индексу вкладок "на лету", т.е. при каждом изменении содержимого поля QLineEdit с применением метода QLineEdit.textChanged(). При этом после каждого изменения содержимого обязательного поля вызывается функция проверки заполнения вкладок def check_tabs(self), чтобы онлайн контролировать заполнение полей и активировать при надобности кнопку self.buttonAdd.

Если создать несколько вкладок (например 3) и удалить содержимое, например средней вкладки, то начав её заполнять, вызовется функция проверки и автоматически откроется последняя вкладка и так будет происходить постоянно при каждом вводе символа. Пользователь не сможет перезаполнить нужную ему вкладку без мучений.

Проблема заключается в том, что я никак не могу сделать так, чтобы либо вкладки не переключались, когда пользователь их редактирует, либо, чтобы цикл проверки запускался "под капотом" без видимого переключения проверяемых вкладок. И если какая-то из вкладок окажется не заполнена, то кнопка self.buttonAdd не активируется. Либо сделать так, чтобы после ввода первого символа далее проверка не запускалась. Метод QLineEdit.editingFinished() не подходит, т.к. он срабатывает после того как убран фокус с заполняемого поля.

Я пробовал уже делать с использованием словаря, куда записывал индексы вкладок и их текущее состояние (0 или 1), без использования цикла, но это приводило к тому, что каждая вкладка обрабатывалась независимо от остальных и, если я в одной вкладке очищу поле, кнопка станет неактивна, но зайдя в другую вкладку я изменю ее содержимое и кнопка снова станет активной, хотя другая вкладка так и останется пустой.

Может быть можно как-нибудь ходить по вкладкам (с помощью индексов например), чтобы GUI не видела этого (типа некоего бекэнда)?

Прошу помощи сообщества, спасибо.

Минимальный рабочий код:

import sys
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.Qt import *


class TabPage_SO(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)

        self.labelType = QLabel("№ типа", self)
        self.lineEditType = QLineEdit(self)
        self.lineEditType.setClearButtonEnabled(True)

        self.labelYearOfIssue = QLabel("Год выпуска *", self)
        self.spinBox = QSpinBox(self)
        self.spinBox.setCursor(QtGui.QCursor(QtCore.Qt.ArrowCursor))
        self.spinBox.setAlignment(QtCore.Qt.AlignCenter)
        self.spinBox.setMinimum(1917)
        self.spinBox.setMaximum(2060)
        self.spinBox.setProperty("value", 2020)

        self.labelSerialNumber = QLabel("Заводской №", self)
        self.lineEditSerialNumber = QLineEdit(self)
        self.lineEditSerialNumber.setClearButtonEnabled(True)

        self.labelSpecifications = QLabel("Характеристики", self)
        self.lineEditSpecifications = QLineEdit(self)
        self.lineEditSpecifications.setClearButtonEnabled(True)

        grid = QGridLayout(self)
        grid.addWidget(self.labelType, 0, 0)
        grid.addWidget(self.labelYearOfIssue, 0, 1)
        grid.addWidget(self.labelSerialNumber, 0, 2)
        grid.addWidget(self.lineEditType, 1, 0)
        grid.addWidget(self.spinBox, 1, 1)
        grid.addWidget(self.lineEditSerialNumber, 1, 2)
        grid.addWidget(self.labelSpecifications, 2, 0)
        grid.addWidget(self.lineEditSpecifications, 3, 0, 1, 3)
        grid.setRowStretch(4, 1)

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.centralWidget = QWidget()
        self.setCentralWidget(self.centralWidget)

        self.red_warning = "border-color: red; border-style: solid; border-width: 2px; font-weight: normal;"

        self.tableWidget = QTableWidget(0, 4)
        self.tableWidget.setHorizontalHeaderLabels(
            ["№ типа", "Год выпуска *", "Заводской №", "Характеристики"])
        self.tableWidget.horizontalHeader().setSectionResizeMode(3, QHeaderView.Stretch)
        self.tableWidget.setSortingEnabled(True)
        self.tableWidget.setAlternatingRowColors(True)

        self.buttonAdd = QPushButton('Добавить из всех вкладок в таблицу')
        self.buttonAdd.clicked.connect(self.addRowTable)
        self.buttonDel = QPushButton('Удалить выбранную строку в таблице')
        self.buttonDel.clicked.connect(self.delRowTable)

        self.tabWidget = QTabWidget()
        self.tabWidget.setTabsClosable(True)
        count = self.tabWidget.count()
        self.nb = QToolButton(text="Добавить", autoRaise=True)
        self.nb.clicked.connect(self.new_tab)
        self.tabWidget.insertTab(count, QWidget(), "")
        self.tabWidget.tabBar().setTabButton(count, QTabBar.RightSide, self.nb)
        self.new_tab()
        self.tabWidget.tabCloseRequested.connect(self.closeTab_SO)

        self.button_activate()

        vbox = QGridLayout(self.centralWidget)
        vbox.addWidget(self.tabWidget, 0, 0, 1, 2)
        vbox.addWidget(self.tableWidget, 1, 0, 1, 2)
        vbox.addWidget(self.buttonAdd, 2, 0)
        vbox.addWidget(self.buttonDel, 2, 1)

    def new_tab(self):
        '''
        Создание нового ТАБа
        '''
        index = self.tabWidget.count() - 1
        self.tabWidget.insertTab(index, TabPage_SO(self), "Tab %d" % index)
        self.tabWidget.setCurrentIndex(index)
        self.nb.setEnabled(False)
        self.count = 0
        self.button_activate()

        self.tabWidget.currentWidget().lineEditType.textChanged.connect(self.check_tabs)
        self.tabWidget.currentWidget().lineEditSerialNumber.textChanged.connect(self.check_tabs)
        self.tabWidget.currentWidget().lineEditSpecifications.textChanged.connect(self.check_tabs)

    def closeTab_SO(self, currentIndex):
        '''
        Удаление ТАБов
        '''
        self.tabWidget.removeTab(currentIndex)
        self.tabWidget.setCurrentIndex(self.tabWidget.count() - 2)
        self.check_tabs()

    def addRowTable(self):
        for i in range(self.tabWidget.count()-1):
            self.tabWidget.setCurrentIndex(i)
            editType = self.tabWidget.currentWidget().lineEditType.text()
            spinYearOfIssue = str(self.tabWidget.currentWidget().spinBox.value())
            editSerialNumber = self.tabWidget.currentWidget().lineEditSerialNumber.text()
            editSpecifications = self.tabWidget.currentWidget().lineEditSpecifications.text()

            if not editType:
                msg = QMessageBox.information(self, 'Внимание', 'Заполните поле!')
                return
            self.tableWidget.setSortingEnabled(False)
            rows = self.tableWidget.rowCount()
            self.tableWidget.insertRow(rows)
            self.tableWidget.setItem(rows, 0, QTableWidgetItem(editType))
            self.tableWidget.setItem(rows, 1, QTableWidgetItem(spinYearOfIssue))
            self.tableWidget.setItem(rows, 2, QTableWidgetItem(editSerialNumber))
            self.tableWidget.setItem(rows, 3, QTableWidgetItem(editSpecifications))
            self.tableWidget.setSortingEnabled(True)

    def delRowTable(self):
        row = self.tableWidget.currentRow()
        if row == -1:
            msg = QMessageBox.information(self, 'Внимание', 'Выберите строку для удаления')
            return
        self.tableWidget.removeRow(row)

    def check_tabs(self):
        '''
        Проверка заполнения поля lineEditType и проход по всем вкладкам для проверки заполнения
        '''
        for i in range(self.tabWidget.count()-1):
            self.tabWidget.setCurrentIndex(i)
            if self.tabWidget.currentWidget().lineEditType.text() == '' or self.tabWidget.currentWidget().lineEditType.text().isspace():
                self.tabWidget.currentWidget().lineEditType.setStyleSheet(self.red_warning)
                self.nb.setEnabled(False)
                self.count = 0
                self.button_activate()
                return
            else:
                self.tabWidget.currentWidget().lineEditType.setStyleSheet('')
                self.nb.setEnabled(True)
                self.count = 1

        self.button_activate()


    def button_activate(self):
        '''
        Активация кнопки self.buttonAdd
        '''
        if self.count == 1:
            self.buttonAdd.setEnabled(True)
        else:
            self.buttonAdd.setEnabled(False)


qss = """
QLabel {
    font: 8pt "MS Shell Dlg 2";
}
QLineEdit {
    font: 12pt "Calibri";
}
QSpinBox {
    font: 12pt "Calibri";
}
"""


if __name__ == "__main__":
    import sys
    app = QApplication(sys.argv)
    app.setStyleSheet(qss)
    window = MainWindow()
    window.show()
    sys.exit(app.exec_())

Ответы (1 шт):

Автор решения: S. Nick

Из выше сказанного я увидел вашу задачу так.

По сути я просто добавил таймер, который переодический проверет то, что вам надо. Интервал таймера (сейчас 1000 мс) установите по вашему разумению.

Я отметил для вас строки, в которые внес изменения.

import sys
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.Qt import *


class TabPage_SO(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)

        self.labelType = QLabel("№ типа", self)
        
        self.lineEditType = QLineEdit(self)
        self.lineEditType.setClearButtonEnabled(True)

        self.labelYearOfIssue = QLabel("Год выпуска *", self)
        self.spinBox = QSpinBox(self)
        self.spinBox.setCursor(QtGui.QCursor(QtCore.Qt.ArrowCursor))
        self.spinBox.setAlignment(QtCore.Qt.AlignCenter)
        self.spinBox.setMinimum(1917)
        self.spinBox.setMaximum(2060)
        self.spinBox.setProperty("value", 2020)

        self.labelSerialNumber = QLabel("Заводской №", self)
        self.lineEditSerialNumber = QLineEdit(self)
        self.lineEditSerialNumber.setClearButtonEnabled(True)

        self.labelSpecifications = QLabel("Характеристики", self)
        self.lineEditSpecifications = QLineEdit(self)
        self.lineEditSpecifications.setClearButtonEnabled(True)

        grid = QGridLayout(self)
        grid.addWidget(self.labelType, 0, 0)
        grid.addWidget(self.labelYearOfIssue, 0, 1)
        grid.addWidget(self.labelSerialNumber, 0, 2)
        grid.addWidget(self.lineEditType, 1, 0)
        grid.addWidget(self.spinBox, 1, 1)
        grid.addWidget(self.lineEditSerialNumber, 1, 2)
        grid.addWidget(self.labelSpecifications, 2, 0)
        grid.addWidget(self.lineEditSpecifications, 3, 0, 1, 3)
        grid.setRowStretch(4, 1)


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.centralWidget = QWidget()
        self.setCentralWidget(self.centralWidget)

        self.red_warning = "border-color: red; border-style: solid; border-width: 2px; font-weight: normal;"

        self.tableWidget = QTableWidget(0, 4)
        self.tableWidget.setHorizontalHeaderLabels(
            ["№ типа", "Год выпуска *", "Заводской №", "Характеристики"])
        self.tableWidget.horizontalHeader().setSectionResizeMode(3, QHeaderView.Stretch)
        self.tableWidget.setSortingEnabled(True)
        self.tableWidget.setAlternatingRowColors(True)

        self.buttonAdd = QPushButton('Добавить из всех вкладок в таблицу')
        self.buttonAdd.clicked.connect(self.addRowTable)
        self.buttonDel = QPushButton('Удалить выбранную строку в таблице')
        self.buttonDel.clicked.connect(self.delRowTable)

        self.tabWidget = QTabWidget()
        self.tabWidget.setTabsClosable(True)
        count = self.tabWidget.count()
        self.nb = QToolButton(text="Добавить", autoRaise=True)
        self.nb.clicked.connect(self.new_tab)
        self.tabWidget.insertTab(count, QWidget(), "")
        self.tabWidget.tabBar().setTabButton(count, QTabBar.RightSide, self.nb)
        self.new_tab()
        self.tabWidget.tabCloseRequested.connect(self.closeTab_SO)

# -        self.button_activate()

        vbox = QGridLayout(self.centralWidget)
        vbox.addWidget(self.tabWidget, 0, 0, 1, 2)
        vbox.addWidget(self.tableWidget, 1, 0, 1, 2)
        vbox.addWidget(self.buttonAdd, 2, 0)
        vbox.addWidget(self.buttonDel, 2, 1)
        
# +++ vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
        self.timer = QTimer()
        self.timer.setInterval(1000)
        self.timer.timeout.connect(self.check_tabs)
        self.timer.start()
# +++ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^        

    def new_tab(self):
        ''' Создание нового ТАБа
        '''
        index = self.tabWidget.count() - 1
        
#        self.tabWidget.insertTab(index, TabPage_SO(self), "Tab %d" % index)
        tabPage_SO = TabPage_SO(self)                                       # +            
        self.tabWidget.insertTab(index, tabPage_SO, "Tab %d" % index)       # +
        
        self.tabWidget.setCurrentIndex(index)
        self.nb.setEnabled(False)
        self.count = 0
        self.button_activate(False)                                         # +++ False
###
#-        self.tabWidget.currentWidget().lineEditType.textChanged.connect(self.check_tabs)
#?        self.tabWidget.currentWidget().lineEditSerialNumber.textChanged.connect(self.check_tabs)
#?        self.tabWidget.currentWidget().lineEditSpecifications.textChanged.connect(self.check_tabs)

    def closeTab_SO(self, currentIndex):
        ''' Удаление ТАБов
        '''
        self.tabWidget.removeTab(currentIndex)
        self.tabWidget.setCurrentIndex(self.tabWidget.count() - 2)
#-        self.check_tabs()

    def addRowTable(self):
        for i in range(self.tabWidget.count()-1):
            self.tabWidget.setCurrentIndex(i)
            editType = self.tabWidget.currentWidget().lineEditType.text()
            spinYearOfIssue = str(self.tabWidget.currentWidget().spinBox.value())
            editSerialNumber = self.tabWidget.currentWidget().lineEditSerialNumber.text()
            editSpecifications = self.tabWidget.currentWidget().lineEditSpecifications.text()

            if not editType:
                msg = QMessageBox.information(self, 'Внимание', 'Заполните поле!')
                return
            self.tableWidget.setSortingEnabled(False)
            rows = self.tableWidget.rowCount()
            self.tableWidget.insertRow(rows)
            self.tableWidget.setItem(rows, 0, QTableWidgetItem(editType))
            self.tableWidget.setItem(rows, 1, QTableWidgetItem(spinYearOfIssue))
            self.tableWidget.setItem(rows, 2, QTableWidgetItem(editSerialNumber))
            self.tableWidget.setItem(rows, 3, QTableWidgetItem(editSpecifications))
            self.tableWidget.setSortingEnabled(True)

    def delRowTable(self):
        row = self.tableWidget.currentRow()
        if row == -1:
            msg = QMessageBox.information(self, 'Внимание', 'Выберите строку для удаления')
            return
        self.tableWidget.removeRow(row)

# !!!
    def check_tabs(self):
        '''
        Проверка заполнения поля lineEditType и проход по всем вкладкам для проверки заполнения
        '''
# +++ vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
        flag = True
        for i in range(self.tabWidget.count()-1):
            line_edit_type = self.tabWidget.widget(i).lineEditType.text()
            if not line_edit_type:
                flag = False
                self.nb.setEnabled(False)
                break
        if flag: self.nb.setEnabled(True)
        else: self.nb.setEnabled(False)
        self.button_activate(flag)
# +++ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        
        '''
        for i in range(self.tabWidget.count()-1):
            self.tabWidget.setCurrentIndex(i)
            if self.tabWidget.currentWidget().lineEditType.text() == '' or self.tabWidget.currentWidget().lineEditType.text().isspace():
                self.tabWidget.currentWidget().lineEditType.setStyleSheet(self.red_warning)
                self.nb.setEnabled(False)
                self.count = 0
                self.button_activate()
                return
            else:
                self.tabWidget.currentWidget().lineEditType.setStyleSheet('')
                self.nb.setEnabled(True)
                self.count = 1
        self.button_activate()
        '''

    def button_activate(self, flag):                                        # +++ flag  
        ''' Активация кнопки self.buttonAdd
        '''
        if flag:                                                            # flag 
            self.buttonAdd.setEnabled(True)                                 # показать
        else:
            self.buttonAdd.setEnabled(False)


qss = """
QLabel {
    font: 8pt "MS Shell Dlg 2";
}
QLineEdit {
    font: 12pt "Calibri";
}
QSpinBox {
    font: 12pt "Calibri";
}
"""


if __name__ == "__main__":
    import sys
    app = QApplication(sys.argv)
    app.setStyleSheet(qss)
    window = MainWindow()
    window.show()
    sys.exit(app.exec_())

введите сюда описание изображения

→ Ссылка