Chris
Chris

Reputation: 1027

PyQt dynamically switch between QSpinBox to QTimeEdit widgets depending on QComboBox data

I have a combo box with 2 options 'time' or 'interval' when 'time' is selected I would like to show a QTimeEdit and When 'interval is selected I would like to show a QSpinBox.

I can hide the interval widget and show the time widget but I do not know how to re-position it so that it is displayed where the interval widget was.

Here is what I have so far:

import sys
from PyQt5 import QtWidgets as qtw
from PyQt5 import QtGui as qtg
from PyQt5 import QtCore as qtc

class MainWindow(qtw.QMainWindow):

    def __init__(self):
        super().__init__()
        
        form = qtw.QWidget()
        self.setCentralWidget(form)
        layout = qtw.QFormLayout()
        form.setLayout(layout)
        
        self.when_list = qtw.QComboBox(currentIndexChanged=self.on_change)
        self.when_list.addItem('Every X Minutes', 'interval')
        self.when_list.addItem('At a specific time', 'time')
        self.interval_box = qtw.QSpinBox()
        self.time_edit = qtw.QTimeEdit()
        self.event_list = qtw.QComboBox()
        self.event_list.addItem('Event 1')
        self.event_list.addItem('Event 2')
        self.event_msg = qtw.QLineEdit()
        self.add_button = qtw.QPushButton('Add Event', clicked=self.add_event)

        layout.addRow(self.when_list, self.interval_box)
        layout.addRow(self.event_list)
        layout.addRow(self.event_msg)
        layout.addRow(self.add_button)

        self.show()

    def on_change(self):
        if self.when_list.currentData() == 'time':
            # Hide interval
            self.interval_box.hide()

            # Show time - how do I put this where interval_box was?
            self.time_edit.show()
        elif self.when_list.currentData() == 'interval':
            # Hide time - ERROR object has no attribute time_edit
            self.time_edit.hide()

            # show interval - ERROR object has no attribute interval_box
            self.interval_box.show()

    def add_event(self):
        pass

if __name__ == '__main__':
    app = qtw.QApplication(sys.argv)
    mw = MainWindow()
    sys.exit(app.exec())

How can I fix the errors and dynamically switch between the widgets?

Upvotes: 0

Views: 131

Answers (1)

musicamante
musicamante

Reputation: 48231

Instead of hiding and showing widgets, you can use a QStackedWidget (which is similar to a tab widget, but without tabs) and use the combo box signal to select which one show.

Note that you should not connect to a *changed signal in the constructor if you're going to set properties that could call that signal and the slot uses objects that don't exist yet: in your case you connected the currentIndexChanged signal in the constructor, but that signal is always called when an item is added to a previously empty combobox, and since at that point the time_edit object has not been created, you'll get an AttributeError as soon as you add the first item.

While using signal connections in the constructor can be useful, it must always be used with care.

class MainWindow(qtw.QMainWindow):

    def __init__(self):
        # ...
        self.when_list = qtw.QComboBox()
        self.when_list.addItem('Every X Minutes', 'interval')
        self.when_list.addItem('At a specific time', 'time')
        self.when_list.currentIndexChanged.connect(self.on_change)
        # ...
        self.time_stack = qtw.QStackedWidget()
        self.time_stack.addWidget(self.interval_box)
        self.time_stack.addWidget(self.time_edit)
        layout.addRow(self.when_list, self.time_stack)
        # ...

    def on_change(self):
        if self.when_list.currentData() == 'time':
            self.time_stack.setCurrentWidget(self.time_edit)
        elif self.when_list.currentData() == 'interval':
            self.time_stack.setCurrentWidget(self.interval_box)

Another solution could be to remove the widget that is to be hidden and insert a row in the same place using QFormLayout functions, but while that layout is useful in many situations, it's mostly intended for pretty "static" interfaces. The alternative would be to use a QGridLayout, which allows setting more widgets on the same "cell": in that case, you can easily toggle visibility of items, but it could create some issues as the layout would also try to adjust its contents everytime (which can be solved by using setRetainSizeWhenHidden() for the widget's size policy.

Upvotes: 1

Related Questions