python-3.xenthoughttraitsui

How are views to be formulated for nested class instances?


I'm trying to create a user interface for a class that contains instances of other classes. Here is an illustrative example:

from traits.etsconfig.api import ETSConfig
ETSConfig.toolkit = 'qt4'
from   traits.api   import HasTraits, Instance, String
from   traitsui.api import Action, Controller, Item, View

import sys, traits, traitsui
print ('python version   :',sys.version)          # 3.6.0 (v3.6.0:41df79263a11, Dec 23 2016, 08:06:12) [MSC v.1900 64 bit (AMD64)]
print ('traits version   :',traits.__version__)   # 4.6.0
print ('traitsui version :',traitsui.__version__) # 5.1.0

class Name (HasTraits):
    first = String
    last  = String
    view  = View (Item('first'), Item('last'), title='Person Name', buttons=['OK','Cancel'])
    def __str__ (self):
        return '{} {}'.format(self.first,self.last)

class Person (HasTraits):
    name = Instance(Name)
    view = View (Item('name'), title='Person Info', buttons=['OK','Cancel'])
    def __str__ (self):
        return str(self.name)

class Datastore (HasTraits):
    def add (self, person:Person):
        print ('added', person)

class Contacts (Controller):
    add  = Action (name='Add', action='_add')
    view = View (title='Datastore', buttons=[add])

    def _add (self, info):
        person = Person (name=Name())
        save   = person.configure_traits (kind='modal')
        if save:
            self.model.add (person)
        else:
            print ('user canceled ADD action')

datastore  = Datastore()
controller = Contacts (model=datastore)
controller.configure_traits()

It seems I have to use kind=modal to pause execution in the Contacts._add method while the user enters information.

A typical user interaction is:

  1. Click the [Add] button in the Datastore dialog. The Person Info dialog appears.
  2. Click the [Name] button in the Person Info dialog. The Person Name dialog appears.

Problem is: the Person Name dialog can not be made active because Person Info has focus.

How can the desired behavior (giving Person Name focus) be achieved?

Thanks.


Solution

  • If you wish to have different windows for the different views (rather than one window with nested views), you could also make the call to person.configure_traits into a call to person.edit_traits and pass in a Handler that adds the Person instance to the data store when the window is closed.

    More specifically, I changed your controller to

    class Contacts (Controller):
        add  = Action (name='Add', action='_add')
        view = View (title='Datastore', buttons=[add])
    
        def _add (self, info):
            person = Person (name=Name())
            person.edit_traits(
                handler=PersonHandler(datastore=self.model)
            )
    

    The handler is passed a reference to the data store, and adds the person to it when the user clicks "OK":

    class PersonHandler(Handler):
        """ Handler class to add a person to a datastore.
        """
        datastore = Instance(Datastore)
    
        def close(self, info, is_ok):
            if is_ok:
                print('adding to datastore')
                self.datastore.add(info.object)
            else:
                print('not added')
            return super(PersonHandler, self).close(info, is_ok)
    

    Full code sample: https://gist.github.com/jvkersch/e72f2fb4ed72b2e9ba8269c6a6a2957a