Some dialogs require so many widgets to present all the options that they make available that they become difficult for the user to understand. The most obvious way to deal with this is to create two or more dialogs and to divide the options between them. This is a good approach when it is possible since it minimizes the demands made on the user, and may also be easier from a maintenance point of view than a single complex dialog. But often we need to use a single dialog because the options we are presenting to the user are related and need to be presented together.
When we must use a single dialog, there are two kinds of groups of options that we must consider. One kind is simply a group of related options. This is most easily handled by using a QTabWidget. A tab widget can have as many "pages" (child widgets and tab captions) as necessary, each one laid out with the widgets that are needed to present the relevant options. Figure 9.1 shows a PaymentDlg, an example of a three-page tab widget that was created using Qt Designer.
In Qt Designer's Widget Box's Containers section there is a Tab Widget. This can be dragged onto a form like any other widget. Like most container widgets, and unlike most other widgets, we normally have to manually resize the tab widget after dropping it on the form, to roughly the size we want. In Qt Designer, the tab widget has context menu options for deleting and adding pages. The current page can be set by clicking the relevant tab or by setting the "currentIndex" property. The current page's tab text can be set by setting the "currentTabText" property.
Once a tab widget has been dragged onto a form and resized, we can drag other widgets onto its pages. These widgets can be laid out in the normal way, and each tab page can be laid out in a similar way to the form itself: by deselecting all the widgets, then clicking the tab page, and then applying a layout manager.
Thanks to their labelled tabs, tab widgets make it obvious to the user that there are more options on other tab pages, and provide an easy means by which the user can navigate between pages. Tab widgets can have rounded or angled tab corners, and can have the tabs at the top, bottom, left, or right.
Although using Qt Designer is quicker and easier than creating the dialog by hand, it is interesting and useful to know how to achieve the same thing purely in code. We won't show the creation of the ordinary widgets, since we have seen that enough times by now; instead, we will focus on the tab widget and the form's overall layout. The following extracts are all from the PaymentDlg class's initializer in chap09/paymentdlg.pyw. (The Qt Designer version is in the files paymentdlg.ui and paymentdlg.py.)
tabWidget = QTabWidget()
cashWidget = QWidget()
cashLayout = QHBoxLayout()
We create the tab widget just like any other widget. Each page in a tab widget must contain a widget, so we create a new widget, cashWidget, and create a layout for it. Then we add the relevant widgets—in this case, just one, paidCheck-Box—to the layout, and then set the layout on the containing widget. Finally, we add the containing widget as a new tab to the tab widget, along with the tab's label text.*
checkWidget = QWidget() checkLayout = QGridLayout() checkLayout.addWidget(checkNumLabel, 0, 0) checkLayout.addWidget(self.checkNumLineEdit, 0, 1) checkLayout.addWidget(bankLabel, 0, 2) checkLayout.addWidget(self.bankLineEdit, 0, 3) checkLayout.addWidget(accountNumLabel, 1, 0) checkLayout.addWidget(self.accountNumLineEdit, 1, 1) checkLayout.addWidget(sortCodeLabel, 1, 2) checkLayout.addWidget(self.sortCodeLineEdit, 1, 3) checkWidget.setLayout(checkLayout) tabWidget.addTab(checkWidget, "Chec&k")
This tab is created in exactly the same way as the first one. The only differences are that we have used a grid layout, and we have more widgets to put in the layout.
We won't show the code for the third tab, because it is structurally the same as the ones we have already seen.
* In the PyQt documentation, and to some extent, the QTabWidget's API, the term "tab" is used to refer to a tab's label alone, and to a tab's label and page together.
layout = QVBoxLayout()
For completeness, we have shown the heart of the dialog's layout, omitting only the creation of the grid layout that holds the labels, line edits, and spinboxes at the top of the form. The buttons are provided by a QDialogButtonBox, a widget that can be laid out like any other. Finally, we lay out the whole form in a vertical box layout: first the grid at the top, then the tab widget in the middle, and then the button box at the bottom.
Another kind of options group is one that is applicable only in certain circumstances. In the simple case where a group of options is applicable or not, we can use a checkable QGroupBox. If the user unchecks the group box, all the widgets it contains are disabled. This means that the user can see what options the group contains, even when they are unavailable, which is often helpful. In other cases, we might have two or more groups of options, only one of which is applicable at any one time. For this situation, a QStackedWidget provides a solution. Conceptually, a stacked widget is a tab widget that has no tabs. So the user has no visual clue that a stacked widget is present, and has no means of navigating between the stacked widget's pages.
Figure 9.2 A dialog that uses a stacked widget
Design Time Preview Time Runtime
To use a stacked widget, we can drag a Stacked Widget onto a form in Qt Designer, and resize it in the same way as for a tab widget. Inside Qt Designer a stacked widget is indicated by two tiny arrowheads in its top right-hand corner. These arrowheads are also present when the form is previewed, but they do not appear at runtime—they are shown at the top right of the color combobox in the first two screenshots in Figure 9.2. Widgets can be dragged onto stacked widget pages and laid out in exactly the same way as for tab widgets. Stacked widgets have a context menu that has options for adding and deleting pages, just like a tab widget, and additional options for navigating between pages and for changing the page order.
Since stacked widgets have no tabs, we must provide the user with a means of navigating between pages. In the VehicleRentalDlg shown in Figure 9.2, the vehicle type combobox is used as a page selector. To make this work, in Qt Designer we have connected the combobox's currentIndexChanged(int) signal to the stacked widget's setCurrentIndex(int) slot. Another commonly used approach that lets users see all the pages available is to use a QListWidget containing the name of each page, and connecting its currentRowChanged(int) signal in the same way we connected the combobox's signal.
We will now see how to create a stacked widget in code. The following extracts are from the VehicleRentalDlg class's initializer in chap09/vehiclerentaldlg.pyw. (The Qt Designer version is in the files vehiclerentaldlg.ui and vehicle-rentaldlg.py.)
self.stackedWidget = QStackedWidget() carWidget = QWidget() carLayout = QGridLayout() carLayout.addWidget(colorLabel, 0, 0) carLayout.addWidget(self.colorComboBox, 0, 1) carLayout.addWidget(seatsLabel, 1, 0) carLayout.addWidget(self.seatsSpinBox, 1, 1) carWidget.setLayout(carLayout) self.stackedWidget.addWidget(carWidget)
Adding a "page" to a stacked widget is very similar to adding a tab to a tab widget. We begin by creating a plain widget, and then create a layout for it and lay out the widgets we want. Then we set the layout on the plain widget and add this widget to the widget stack. We have not shown the code for the vanWidget, because it is structurally identical.
topLayout = QHBoxLayout()
bottomLayout = QHBoxLayout()
layout = QVBoxLayout()
Once again, for completeness we have shown the whole dialog's layout. We begin with a top layout that has the combobox that is used to set the stacked widget's current widget. Then we create a bottom layout of the mileage labels, and then a button layout for the buttons. Next, we add all of these layouts, and the stacked widget itself, to a vertical box layout.
self.connect(self.buttonBox, SIGNAL("accepted()"), self.accept) self.connect(self.buttonBox, SIGNAL("rejected()"), self.reject) self.connect(self.vehicleComboBox,
SIGNAL("currentIndexChanged(QString)"), self.setWidgetStack) self.connect(self.weightSpinBox, SIGNAL("valueChanged(int)"), self.weightChanged)
We must provide the user with a navigation mechanism, and we do this by connecting the vehicle combobox's currentIndexChanged() signal to a custom setWidgetStack() slot. The last slot is simply part of the form's validation; it is there to set the maximum mileage, which is fixed for cars but for vans is dependent on their weight.
def setWidgetStack(self, text): if text == "Car":
self.stackedWidget.setCurrentIndex(G) self.mileageLabel.setText("1000 miles") else:
def weightChanged(self, amount):
self.mileageLabel.setText("%d miles" % (8000 / amount))
The setWidgetStack() slot makes the appropriate widget visible, and handles part of the mileage setting since this varies depending on whether the vehicle is a car or a van.
We have used the combobox's current text to determine which widget to make visible. A possibly more robust approach would be to associate a data item with each combobox item (using the two-argument QComboBox.addItem() method), and use the current item's data item to choose which widget to show.
Was this article helpful?
Uncover The Instant Traffic System That Will TRIPLE Your Income Overnight While Putting You In Direct Contact With THOUSANDS Of Hungry Buyers. How to generate instant traffic to your website, easily! Siphon high quality, ultra-targeted traffic to your offers. The easiest way to dominate your market with lase targeted video based campaigns. These video ads will suck in customers by the minute, driving your message out to a global audience of buyers.