File: tut_employee.rst

package info (click to toggle)
python-enaml 0.19.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 13,284 kB
  • sloc: python: 31,443; cpp: 4,499; makefile: 140; javascript: 68; lisp: 53; sh: 20
file content (253 lines) | stat: -rw-r--r-- 9,816 bytes parent folder | download
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
.. _tut_employee:


.. highlight:: enaml

Employee tutorial
==============================================================================

This tutorial shows how we can build more complex and dynamic user interfaces
based on Enaml. It introduces the concepts of constraints and validators. It
sets up a GUI to edit employee details.


Here is the Enaml file (:download:`download here
<../../../examples/tutorial/employee/employee_view.enaml>`):

.. literalinclude:: ../../../examples/tutorial/employee/employee_view.enaml
    :language: enaml


Here is the Python code (:download:`download here
<../../../examples/tutorial/employee/employee.py>`):

.. literalinclude:: ../../../examples/tutorial/employee/employee.py
    :language: python

Here is the Python code for the phone number validator (:download:`download here
<../../../examples/tutorial/employee/phone_validator.py>`):

.. literalinclude:: ../../../examples/tutorial/employee/phone_validator.py
    :language: python


``EmployeeForm`` Definition block
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

This block summarizes most of the concepts seen in the previous tutorial. It
creates a new ``enamldef`` based on the :py:class:`~enaml.widgets.form.Form`
widget. Two attributes are exposed in the widget: an ``employee`` attribute and
a ``show_employer`` boolean value that defaults to True. The form itself contains
a set of :py:class:`~enaml.widgets.label.Label` widgets with associated
:py:class:`~enaml.widgets.field.Field` widgets.

Using validation on fields
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

The ``"Home phone:"`` field must be validated to make sure the user can't insert
a phone number that is not valid. The user interface must also signal the user
when the current entry is invalid.

A ``PhoneNumberValidator`` class implements the ``validate(...)`` method of the
:py:class:`~enaml.validation.validator.Validator` abstract class. If the
validation succeeds the returned value of the validate call is standardized
formatted text.


.. literalinclude:: ../../../examples/tutorial/employee/phone_validator.py
    :language: python

In the ``Field`` definition, every time the text is updated with a properly
validated entry, the employee phone attribute is updated. ::

    Field:
        validator = PhoneNumberValidator()
        text << '(%s) %s-%s' % employee.phone
        text ::
            match = validator.proper.match(text)
            if match:
                area = match.group(1)
                prefix = match.group(2)
                suffix = match.group(3)
                employee.phone = tuple(map(int, (area, prefix, suffix)))


Dynamic interaction with widgets
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

The widget attributes all support the special Enaml operators. One can thus
assign the result of arbitrary Python code to interact with the status of the
widget::

   Label:
        text = 'Password:'
    Field:
        echo_mode << 'password' if not pw_cb.checked else 'normal'
        text :: print 'Password:', text
    Label:
        text = 'Show Password:'
    CheckBox: pw_cb:
        checked = False


In this example, the user can activate or deactivate the ``echo_mode`` of the
password Field based on the state of another widget, the password ``CheckBox``.
The user can refer to the password :py:class:`~enaml.widgets.check_box.CheckBox`
using the `id` of the widget.

Visibility is controled with the ``visible`` attribute of a widget. In the
``EmployeeMainView``, the ``btm_box`` visibility is connected to the
``top_form.show_employer`` attribute. Enaml will take care of the related
relayout issues. See the constraints section for more information.

The very same pattern is used in the ``EmployerForm`` to enable or disable a
group of ``Field`` widgets baesd on a ``CheckBox``.


Customizing your layout
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Once you have created the components of your main view, you can assemble them
using the differeent containers:

 * :py:class:`~enaml.widgets.container.Container`,
 * :py:class:`~enaml.widgets.form.Form`,
 * :py:class:`~enaml.widgets.group_box.GroupBox`,


Those widgets take care of aranging the layout of the child widgets using a set
of constraints. In this tutorial, the only one that defines constraints is the
outer container::

    Container:
        constraints << [
            vertical(
                top, top_box, btm_box.when(btm_box.visible), spacer, bottom
            ),
            horizontal(left, spacer.flex(), top_box, spacer.flex(), right),
            horizontal(left, spacer.flex(), btm_box, spacer.flex(), right),
            align('midline', top_form, btm_form)
        ]

.. image:: images/tut_employee_layout.png


The constraints attribute of the :py:class:`~enaml.widgets.container.Container`
is populated with a list of constraints. The user expresses how he wants the
layout to be aranged:

 * a vertical constraint on the widgets named by id's.
 * two horizontal constraints on the widgets with spacers
 * a special constraint on the two forms that aligns their midline, the line
   between the two columns of the form. Note that we refer to the id's of the
   forms and not the ones of the ``GroupBox``. ``GroupBoxes`` do not have a
   ``midline`` attribute.

Using ``spacer``, you can add empty space between widgets. This space could
either be fixed  space or flexible when using ``spacer.flex()``. In this case,
the spacer will expose a weaker preference for being the fixed value. The
following set of constraints will make the first form compressed horizontally
by setting the target fixed size of the spacer to 50 pixels::

    Container:
        constraints << [
            vertical(
                top, top_box, btm_box.when(btm_box.visible), spacer, bottom
            ),
            horizontal(left, spacer(50).flex(), top_box, spacer(50).flex(), right),
            horizontal(left, spacer.flex(), btm_box, spacer.flex(), right),
            align('midline', top_form, btm_form)
        ]

Specialized containers can expose particular ways of managing their layout. The
:py:class:`~enaml.widgets.form.Form` exposes a ``midline`` attribute that can be
used to align the midline of different forms together. If it was not activated,
the layout would have been:

.. image:: images/tut_employee_layout_no_midline.png


Tweaking the layout
******************************************************************************

Enaml provides many different ways of tweaking the constraints to make sure the
layout engine gives you exactly what you want.

A user can give a weight to each constraint. Valid weights are: ``'weak'``,
``'medium'``, ``'strong'`` and ``'ignore'``. If the user wants to make the width
of the container equal to 233 pixels but with some latitude, he could add the
following constraint::

    Container:
        constraints << [
            vertical(
                top, top_box, btm_box.when(btm_box.visible), spacer, bottom
            ),
            horizontal(left, spacer.flex(), top_box, spacer.flex(), right),
            horizontal(left, spacer.flex(), btm_box, spacer.flex(), right),
            align('midline', top_form, btm_form),
            (width == 233) | 'weak'
        ]

The :py:class:`~enaml.widgets.container.Container` exposes some content related
attributes to the constraints system: ``width``, ``height``, ``left``, ``right``,
``bottom``, ``top``, ``v_center`` and ``h_center``. They can be used as shown
in the previous example.

Depending on the flexiblity you need, you might want to use some of the other
layout function like ``hbox`` or ``vbox``. You could have created a layout
pretty close to this one with the following constraints::

    Container:
        constraints = [
            vbox(top_box, btm_box.when(btm_box.visible)),
            align('midline', top_form, btm_form)
        ]

The advantage of using ``hbox`` and ``vbox`` is that you can nest them. The
``vertical`` and ``horizontal`` functions cannot be nested.

The set of constraints can be nested by using the ``hbox``, ``vbox`` or by
providing constraints of containers that belongs to the outer container. The
:py:class:`~enaml.widgets.group_box.GroupBox` provides some internal constraints
regarding its size to allow the title to be properly displayed. A
:py:class:`~enaml.widgets.form.Form` automatically lays out the widgets in two
columns. If the user wanted to have an ``EmployerForm`` laid out in two horizontal
rows in place of two columns, he could have edited the ``EmployerForm`` with the
following set of changes:

- update the base class to be a ``Container`` instead of a ``Form``
- provide ids for each of the child widgets
- provide a list of constraints for the desired layout
- remove the alignment constraint in the main container

::

    enamldef EmployerForm(Container):
        attr employer
        constraints = [
            vbox(
                hbox(cmp_lbl, mng_lbl, edit_lbl),
                hbox(cmp_fld, mng_fld, en_cb),
            ),
            cmp_lbl.width == cmp_fld.width,
            mng_lbl.width == mng_fld.width,
            edit_lbl.width == en_cb.width,
        ]
        Label: cmp_lbl:
            text = "Company:"
        Field: cmp_fld:
            text << employer.company_name
            enabled << en_cb.checked
        Label: mng_lbl:
            text = "Reporting Manager:"
        Field: mng_fld:
            text << "%s %s" % (employer.first_name, employer.last_name)
            enabled << en_cb.checked
        Label: edit_lbl:
            text = "Allow Editing:"
        CheckBox: en_cb:
            checked = True

.. image:: images/tut_employee_layout_nested_container.png