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
|
.. _tut_person:
.. highlight:: enaml
Person tutorial
===============================================================================
This tutorial expands on the :ref:`"Hello World" Tutorial <tut_hello_world>` to
introduce the concepts of reusable component declarations and components from
the standard widget library in Enaml. It sets up a GUI with the name and age
of a person.
Here is the Enaml file (:download:`download here
<../../../examples/tutorial/person/person_view.enaml>`):
.. literalinclude:: ../../../examples/tutorial/person/person_view.enaml
:language: enaml
Here is the Python code (:download:`download here
<../../../examples/tutorial/person/person.py>`):
.. literalinclude:: ../../../examples/tutorial/person/person.py
:language: python
The resulting GUI looks like this (on Windows 7):
.. image:: images/tut_john_doe.png
Enaml Imports
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
This ``.enaml`` file begins like the :ref:`"Hello World" <tut_hello_world>` example
with comments, but next we see that we can import other ``.enaml`` files in Enaml,
just like we can import ``.py`` files.::
from enaml.stdlib.fields import IntField
In this case, we are importing the integer field widget ``IntField`` from
Enaml's standard widget library. This widget
lets us assign an integer to the ``value`` attribute of the widget. The
widget automatically converts to and from the ``text`` representation of the
integer complete with validation and error checking.
Note that this import points to a widget definition in an ``.enaml`` file.
*The import statement looks like Python but imports from an* ``.enaml`` *file.*
``PersonForm`` Definition Block
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Next, there is a **component definition** block. We define a component, in this
case an entry form, using a component hierarchy definition. With this block of
code, we define a reusable component derived from other components.
::
enamldef PersonForm(Form):
attr person
Label:
text = 'First Name'
Field:
text := person.first_name
Label:
text = 'Last Name'
Field:
text := person.last_name
Label:
text = 'Age'
IntField:
minimum = 0
value := person.age
A component definition block header line begins with ``enamldef`` followed by
the name of the component followed by the base component or widget from which
it inherits. *A widget defined with* ``enamldef`` *must inherit from a builtin
widget or another* ``enamldef``. The header line ends with a colon::
enamldef PersonForm(Form):
Indented under the header line are statements declaring either attributes or
children. ``attr person`` declares a ``person`` attribute of ``PersonForm``.
Because no default value is specified, this attribute must be supplied by code
which uses the ``PersonForm``.
Built-in Components
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Next, we add a series of labels and fields. ``Form``, ``Label`` and ``Field``
are all from the library of Enaml built-in widgets.
:py:class:`~enaml.widgets.form.Form` is a built-in container that arranges
alternating child components into two columns. This is typically done in the
way seen here with alternating ``Label`` and widget children, though there
are no restrictions on the types of widgets which can be used. In a form with
an odd number of components, the last component spans both columns.
:py:class:`~enaml.widgets.label.Label` is a built-in component for displaying
read-only text.
:py:class:`~enaml.widgets.field.Field` is a built-in widget for entering a text
value. Field is used as the base component for many other components that do
type conversions.
Delegation Operator :=
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
In the ``Field`` code block, we notice a new operator ``:=``. This is the
delegation operator, one of the five special operators in
the Enaml grammar. It sets up a two-way synchronization
between the objects on the left-hand and right-hand sides. That is, changes to
the value of the ``text`` field in the GUI widget are applied to the value of
``person.first_name``, and changes to the value of ``person.first_name`` are
displayed in the GUI component.
Standard Library of Derived Components
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
The ``IntField`` component is derived from ``Field`` and provides
string-to-integer conversion, validation, and error reporting functions. By
using the ``IntField`` component, we add validation to the GUI, as shown in the
example below, where a non-integer value was entered in the age field:
.. image:: images/tut_john_doe_error.png
``PersonView`` Definitions Block
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Now, with another ``enamldef`` block, we can make a view available using our
previously declared ``PersonForm``. If we wanted to, we could add ``PersonForm``
many times over in this view or any other view, but for now we'll keep it
simple. Note that we will pass a ``person`` object to the view when we create
an instance of it.
::
enamldef PersonView(Window):
attr person
PersonForm:
person = parent.person
Now, on to the Python code.
Atom Object
-------------------------------------------------------------------------------
Enaml is designed to be model framework independent and ships with a formal
API for attaching to any Python model framework which provides notification of
state change. However, Enaml itself is built with Atom and will work
with `Atom objects <https://github.com/nucleic/atom/>`_ out of the
box. The important thing to note is that the ``Person`` attribute names match
the attribute names of the ``person`` object used by the ``PersonForm`` in the
.enaml file.
::
class Person(Atom):
""" A simple class representing a person object.
"""
last_name = Str()
first_name = Str()
age = Range(low=0)
debug = Bool(False)
@observe('age')
def debug_print(self, change):
""" Prints out a debug message whenever the person's age changes.
"""
if self.debug:
templ = "{first} {last} is {age} years old."
s = templ.format(
first=self.first_name, last=self.last_name, age=self.age,
)
print s
Note that our ``Person`` class is designed to print out the name and age of the
person when the ``age`` attribute changes.
Hooking up an Enaml View to an Atom Object
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
In the code block for launching the script from the command line, we create a
``Person`` object and create an application which serves it using the
``PersonView`` for the GUI::
if __name__ == '__main__':
with enaml.imports():
from person_view import PersonView
john = Person(first_name='John', last_name='Doe', age=42)
john.debug = True
app = QtApplication()
view = PersonView(person=john)
view.show()
app.start()
Running it from the command line, we see
.. code-block:: shell
$ python person.py
.. image:: images/tut_john_doe.png
We can then make a change in the GUI and see::
John Doe Jr. is 22 years old.
.. image:: images/tut_john_doe_jr.png
|