Window Layout

First and foremost, the Tasks plugin is designed to facilitate the layout of modern, customizable user interfaces. Accordingly, we shall begin by learning how to use Tasks to perform basic window layout.

Lifecycle of a Task

The Tasks plugin adheres to the model-view-controller (MVC) design pattern [1], for which the Task class functions as the controller and the TaskWindow class as the view, with the model being application-specific [2]. Insofar as a Task is a controller, it does not itself instantiate or contain any GUI controls, although it does provide factories and a default layout for the controls that it manages. Controls are instantiated when a Task is assigned to a TaskWindow. Specifically, the following actions are taken at this time:

  1. The task’s central pane is constructed and attached to the window

  2. All of the task’s dock panes are constructed, even if they are not by default visible

  3. The visible dock panes are laid out around the central pane according to the task’s default layout or persisted UI state information

While the Task remains connected to the TaskWindow, neither its central pane nor its dock panes will be destroyed. If the user “closes” a dock pane, the pane will be merely hidden, and can be re-activated via the application’s View menu. Only when the Task itself is closed—that is, when it is removed from the TaskWindow—will its panes be destroyed. This marks the end of the Task’s lifecycle. If the task is to be re-used, a new instance should be constructed.

Defining a Task

Minimally, a task is defined by subclassing Task and providing a central pane. For example, we define a task for editing Python scripts:

from pyface.tasks.api import Task

class ExampleTask(Task):

    id = 'example.example_task'

    name = 'Python Script Editor'

    def create_central_pane(self):
        return PythonEditorPane()

The id attribute is a unique internal identifier for the task, whereas name is a user-visible descriptor for the task. We shall see that this is a common pattern in the Tasks framework.

All tasks must implement create_central_pane() and return a TaskPane instance. We might define the PythonEditorPane pane above as follows, making use of the PythonEditor available in Pyface:

from pyface.api import PythonEditor
from pyface.tasks.api import TaskPane
from traits.api import Instance

class PythonEditorPane(TaskPane):

    id = 'example.python_editor_pane'

    name = 'Python Editor'

    editor = Instance(PythonEditor)

    def create(self, parent):
        self.editor = PythonEditor(parent)
        self.control = self.editor.control

    def destroy(self):
        self.editor.destroy()
        self.control = self.editor = None

Besides providing an ID and a name for the pane, we implement the two basic methods of TaskPane, namely create(parent) and destroy(). The former constructs a toolkit-specific control for the pane and assigns its to the pane’s control attribute. The latter performs the inverse operation, destroying the control and clearing control attribute. These methods give one full control over how a pane is constructed, but as we shall see below there are other, more convenient methods for defining a pane.

Defining a Dock Pane

Now we imagine that we are building a very primitive Python IDE and that we would like to add a dock pane for browsing the local filesystem. We could create a DockPane subclass similarly to the TaskPane above, implementing the create_contents(parent) method of DockPane to provide the toolkit-specific control for the file browser. But if we are familiar with Traits UI we see that it would be more convenient to use the Traits UI FileEditor for this purpose. The Tasks framework provides the TraitsDockPane class to facilitate this. We define the pane as follows:

from pyface.tasks.api import TraitsDockPane
from traits.api import Event, File, List, Str
from traitsui.api import View, Item, FileEditor

class FileBrowserPane(TraitsDockPane):

    #### TaskPane interface ###############################################

    id = 'example.file_browser_pane'
    name = 'File Browser'

    #### FileBrowserPane interface ########################################

    # Fired when a file is double-clicked.
    activated = Event

    # The list of wildcard filters for filenames.
    filters = List(Str)

    # The currently selected file.
    selected_file = File

    # The view used to construct the dock pane's widget.
    view = View(
        Item(
            'selected_file',
            editor=FileEditor(
                dclick_name='activated',
                filter_name='filters',
            ),
            style='custom',
            show_label=False
        ),
        resizable=True,
    )

When a control is needed for the pane, it will be constructed using the standard Traits UI mechanisms. There exist additional options, not described here, for specifying a model object, which is often important when building a complex application. There is also a TraitsTaskPane class that provides similar functionality for defining Traits-based central panes. As always, the reader is referred to the Tasks API documentation for more information.

Now let us amend the example task defined above with a create_dock_panes() method. This method returns the list of dock pane instances associated with the task. We also define a method on our task for opening a file in the editor, which we connect to the dock pane’s activated event:

class ExampleTask(Task):

    [ ... ]

    def create_dock_panes(self):
        """ Create the file browser and connect to its double click event.
        """
        browser = PythonScriptBrowserPane()
        handler = lambda: self.open_file(browser.selected_file)
        browser.on_trait_change(handler, 'activated')
        return [browser]

    def open_file(self, filename):
        """ Open the file with the specified path in the central pane.
        """
        self.window.central_pane.editor.path = filename

Providing a Default Layout

Although dock panes are designed to be moved around and otherwise manipulated by the user, we often have a particular default layout in mind when designing an application. The Tasks framework provides the TaskLayout class to make the specification of this layout possible. Usually, we are only concerned with four attributes of this class, namely left, right, bottom, and top. Each of these attributes may be assigned a layout item, which is either a PaneItem, for specifying a particular dock pane; a Tabbed item, containing other PaneItem instances; or a Splitter, containing arbitrary subitems.

A few examples should suffice to make this clear. To stack the dock pane with ID ‘dock_pane_1’ on top of that with ID ‘dock_pane_2’, with both to the left of the central pane, one specifies:

left = Splitter(
    PaneItem('dock_pane_1'),
    PaneItem('dock_pane_2'),
    orientation='vertical',
)

We could also have used VSplitter, which is a convenient abbreviation for a splitter with vertical orientation. Similarly, HSplitter is an abbrevation for a splitter with horizontal orientation.

To put these dock panes in tab group below the central pane, we might write:

bottom_panes = Tabbed(
    PaneItem('dock_pane_1', height=400),
    PaneItem('dock_pane_2'),
)

Observe that we have explicitly provided a height for the first dock pane. Provided that the dock pane’s underlying control does not have a conflicting minimum or maximum size constraint, Tasks guarantees that it will honor this height exactly. Of course, if width or height are not provided, Tasks will use the dock pane’s toolkit-specific size hint to determine its size.

Now we will provide our example task with a simple layout using the default_layout attribute of Task:

class ExampleTask(Task):

    [ ... ]

    default_layout = TaskLayout(
        left=PaneItem('example.python_script_browser_pane')
    )

Note that dock panes that do not appear in the layout will not be visible by default. A task without a default layout is equivalent to a task with an empty layout; in both cases, only the central pane will be visible by default. Finally, note that the layout behavior is undefined if a dock pane appears multiple times in a layout.

Footnotes