1

I'm designing a HasTraits subclass with dependent properties:

#!/usr/bin/env python

# Example for SO question on dynamically changing Dict contents.
from traits.api   import HasTraits, Dict, Property, Trait, Int, cached_property
from traitsui.api import View, Item

class Foo(HasTraits):
    "Has dependent properties, which I'd like to remain up-to-date in the GUI."

    _dicts = [
        {"zero": 0, "one": 1},
        {"zero": 1, "one": 2},
        {"zero": 2, "one": 3},
    ]

    zap = Int(0)
    bar = Property(Trait, depends_on=["zap"])
    baz = Trait(list(_dicts[0])[0], _dicts[0])

    @cached_property
    def _get_bar(self):
        return Trait(list(self._dicts)[self.zap], self._dicts)

    traits_view = View(
        Item("zap"),
        Item("bar"),
        Item("baz"),
        width=500,
        )

if __name__ == '__main__':
    Foo().configure_traits()

When I run this code I see:

Initial GUI

And if I change the value of Zap:

After changing Zap

Note the following:

  1. After changing Zap, the address of Bar has changed.

    This means that changes to Bar are being dynamically updated in the GUI, while it's still opened; that's great! However...

  2. The way Bar is displayed in the GUI is not very useful.

    I'd love to have Bar displayed as Baz is displayed: selectable by the user.

What I'd like is to have the best of both worlds:

  • the dynamic GUI updating I see with Bar, and
  • the display format of Baz.

Does anyone know how I can get this?

I've tried several ways of updating a Baz-like item dynamically, to no avail. (See this previous SO question.)

dbanas
  • 1,707
  • 14
  • 24

2 Answers2

1

I assume you wish both bar and baz to be dict type (in traits Dict). Actually, there are default display widgets for pre-defined trait types, which are more useful than showing address. I believe traitsui doesn't know how to properly display your custom Trait object unless you explicitly assign an editor for it. Note that for baz, although a dropdown menu is generated, it is only displaying the keys, which is not very useful either.

With that said, the following codes might meet your expectations.

class Foo(HasTraits):
    "Has dependent properties, which I'd like to remain up-to-date in the GUI."

    _dicts = [
        {"zero": 0, "one": 1},
        {"zero": 1, "one": 2},
        {"zero": 2, "one": 3},
    ]

    zap = Int(0)
    bar = Property(Dict, depends_on=["zap"])
    baz = Trait(list(_dicts[0])[0], _dicts[0])

    @cached_property
    def _get_bar(self):
        return self._dicts[self.zap]

    traits_view = View(
        Item("zap"),
        Item("bar", style="custom"),
        Item("baz"),
        width=500,
        )
BroCannon
  • 13
  • 3
  • This answer has the fundamental problem right: There is no default viewer in TraitsUI for a generic `Trait` type. You can get what you want by using a built-in Trait, as @BroCannon shows, or by writing your own class with a `default_traits_view`. – Tim D Aug 21 '20 at 18:26
1

The following code gets me the behavior I want:

#!/usr/bin/env python

# Example for SO question on dynamically changing Dict contents.
from traits.api   import HasTraits, Dict, Property, Trait, Int, cached_property, Enum, List
from traitsui.api import View, Item

class Foo(HasTraits):
    "Has dependent properties, which I'd like to remain up-to-date in the GUI."

    _dict = {
        "zero": 0,
        "one":  1,
        "two":  2,
    }

    _zaps = [
        ["zero", "one"],        
        ["one",  "two"],
        ["zero", "two"],
    ]
    zaps = List(_zaps[0])
    zap  = Enum([0,1,2])  # Selection of `zap` should limit the items of `_dict` available for selection.
    bar  = Enum(_zaps[0][0], values="zaps")
    bar_ = Int(_dict[_zaps[0][0]])

    def _zap_changed(self, new_value):
        self.zaps = self._zaps[new_value]
        self.bar_ = self._dict[self.bar]

    def _bar_changed(self, new_value):
        self.bar_ = self._dict[self.bar]

    traits_view = View(
        Item("zap"),
        Item("bar"),
        Item("bar_", style="readonly"),
        width=500,
        )

if __name__ == '__main__':
    Foo().configure_traits()

Immediately after program start-up:

enter image description here

And after changing to Zap to '1':

enter image description here

dbanas
  • 1,707
  • 14
  • 24
  • 1
    I found this solution here: https://docs.enthought.com/traits/traits_user_manual/defining.html, at the end of the section just before the "Trait Metadata" section. – dbanas Nov 28 '19 at 14:27