Personal tools
You are here: Home Members srubio's Home HowToS How To code with PyQt and TAU

How To code with PyQt and TAU

Steps to create the minimum PyQt gui

How to create a TAU and PyQt GUI or Widgets

Resources

[Controls website, gui framworks] [Qt4 Documentation] [Qt4 Classes reference]

ᅵ see Thiago's internal presentation http://www.cells.es/CELLS/Intranet/Divisions/Computing/Controls/Applications/tau/Tau_Internal_Presentation

Create a GUI

Copied from http://www.cells.es/Members/srubio/howto/pyqt

  1. open qt designer
  2. create your widget
  3. save the ui_XXX.ui file
  4. > pyuic4 ui_XXX.ui > ui_XXX.py
  5. edit your XXX.py file
  6. Create a new class inheriting from the object that you want to create (QMainWindow or QDialog)
  7. in the __init__ method create an ui_XXX object and call to setupUi and retranslateUi methods
  8. This self.ui object will contain all the widgets and other objects that you added on QtDesigner.
  9. Add to your class the methods (Slots) that you want to execute for each Widget event (Signal)
  10. Connect Signals and Slots
  11. Browse to http://www.riverbankcomputing.com/static/Docs/PyQt4/html/classes.html to see what each PyQt class offers to you.
# This is the contents of the MyGui.py file
# It assumes that you have created ui_MyGui.ui with QtDesigner
# You have to execute >/pyuic4 ui_MyGui.ui > ui_MyGui.py to generate the file to import

from PyQt4 import QtGui,QtCore
from ui_MyGui import ui_MyGui

