PyQt is a set of Python bindings to Qt - Nokia's cross-platform GUI toolkit. Qt is mature, versatile and is released under an Open Source license. It is used in software like Google Earth, KDE, Opera, Skype, VLC media player and VirtualBox (Source: Wikipedia).

One of the advantages of using PyQt for graphical user interface (GUI) development is the bundled Qt Designer makes GUI design very easy and efficient. Widgets can be dragged and dropped onto dialogs, main windows and widgets. The resulting XML user interface(ui) code can be converted to Python code using the scripts included with PyQt.

Using PyQt within Bionumerics scripts opens up a number of possibilities. It is possible to use simple message boxes with error or informative texts, dialogs and main windows for working with databases etc., Bionumerics includes BioPython and some other modules but PyQt isn't included. It is fairly simple to use an existing Python/PyQt installation.

Requirements

  1. Install Python 2.6 The latest stable version in the 2.6 series as of this writing is 2.6.5 and can be downloaded from http://python.org/download/releases/
  2. PyQt Latest version can be downloaded and installed from http://www.riverbankcomputing.co.uk/software/pyqt/download [under Binary packages]. The version as of this writing is PyQt-Py2.6-gpl-4.7.3-2.exe
  3. To be able to import PyQt modules from within Bionumerics, the module directory C:\Python26\Lib\site-packages should be added to PYTHONPATH or appended to sys.path before the import statements.
  4. The Qt DLL's must be available or added to the system PATH. This will be done automatically by the PyQt installer. If for some reason, this doesn't happen, it will lead to errors like DLL load failed and the script will not proceed past the import statements.

The Example Application

The example I am going to use is a simple dialog application to set the field of selected entries. It looks like this when run

The Set field dialog

Step 1 - Creating the dialog using Qt Designer

This dialog was created in Qt Designer and the file was saved as setfielddlg.ui. The informative text at the top, Field and Value are of type QLabel. The combobox displaying all existing field names from the database is of type QComboBox. The input field corresponding to Value is of type QLineEdit. The Apply, Close and Deselect All buttons are all of type QPushButton.

A list of tutorials on PyQt can be found here. The book Rapid GUI Programming with Python and Qt is an excellent reference on the topic. Also useful is Riverbank's documentation of PyQt's classes.

Step 2 - Generating Python code for the Dialog

The file setfielddlg.ui generated by Qt Designer in the previous step is an XML file describing the elements of the dialog. This can be converted using the pyuic4 script included with PyQt4 which converts it into Python code. Once converted the dialog can be called from other scripts:

pyuic4 -o ui_setfielddlg.py setfielddlg.ui

The -o option specifies the name of the output file

Note

C:\Python26\Lib\site-packages\PyQt4\bin should be in the system PATH.

Step 3 - The main script

set_field.py

"""A simple dialog application to set the field of selected entries in
Bionumerics 6

"""
import sys
import bns

#append paths to PyQt4 and the current directory to sys.path
moduledir = [r'C:\Python26\Lib\site-packages', r'G:\Python\Bionumerics_scripts\src']

for moddir in moduledir:
        if moddir not in sys.path:
                sys.path.append(moddir)

import dbf
from PyQt4.QtCore import SIGNAL
from PyQt4.QtGui import QApplication, QDialog, QMessageBox
import ui_setfielddlg

class Dlg(QDialog, ui_setfielddlg.Ui_Dialog):

    def __init__(self, fieldnames, parent=None):
        super(Dlg, self).__init__(parent)
        self.setupUi(self)

        self.connect(self.close_button, SIGNAL('clicked()'), self.accept)
        self.connect(self.apply_button, SIGNAL('clicked()'), self.setfield)
        self.connect(self.deselect_button, SIGNAL('clicked()'), self.deselect)

        if fieldnames:
            self.field_combo_box.addItems(fieldnames)
        else:
            QMessageBox.critical(None, 'Error', 'Could not get fieldnames')
            return

    def deselect(self):
        '''Deselect all entries if selected'''

        if len(bns.Database.Db.Selection):
            bns.Database.Db.Selection.Clear()
        else:
            return

    def setfield(self):
                '''Sets the field value of selected entries'''

        field = unicode(self.field_combo_box.currentText())
        value = unicode(self.value_line_edit.text())

        selected = dbf.getSelected()

        if selected:
            for item in selected:
                key = item.Key
                try:
                    bns.Database.EntryField(key, field).Content = value
                except Exception, e:
                    QMessageBox.critical(None, 'Error', str(sys.exc_info()))
                bns.Database.Db.Fields.Save()
                else:
                    QMessageBox.information(None,'No entries selected',
                       'Please select entries before running script')

if __name__ == '__main__':
    sys.__dict__['argv'] = ['argv']
    app = QApplication(sys.argv)
    fields = dbf.getFieldNames()
    dialog = Dlg(fields)
    dialog.show()
    __bnscontext__.Stop(app.exec_())

Step 4 - Running the script

Source code of all the scripts used here can be downloaded from my github repository.

The scripts set_field.py and ui_setfielddlg.py and the module dbf.py should be in the same directory. In my case, this was G:\Python\Bionumerics_scripts\src and also specified in the moduledir list in the set_field.py script.

The dialog can be called by using the Scripts -> Run script from file option in Bionumerics and selecting set_field.py

Entries must be selected in the main window of Bionumerics else it displays a message and does nothing. Select the field from the field combo box, type in the value that needs to be set for the field and hit Apply. The value field has an 80 character maximum length, which is the maxiumum length of a field in Bionumerics.

The script in detail

Some additional notes

Imports

  • bns is the Bionumerics module
  • PyQt4 module directories are appended to sys.path as is the path to the directory of the running script. This is done to import any additional modules used by the script.
  • dbf is a module I created for general database functions. In this script, it is used for getting a list of selected entries get_selected and for getting a list of field names in the database - get_field_names. ui_setfielddlg contains code for the dialog:
import sys
import bns

#append paths to PyQt4 and the current directory to sys.path
moduledir = [r'C:\Python26\Lib\site-packages', r'G:\Python\Bionumerics_scripts\src']

for moddir in moduledir:
    if moddir not in sys.path:
        sys.path.append(moddir)

import dbf
from PyQt4.QtCore import SIGNAL
from PyQt4.QtGui import QApplication, QDialog, QMessageBox
import ui_setfielddlg

The dialog class - Dlg

Takes the fieldnames as an argument and populates the Field combo box if it is not empty. The clicked() signal of the close button is connected to the accept slot which closes the dialog. This is the same signal that is emitted by when the window is closed from the menubar. The other buttons are connected to their respective slots which are defined within the class.

main

The: sys.__dict__['argv'] = ['argv'] does nothing special. For some reason, argv is empty or not defined. and the line of the code QApplication(sys.argv) refuses to work without it. As no commandline arguments are used in the above code, it should cause no harm: __bnscontext__.Stop(app.exec_()) stops the program once the event loop - app.exec_() returns. This is similar to sys.exit(app.exec_()) used in standard PyQt4 programs.

Troubleshooting

These are the issues I am aware of

  • Only one instance of the script should run. Calling it twice causes a crash.
  • The dialog must be closed before exiting Bionumerics else the program crashes.