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
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
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.
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.