1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492
|
.. _controlling-the-interface-the-handler:
======================================
Controlling the Interface: the Handler
======================================
Most of the material in the preceding chapters is concerned with the
relationship between the model and view aspects of the :term:`MVC` design
pattern as supported by TraitsUI. This chapter examines the third aspect: the
:term:`controller`, implemented in TraitsUI as an :term:`instance` of the
:term:`Handler` class. [11]_
A controller for an MVC-based application is essentially an event handler for
GUI events, i.e., for events that are generated through or by the program
interface. Such events can require changes to one or more model objects (e.g.,
because a data value has been updated) or manipulation of the interface itself
(e.g., window closure, dynamic interface behavior). In TraitsUI, such actions
are performed by a Handler object.
In the preceding examples in this guide, the Handler object has been implicit:
TraitsUI provides a default Handler that takes care of a common set of GUI
events including window initialization and closure, data value updates, and
button press events for the standard TraitsUI window buttons (see
:ref:`command-buttons-the-buttons-attribute`).
This chapter explains the features of the TraitsUI Handler, and shows how to
implement custom GUI behaviors by building and instantiating custom subclasses
of the Handler class. The final section of the chapter describes several
techniques for linking a custom Handler to the window or windows it is designed
to control.
.. _backstage-introducing-the-uiinfo-object:
Backstage: Introducing the UIInfo Object
----------------------------------------
TraitsUI supports the MVC design pattern by maintaining the model, view, and
controller as separate entities. A single View object can be used to construct
windows for multiple model objects; likewise a single Handler can handle GUI
events for windows created using different Views. Thus there is no static link
between a Handler and any particular window or model object. However, in order
to be useful, a Handler must be able to observe and manipulate both its
corresponding window and model objects. In TraitsUI, this is accomplished by
means of the UIInfo object.
Whenever TraitsUI creates a window or panel from a View, a UIInfo object is
created to act as the Handler's reference to that window and to the objects
whose :term:`trait attribute`\ s are displayed in it. Each entry in the View's
context (see :ref:`the-view-context`) becomes an attribute of the UIInfo
object. [12]_ For example, the UIInfo object created in
:ref:`Example 7 <example-7-using-a-multi-object-view-with-a-context>`
has attributes **h1** and **h2** whose values are the objects **house1** and
**house2** respectively. In :ref:`Example 1 <example-1-using-configure-traits>`
through
:ref:`Example 6 <example-6-defining-multiple-view-objects-in-a-hastraits-class>`,
the created UIInfo object has an attribute **object** whose value is the object
**sam**.
Whenever a window event causes a Handler method to be called, TraitsUI passes
the corresponding UIInfo object as one of the method arguments. This gives the
Handler the information necessary to perform its tasks.
.. _assigning-handlers-to-views:
Assigning Handlers to Views
---------------------------
In accordance with the MVC design pattern, Handlers and Views are separate
entities belonging to distinct classes. In order for a custom Handler to provide
the control logic for a window, it must be explicitly associated with the View
for that window. The TraitsUI package provides three ways to accomplish this:
- Make the Handler an attribute of the View.
- Provide the Handler as an argument to a display method such as edit_traits().
- Define the View as part of the Handler.
.. _binding-a-singleton-handler-to-a-view:
Binding a Singleton Handler to a View
`````````````````````````````````````
To associate a given custom Handler with all windows produced from a given View,
assign an instance of the custom Handler class to the View's **handler**
attribute. The result of this technique, as shown in
:ref:`Example 9 <example-9-using-a-handler-that-reacts-to-trait-changes>`, is
that the window created by the View object is automatically controlled by the
specified handler instance.
.. _linking-handler-and-view-at-edit-time:
Linking Handler and View at Edit Time
`````````````````````````````````````
It is also possible to associate a custom Handler with a specific window without
assigning it permanently to the View. Each of the three TraitsUI
window-building methods (the configure_traits() and edit_traits() methods of the
HasTraits class and the ui() method of the View class) has a *handler* keyword
argument. Assigning an instance of Handler to this argument gives that handler
instance control *only of the specific window being created by the method call*.
This assignment overrides the View's **handler** attribute.
.. _creating-a-default-view-within-a-handler:
Creating a Default View Within a Handler
````````````````````````````````````````
You seldom need to associate a single custom Handler with several different
Views or vice versa, although you can in theory and there are cases where it is
useful to be able to do so. In most real-life scenarios, a custom Handler is
tailored to a particular View with which it is always used. One way to reflect
this usage in the program design is to define the View as part of the Handler.
The same rules apply as for defining Views within HasTraits objects; for
example, a view named 'trait_view' is used as the default view.
The Handler class, which is a subclass of HasTraits, overrides the standard
configure_traits() and edit_traits() methods; the subclass versions are
identical to the originals except that the Handler object on which they are
called becomes the default Handler for the resulting windows. Note that for
these versions of the display methods, the *context* keyword parameter is not
optional.
.. _handler-subclasses:
Handler Subclasses
------------------
TraitsUI provides two Handler subclasses: ModelView and Controller. Both of
these classes are designed to simplify the process of creating an MVC-based
application.
Both ModelView and Controller extend the Hander class by adding the following
trait attributes:
- **model**: The model object for which this handler defines a view and
controller.
- **info**: The UIInfo object associated with the actual user interface window
or panel for the model object.
The **model** attribute provides convenient access to the model object
associated with either subclass. Normally, the **model** attribute is set in the
constructor when an instance of ModelView or Controller is created.
The **info** attribute provides convenient access to the UIInfo object
associated with the active user interface view for the handler object. The
**info** attribute is automatically set when the handler object's view is
created.
Both classes' constructors accept an optional *model* parameter, which is the
model object. They also can accept metadata as keyword parameters.
.. class:: ModelView( [model = None, **metadata] )
.. class:: Controller( [model = None, **metadata] )
The difference between the ModelView and Controller classes lies in the context
dictionary that each one passes to its associated user interface, as described
in the following sections.
.. _controller-class:
Controller Class
````````````````
The Controller class is normally used when implementing a standard MVC-based
design, and plays the "controller" role in the MVC design pattern. The "model"
role is played by the object referenced by the Controller's **model** attribute;
and the "view" role is played by the View object associated with the model
object.
The context dictionary that a Controller object passes to the View's ui() method
contains the following entries:
- ``object``: The Controller's model object.
- ``controller``: The Controller object itself.
Using a Controller as the handler class assumes that the model object contains
most, if not all, of the data to be viewed. Therefore, the model object is used
for the object key in the context dictionary, so that its attributes can be
easily referenced with unqualified names (such as Item('name')).
.. _modelview-class:
ModelView Class
```````````````
The ModelView class is useful when creating a variant of the standard MVC
design pattern. In this variant, the ModelView subclass reformulates a number
of trait attributes on its model object as properties on the ModelView, usually
to convert the model's data into a format that is more suited to a user
interface.
The context dictionary that a ModelView object passes to the View's ui() method
contains the following entries:
- ``object``: The ModelView object itself.
- ``model``: The ModelView's model object.
In effect, the ModelView object substitutes itself for the model object in
relation to the View object, serving both the "controller" role and the "model"
role (as a set of properties wrapped around the original model). Because the
ModelView object is passed as the context's object, its attributes can be
referenced by unqualified names in the View definition.
.. _writing-handler-methods:
Writing Handler Methods
-----------------------
If you create a custom Handler subclass, depending on the behavior you want to
implement, you might override the standard methods of Handler, or you might
create methods that respond to changes to specific trait attributes.
.. _overriding-standard-methods:
Overriding Standard Methods
```````````````````````````
The Handler class provides methods that are automatically executed at certain
points in the lifespan of the window controlled by a given Handler. By
overriding these methods, you can implement a variety of custom window
behaviors. The following sequence shows the points at which the Handler methods
are called.
1. A UIInfo object is created
2. The Handler's init_info() method is called. Override this method if the
handler needs access to viewable traits on the UIInfo object whose values
are properties that depend on items in the context being edited.
3. The UI object is created, and generates the actual window.
4. The init() method is called. Override this method if you need to initialize
or customize the window.
.. TODO: Add a non-trivial example here.
5. The position() method is called. Override this method to modify the position
of the window (if setting the x and y attributes of the View is insufficient).
6. The window is displayed.
.. _when-handler-methods-are-called-and-when-to-override-them-table:
.. rubric:: When Handler methods are called, and when to override them
+---------------------------+--------------------------+-----------------------+
|Method |Called When |Override When? |
+===========================+==========================+=======================+
|apply(info) |The user clicks the |To perform additional |
| |:guilabel:`Apply` button, |processing at this |
| |and after the changes have|point. |
| |been applied to the | |
| |context objects. | |
+---------------------------+--------------------------+-----------------------+
|close(info, is_ok) |The user requests to close|To perform additional |
| |the window, clicking |checks before |
| |:guilabel:`OK`, |destroying the window. |
| |:guilabel:`Cancel`, or the| |
| |window close button, menu,| |
| |or icon. | |
+---------------------------+--------------------------+-----------------------+
|closed(info, is_ok) |The window has been |To perform additional |
| |destroyed. |clean-up tasks. |
+---------------------------+--------------------------+-----------------------+
|revert(info) |The user clicks the |To perform additional |
| |:guilabel:`Revert` button,|processing. |
| |or clicks | |
| |:guilabel:`Cancel` in a | |
| |live window. | |
+---------------------------+--------------------------+-----------------------+
|setattr(info, object, name,|The user changes a trait |To perform additional |
|value) |attribute value through |processing, such as |
| |the user interface. |keeping a change |
| | |history. Make sure that|
| | |the overriding method |
| | |actually sets the |
| | |attribute. |
+---------------------------+--------------------------+-----------------------+
|show_help(info, |The user clicks the |To call a custom help |
|control=None) |:guilabel:`Help` button. |handler in addition to |
| | |or instead of the |
| | |global help handler, |
| | |for this window. |
+---------------------------+--------------------------+-----------------------+
.. _reacting-to-trait-changes:
Reacting to Trait Changes
`````````````````````````
The setattr() method described above is called whenever any trait value is
changed in the UI. However, TraitsUI also provides a mechanism for calling
methods that are automatically executed whenever the user edits a *particular*
trait. While you can use static notification handler methods on the HasTraits
object, you might want to implement behavior that concerns only the user
interface. In that case, following the MVC pattern dictates that such behavior
should not be implemented in the "model" part of the code. In keeping with this
pattern, TraitsUI supports "user interface notification" methods, which must
have a signature with the following format:
.. method:: extended_traitname_changed(info)
This method is called whenever a change is made to the attribute specified by
*extended_traitname* in the **context** of the View used to create the window
(see :ref:`multi-object-views`), where the dots in the extended trait reference
have been replaced by underscores. For example, for a method to handle changes
on the **salary** attribute of the object whose context key is 'object' (the
default object), the method name should be object_salary_changed().
By contrast, a subclass of Handler for
:ref:`Example 7 <example-7-using-a-multi-object-view-with-a-context>` might
include a method called h2_price_changed() to be called whenever the price of
the second house is edited.
.. note:: These methods are called on window creation.
User interface notification methods are called when the window is first
created.
To differentiate between code that should be executed when the window is first
initialized and code that should be executed when the trait actually changes,
use the **initialized** attribute of the UIInfo object (i.e., of the *info*
argument)::
def object_foo_changed(self, info):
if not info.initialized:
#code to be executed only when the window is
#created
else:
#code to be executed only when 'foo' changes after
#window initialization}
#code to be executed in either case
The following script, which annotates its window's title with an asterisk ('*')
the first time a data element is updated, demonstrates a simple use of both an
overridden setattr() method and user interface notification method.
.. _example-9-using-a-handler-that-reacts-to-trait-changes:
.. rubric:: Example 9: Using a Handler that reacts to trait changes
::
# handler_override.py -- Example of a Handler that overrides
# setattr(), and that has a user interface
# notification method
from traits.api import HasTraits, Bool
from traitsui.api import View, Handler
class TC_Handler(Handler):
def setattr(self, info, object, name, value):
Handler.setattr(self, info, object, name, value)
info.object._updated = True
def object__updated_changed(self, info):
if info.initialized:
info.ui.title += "*"
class TestClass(HasTraits):
b1 = Bool
b2 = Bool
b3 = Bool
_updated = Bool(False)
view1 = View('b1', 'b2', 'b3',
title="Alter Title",
handler=TC_Handler(),
buttons = ['OK', 'Cancel'])
tc = TestClass()
tc.configure_traits(view=view1)
.. image:: images/alter_title_before.png
:alt: Dialog box with empty checkboxes and a title of "Alter Title"
.. figure:: images/alter_title_after.png
:alt: Dialog box with one filled checkbox and a title of "Alter Title*"
Figure 7: Before and after views of Example 9
.. _implementing-custom-window-commands:
Implementing Custom Window Commands
```````````````````````````````````
Another use of a Handler is to define custom window
actions, which can be presented as buttons, menu items, or toolbar buttons.
.. _actions:
Actions
:::::::
In TraitsUI, window commands are implemented as instances of the Action class.
Actions can be used in :term:`command button`\ s, menus, and toolbars.
Suppose you want to build a window with a custom **Recalculate** action. Suppose
further that you have defined a subclass of Handler called MyHandler to provide
the logic for the window. To create the action:
#. Add a method to MyHandler that implements the command logic. This method can
have any name (e.g., do_recalc()), but must accept exactly one argument: a
UIInfo object.
#. Create an Action instance using the name of the new method, e.g.::
recalc = Action(name = "Recalculate",
action = "do_recalc")
.. _custom-command-buttons:
Custom Command Buttons
::::::::::::::::::::::
The simplest way to turn an Action into a window command is to add it to the
**buttons** attribute for the View. It appears in the button area of the window,
along with any standard buttons you specify.
#. Define the handler method and action, as described in :ref:`actions`.
#. Include the new Action in the **buttons** attribute for the View::
View ( #view contents,
# ...,
buttons = [ OKButton, CancelButton, recalc ])
.. _menus-and-menu-bars:
Menus and Menu Bars
:::::::::::::::::::
Another way to install an Action such as **recalc** as a window command is to
make it into a menu option.
#. Define the handler method and action, as described in :ref:`actions`.
#. If the View does not already include a MenuBar, create one and assign it to
the View's **menubar** attribute.
#. If the appropriate Menu does not yet exist, create it and add it to the
MenuBar.
#. Add the Action to the Menu.
These steps can be executed all at once when the View is created, as in the
following code::
View ( #view contents,
# ...,
menubar = MenuBar(
Menu( my_action,
name = 'My Special Menu')))
.. _toolbars:
Toolbars
::::::::
A third way to add an action to a Traits View is to make it a button on a
toolbar. Adding a toolbar to a Traits View is similar to adding a menu bar,
except that toolbars do not contain menus; they directly contain actions.
1. Define the handler method and the action, as in :ref:`actions`, including a
tooltip and an image to display on the toolbar. The image must be a Pyface
ImageResource instance; if a path to the image file is not specified, it is
assumed to be in an images subdirectory of the directory where ImageResource
is used::
From pyface.api import ImageResource
recalc = Action(name = "Recalculate",
action = "do_recalc",
toolip = "Recalculate the results",
image = ImageResource("recalc.png"))
2. If the View does not already include a ToolBar, create one and assign it to
the View's **toolbar** attribute.
3. Add the Action to the ToolBar.
As with a MenuBar, these steps can be executed all at once when the View is
created, as in the following code::
View ( #view contents,
# ...,
toolbar = ToolBar( my_action))
.. rubric:: Footnotes
.. [11] Except those implemented via the **enabled_when**, **visible_when**,
and **defined_when** attributes of Items and Groups.
.. [12] Other attributes of the UIInfo object include a UI object and any
*trait editors* contained in the window (see
:ref:`introduction-to-trait-editor-factories` and
:ref:`the-predefined-trait-editor-factories`).
|