Thursday, February 5, 2009

My PyQt Scribbles (Python and Qt) #4: Layout Managers (QGridLayout() and more)

Last time I made a calendar, a quite convenient and clean app in just a few lines of code. Despite its elegant look and feel the code was very basic. By the way this isn't something bad, however the widgets arrangement was inelegant and that's something we're going to fix in this installment.
As usual we'll focus only on a few objects in order to get a glimpse of a certain subject without exploring it in depth. Our purpose, at the moment, is to understand the basic concept which enable us to create something functional. In particular we will work only on certain classes and certain members of those classes. After having absorbed the concepts, and with the class reference at hand, you can further experiment and refine the subject of your interest.
Lets then put our hands on the calendar and restructure the widgets arrangement.

import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *

class WizAndChipsCal(QWidget):

        def __init__(self, parent = None):
                QWidget.__init__(self)
                self.setWindowTitle("Wiz and Chips Calendar")
                self.setWindowIcon(QIcon("C:/Python26/PyQt/Icon/date.png"))
                self.setToolTip("Hello to this Wiz and Chips fancy calendar!")                

                self.title = (""+\
                             "Wiz and Chips Pushable Calendar!"+\
                             "")
                self.Label = QLabel(self.title)
                self.Label.setAlignment(Qt.AlignCenter | Qt.AlignJustify)

                self.calendar = QCalendarWidget()
                self.calendar.setGridVisible(1)
                self.calendar.setMinimumHeight(180)
                self.calendar.setMinimumWidth(110)

                self.check1 = QCheckBox("check1")
                self.check2 = QCheckBox("check2")
                self.TextBox = QTextEdit("type something here")
                self.TextBox.setMaximumHeight(50)

                self.dateLabel = QLabel("Date:")
                self.dateLabel.setMaximumWidth(80)
                CurrDate = QDate.currentDate()
                self.date = QDateEdit(CurrDate)
                self.date.setMaximumWidth(80)

                self.CloseButton.setToolTip(""+\
                                            "Press here to Quit"+\
                                            "")
                self.CloseButton.setMaximumSize(50, 25)

                self.infobox = QGroupBox("Info Box")
                self.infobox.setCheckable(1)
                self.infobox.setChecked(0)

                dateLayout = QHBoxLayout()
                dateLayout.addWidget(self.dateLabel)
                dateLayout.addWidget(self.date)
                dateLayout.addSpacing(170)

                GBoxLayout = QVBoxLayout(self.infobox)
                GBoxLayout.setSpacing(1)
                GBoxLayout.addLayout(dateLayout)
                GBoxLayout.addWidget(self.check1)
                GBoxLayout.addWidget(self.check2)
                GBoxLayout.addWidget(self.TextBox)

                Layout = QGridLayout()
                Layout.addWidget(self.Label, 0, 0)
                Layout.addWidget(self.calendar, 1, 0)
                Layout.addWidget(self.CloseButton, 4, 0)
                Layout.addWidget(self.infobox, 3, 0)
                self.setLayout(Layout)
                self.connect(self.CloseButton,
                             SIGNAL("pressed()"),
                             self.close) 

app = QApplication(sys.argv)
main_window = WizAndChipsCal()
main_window.show()
app.exec_()
Now we comment the most important passsages.

class WizAndChipsCal(QWidget):
QWidget() is the base class of all user interface objects. QWidget is the core of the user interface: all the other widget or window classes inherit from it.

self.calendar = QCalendarWidget()
self.calendar.setGridVisible(1)
self.calendar.setMinimumHeight(180)
self.calendar.setMinimumWidth(110)
We begin by creating all the various widgets we need to compose our GUI.
We use the .setMinimumHeight and .setMinimumWidth methods to fix the minimum dimensions that we desire for our calendar widget. These method can hold integers.

self.check1 = QCheckBox("check1")
self.check2 = QCheckBox("check2")
self.TextBox = QTextEdit("type something here")
self.TextBox.setMaximumHeight(50)
We create two check boxes with QcheckBox() class to use them later. We create a box which can be filled of text by the user with QTextEdit() class. For layout convenience we fix the edit box’s maximum height with .setMaximumHeight method.

self.dateLabel = QLabel("Date:")
self.dateLabel.setMaximumWidth(80)
CurrDate = QDate.currentDate()
self.date = QDateEdit(CurrDate)
self.date.setMaximumWidth(80)
Now we create a plain label with QLabel() class and set the label maximum width. At this point we wanna create a date spinbox, an object which shows the date, in a certain format, and that can be managed by the user. The first thing we do is to create an object to hold the current date. .currentDate method, which belong to QDate() class, gets the current date from the system clock.
Having done this we create the date spinbox with the QDateEdit() class and pass it the current date container we created before as argument of the class. At this point we begin to take care of the arrangement of the various widgets inside the parent QWidget window. To reach this general purpose we focus on the layout. In PyQt there are various classes which takes care of laying out the widgets. For exploration’s sake we’ll use three of them here.
self.infobox = QGroupBox("Info Box")
self.infobox.setCheckable(1)
self.infobox.setChecked(0)
QGroupBox class provides a box which contains child widgets. It's an elegant way to organise the space inside a GUI. We call the .setCheckable and set it to true to turn the info box title in a check box. In this way the child widgets are accessible only if the QGroupBox is checked otherwise they're greyed and inaccessible. By default checkable QgroupBox are checked. We don’t want it to be in this state thus we set the boolean parameter of .setChecked method to false.
dateLayout = QHBoxLayout()
dateLayout.addWidget(self.dateLabel)
dateLayout.addWidget(self.date)
dateLayout.addSpacing(170)
We use a specific layout manager to arrange the widgets relevant to the date subject. We want the date spinbox to have a label on its left. This is an horizontal arrangement so we call the QHBoxLayout() class which inherits from QBoxLayout().
We add first the label widget then the date spinbox widget and finally a spacer, by means of .addSpacing method, and givie it a dimension in pixel. We need this spacer because the general layout of the GUI –which we will take care of very soon- will force this widgets to position themselves so to get all the space pre-sized for them by the largest widget in the GUI –which in our case is the calendar itself. The spacer is basically an invisible widget which we can comfortably use to obtain our desired arrangement.

GBoxLayout = QVBoxLayout(self.infobox)
GBoxLayout.setSpacing(1)
GBoxLayout.addLayout(dateLayout)
GBoxLayout.addWidget(self.check1)
GBoxLayout.addWidget(self.check2)
GBoxLayout.addWidget(self.TextBox)
At this point we arrange the full content of the QgroupBox. At first we create a layout and since we want to arrange all the widget vertically we now use the QVBoxLayout. Being a brother or sister of QHBoxLayout, QVBoxLayout provides a vertical layout arrangement for the contained objects.
We want the widgets to occupy the fewest possible space so we call the .setSpacing method set the vertical space between the widgets to the value of 1. Before adding widgets to the layout we add a complete layout. In this way we can nest the horizontal layout which we create before, into the vertical layout. Then we add the remaining widgets to the vertical layout container.

Layout = QGridLayout()
Layout.addWidget(self.Label, 0, 0)
Layout.addWidget(self.calendar, 1, 0)
Layout.addWidget(self.CloseButton, 4, 0)
Layout.addWidget(self.infobox, 3, 0)
self.setLayout(Layout)
At this point we take care of the general GUI layout (without mentioning the underlying main window). To to this we use the QGridLayout() class. QGridLayout() is a class meant to lay out widgets in a grid. It inherits from QLayout class which is the abstract base class of geometry managers. QGridLayout gets the available space (the parent layout or parent widget) and slice it in columns and rows thus forming cells. We can then easily place our widgets in the desired cells in order to structure our GUI space.
After creating the layout we call the .addWidget method to add the widgets to the grid at the desired row and column position. The first parameter of the method is the widget (or layout also) to be added, then we specify the column and row by means of integer numbers. After having finished to position the widgets we call .setLayout with our layout as argument to set the general layout to the parent widget. Here below you can see the result both in Windows XP and in Kubuntu 8.10 with KDE 4.2.



No comments:

Post a Comment