Be productive with PyQt4 :

Some PyQt4 tips

I enjoy working with PyQt. Here are several tips I use often to build applications.

Some of them uses the excellent book from Mark Summerfield :

Rapid GUI Programming with Python and Qt

Transforming the ui file

Suppose you've ui_myfile.ui. You want to build an Python executable with it. Open a command line and type in :

pyuic4 -o ui_myfile.py -x ui_myfile.ui

Ressource files

As Mark Summerfield said

Often your program assumes that the application directory is the directory where it is located. But if your program is executed from a different directory, it won't work as expected.

So how to solve this problem ?

  • trying to solve it with Python's os.getcwd() won't work because this returns the directory where we invoked the application, wich as we have noted, may not be the directory where the application actually resides.
  • Nor does QApplication.applicationDirPath() method help, since this returns the path to the Python executable, not to our application itself.
  • Another solution is to put all our ressources (help files, images, etc.) into a single .py module and access them from here.
    This not only solve the path problem (because Python knows how to look for a module to be imported), but also means that instead of having dozens of icons, help files, and similar, some of wich could easily become lost, we have a single module containing them all.

So, what's your choice here ? :)

To produce a ressource module, we must do two things :

  1. create a .qrc file that contains the details of the ressources we want included. It's a simple XML file.
  2. run the pyrcc4 utility which reads a .qrc file and produces a ressource module.

Suppose you have done a resources.qrc file, you generate a ressources module (here qrc_resources_rc.py ) by :

pyrcc4 -o qrc_resources_rc.py resources.qrc

Make a new custom class to use your generated file

Suppose you've generated a ui_myfile.py file , create a main.py file with the following contents :

import sys
from PyQt4 import QtGui, QtCore
from ui_myfile import Ui_MainWindow

class MyApp(QtGui.QMainWindow, Ui_MainWindow):
    def __init__(self):
        QtGui.QMainWindow.__init__(self)
        Ui_MainWindow.__init__(self)
        self.setupUi(self)

if __name__ == "__main__":
    app = QtGui.QApplication(sys.argv)
    window = MyApp()
    window.show()
    sys.exit(app.exec_())

Changing the Look & Feel :

Inside main.py, and in the if \name\ == "\main\":, put the following lines to ie use the predefined Cleanlooks style.

QtGui.QApplication.setStyle(QtGui.QStyleFactory.create("Cleanlooks"))
QtGui.QApplication.setPalette(QtGui.QApplication.style().standardPalette())

A little utility

Doing all this is painfull. I've made a little script wich generates a .py file from a given .ui one, then it creates a 'main.py' file with all you need.

Beware, I'm using Python 2.6 new feature (the with statement). Here it is : 'make.py'.

Launch it with :

python make.py myfile.ui

But be aware to launch this script only one time, otherwise the 'main.py' script will be overwritten and you'll lost all of your code !


#!/usr/bin/env python
# -*- coding: utf-8 -*-

import sys
import os

template = """#!/usr/bin/env python
# -*- coding: utf-8 -*-

import sys
from PyQt4 import QtGui, QtCore
from %(name)s import Ui_MainWindow

class MyApp(QtGui.QMainWindow, Ui_MainWindow):
    def __init__(self):
        QtGui.QMainWindow.__init__(self)
        Ui_MainWindow.__init__(self)

        # Setup the ui.
        self.setupUi(self)

if __name__ == "__main__":
    app = QtGui.QApplication(sys.argv)
    
    ## Look and feel changed to CleanLooks
    QtGui.QApplication.setStyle(QtGui.QStyleFactory.create("Cleanlooks"))
    QtGui.QApplication.setPalette(QtGui.QApplication.style().standardPalette())
    
    window = MyApp()
    window.show()
    sys.exit(app.exec_())"""
    

def main(filename):
    os.system('pyuic4 -o %(name)s.py -x %(name)s.ui'%{'name': filename})
    with open('main.py', 'w') as f:
        f.write(template%{'name': filename})
        
if __name__ == "__main__":
    filename = sys.argv[1]
    main(filename)

Translating signals/slot between C++ and Python

If you read Qt4's doc, you may find some signals like this in C++:

void itemDoubleClicked ( QTableWidgetItem * item )

In PyQt4, you'll have to use the following signal (don't forget the star char !) :

