Create GUI in Python using Qt

Introduction

Qt is a C++ cross-platform application and user-interface framework. It is developed by Nokia and contains a comprehensive library of GUI classes.

Using the visual editor “Qt Designer” GUIs are easily created and maintained.

This notebook describes a simple method to integrate Qt GUIs in Python programs using PyQt and the Python module QtGuiLoader.

In Example 1 a simple but flexible widget is created. It can be used as Widget, Dialog or MainWindow.

In Exercise 1 the widget is extended with more stuff to control a plot

Requirements

Install a distribution, e.g. Pythonxy, winpython or Anaconda

or

Install Python 2.7, Qt4 (including QtDesigner), and PyQt4 (alternatively PySide)

Example 1

In this example a simple but flexible widget is created. It can be used as Widget, Dialog or MainWindow (Menubar, toolbar and statusbar cannot be added to this widget).

Create ui

  • Open Qt Designer and create new "Widget"
  • Drag a "Horizontal Layout" from the "Widget Box" and drop it on the Form
  • Add a "LineEdit" to layout
  • Add a "PushButton" to layout next to the "LineEdit"

Add action

  • Create new action in the Action Editor
    • Text: "PrintText"
    • Object name: "actionPrintText" (default)

Connect button to action

  • Add Signal/Slot in Signal/Slot Editor
    • Sender: pushButton
    • Signal: clicked()
    • Receiver: actionPrintText
    • Slot: trigger()

Prepare for integrate in Python

  • Save as "MyUI/MyWidgetUI.ui" in the same folder as this notebook
  • Create an empty "MyUI/MyWidgetUI.py" file in the same folder (This is required in order to import the widget. Its content will be autogenerated if the file is completely empty or older than "MyUI/MyWidgetUI.ui")

Integrating in Python

The widget can be integrated as QMainWindow, QDialog or QWidget.

  • A QMainWindow is a separate window, i.e. two main windows can be closed independently.
  • A QDialog is a sub window, i.e. if its parent window is closed the dialog is closed too. A dialog may be modal, such that other windows cannot be operated before the dialog is closed.
  • A QWidget can be inserted into an existing widget or layout.

In order to use it, subclass QtMainWindowLoader, QtDialogLoader or QtWidgetLoader from QtGuiLoader and instantiate with MyUI.MyWidgetUI as ui_module argument.

When using QtGuiLoader, actions are by default connected to methods with the same name.

All PyQt elements are found in the self.ui-object, e.g. self.ui.lineEdit and documentation of the PyQt elements is found at http://pyqt.sourceforge.net/Docs/PyQt4/classes.html

In the following script, MYWidgetUI is used as a QMainWindow. Run it using Shift-Enter.

A new window will open and the text in the QLineEdit will be printet when clicking the QPushButton


In [ ]:
import MyUI.MyWidgetUI
from QtGuiLoader import QtMainWindowLoader
class MyMainWindow(QtMainWindowLoader):
    def __init__(self):
        QtMainWindowLoader.__init__(self, ui_module=MyUI.MyWidgetUI)
        
    def actionPrintText(self):
        print "Mainwindow text: %s"%str(self.ui.lineEdit.text())

print #required to print at first click (only when running as IPython Notebook)
MyMainWindow().start()

In the following example MyWidgetUI is reused as both QMainWindow, QDialog and QWidget.

On some platforms you need to close the window opened in the previous script before running the next script


In [ ]:
from QtGuiLoader import QtMainWindowLoader, QtDialogLoader, QtWidgetLoader
import MyUI.MyWidgetUI
from PyQt4 import QtGui

class MyMainWindow(QtMainWindowLoader):
    def __init__(self):
        QtMainWindowLoader.__init__(self, ui_module=MyUI.MyWidgetUI)
        self.setWindowTitle("Mainwindow")
        
        #Add as QWidget
        self.ui.horizontalLayout.addWidget(MyWidget(self))
        
        #Add button that opens as QDialog
        self.ui.horizontalLayout.addWidget(QtGui.QPushButton("Open dialog", self, clicked=self.open_dialog))
        
    def open_dialog(self):
        #open as QDialog
        MyDialog(parent=self,modal=True).start()
        
    def actionPrintText(self):
        print "Mainwindow text: %s"%self.ui.lineEdit.text()

        
class MyDialog(QtDialogLoader):
    def __init__(self,parent,modal):
        QtDialogLoader.__init__(self, MyUI.MyWidgetUI, parent, modal)
        self.setWindowTitle("Dialog")
        
    def actionPrintText(self):
        print "Dialog text: %s"%self.ui.lineEdit.text()        
        
        
class MyWidget(QtWidgetLoader):
    def __init__(self, parent):
        QtWidgetLoader.__init__(self, ui_module=MyUI.MyWidgetUI, parent=parent)
        self.show()

    def actionPrintText(self):
        print "Widget text: %s"%str(self.ui.lineEdit.text())
        
print #required to print at first click (only when running as IPython Notebook)
MyMainWindow().start()

Exercise 1

