traitsui

configure_traits freezes console in Spyder/Anaconda


Trying the very first example in traitsui doc:

from traits.api import HasTraits, Str, Int
from traitsui.api import View, Item
import traitsui

class SimpleEmployee(HasTraits):
    first_name = Str
    last_name = Str
    department = Str
    employee_number = Str
    salary = Int

view1 = View(Item(name = 'first_name'),
             Item(name = 'last_name'),
             Item(name = 'department'))

sam = SimpleEmployee()
sam.configure_traits(view=view1)

makes the Spyder IPython console hang, even after the UI window has been closed.

MacOSX 10.14.6, Spyder 4.0.0, Python 3.7.0, IPython 7.10.2, traitsui 6.1.3

Probably something to configure about the UI event loop, but what and how ?


Solution

  • The difference between the Canopy IPython console and the Spyder IPython console is that

    app = QtGui.QApplication.instance()
    print(app)
    

    returns

    <PyQt5.QtWidgets.QApplication at 0xXXXXXXXX>
    

    for both, but

    is_event_loop_running_qt4(app)
    

    returns Truein Canopy IPython terminal and False in Spyder IPython terminal.

    It is necessary to patch view_application() in traits.qt4.view_application by commenting out the lines starting a new ViewApplication instance depending on that test. Instead of changing the original file, it is possible to run the following code:

    # -*- coding: utf-8 -*-
    
    """Avoid Spyder console to hang when using configure_traits()
    """
    
    from IPython import get_ipython
    
    from pyface.qt import QtCore, QtGui, qt_api
    from pyface.util.guisupport import is_event_loop_running_qt4
    
    from traitsui.api import toolkit
    from traitsui.qt4.view_application import ViewApplication
    
    KEEP_ALIVE_UIS = set()
    
    def on_ui_destroyed(object, name, old, destroyed):
        """ Remove the UI object from KEEP_ALIVE_UIS.
        """
        assert name == 'destroyed'
        if destroyed:
            assert object in KEEP_ALIVE_UIS
            KEEP_ALIVE_UIS.remove(object)
            object.on_trait_change(on_ui_destroyed, 'destroyed', remove=True)
    
    def _view_application(context, view, kind, handler, id, scrollable, args):
        """ Creates a stand-alone PyQt application to display a specified traits UI
            View.
    
        Parameters
        ----------
        context : object or dictionary
            A single object or a dictionary of string/object pairs, whose trait
            attributes are to be edited. If not specified, the current object is
            used.
        view : view object
            A View object that defines a user interface for editing trait attribute
            values.
        kind : string
            The type of user interface window to create. See the
            **traitsui.view.kind_trait** trait for values and
            their meanings. If *kind* is unspecified or None, the **kind**
            attribute of the View object is used.
        handler : Handler object
            A handler object used for event handling in the dialog box. If
            None, the default handler for Traits UI is used.
        scrollable : Boolean
            Indicates whether the dialog box should be scrollable. When set to
            True, scroll bars appear on the dialog box if it is not large enough
            to display all of the items in the view at one time.
    
    
        """
        if (kind == 'panel') or ((kind is None) and (view.kind == 'panel')):
            kind = 'modal'
    
        # !!!!!!! The following 4 lines commented out !!!!!!!!!!!
        # app = QtGui.QApplication.instance()
        # if app is None or not is_event_loop_running_qt4(app):
        #     return ViewApplication(context, view, kind, handler, id,
        #                            scrollable, args).ui.result
        # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    
        ui = view.ui(context,
                     kind=kind,
                     handler=handler,
                     id=id,
                     scrollable=scrollable,
                     args=args)
    
        # If the UI has not been closed yet, we need to keep a reference to
        # it until it does close.
        if not ui.destroyed:
            KEEP_ALIVE_UIS.add(ui)
            ui.on_trait_change(on_ui_destroyed, 'destroyed')
        return ui.result
    
    def view_application(self, context, view, kind=None, handler=None,
                         id='', scrollable=None, args=None):
        """ Creates a PyQt modal dialog user interface that
            runs as a complete application, using information from the
            specified View object.
    
        Parameters
        ----------
        context : object or dictionary
            A single object or a dictionary of string/object pairs, whose trait
            attributes are to be edited. If not specified, the current object is
            used.
        view : view or string
            A View object that defines a user interface for editing trait
            attribute values.
        kind : string
            The type of user interface window to create. See the
            **traitsui.view.kind_trait** trait for values and
            their meanings. If *kind* is unspecified or None, the **kind**
            attribute of the View object is used.
        handler : Handler object
            A handler object used for event handling in the dialog box. If
            None, the default handler for Traits UI is used.
        id : string
            A unique ID for persisting preferences about this user interface,
            such as size and position. If not specified, no user preferences
            are saved.
        scrollable : Boolean
            Indicates whether the dialog box should be scrollable. When set to
            True, scroll bars appear on the dialog box if it is not large enough
            to display all of the items in the view at one time.
    
        """
        return _view_application(context, view, kind, handler,
                                                 id, scrollable, args)
    
    from traitsui.qt4.toolkit import GUIToolkit
    
    GUIToolkit.view_application = view_application
    
    

    Thus, the behavior is OK in both Spyder and Canopy IPython consoles. Note that configure_traits(kind='modal') is also OK in both.