QtCore.SIGNAL("itemDoubleClicked (QTableWidgetItem*)")

Another one, a QComboBox signal:

void currentIndexChanged ( const QString & text )

it has to be translated like this (the string is a constant here, so you'll have to tell it to PyQt):

QtCore.SIGNAL("currentIndexChanged(const QString)")

Mouse handling

Wheel events

You can catch any wheel event by defining a wheelEvent method on your widget, ie :

def wheelEvent(self, event):
    if event.delta() > 0 :
        print "delta positive"
    else:
        print "delta negative"

Fullscreen apps

You can show a widget in fullscreen mode by using the showFullScreen () method of the widget. Note that this apply only to windows. If you have a toolbar, it will be invisible in fullscreen mode.

Here's a sample of a method I've used inside a MainWindow (in wich self.fullScreen = False by default):

class MonAppli(QtGui.QMainWindow, Ui_MainWindow):
    def __init__(self):
        QtGui.QMainWindow.__init__(self)
        Ui_MainWindow.__init__(self)
        self.setupUi(self)
        
        self.fullScreen = False
        QtGui.QShortcut(QtGui.QKeySequence("Ctrl+Shift+W"), self, self.toogleFullScreen )
        
    def toogleFullScreen(self):
        if not self.fullScreen :
            self.showFullScreen()
        else:
            self.showNormal()
            
        self.fullScreen = not (self.fullScreen)

Scintilla

This snippet will show you how to handle QScintilla basically (it prints out all the lexers and the editor methods):

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
Basic use of the QScintilla2 widget

Note : name this file "qt4_sci_test.py"
"""

import PyQt4.Qsci
langs = [i for i in dir(PyQt4.Qsci) if i.startswith('QsciLexer')]

for i,l in enumerate(langs):
    print i,l[9:] # we don't need to print "QsciLexer" before each name

import sys
from PyQt4.QtGui import QApplication
from PyQt4 import QtCore, QtGui
from PyQt4.Qsci import QsciScintilla, QsciScintillaBase, QsciLexerPython

if __name__ == "__main__":
    app = QApplication(sys.argv)
    editor = QsciScintilla()

    ## define the font to use
    font = QtGui.QFont()
    font.setFamily("Consolas")
    font.setFixedPitch(True)
    font.setPointSize(12)
    # the font metrics here will help
    # building the margin width later
    fm = QtGui.QFontMetrics(font)

    ## set the default font of the editor
    ## and take the same font for line numbers
    editor.setFont(font)
    editor.setMarginsFont(font)

    ## Line numbers
    # conventionnaly, margin 0 is for line numbers
    editor.setMarginWidth(0, fm.width( "00000" ) + 5)
    editor.setMarginLineNumbers(0, True)

    ## Edge Mode shows a red vetical bar at 80 chars
    editor.setEdgeMode(QsciScintilla.EdgeLine)
    editor.setEdgeColumn(80)
    editor.setEdgeColor(QtGui.QColor("#FF0000"))

    ## Folding visual : we will use boxes
    editor.setFolding(QsciScintilla.BoxedTreeFoldStyle)

    ## Braces matching
    editor.setBraceMatching(QsciScintilla.SloppyBraceMatch)

    ## Editing line color
    editor.setCaretLineVisible(True)
    editor.setCaretLineBackgroundColor(QtGui.QColor("#F5F5DC"))

    ## Margins colors
    # line numbers margin
    editor.setMarginsBackgroundColor(QtGui.QColor("#333333"))
    editor.setMarginsForegroundColor(QtGui.QColor("#CCCCCC"))

    # folding margin colors (foreground,background)
    editor.setFoldMarginColors(QtGui.QColor("#99CC66"),QtGui.QColor("#333300"))

    ## Choose a lexer
    lexer = QsciLexerPython()
    lexer.setDefaultFont(font)
    editor.setLexer(lexer)

    ## Render on screen
    editor.show()

    ## Show this file in the editor
    editor.setText(open("test_sci_qt4.py").read())
    
    # Show all the methods of the editor
    methods = sorted(QsciScintilla.__dict__.keys())
    for m in methods :
        print m

    sys.exit(app.exec_())

... to be continued

Comments
Back to index
blog comments powered by Disqus

Atom feeds