In this exercise we will make a widget to control our plot

  • Open Qt Designer and create new "Widget"
  • Add "Form Layout" to "Form"

  • x:   

    • Double click on formLayout and add an entry for x:
      • Label text: "x:"
      • Label name: "xLabel" (default)
      • Field type: QLineEdit (default)
      • Field name: "xLineEdit" (default)
    • Select xLineEdit and set properties in Property editor:
      • text: "np.arange(0,10,0.1)"
  • y:    

    • Double click again and add similar entry for y:
      • Label text: "y:"
      • Label name: "yLabel" (default)
      • Field type: QLineEdit (default)
      • Field name: "yLineEdit" (default)
    • Select yxLineEdit and set properties in Property editor:
      • text: "3*np.sin(x)"
  • Color:    

    • Double click on formLayout again and add entry for color:
      • Label text: "width:"
      • Label name: "colorLabel" (default)
      • Field type: QComboBox
      • Field name: "colorComboBox" (default)
    • Double click on colorComboBox and add three items:
      • 'red'
      • 'green'
      • 'blue'
  • Width:    

    • Double click on formLayout again and add entry for width:
      • Label text: "Width:"
      • Label name: "widthLabel" (default)
      • Field type: QLineEdit (default)
      • Field name: "widthLineEdit" (default)
    • Delete widthLineEdit (The one you just added)
    • Replace with a new "Horizontal Layout"
    • Add "Horizontal Slider" to horizontal layout
    • Select horizontalSlider and set properties in Property editor:
      • minimum: 1
      • maximum: 10
    • Add "Label" next to horizontalSlider
    • Select the label and set properties in Property editor:
      • objectName: "labelWidth"
      • minimumSize->Width: 30
      • text: "1"
  • yLim:    
    • Double click on formLayout again and add entry for yLim:
      • Label text: "yLim:"
      • Label name: "yLimLabel" (default)
      • Field type: QLineEdit (default)
      • Field name: "yLimLineEdit" (default)
    • Delete yLimLineEdit (The one you just added)
    • Replace with a new "Horizontal Layout"
    • Add "Spin Box" to horizontal layout
    • Select spinBox and set properties in Property editor:
      • minimum: -10
      • maximum: 10
      • value: -5
    • Add Label next to spinBox
    • Select the label and set properties in Property editor:
      • text: "to"
      • alignment->Horizontal: alignHCenter
    • Add "Double Spin Box" next to label
    • Select doubleSpinBox and set properties in Property editor:
      • minimum:-10
      • maximum:10
      • value: 5
      • singleStep: "0.1" (or "0,1" depending on language settings)
  • Add Action
    • text: "updatePlot"
  • Add internal widget signals:
    • Menu - Edit - Edit Signals/Slots (F3)
    • Drag width-slider and drop in width-label
    • valueChanged(int) -> setNum(int)
    • Ok
    • Menu - Edit Edit Widgets (F4)
    • Menu - Form - Preview (Ctrl + R)
    • Move width-slider and check that width-label changes
  • Add Signals
    • To save time, the following signals will be added by code:
SenderSignalReceiverSlot
`xLineEdit`editingFinished() acitonUpdatePlottrigger()
`yLineEdit`editingFinished()acitonUpdatePlottrigger()
`colorComboBox`currentIndexChanged(int)acitonUpdatePlottrigger()
`horizontalSlider`sliderReleased()acitonUpdatePlottrigger()
`spinBox`valueChanged(int)acitonUpdatePlottrigger()
`doubleSpinBox`valueChanged(double)acitonUpdatePlottrigger()
  • Save as "MyUI/MyPlotControlUI.ui" in the same folder as this notebook
  • Create an empty "MyUI/MyPlotcontrolUI.py" file in the same folder (This is required in order to import the widget. Its content will be autogenerated if the file is completely empty or older than "MyUI/MyPlotControlUI.ui")
  • Hints:

    • y_str: Obtain the text from self.ui.yLineEdit. The text must be converted to a string using str(x)
    • color: Obtain the current text from the combobox. The method to be used starts with "c", find it in the documentation
    • Width: Obtain the value from the slider. The method is defined in QAbstractSlider(super class of QSlider). See link in top of the QSlider documentation
    • yLim: Obtain the lower bound from spinBox and the upper bound from doubleSpinBox
  • Run again


In [ ]:
import MyUI.MyPlotControlUI
from QtGuiLoader import QtMainWindowLoader
import numpy as np
import matplotlib.pyplot as plt

    
class MyPlotControlMainWindow(QtMainWindowLoader):
    def __init__(self):
        try: self.ui = MyUI.MyPlotControlUI.Ui_Form() # Enables autocompletion (maybe...)
        except: pass
        QtMainWindowLoader.__init__(self, ui_module=MyUI.MyPlotControlUI)
                
        #Connect widget signals to actionUpdatePlot
        self.ui.xLineEdit.editingFinished.connect(self.actionUpdatePlot)
        self.ui.yLineEdit.editingFinished.connect(self.actionUpdatePlot)
        self.ui.colorComboBox.currentIndexChanged.connect(self.actionUpdatePlot)
        self.ui.horizontalSlider.valueChanged.connect(self.actionUpdatePlot)
        self.ui.spinBox.valueChanged.connect(self.actionUpdatePlot)
        self.ui.doubleSpinBox.valueChanged.connect(self.actionUpdatePlot)
        
        self.actionUpdatePlot()
        
    x_str = property(lambda self : str(self.ui.xLineEdit.text()))
    y_str = property(lambda self : "x")
    color = property(lambda self : 'Red')
    width = property(lambda self : 1)
    ylim = property(lambda self : (0,10))
        
        
    def actionUpdatePlot(self):
        print (self.x_str, self.y_str, self.color, self.width, self.ylim)
    
MyPlotControlMainWindow().start()

In [ ]: