It’s been a while since my last post on this subject and this was because of several causes. The biggest among these it’is me; yeah because not being a professional programmer
I find difficult to find the time to delve into python programming lately. We can say that my math is x=1/y with x being my python hacking time and y my duties. And that’s self explanatory.
Anyway there’s a important reason why I think this will be the last article of the “season”. If you read one of my past article you know that Nokia decided to release the Qt library under
LGPL license enabling developers to use the library for free but in the scope of a commercial project (as far as they comply with LGPL of course). Soon after Nokia got in contact with
Riverbank Computing to work out an agreement (Riverbank business model on PyQt of selling the commercial use license for PyQt found in conflict with Nokia’s new license program).
As far as I read the agreement wasn’t reached and so Nokia asked OpenBossa to develop PySide, an alternative and official Python
implementation of Qt. As much as I am thankful to Riverbank for having ported Qt to Python thus making non commercial developers able to use Nokia’s powerful library, I think it will be more
more sensitive for the future to look at the upcoming official implementation as it will probably be more solid and better supported and stickier to the future official releases of Qt.
Coming back to our scribbles, this installment will delve a bit more in widgets arrangement, widget nesting and scrolling areas.
I must thank Christian Brugger for helping me out with a trick to nest dynamically generated widgets to a scroll widget (later you will see).
As usual I’ll post the whole code below and after that the parts with comments and explanation.
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._labels = []
self._edits = []
self.CurrDate = QDate.currentDate()
self.calendar = QCalendarWidget()
self.calendar.setGridVisible(1)
self.calendar.setMaximumHeight(200)
self.calendar.setMaximumWidth(250)
self.connect(self.calendar,
SIGNAL('selectionChanged()'),
self.SelDate)
self.AddButton = QPushButton("&AddProject")
self.AddButton.setMaximumSize(70, 25)
self.AddButton.setToolTip("Press here to add a new job")
self.connect(self.AddButton,
SIGNAL('pressed()'),
self.AddProj)
self.summaryBox = QGroupBox("Project Management Layout")
self.summaryBox.setMinimumHeight(300)
self.summaryBox.setMinimumWidth(800)
self.summaryBoxScroll = QScrollArea()
self.summaryBoxScroll.setFrameStyle(QFrame.NoFrame)
self.summaryBoxTopLayout = QVBoxLayout(self.summaryBox)
self.summaryBoxTopLayout.setContentsMargins(1,1,1,1)
self.summaryBoxTopLayout.addWidget(self.summaryBoxScroll)
self.summaryBoxTopWidget = QWidget()
self.summaryBoxScroll.setWidget(self.summaryBoxTopWidget)
self.summaryBoxLayout = QFormLayout()
self.summaryBoxLayout.setSpacing(1)
self.summaryBoxLayout.setSizeConstraint(QLayout.SetFixedSize)
self.summaryBoxLayout = QFormLayout(self.summaryBoxTopWidget)
self.summaryBoxLayout.setSpacing(1)
self.summaryBoxLayout.setSizeConstraint(QLayout.SetMinAndMaxSize)
self.CloseButton = QPushButton("&Quit")
self.CloseButton.setToolTip(""
"Press here to Quit")
self.CloseButton.setMaximumSize(50, 25)
GeneralLayout = QGridLayout()
GeneralLayout.setSizeConstraint(QLayout.SetMinAndMaxSize)
GeneralLayout.addWidget(self.Label, 0, 0)
GeneralLayout.addWidget(self.AddButton, 1,0)
GeneralLayout.setAlignment(self.AddButton, Qt.AlignTop)
GeneralLayout.addWidget(self.calendar, 1, 1)
GeneralLayout.addWidget(self.CloseButton, 4, 0)
GeneralLayout.addWidget(self.summaryBox, 3, 0)
self.setLayout(GeneralLayout)
self.connect(self.CloseButton, SIGNAL("pressed()"),
self.close)
def moveEvent(self, event):
self.setWindowOpacity(0.7)
QTimer.singleShot(50, self.opac)
def opac(self):
self.setWindowOpacity(1)
def closeEvent(self, event):
self.CloseDialog = QMessageBox.question(self,
"The application is being closed",
"Do you really want to quit?",
QMessageBox.Save|QMessageBox.Yes|QMessageBox.Discard,
QMessageBox.Discard)
if self.CloseDialog == QMessageBox.Yes:
event.accept()
elif self.CloseDialog == QMessageBox.Save or QMessageBox.Discard:
event.ignore()
def SelDate(self):
self.SelectedDate = self.calendar.selectedDate()
print(self.SelectedDate)
def AddProj(self):
projLabelLayout = QHBoxLayout()
projLabelLayout.setSpacing(3)
label_list = []
projTextBoxesLayout = QHBoxLayout()
projTextBoxesLayout.setSpacing(3)
edit_list = []
for name, width in [('Job No.', 80), ('Cust.Job', 80),
('Cust.Ords.', 80), ('Product', 80), ('Q.ty', 80),
('Serial No', 80), ('Quality', 80), ('Packing', 80)]:
label = QLabel(name)
label.setMinimumWidth(width)
projLabelLayout.addWidget(label)
label_list.append(label)
TextEdit = QTextEdit()
TextEdit.setMaximumHeight(20)
TextEdit.setMinimumWidth(width)
TextEdit.setTabChangesFocus(1)
projTextBoxesLayout.addWidget(TextEdit)
edit_list.append(TextEdit)
for name1, width1 in [('delivery', 80), ('drawings', 80), ('approval', 80),
('cust. mat. deliv.',80), ('mat. delivery', 80), ('end prod.', 80),
('test date', 80), ('ship date', 80)]:
label1 = QLabel(name1)
label1.setMinimumWidth(width1)
projLabelLayout.addWidget(label1)
label_list.append(label1)
DateEdit = QDateEdit(self.CurrDate)
DateEdit.setCalendarPopup(True)
DateEdit.setMaximumHeight(20)
DateEdit.setMinimumWidth(width)
projTextBoxesLayout.addWidget(DateEdit)
edit_list.append(DateEdit)
self._labels.append(tuple(label_list))
self._edits.append(tuple(edit_list))
self.summaryBoxLayout.addRow(projLabelLayout)
self.summaryBoxLayout.addRow(projTextBoxesLayout)
app = QApplication(sys.argv)
main_window = WizAndChipsCal()
main_window.show()
app.exec_()
[/sourcecode]
[sourcecode language="python"]
self._labels = []
self._edits = []
self.CurrDate = QDate.currentDate()
We create two empty lists to store the information about the rows added to the job management application. This will become clear afterwards.
We retrive the current date from the system clock to use it in our application
self.AddButton = QPushButton("&AddProject")
self.AddButton.setMaximumSize(70, 25)
self.AddButton.setToolTip("Press here to add a new job")
self.connect(self.AddButton,
SIGNAL('pressed()'),
self.AddProj)
We create a button and then set it to add new rows in our job management overview project.
To do this we must connect its signal "pressed()" to our custom slot which in this case is a function conveniently called "AddProj"
Now we create the layouts and widgets which will become the core of our job management overview. This part is tricky and involves several widgets and layout being created.
The summary is:
- We create a QGroupBox widget as base container of the other widgets
- We create a QScrollArea widget
- We create a top layout (in the form of a QVBoxLayout) for our QGroupBox widget and assign the QScrollArea widget to it
- We create a top bare QWidget to link it later to the QScrollArea
- We create a top layout for the QGroupBox and the top widget
Now in detail:
self.summaryBox = QGroupBox("Project Management Layout")
self.summaryBox.setMinimumHeight(300)
self.summaryBox.setMinimumWidth(800)
This is the space containing the dynamic list of our jobs. So we create the main QGroupBox widget and we set the minimum dimensions to control the appearance of the whole area.
self.summaryBoxScroll = QScrollArea()
self.summaryBoxScroll.setFrameStyle(QFrame.NoFrame)
Now we create the scoller itself. To do this we use the QScrollArea() class which provides a scrolling view onto another widget.
Normally the QScrollArea() just needs to take care of the widget as explained in the API, that is to say:
label = QLabel("image.png")
scroller = QScrollArea()
scroller.setWidget(label)
This is very straightforward but in our case it doesn't work because we have several widgets which are dynamically created and get nested inside another widget.
So we need a trick: we create a top widget and that's the one we want to scroll. We assign this widget to the scroller.
Imagine a cupcake. We have the cake composed of many ingredients, and this represents our job widgets.
Then we have the cup which contains the cake, and this is our top widgets.
Finally we have the kid's hand moving the cupcake to eat it, and that's our scroller.
self.summaryBoxTopLayout = QVBoxLayout(self.summaryBox)
self.summaryBoxTopLayout.setContentsMargins(1,1,1,1)
This is the top layout which will contain the scroll widget to take care of the scroll area.
self.summaryBoxTopLayout.addWidget(self.summaryBoxScroll)
now we add the QScrollArea() to the top layout.
self.summaryBoxTopWidget = QWidget()
self.summaryBoxScroll.setWidget(self.summaryBoxTopWidget)
To scroll the area flawlessly we need to create a top widget which must then be assigned to the QScrollArea.
In this way the QScrollArea will take care of scrolling the top widget which contains all the other dynamically changed sub widgets
self.summaryBoxLayout = QFormLayout()
self.summaryBoxLayout.setSpacing(1)
self.summaryBoxLayout.setSizeConstraint(QLayout.SetFixedSize)
This is the layout relevant to the summaryBox widget.
We assign our QGroupBox() to the layout, we set the internal spacing between the elements to be '1' and finally we set the size constraint policy to be sure that the widget can't
be resized at all with respect to its sizes defined at sizeHint()
self.summaryBoxLayout = QFormLayout(self.summaryBoxTopWidget)
self.summaryBoxLayout.setSpacing(1)
self.summaryBoxLayout.setSizeConstraint(QLayout.SetMinAndMaxSize)
To the same general layout we assign also the top widget and specify the size constraints
GeneralLayout.addWidget(self.summaryBox, 3, 0)
The QGroupBox widget which represents our job management overview (and contains all the other widgets and layouts)is laid out.
Now it’s the time to discuss the function that we called AddProj and that will take care of the widgets generation inside the QGroupBox.
def AddProj(self):
projLabelLayout = QHBoxLayout()
projLabelLayout.setSpacing(3)
This function is called every time the user presses the AddButton.
The job management overview is composed of rows and columns. Each row represents a job and each column represents an element related to that job.
This is clearly a very simple way to provide an overview of a series of jobs within a company.
label_list = []
projTextBoxesLayout = QHBoxLayout()
projTextBoxesLayout.setSpacing(3)
edit_list = []
We create empty lists that will contain each additional label for every job element we decide to add.
for name, width in [('Job No.', 80), ('Cust.Job', 80),
('Cust.Ords.', 80), ('Product', 80), ('Q.ty', 80),
('Serial No', 80), ('Quality', 80), ('Packing', 80)]:
label = QLabel(name)
label.setMinimumWidth(width)
projLabelLayout.addWidget(label)
label_list.append(label)
TextEdit = QTextEdit()
TextEdit.setMaximumHeight(20)
TextEdit.setMinimumWidth(width)
TextEdit.setTabChangesFocus(1)
projTextBoxesLayout.addWidget(TextEdit)
edit_list.append(TextEdit)
Here we populate the label list that we have previously created. We use the 'for' statement so that we can easily add/remove future elements to our system without the need -at least in theory- to review our design.
Each label widget will get the name and the with in sequence from the list.
Then we add each label to the label layout and we append every label to the list which is hence populated.
We make the same process for the text boxes with only two additional staps. First of all we set a maximum height for the cell.
Secondly, we use the setTabChangesFocus() property so that we can switch from one cell to the next one using the tab key.
The 'for' statement used to populate the list of elements which are used to generates the widgets for the job management part, is convenient because it helps us to write a code more elegant and concise, but it has a major drawback. Using this systme we can generate only one type of widgets because this is closed inside a loop which will simply iterate through the list and create a specific
widget for each element of the list. As a matter of fact in order to include calendar edit widgets to our job management structure we must create a second list whose elements are sistematically used to create pop up calendars.
for name1, width1 in [('delivery', 80), ('drawings', 80), ('approval', 80),
('cust. mat. deliv.',80), ('mat. delivery', 80), ('end prod.', 80),
('test date', 80), ('ship date', 80)]:
label1 = QLabel(name1)
label1.setMinimumWidth(width1)
projLabelLayout.addWidget(label1)
label_list.append(label1)
DateEdit = QDateEdit(self.CurrDate)
DateEdit.setCalendarPopup(True)
DateEdit.setMaximumHeight(20)
DateEdit.setMinimumWidth(width)
projTextBoxesLayout.addWidget(DateEdit)
edit_list.append(DateEdit)
Given that this part of the job management application concerns dates, it's more confortable for our user to input the dates by means of a pop up calendar than simply type them (with the correct format) inside text boxes. QDateEdit is the class which does the trick.
By setting .setCalendarPopup(True) we makes the calendar popping up from the widgets only when the user clicks on the specific box.
self._labels.append(tuple(label_list))
self._edits.append(tuple(edit_list))
We append the label list and the edit list to two other lists but we convert them to tuple before.
Here below you can see screenshots of the Ubuntu and Windows XP version of the application
No comments:
Post a Comment