Monday, October 22, 2012

My Poetic License



(My Poetic License: A poem written by me after I was dead)
O'God, when I could write,
I wrote a couple of poems thinking of you,
now let me sleep through the Resurrection day
when my body is going to testify against me,
Yes, I know, I succumbed to my wishes all my life
But You have been one of those wishes always,
Yes, I know, I haven't remembered you all the time
But I knew you were there for me always,
now let me sleep through the Resurrection day into paradise
I know I am overusing it,
But Its my poetic license!
(Oct. 21, 2012)

Saturday, August 11, 2012

Creating User Interfaces in Python with Qt Designer

I am relatively new to Python. Moreover, I use it in Windows and I don't feel guilty about it. I wanted to learn how to create a simple user interface in Python. I looked up on the Internet and found a number of good tutorials. However, I had to look quite a lot for a tutorial for beginners. And as a consequence, I decided to write a simple tutorial for this purpose. I do assume previous Python and GUI building experience (in a different language). 
I have pythonxy on my Windows 7 computer together with QtDesigner. I use Spyder for editing the code and running it. However, if you are a fan of anaconda distribution, then it is possible to do it there too (QtDesigned comes installed in C:\Users\<your user>\Anaconda3\Library\bin\designer.exe).
In this tutorial, we will create a very simple, demonstration-only user interface in QtDesigner and add event handlers for it in Python. There will be two buttons: clicking on one of the buttons will write some text on the interface whereas clicking on the other one will clear it.
Here are the steps:
1. Open QtDesigner
2. Click File > new and select "Dialog without buttons" on the resulting New Form dialog.
3. Add two buttons from the widget box on the left and name the buttons "cmdWrite" and "cmdClear" with the display text set to "Write" and "Clear" respectively. You can search for different controls by typing their names (such as 'button') in the text box on the widget box.
4. Add a text label (name it lblShow), a line edit box (name it txtLine) and a text edit Box (name it txtEdit).
5. Arrange the controls by aligning them horizontally and vertically as shown below.


6. One good thing about Qt Desginer is that you can attach some functionality to your GUI without writing any code. For example, we would like the clear button to clear the text in all the text controls. This can be accomplished very easily by clicking on the "Edit signals and slots" button on the "Tools" toolbar in the Qt Designer interface. This can also be accomplished by pressing F4 on the keyboard.
7. Click on cmdClear and a red line will appear. Connect the cmdClear through this line to a text control (say lblShow) using the mouse. Once the connection is complete, a "Configure Connection" dialog will appear and you can select the "clicked()" signal in the left pane and the "clear()" slot in the right pane. Now when you click on the cmdClear, the clear() function of the lblShow will be called automatically and this will clear the label. You can test this simple functionality in the Qt Designer by pressing Ctrl+R and clicking on the Clear button. Do the same for txtLine and txtEdit. Below is a screen shot of how this will look in Qt Designer.

8. Save the project to "hw.ui".
9. Create the python code for hw.ui by using the following command in a command window (not in python or ipython interpreters):
> pyuic4 -x hw.ui -o hw.py
This command creates a file "hw.py" which contains the python code corresponding to the Qt GUI you have created. You can run the GUI using python by typing python hw.py in command window. It will show the same GUI as seen in the preview from Qt Designer. You can click on clear to clear the label. If you are using PyQt5 (comes with Anaconda distributions), you can run the equivalent command as follows by first accessing the base directory for pyuic5. You may also need to specify appropriate paths to your files:

C:\Users\<your user>\Anaconda3\Library\bin>pyuic5 -x c:\Users\<your user>\Desktop\hw.ui
-o c:\<your user>\afsar\Desktop\hw.py

10. If you need more assistance on steps 1-7 you can view this excellent video which I came across during my quest. 
11. The python file hw.py is shown below.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# -*- coding: utf-8 -*- hw.py

# Form implementation generated from reading ui file 'hw.ui'
#
# Created: Sat Aug 11 14:06:40 2012
#      by: PyQt4 UI code generator 4.8.6
#
# WARNING! All changes made in this file will be lost!

from PyQt4 import QtCore, QtGui

try:
    _fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
    _fromUtf8 = lambda s: s