class MyGUI(QtGui.QMainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
self.ui = ui_MyGui()
self.ui.setupUi(self)
self.connect(self.ui.push_button,QtCore.SIGNAL('clicked(bool)'),self.DoAction)

def DoAction(self,arg = None):
print 'What a %s action.' % ('wonderful' if arg else 'sad')

app = QtGui.QApplication(sys.argv[0:1])
gui = MyGui()
gui.show()
sys.exit(app.exec_())


How to create a TAU Widget


Writing your own widgets from a template

  • goto widget/utils
  • type:
    python widgetgen.py <classname> <superclass> <outputfile> [<qtfile>]

But it needed some patches (DbWidget example using svn tau.widget.utils package)!!! :

  • First the template is generated:
    python ../widget/utils/widgetgen.py DbWidget QtGui.QTextEdit dbwidget.py
  • Editing dbwidget.py :
    • Added taubase to imports
      7c7
      < from tau.widget import TauBaseWidget

      ---
      > from tau.widget import TauBaseWidget,taubase
    • Modified init to avoit non-TAU parents to be passed to TauBaseWidget?.init
      it caused exceptions in designer
      23c23,28
      < self.call__init__(TauBaseWidget, name, parent, designMode=designMode)

      ---
      > if isinstance(parent,TauBaseWidget): #It was needed to avoid exceptions in TauDesigner!
      > print 'in DbWidget->TauBaseWidget __init__(parent)'

      > self.call__init__(TauBaseWidget, name, parent, designMode=designMode)

      > else:
      > print 'in DbWidget->TauBaseWidget __init__()'
      > self.call__init__(TauBaseWidget, name, designMode=designMode)

      50c55,56
      < raise RuntimeError("Forgot to overwrite %s.getModelClass" % str(self))

      ---
      > return tau.core.TauDatabase
      > #raise RuntimeError("Forgot to overwrite %s.getModelClass" % str(self))
    • Instead of eventReceived I overwrote eventHandle;
      because in that method appeared the execution exceptions related to setToolTip() and setText(value)
      112a119,141

      > def eventHandle(self, value):
      > """ eventHandle, called from setChangeEvent and setConfigEvent
      > param @value set previously to self.getDisplayValue() if nothing specific received
      > """


      > #update text/title #Commented because it was setting text = "controls01:10000"
      > #if not self.getShowText(): value = ''
      > #if self._setText: self._setText(value)
      >
      > #update tooltip #Commented because there's no tooltip for Db; it caused exception!
      > #self.setToolTip(self.getFormatedToolTip())
      >

      > print 'in DbWidget eventHandle'
      > obj = self.getModelObj()
      > if obj:

      > print 'obj is not null'
      > # sergi: this is the command I added to update the info displayed
      > text = obj.get_info()

      > self.setText(text)
      > print 'text set as "%s"' % text

      > ##update appearance
      > self.updateStyle()
      > print 'out of DbWidget eventHandle'
    • getModelClass modified to return TauDatabase
      50c55,56

      < raise RuntimeError("Forgot to overwrite %s.getModelClass" % str(self))
      ---

      > return tau.core.TauDatabase
      > #raise RuntimeError("Forgot to overwrite %s.getModelClass" % str(self))

Now all execution/designer problems are solved

Customization of the Widget

  • Setup the signals to be subscribable from Qt:
    __pyqtSignals__ = ("modelChanged(const QString &)",)
  • Modify methods needed for your widget:
    • Model initialization:
      @QtCore.pyqtSignature("setModel(QString)")
      def setModel(self,model):

      self.setModelCheck(model) ##It must be included
    • Rewrite event Handling (launched by changeEvent(QString) and configEvent()):
      @QtCore.pyqtSignature("changeEvent(QString)")

      def setChangeEvent(self, value):
      self.eventHandle(value)

      @QtCore.pyqtSignature("configEvent()")
      def setConfigEvent(self):
      self.eventHandle(self.getDisplayValue() or self.getNoneValue())

      def eventHandle(self, value):
      # update text/title
      if not self.getShowText(): value = ''

      if self._setText: self._setText(value) ##It must be included

      #update tooltip
      self.setToolTip(self.getFormatedToolTip()) ##It must be included
      #TODO: update whatsThis

      #update appearance
      self.updateStyle() ##It must be included

Any of these methods could be rewritten; those decorated will appear in QtDesigner connections view.

  • Rewrite updateStyle : use it to customize look&feel, call it from eventHandle!
    NOTE: Not event-driven objects can be customized using this method.
  • Signals to connect with:
    • getChangeSignal : returns QtCore.SIGNAL('changeEvent(QString)')
    • getConfigSignal : QtCore.SIGNAL('configEvent()')
      QtCore.QObject.connect(self, self.getChangeSignal(), self.setChangeEvent)

About declaring functions available to Qt Signals/Slots connection see #SignaturesusedbyQttorecognizeSignalsSlots

More things …

from unfinished Thiago's tutorial

  • Must inherit from TauBaseWidget
  • Must inherit from QWidget or any of its subclasses
  • Must provide the following constructor:
    def __init__(self, parent = None, designMode = False)

  • must call the TauBaseWidget Constructor:
    self.call__init__(TauBaseWidget, name, designMode=designMode)

  • Must define two properties:
    • model
    • parentModel
  • Should implement getModelClass
  • Should implement updateStyle

Event Sequence

e.g.: two ways of launching fireEvent(...):

  • setModel(QString) -> setModelCheck(QString)
  • eventReceived : handle for events received (from Tango or also from Qt?)

Once fireEvent is called it notifies all listeners (included the widget itself!):

  • fireEvent(type)
  • emit(self.getConfigSignal() or (self.getChangeSignal(),getDisplayValue())

Note: This signals are connected to setConfigEvent and setChangeEvent Note: To control what is send with ChangeSignal the DisplayValue must be set first.

Once event is received the sequence is:

  • setChangeEvent(value) / setConfigEvent() #available slots for events:
    @QtCore.pyqtSignature("changeEvent(QString)") / @QtCore.pyqtSignature("configEvent()")
  • eventHandle(value or getDisplayValue) #it performs real event management.
    It must be reloaded and include a call for updateStyle() or TauBaseWidget.eventHandle(self,value).
  • updateStyle() #It should always contain a self.update() call

Notes:

  • getDisplayValue(): by default returns getModelObj().getDisplayValue()
  • ...

Accessing Models from ipython

Minimal example (take care of how getFactory is used!)

#! python
import tau.core
tfact = tau.core.TauManager().getFactory()()
mks2 = tfact.getDevice('alba02:10000/LAB/VC/MKS-02')
<tau.core.TauDevice Object>
attr = mks2.getAttribute('Status')
attr.read()
<PyTango AttributeValue Object>

print attr.read().value
...
mks2.getHWObj()
<PyTango DeviceProxy Object>
print mks2.getHWObj().get_property(['SerialLine'])
...

Reading/Writing properties of a TauDevice

import tau.core

#From inside any widget using a TauDevice as Model:
model = self.getModel()

#A TauDatabase method is used to read properties list
all_properties = tau.core.TauManager().getFactory()().getDatabase().get_device_property_list(model.name(),'*')

#Any PyTango.DeviceProxy method can be used
all_props_values = model.get_property(all_properties) #it returns a dict object

#For updating the value of a property:

model.put_property({'prop_name':prop_value})
#OR
model.put_property(all_props_values)

Adding a widget to TAU packages and/or Qt Designer

  • Widget plugin files must be placed at $PYTHONPATH/tau/widget/designer or any other folder in PYQTDESIGNERPATH
  • Must inherit from TauWidgetPlugin and implement a few methods:
    import tauplugin
    from tau.widget.dbwidget import DbWidget


    class DbWidgetPlugin(tauplugin.TauWidgetPlugin):

    """TauPlotPlugin(tauplugin.TauWidgetPlugin)

    Provides a Python custom plugin for Qt Designer by implementing the
    QDesignerCustomWidgetPlugin via a PyQt-specific custom plugin class.
    """

    def getWidgetClass(self):

    return DbWidget
    def getIconName(self):
    return 'columnview.png'
    def includeFile(self):

    return "tau.widget.dbwidget"

tau.widget Remarks:

  • widget module is for generic tango widgets only
  • there will be modules for gl, qwt, etc
  • if you implement a widget that has reference to any system or interprets tango data in a very particular way it is not a widget. Just part of your application

Some Recipes

Standard Dialogs and Widgets

QMessageBox(parent,Title,Question,FlagsForButtons)

v = QtGui.QMessageBox.warning(None,'SplitterTester', \
'You must introduce or select an IP from IPAddress Combo Box', \
QtGui.QMessageBox.Ok|QtGui.QMessageBox.Cancel);

if v == QtGui.QMessageBox.Cancel:
return

QInputDialog(parent,title,question,QLineEdit,defaultText)
returns (text,OkPressed)

In [29]:QtGui.QInputDialog.getText(None,'hola','dame argo',QtGui.QLineEdit.Normal,'una moneiya')

Out[29]:(<PyQt4.QtCore.QString object at 0x83ad2ac>, True)

QFont: Setting a monospaced font in a QTextEdit panel

font = QtGui.QFont()
font.setStyleHint(QtGui.QFont.TypeWriter)

font.setFamily("unexistentfont")
self.ui.TextPanel.setFont(font)

Focus: .setFocus(), hasFocus() available on most of widgets

QDialog vs QMainWindow (KeyEvents)

  • QDialog automatically connects Return and Escape keys to Ok and Cancel buttons.


  • In QMainWindow it must be hacked using keyPressEvent methods

Managing Key Events

    def keyPressEvent(self,event):
if event.key() == QtCore.Qt.Key_Escape:

self.reject()
elif event.key() in [QtCore.Qt.Key_Return,QtCore.Qt.Key_Enter]:

buttons = [b for b in \
[self.ui.ScanButton,self.ui.buttonBox.buttons()[1],self.ui.AddressEdit,self.ui.RangeEdit] \
if b.hasFocus()]

if buttons:
for b in buttons:
if hasattr(b,'click'): b.click()

else:
self.accept()
return

Signals and Slots

Connecting signals and slots

Connections between signals and slots (and other signals) are made using the QtCore.QObject.connect() method. For example:

QtCore.QObject.connect(a, QtCore.SIGNAL("QtSig()"), b, QtCore.SLOT("QtSlot()"))
QtCore.QObject.connect(a, QtCore.SIGNAL("PySig()"), b, QtCore.SLOT("QtSlot()"))
QtCore.QObject.connect(a, QtCore.SIGNAL("QtSig()"), pyFunction) #A python function with NO arguments
QtCore.QObject.connect(a, QtCore.SIGNAL("QtSig()"), pyClass.pyMethod) #A python function with NO arguments
QtCore.QObject.connect(a, QtCore.SIGNAL("PySig"), pyFunction) #A python signal WITH arguments

"modelChanged(const QString &)": it means that an string is passes as argument to the slot

Emitting signals

Any instance of a class that is derived from the QtCore.QObject class can emit a signal using its emit() method. This takes a minimum of one argument which is the signal. Any other arguments are passed to the connected slots as the signal arguments. For example:

a.emit(QtCore.SIGNAL("clicked()")) #Connect it to a python function with NO arguments
a.emit(QtCore.SIGNAL("pySig"), "Hello", "World") #To be connected to a python function WITH arguments

Signatures used by Qt to recognize Signals/Slots

Signals emitted by the class must be added to __pyqtSignals__ member

    __pyqtSignals__ = ("modelChanged(const QString &)",)

Slots available must be decorated providing a header for Qt

@QtCore.pyqtSignature("setModel(QString)")
def setModel(self,model):

self.setModelCheck(model)

QThreads

QThreads object cannot access directly to GUI objects ... All access must be done using Signals and Slots; so any change to GUI (including displaying messages) must be done through emit and declared functions with arguments.

class QPinger(QtCore.QThread):
def __init__(self,parent,ipstart,iprange):

QtCore.QThread.__init__(self)
...
if hasattr(parent,'scan_finished'):

QtCore.QObject.connect(self, QtCore.SIGNAL("scan_finished"), parent.scan_finished)

def run(self):

...
self.emit(QtCore.SIGNAL("scan_finished"), founds)

Random remarks

Note: it works with milliseconds instead of seconds'':

  • QThread.start() to launch QThread.run() method
  • QThread.wait() equals to threading.Thread.join()
  • QThread.wait(millis): equals to threading.Event.wait(seconds)
  • QMutex.lock() ; QMutex.unlock ... typical mutex
  • QWaitCondition: Like threading.Event but a mutex is unlocked during wait and locked again when the wait finishes.
    • QWaitCondition.wait(QMutex): Forces the Thread to wait until QWaitCondition.wakeOne() or .wakeAll() is called in another thread.
    • QWaitCondition.wait(QMutex, time=int(millis)): This works like threading.Event.wait(seconds).
  • Signals: QThread.started,.finished,.terminated (launched by terminate(); it is thread unsafe!)

TAU Concepts

from Thiago's internal presentation

  • Database
    (tango://)?<host>:<port>
  • Device
    (<database>)?(<devicename>|<devicealias>)

  • Attribute
    <device>/<attrname>
  • Configuration
    <attribute>?configuration=<confname>
  • Property
    <device>?property=<propname>
    <attribute>?property=<propname>

  • Command
    <device>?command=<cmdname>

Document Actions