.. _traitslikecontextwrapper: TraitslikeContextWrapper ======================== As noted, DataContexts are often put into the role of Model in a MVC UI. However, the DataContext namespace doesn't have Traits information associated with it, which can be an obstacle to its use in a Traits UI. For fairly homogeneous namespaces, or those where it is hard to know what variables will be present, one approach is to extract and wrap the individual items in the DataContext namespace and use them directly in the UI (often in a TableEditor). However, sometimes we want the DataContext itself to appear like a regular HasTraits object. This approach involves listening to the events generated by the DataContext and using them to keep local copies of the DataContext's items synchronised with it. This pattern is sufficiently common and useful that the TraitslikeContextWrapper class is available to simplify this procedure. To use the TraitslikeContextWrapper, you need to use the :meth:`add_traits` method to tell it which names in the Context should appear as traits:: >>> from traits.api import Int >>> from traitsui.api import View, Item >>> from traitsui.menu import OKButton, CancelButton >>> from codetools.contexts.api import DataContext, TraitslikeContextWrapper >>> d = DataContext(subcontext={'a': 1, 'b': 2, 'z': 20}) >>> tcw = TraitslikeContextWrapper(_context=d) >>> tcw.add_traits('a', 'b', c=Int) >>> d.items() [('a', 1), ('c', 0), ('b', 2), ('z', 20)] >>> view = View(Item(name='a'), Item(name='b'), Item(name='c'), ... buttons = [OKButton, CancelButton]) >>> tcw.configure_traits(view=view) .. image:: tcw_1.png As can be seen from the window, the TraitslikeContextWrapper makes the wrapped object act just like a regular HasTraits object. The example also demonstrates that you can add items into the DataContext object via the :meth:`add_traits` call, and that you can specify trait types in the call (i.e., ``c=Int``). Example: Simple Block Context Application ----------------------------------------- Putting TraitslikeContextWrapper together with the Block-Context-Execution Manager pattern, we can easily create simple Traits UI applications around a code block. The following is a simple but general application that can be found in the CodeTools examples:: """Simple Block Context Application This application demonstrates the use of the Block-Context-Execution Manager pattern, together with using a TraitslikeContextWrapper to make items inside a data context appear like traits so that they can be used in a TraitsUI app. """ from traits.api import HasTraits, Instance, Property, Float, \ on_trait_change, cached_property from traitsui.api import View, Group, Item from codetools.contexts.api import DataContext, TraitslikeContextWrapper from codetools.contexts.items_modified_event import ItemsModified from codetools.blocks.api import Block code = """# my calculations velocity = distance/time momentum = mass*velocity """ class SimpleBlockContextApp(HasTraits): # the data context we are listening to data = Instance(DataContext) # the block we are executing block = Instance(Block) # a wrapper around the data to interface with the UI tcw = Property(Instance(TraitslikeContextWrapper), depends_on=["block", "data"]) # a view for the wrapper tcw_view = Property(Instance(View), depends_on="block") @on_trait_change('data.items_modified') def data_items_modified(self, event): """Execute the block if the inputs in the data change""" if isinstance(event, ItemsModified): changed = set(event.added + event.modified + event.removed) inputs = changed & self.block.inputs if inputs: self.execute(inputs) @cached_property def _get_tcw_view(self): """Getter for tcw_view: returns View of block inputs and outputs""" inputs = tuple(Item(name=input) for input in sorted(self.block.inputs)) outputs = tuple(Item(name=output, style="readonly") for output in sorted(self.block.outputs)) return View(Group(*(inputs+outputs)), kind="live") @cached_property def _get_tcw(self): """Getter for tcw: returns traits-like wrapper for data context""" in_vars = dict((input, Float) for input in self.block.inputs) out_vars = tuple(self.block.outputs) tcw = TraitslikeContextWrapper(_context=self.data) tcw.add_traits(*out_vars, **in_vars) return tcw def execute(self, inputs): """Restrict the code block to inputs and execute""" # only execute if we have all inputs if self.block.inputs.issubset(set(self.data.keys())): try: self.block.restrict(inputs=inputs).execute(self.data) except: # ignore exceptions in the block pass if __name__ == "__main__": block = Block(code) data = DataContext(subcontext=dict(distance=10.0, time=2.5, mass=3.0)) execution_manager = SimpleBlockContextApp(block=block, data=data) execution_manager.tcw.configure_traits(view=execution_manager.tcw_view) The interface looks like this: .. image:: tcw_2.png Notice that the SimpleBlockContextApp has no explicit knowledge of the contents either of the Block or of the DataContext other than expecting floats for the input variable values. If the code variable were replaced with any other code block, the code would work just as well.