简体   繁体   中英

PyQt5 dynamic creation/destruction of widgets

I have an application where upon start up the user is presented with a dialog to chose number of 'objects' required. This then generates necessary objects in the main window using a for loop (ie object1, object2, etc.). I want to move this selection into the main window so that this can be changed without the need to restart the application. I have no idea how to approach this as I'm not sure how to dynamically create/destroy once the application is running. Here's an example code that generates tabs in a tab widget with some elements in each tab.

from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *


class SelectionWindow(QDialog):
    def __init__(self):
        QDialog.__init__(self)
        self.settings = QSettings('Example', 'Example')
        self.numberOfTabs = QSpinBox(value = self.settings.value('numberOfTabs', type=int, defaultValue = 3), minimum = 1)
        self.layout = QFormLayout(self)
        self.button = QPushButton(text = 'OK', clicked = self.buttonClicked)
        self.layout.addRow('Select number of tabs', self.numberOfTabs)
        self.layout.addRow(self.button)

    def buttonClicked(self):
        self.settings.setValue('numberOfTabs', self.numberOfTabs.value())
        self.accept()


class MainWindow(QMainWindow):
    def __init__(self):
        QMainWindow.__init__(self)
        self.settings = QSettings('Example', 'Example')
        self.tabs = self.settings.value('numberOfTabs', type = int)
        self.tabWidget = QTabWidget()

        for i in range(1, self.tabs + 1):
            exec(('self.tab{0} = QWidget()').format(i))
            exec(("self.tabWidget.addTab(self.tab{0}, 'Tab{0}')").format(i))
            exec(('self.lineEdit{0} = QLineEdit()').format(i))
            exec(('self.spinBox{0} = QSpinBox()').format(i))
            exec(('self.checkBox{0} = QCheckBox()').format(i))
            exec(('self.layout{0} = QFormLayout(self.tab{0})').format(i))
            exec(("self.layout{0}.addRow('Name', self.lineEdit{0})").format(i))
            exec(("self.layout{0}.addRow('Value', self.spinBox{0})").format(i))
            exec(("self.layout{0}.addRow('On/Off', self.checkBox{0})").format(i))

        self.setCentralWidget(self.tabWidget)


if __name__ == "__main__":
    import sys
    app = QApplication(sys.argv)
    dialog = SelectionWindow()
    dialog.show()

    if dialog.exec_() == SelectionWindow.Accepted:
        mainwindow = MainWindow()
        mainwindow.show()
        sys.exit(app.exec_())

First of all, you should never use exec for things like these. Besides the security issues of using exec , it also makes your code less readable (and then much harder to debug) and hard to interact with.

A better (and more "elegant") solution is to use a common function to create tabs and, most importantly, setattr .

Also, you shouldn't use QSettings in this way, as it is mostly intended for cross-session persistent data, not to initialize an interface. For that case, you should just override the exec() method of the dialog and initialize the main window with that value as an argument.
And, even if it was the case (but I suggest you to avoid the above approach anyway), remember that to make settings persistent, at least organizationName and applicationName must be set.

class MainWindow(QMainWindow):
    def __init__(self):
        QMainWindow.__init__(self)
        self.settings = QSettings('Example', 'Example')
        # this value does not need to be a persistent instance attribute
        tabCount = self.settings.value('numberOfTabs', type = int)

        # create a main widget for the whole interface
        central = QWidget()
        mainLayout = QVBoxLayout(central)

        tabCountSpin = QSpinBox(minimum=1)
        mainLayout.addWidget(tabCountSpin)
        tabCountSpin.setValue(tabCount)
        tabCountSpin.valueChanged.connect(self.tabCountChanged)

        self.tabWidget = QTabWidget()
        mainLayout.addWidget(self.tabWidget)

        for t in range(tabCount):
            self.createTab(t)

        self.setCentralWidget(central)

    def createTab(self, t):
        t += 1
        tab = QWidget()
        self.tabWidget.addTab(tab, 'Tab{}'.format(t))
        layout = QFormLayout(tab)

        # create all the widgets
        lineEdit = QLineEdit()
        spinBox = QSpinBox()
        checkBox = QCheckBox()
        # add them to the layout
        layout.addRow('Name', lineEdit)
        layout.addRow('Value', spinBox)
        layout.addRow('On/Off', checkBox)

        # keeping a "text" reference to the widget is useful, but not for
        # everything, as tab can be accessed like this:
        # tab = self.tabWidget.widget(index)
        # and so its layout:
        # tab.layout()
        setattr(tab, 'lineEdit{}'.format(t), lineEdit)
        setattr(tab, 'spinBox{}'.format(t), spinBox)
        setattr(tab, 'checkBox{}'.format(t), checkBox)

    def tabCountChanged(self, count):
        if count == self.tabWidget.count():
            return
        elif count < self.tabWidget.count():
            while self.tabWidget.count() > count:
                # note that I'm not deleting the python reference to each object;
                # you should use "del" for both the tab and its children
                self.tabWidget.removeTab(count)
        else:
            for t in range(self.tabWidget.count(), count):
                self.createTab(t)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM