4.3.8. Taurus Databinding Widgets

To add declarative databinding capabilities to a custom widget the developer needs to create a set of classes:

TaurusExampleDmsLabel

It inherits from the new widget itself, and from TaurusBaseWidget. It is in charge of creating a new instance of the appropiate Controller class, and storing properties that are used by the declarative databinding layer.

This class is used also by the designer, so the properties it declares, are present in the Property Editor tab.

TaurusExampleDmsLabelController

Inherits from TaurusBaseController, which already makes the last value available using the provided valueObj(). This class should take the value, and apply it to the class created above.

The TaurusBaseController.update() method invokes the self._updateConnections(), self._updateForeground(), self._updateBackground(), self._updateToolTip(). Developers needs to implement or reuse existing ones.

Tip

The examples/widget_library/taurus includes an example widget.

4.3.8.1. Taurus Controller

Looking into the taurus adaptation layer file in the examples taurusexampledmslabel.py

examples/widget_library/taurus/src/cut/examples/widgets/taurus/taurusexampledmslabel.py
 1class TaurusExampleDmsLabelController(TaurusBaseController):
 2    """Controller for the TaurusExampleDmsLabel widget.
 3
 4    It uses the values received by the TaurusBaseController.handleEvent() method to provide
 5    the appropiete data manipulation for the Widget.
 6
 7    This one in particular reuses the _updateBrackground from the TaurusLabel, but provides
 8    its own _updateForeground, which propagates the value magnitude to the radians QeDmsLabel
 9    property.
10    """
11
12    _updateBackground = updateLabelBackground
13
14    def __init__(self, label):
15        TaurusBaseController.__init__(self, label)
16
17    def _setStyle(self):
18        TaurusBaseController._setStyle(self)
19        label = self.widget()
20        # if update as palette
21        if self.usePalette():
22            label.setFrameShape(Qt.QFrame.Box)
23            label.setFrameShadow(Qt.QFrame.Raised)
24            label.setLineWidth(1)
25
26    def _updateForeground(self, widget):
27        value = None
28        format = None
29        value = 0.0
30        valueObj = self.valueObj()
31        if valueObj is not None and valueObj.rvalue is not None:
32            format = self.attrObj().data_format
33            value = valueObj.rvalue
34            if format == DataFormat._0D:
35                self.widget().setRadians(value)

The class TaurusExampleDmsLabelController inherits from TaurusBaseController. This class will only concern itself with the manipulation of the last value gotten from an event (Change or Periodic). For that, we need to implement two methods: _updateBackground and _updateForeground.

Since we want the background role to behave the same as the TaurusLabel, we just import that implementation, as assign to the _updateBackground that implementation.

The _updateForeground implementation custom, but rather simple. The last value from an event can be retrieved using self.valueObj(). A reference to the widget this Controller class manipulates can be retrived using self.widget(). This implementation just makes a simple check to see if the data format is a scalar, and sets the value using the radians property in the QeExampleDmsLabel.

4.3.8.2. Taurus Widget

examples/widget_library/taurus/src/cut/examples/widgets/taurus/taurusexampledmslabel.py
 1    def __init__(self, parent=None, designMode=False):
 2        """Sets the initial values.
 3        It is important the the value and assignations are separate, as the resetXYZ method for
 4        properties needs to get the same value to reset the property.
 5        """
 6        self._bgRole = self.DefaultBgRole
 7        self._fgRole = self.DefaultFgRole
 8        self._modelIndex = self.DefaultModelIndex
 9        self._modelIndexStr = ""
10        self._controller = None
11        name = self.__class__.__name__
12        self.call__init__wo_kw(QeExampleDmsLabel, parent)
13        self.call__init__(TaurusBaseWidget, name, designMode=designMode)
14        self.setAlignment(self.DefaultAlignment)

The constructor only prepepared initial values. It is important to notice that the Default values are not stored in the __init__ method, since these are reused later in the reset methods.

examples/widget_library/taurus/src/cut/examples/widgets/taurus/taurusexampledmslabel.py
1    def _calculate_controller_class(self):
2        ctrl_map = _CONTROLLER_MAP
3        model_type = self.getModelType()
4        ctrl_klass = ctrl_map.get(model_type, TaurusExampleDmsLabelController)
5        return ctrl_klass
 1    def controller(self):
 2        ctrl = self._controller
 3        # if there is a controller object and it is not the base controller...
 4        if ctrl is not None and not ctrl.__class__ == TaurusExampleDmsLabelController:
 5            return ctrl
 6
 7        # if there is a controller object and it is still the same class...
 8        ctrl_klass = self._calculate_controller_class()
 9        if ctrl is not None and ctrl.__class__ == ctrl_klass:
10            return ctrl
11
12        self._controller = ctrl = ctrl_klass(self)
13        return ctrl
1    def controllerUpdate(self):
2        ctrl = self.controller()
3        if ctrl is not None:
4            ctrl.update()

These three methods deal with the controller for the Widget They provides a manner to retrive the proper controller for a given situation. In most cases, the implementation will look similar.

1    def handleEvent(self, evt_src, evt_type, evt_value):
2        ctrl = self.controller()
3        if ctrl is not None:
4            self.controller().handleEvent(evt_src, evt_type, evt_value)

The handleEvent method forwards the event information to the controller. This allows to move the manipulation of new values away from the widget into the controller.

1    def setModel(self, m, **kwargs):
2        # force to build another controller
3        self._controller = None
4        TaurusBaseWidget.setModel(self, m, **kwargs)
5        if self.modelFragmentName:
6            self.setFgRole(self.modelFragmentName)

The setModel implementation calls its ancestors implementation; the main difference is that it forces the re-creation of the controller, in order to react to possible need of a different kind of controller.

1    def isReadOnly(self):
2        return True

Returning True in isReadOnly indicates that this widget provides no write capability.

The rest of the implementation is declaration of properties, which is already explained in Common to all kind of widgets.

Tip

If this is your first time adapting a widget to a databinding layer such as Taurus, we recommend that you follow the simple solution first. You can try later to follow the more complete solution.