class Ui_Dialog(object):
    def setupUi(self, Dialog):
        Dialog.setObjectName(_fromUtf8("Dialog"))
        Dialog.resize(368, 319)
        Dialog.setWindowTitle(QtGui.QApplication.translate("Dialog", "Dialog", None, QtGui.QApplication.UnicodeUTF8))
        self.cmdClear = QtGui.QPushButton(Dialog)
        self.cmdClear.setGeometry(QtCore.QRect(140, 10, 75, 23))
        self.cmdClear.setText(QtGui.QApplication.translate("Dialog", "Clear!", None, QtGui.QApplication.UnicodeUTF8))
        self.cmdClear.setObjectName(_fromUtf8("cmdClear"))
        self.widget = QtGui.QWidget(Dialog)
        self.widget.setGeometry(QtCore.QRect(60, 100, 258, 213))
        self.widget.setObjectName(_fromUtf8("widget"))
        self.verticalLayout = QtGui.QVBoxLayout(self.widget)
        self.verticalLayout.setMargin(0)
        self.verticalLayout.setObjectName(_fromUtf8("verticalLayout"))
        self.txtEdit = QtGui.QTextEdit(self.widget)
        self.txtEdit.setObjectName(_fromUtf8("txtEdit"))
        self.verticalLayout.addWidget(self.txtEdit)
        self.lblShow = QtGui.QLabel(self.widget)
        self.lblShow.setText(QtGui.QApplication.translate("Dialog", "TextLabel", None, QtGui.QApplication.UnicodeUTF8))
        self.lblShow.setObjectName(_fromUtf8("lblShow"))
        self.verticalLayout.addWidget(self.lblShow)
        self.widget1 = QtGui.QWidget(Dialog)
        self.widget1.setGeometry(QtCore.QRect(80, 50, 216, 25))
        self.widget1.setObjectName(_fromUtf8("widget1"))
        self.horizontalLayout = QtGui.QHBoxLayout(self.widget1)
        self.horizontalLayout.setMargin(0)
        self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout"))
        self.txtLine = QtGui.QLineEdit(self.widget1)
        self.txtLine.setObjectName(_fromUtf8("txtLine"))
        self.horizontalLayout.addWidget(self.txtLine)
        self.cmdWrite = QtGui.QPushButton(self.widget1)
        self.cmdWrite.setText(QtGui.QApplication.translate("Dialog", "Write!", None, QtGui.QApplication.UnicodeUTF8))
        self.cmdWrite.setObjectName(_fromUtf8("cmdWrite"))
        self.horizontalLayout.addWidget(self.cmdWrite)

        self.retranslateUi(Dialog)
        QtCore.QObject.connect(self.cmdClear, QtCore.SIGNAL(_fromUtf8("clicked()")), self.txtLine.clear)
        QtCore.QObject.connect(self.cmdClear, QtCore.SIGNAL(_fromUtf8("clicked()")), self.txtEdit.clear)
        QtCore.QObject.connect(self.cmdClear, QtCore.SIGNAL(_fromUtf8("clicked()")), self.lblShow.clear)
        QtCore.QMetaObject.connectSlotsByName(Dialog)

    def retranslateUi(self, Dialog):
        pass


if __name__ == "__main__":
    import sys
    app = QtGui.QApplication(sys.argv)
    Dialog = QtGui.QDialog()
    ui = Ui_Dialog()
    ui.setupUi(Dialog)
    Dialog.show()
    sys.exit(app.exec_())

Most of the code is responsible for creating and positioning the controls on the dialog. However, lines 54-56 are responsible for the 'clearing' behavior resulting from clicking cmdClear.
12. How to add more functionality? One way would be to edit the file (hw.py) created using pyuic4. However, there are two types of problems in doing this:
a. Modifications of the GUI: Whenever you change the GUI, you would have to re-run pyuic4 to update hw.py and this will clear out any changes you have made. Hence the warning on line number 8.
b. Object oriented correctness: Suppose you were implementing a browser and you chose to make changes to hw.py to implement the application logic. Then this will make your application logic and your user interface to be intertwined. 
13. A better way to solve this issue is to create another file say hwlogic.py as shown below.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# -*- coding: utf-8 -*- hwlogic.py
"""
@author: Afsar
This is the Implementation logic for Hello World! (hw.py)
"""
from PyQt4 import QtCore,QtGui
import sys
import hw
class hwl(QtGui.QDialog,hw.Ui_Dialog):
    """
    hwl is inherited from both QtGui.QDialog and hw.Ui_Dialog
    """
    def __init__(self,parent=None):
        """
            Initialization of the class. Call the __init__ for the super classes
        """
        super(hwl,self).__init__(parent)
        self.setupUi(self)
        self.connectActions()
    def main(self):
        self.show()
    def connectActions(self):
        """
        Connect the user interface controls to the logic
        """
        self.cmdWrite.clicked.connect(self.myprint)      
        
    def myprint(self):
        """
        Even handler for the pushButton click
        """
        self.txtLine.setText('Python -- ')        
        self.txtEdit.setText('This')
        self.lblShow.setText('is a test')
if __name__=='__main__':
    app = QtGui.QApplication(sys.argv)
    hwl1 = hwl()
    hwl1.main()
    sys.exit(app.exec_())

In this file, we have created a class 'hwl' which inherits from both QtGui.QDialog and hw.Ui_Dialog. Remember hw.Ui_Dialog is the name of the class in hw.py which contains the user interface constructs. The __init__ function of hwl does the following:
line 17: Calls the __init__ functions of the super classes of hwl.
line 18: Calls the setupUi function implemented in hw.py which is responsible for setting up the controls on the dialog and also associating some functionality (the clearing resulting from clicking cmdClear) to the controls. 
line 19: Connects the application logic (actions) to the controls by calling the function self.connectActions(). The connectActions function links the execution of the function myprint to the click of the cmdWrite button (line 27). The myprint function writes three different texts to the three text controls (line 32-34). One can also add the clearing of these controls linked to the clicking of cmdClear to the connectActions functions to completely disassociate the application logic from the user interface. 
lines 35-39: Create an application and an instance of the hwl class and call its main function which brings up the application and runs it.
14. You can now run the 'hwl.py' file to see the results of our labor.

Acknowledgements: Most of the ideas for this tutorial are taken from:
Videos by Kris.
Tutorial by Harsh J.
The code was published using pygmentize through the following command (example):
pygmentize -O full=true,style=colorful, linenos=1 -o hwlogic.html hwlogic.py 
I welcome comments and criticism.