0% encontró este documento útil (0 votos)
17 vistas

TutorialDaboParte 3

Este documento describe los primeros pasos para crear una aplicación Dabo desde cero para registrar tiempo de consultoría y generar facturas. Se explica cómo crear la estructura de directorios, conectarse a una base de datos MySQL y generar el archivo de conexión.

Cargado por

Emanuel Toro
Derechos de autor
© © All Rights Reserved
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd
0% encontró este documento útil (0 votos)
17 vistas

TutorialDaboParte 3

Este documento describe los primeros pasos para crear una aplicación Dabo desde cero para registrar tiempo de consultoría y generar facturas. Se explica cómo crear la estructura de directorios, conectarse a una base de datos MySQL y generar el archivo de conexión.

Cargado por

Emanuel Toro
Derechos de autor
© © All Rights Reserved
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd
Está en la página 1/ 32

DESARROLLANDO CON DABO

PARTE 3

CREAR UNA APLICACIÓN


DESDE CERO
Crear una Aplicación Dabo Desde Cero
Aun cuando el Asistente para Aplicaciones es poderoso, él esta diseñado para crear un tipo
particular de aplicaciones. Pero, por supuesto, no toda necesidad de una aplicación puede ser
resuelta con tal herramienta, y Dabo no tiene límites los tipos de aplicaciones que puede
crear. Vamos a construir desde cero una aplicación que funciona, y en el proceso, introducirlo
a usted a importantes conceptos dentro de la estructura de trabajo.

La Aplicación
Ambos, Paul y yo hemos hecho nuestras vidas como consultores, y como tales nosotros
hemos creado aplicaciones para otros y facturádosles por nuestro tiempo. Así que para este
ejercicio crearemos una aplicación parra registrar el tiempo empleado para varios clientes y
luego, con permiso del tiempo, una manera de generar las facturas para enviarlas a nuestros
clientes.

Los Datos
Hay dos tablas para esta aplicación: una para contener la información sobre los clientes, y
otra para registrar el tiempo empleado. En la vida real puede haber muchos tipos de tazas de
facturación, pero nosotros mantendremos esto sencillo y sólo trabajaremos con una
facturación por horas, con incrementos de cuarto de hora. Estas dos tablas se hallan en la
base de datos MySQL “pycon” como las tablas recipe que usamos antes.

Primeros Pasos
Empezaremos por determinar un directorio donde queremos crear nuestra aplicación. En
verdad no importa donde, así siga cualquier organización de archivos que acostumbre a usar.
Yo voy a usar el directorio ~/apps/pycon_hours en mi disco.

Abra una ventana terminal (Usuarios Windows, use Start|Run, y escriba 'cmd' para el nombre
del programa), cámbiese al que será el directorio padre de su aplicación; en este ejemplo,
sería ~/apps. Si yo fuera a ejecutar un listado de directorio obtendría algo como:

[ed@Homer ~]$ cd apps/


[ed@Homer ~/apps]$ ll
total 0
drwxr-xr-x 19 ed staff 646B Feb 2 12:20 pycon_appwizard
[ed@Homer ~/apps]$

Luego desde aquí ejecute el siguiente comando:

[ed@Homer ~/apps]$ python -c "import dabo; dabo.quickStart('pycon_hours')"


[ed@Homer ~/apps]$ ll
total 0
drwxr-xr-x 19 ed staff 646B Feb 2 12:20 pycon_appwizard
drwxr-xr-x 9 ed staff 306B Feb 5 15:25 pycon_hours

Nota. Como ahora tenemos un directorio llamado 'pycon_hours'. Cámbiese a ese directorio y
luego haga un listado de él:

[ed@Homer ~/apps]$ cd pycon_hours/


[ed@Homer ~/apps/pycon_hours]$ ll
total 8
drwxr-xr-x 3 ed staff 102B Feb 5 15:25 biz
drwxr-xr-x 3 ed staff 102B Feb 5 15:25 db
-rwxr--r-- 1 ed staff 282B Feb 5 15:25 main.py
drwxr-xr-x 3 ed staff 102B Feb 5 15:25 reports
drwxr-xr-x 3 ed staff 102B Feb 5 15:25 resources
drwxr-xr-x 3 ed staff 102B Feb 5 15:25 test
drwxr-xr-x 3 ed staff 102B Feb 5 15:25 ui
[ed@Homer ~/apps/pycon_hours]$

Este es la estructura de directorios principal de una aplicación Dabo, junto con un archivo
llamado main.py que se usa para lanzar su aplicación. De hecho, si usted corre main.py ahora mismo,
usted obtendrá una aplicación real en ejecución. Cierto, esta es sólo una ventana vacía y un
menú , pero corre! Esto es lo que contiene main.py:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import dabo
dabo.ui.loadUI("wx")
app = dabo.dApp()

# IMPORTANTE! Cambie el valor de app.MainFormClass al nombre


# de la clase de formulario que usted quiere ejecutar cuando su
# aplicación se inicie.
app.MainFormClass = dabo.ui.dFormMain

app.start()

El script es muy simple, pero contiene algunas cosas importantes por tomar en cuenta.
Primero, importamos el módulo dabo y llamamos al comando dabo.ui.loadUI("wx") para
mover todo el código específico de wxPython al espacio de nombres dabo.ui.
A continuación creamos una instancia de aplicación. Luego asignamos su propiedad
MainFormClass a la clase formulario principal predeterminada proporcionada por Dabo. Esto
le dice a la aplicación cual formulario mostrar desde el inicio.; cuando creemos nuestro propio
formulario para esta aplicación, vamos a cambiar esta por nuestra propia clase.
Finalmente, obtenemos que todo corra mediante la llamada a app.start(). Esto cargará todas
las definiciones de conexión, clases bizobj , clase ui, y cualquier otra cosa más que usted
haya creado, en el espacio de nombres de la aplicación, desplegará el formulario principal, y
luego inicia el ciclo principal de eventos de la IU. Cualquier código agregado después de esta
línea no será ejecutado hasta después de que la aplicación finalice.
El objeto aplicación es importante en Dabo: si una aplicación es una sinfonía de todas sus
partes, el objeto aplicación es el director. Para hacerle fácil codificar para referirse a este
objeto, hemos añadido una propiedad a todas las clases Dabo de manera que usted pueda
referirse a la aplicación usando self.Application.
Creación de la Conexión a la Base de Datos
Dabo tiene una herramienta visual para la creación y prueba de las conexiones a la base de
datos. Esta es denominada CxnEditor (abreviado de 'Connection Editor), y está localizada,
unto con las otras herramientas visuales , en el directorio 'ide'. Iníciela y verá un formulario
cuya apariencia es como la de la imagen de abajo. He introducido los valores que
necesitaremos para este tutorial; allí no puede leer el password, pero éste es 'atlanta'. Le di a
la conexión el nombre 'hours'; el nombre exacto que le usted le de no es importante, pero es
mejor darle algún nombre descriptivo, porque el código de su aplicación obtendrá esa
conexión refiriéndose a ese nombre. Usted puede almacenar múltiples conexiones en un solo
archivo, pero para ser honesto, yo aun no he tenido la necesidad de más de una.

Para estar seguro de que usted escribió todo correctamente, presione el botón 'Test...'
Obtendrá un mensaje informándole que tuvo éxito, o la razón de por qué falló la conexión.
Cuando la tenga trabajando, pulse el botón 'Save'. Éste le presentará el diálogo estándar
'Save as...' de su SO; navegue hasta la carpeta 'db' fuera del directorio principal que creamos
con quickStart, dele a su archivo un nombre como 'hours' y guárdelo. Esto creará un archivo
denominado 'hourcnxml' en el directorio 'db', este es simplemente un archivo XLM que
contiene la información de la conexión.
Usted debe haber notado que la barra de estado de la captura de pantalla de arriba tiene lo
que parece un hipervínculo con el texto 'HomeDirectory' en él. Este documento está siendo
escrito unas pocas semanas antes de PyCon, y nosotros estamos añadiendo nuevas cosas a
nuestras herramientas visuales. En este caso estamos añadiendo el concepto de Home
Directory a nuestras aplicaciones para ayudar con alguna confusión de ruta que pueda surgir.
Dependiendo de cuanto tiempo se disponga para desarrollo de aquí a PyCon, esto pudiera o
no ser liberado. Y si, ese es un control dHyperlink de Dabo que está configurado para abrir un
diálogo estándar para seleccionar un directorio.
Una Nota Sobre Encriptación
Si usted guarda su conexión, Dabo crea un archivo con una extensión '.cnxml'. Este es un
Este es un archivo XML plano y se mostrará como lo que sigue:
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<connectiondefs xmlns="http://www.dabodev.com"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.dabodev.com conn.xsd"
xsi:noNamespaceSchemaLocation = "http://dabodev.com/schema/conn.xsd">

<connection dbtype="MySQL">
<name>hours</name>
<host>dabodev.com</host>
<database>pycon</database>
<user>pycon</user>
<password>Z6DEC5O86ZEEH53Y85O07</password>
<port>3306</port>
</connection>

</connectiondefs>

Esto parece muy directo excepto por el elemento 'password': hemos escrito 'atlanta' como
nuestro password, pero Dabo almacenó en el archivo una versión encriptada. Dabo viene con
un método muy sencillo de encriptación/desencriptación que en verdad no hace mucho
excepto obscurecer el password real ante un vistazo casual, pero si alguien quiere ver cual es
su password, todo lo que tendría que hacer es obtener una copia de Dabo y ejecutar lo
siguiente:
>>> import dabo
>>> app = dabo.dApp()
>>> print "The password is:", app.decrypt("Z6DEC5O86ZEEH53Y85O07")
/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/site-
packages/dabo/lib/SimpleCrypt.py:52: UserWarning: WARNING: SimpleCrypt is not
secure. Please see http://wiki.dabodev.com/SimpleCrypt for more information
warnings.warn("WARNING: SimpleCrypt is not secure. Please see
http://wiki.dabodev.com/SimpleCrypt for more information")
The password is: atlanta

Cuando usted llama los métodos encriptar/desencriptar predeterminados, Dabo imprime la


advertencia que ve arriba. Sí, es fastidioso tener eso impreso todo el tiempo, pero ese es el
punto: usted no debería descansar en una encriptación débil. Afortunadamente existen dos
alternativas mejores:

PyCrypto
Esta librería está disponible en http://www.dlitz.net/software/pycrypto/. Tiene muchas, muchas
, pero la que nosotros vamos a usar son las rutinas de encriptación DES3. Asegúrese de que
la librería está instalada en su maquina, usando pip, easy_install, o una configuración manual.
Una vez que PyCrypto está instalada, lo único que usted necesitará es asignar la propiedad
CryptoKey del objeto Aplicación. Esta puede ser una cadena o, para una mejor seguridad, un
invocable que retorne la cadena. Recuerde, la mejor encriptación es de valor ninguno si usted
la deja por allí en un archivo de texto plano donde cualquiera pueda leerla.
Su Propio Objeto de Encriptación
Para requerimientos mayores de encriptación, usted debería proporcionar su propia clase
personalizada para manejar toda encriptación/desencriptación. La clase debe exponer dos
métodos: encriptar() y desencriptar(). Luego, usted necesita asignar a propiedad
CryptoKey del objeto Aplicación a una instancia de su clase criptografía, y Dabo usará esas
rutinas del objeto antes que las suyas.

Creación de las Clases Bizobj


Ahora que tenemos nuestra conexión definida y funcionando, la siguiente cosa que hacer es
crear las clase bizobj para que nuestra aplicación pueda acceder a los datos. Vamos a crear
dos clases: una para la tabla 'clientes' y otra para la tabla 'hours'. Comenzaremos con la tabla
clientes y crearemos es a mano; crearemos la clase horas con una de nuestras herramientas
visuales.
Usando su editor de texto favorito, cree un archivo en el directorio 'biz' de la carpeta de su
aplicación llamado 'ClienBizobj.py'. El código es el siguiente:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import dabo

class ClientBizobj(dabo.biz.dBizobj):
def afterInit(self):
self.DataSource = "clients"
self.KeyField = "pkid"
self.addFrom("clients")
self.addField("pkid")
self.addField("clientname")
self.addField("attn")
self.addField("rate")
self.addField("street1")
self.addField("street2")
self.addField("city")
self.addField("stateprov")
self.addField("postalcode")

Aquí estamos utilizando la aproximación SQL Builder para definir la estructura de datos del
'bizobj. Comenzamos importando dabo, y heredando nuestra clase de dabo.biz.dBizobj, la
cual es la clase base bizobj. Añadimos nuestro código al método afterInit(), y empezamos
por definir las propiedades más importantes: DataSource y KeyField. Después de eso,
añadimos desde la tabla todos los campos de que queremos usar en nuestra aplicación, en
este caso, todos los campos. Podemos agregar más lógica a la clase posteriormente, pero
esto es suficiente para iniciarnos.
También necesitamos agregar esta clase al espacio de nombres del módulo 'biz'; hacemos
esto editando el archivo __init__.py (el cual fue creado por quickStart ) en el directorio biz.
Añada la línea al final de modo que se lea:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
######
# A fin de que Dabo 'vea' las clases en su directorio biz, añada una
# declaración import aquí por cada clase. E.g., si usted tiene un archivo llamado
# 'MisClases.py' en este directorio, y el define dos clase llamadas 'PrimeraClase'
# y 'SegundaClase', añada estas líneas:
#
# from MisClases import PrimeraClase
# from MisClases import SegundaClase
#
# Ahora, usted puede referirse a esas clase como:
# self.Application.biz.PrimeraCalse y
# self.Application.biz.Segunda Clase
######

from ClientBizobj import ClientBizobj

Cuando creemos el HoursBizobj más adelante, añadiremos eso a este archivo, también.

Creación de la Interfaz de Usuario


Vamos a crear un formulario que será usado para introducir nuestras horas facturables. Este
también mostrará todas las horas no facturadas, y nos proveerá de una vía para crear las
facturas para nuestros clientes. Comenzaremos por crear el formulario de entrada de datos
primero.
Desde el directorio raíz de su aplicación ejecute la siguiente instrucción:
/path/to/ClassDesigner.py ui/hours.cdxml
Asegúrese de sustituir la ruta real a su directorio 'ide'. Sería un dolor tener que escribir esto
todo el tiempo, así que en mi sistema yo cree un alias:
alias des='/Users/ed/projects/ide/ ClassDesigner.py'
y entonces puedo escribir solamente:
des ui/hours.cdxml
Cuando usted corre esta instrucción, obtendrá un diálogo informándole que el archivo no
existe, y preguntándole si desea crearlo. Responda 'Yes' y entonces usted verá un diálogo
preguntándole qué tipo de clase desea crear (izquierda)
Una cosa a tener en cuenta es que el Diseñador de Clases no es solamente para clases
formulario; usted puede crear clases componentes IU especializadas que puede reutilizar en
múltiples formularios y proyectos. Pero por ahora sencillamente acepte lo predeterminado;
verá entonces aparecer tres ventanas: la superficie de diseño del Diseñador de Clases, la
ventana Info de Objeto, y la ventana de Edición de Código (arriba, derecha).
Comience redimensionando la superficie de diseño para que sea más grande, y luego,
presione el botón derecho en ella. Este es la forma primaria de añadir o cambiar elementos
de diseño en la superficie. Obtendrá un emergente parecido a la ventana de abajo, izquierda.
Observe que los controles están repartidos en varias categorías para hacer que encontrar el
que usted quiere sea un poco más fácil; vaya y explore a través de esas opciones para que
tenga una idea de los diversos controles disponibles. Pero por ahora, nosotros queremos
añadirle un dimensionador vertical para dividir el formulario en dos secciones. Cuando
seleccione la opción 'Add New Vertical Sizer', verá un diálogo como el de abajo, derecha.

Cambie el número de ranuras a dos y presione 'OK'. Ahora verá que el formulario esta
dividido verticalmente en dos secciones. Hablaremos un poco más sobre los dimensionadores
dentro de poco. Sencillamente tenga en cuenta que ellos son básicamente reglas para
disponer los elementos en un formulario. Vamos a añadir los controles para la entrada de
datos a la sección superior y el listado de las horas no facturadas a la mitad inferior.

Uso del Ayudante Entorno de Datos


Con botón derecho presione en la mitad superior y seleccione la opción más inferior abajo,
izquierda. Podríamos añadir los controles para cada campo en el bizobj Hours uno a uno, pero
esto lo hará más rápido y menos propenso a errores. ¡Esto también creará el bizobj 'Horas'
por nosotros!. Después de que seleccione esta opción, verá el ayudante Entorno de Datos
(abajo, derecha).
Usted debería ver la conexión 'horas que Dabo halló automáticamente cuando usted inició el
Diseñador de Clases desde el directorio principal de su aplicación; si no la ve, puede presionar
en la opción Open a Connection File' y seleccionar ese archivo. Hay una opción para crear una
nueva conexión, pero ella parece trabajar consistentemente sobre Mac y Windows. También
debería ver una conexión llamada 'sample', como en la captura de pantalla de abajo; este es
un archivo de conexión ejemplo que está incluido en el directorio ide para que Dabo
generalmente halle una, también, cuando se corran las herramientas visuales.
Seleccione 'hours ' y presione 'Next. Usted debería ver algo parecido a la imagen de abajo,
derecha. La tabla que muestra inicialmente probablemente sea 'clients', dado que está
ordenada alfabéticamente. Cambie esto a 'horas', y deberá ver las columnas en la tabla horas
desplegadas. Queremos todos ellos, excepto la PK por que no necesitamos mostrar el valor
del campo clave. Presione el botón 'Select All', y luego 'Ctrl-click (Command-click en Mac)
para de-seleccionarla. Presione el botón 'Next'; ahora se le da la oportunidad de re-arreglar el
orden en que esos campos están desplegados. Para moverlos, pulse en su nombre para
seleccionarlo y luego presione las flechas arriba/abajo para desplazarlos en la lista. Cuando
haya terminado esto debería parecerse a lo siguiente:

Presione 'Next' y se le presentará una selección de diseños (abajo izquierda ). La primera


opción predeterminada es la que queremos, así que presione 'Next'. Ahora verá una vista
previa del diseño que el ayudante creará (abajo derecha). Podemos personalizarla antes de
que añadamos los controles al formulario; es más fácil hacerlo aquí, por que esta es una vista
previa viva, y cuando hacemos cambios, la vista cambia para reflejar esos cambios.
Comencemos personalizando las etiquetas. Ellas están predeterminadas a los nombres de
columna en la base de datos, pero esto no es muy amigable para el usuario. Para cambiarlas.
Sencillamente haga doble presión en la etiqueta y obtendrá una caja de edición. Por ejemplo,
presione doble sobre la etiqueta 'servicedate' e introduzca el texto 'Date' en su lugar. Usted
verá algo como la imagen de la izquierda abajo. Presione Intro cuando finalice y ¡la etiqueta
habrá cambiado! Tenga en cuenta también que el diseño se ha ajustado para reflejar la
etiqueta más pequeña– ¡estos son dimensionadores en acción! Cambie las etiquetas
restantes para hacerlas aparecer de mejor gusto. También trate de cambiar las opciones de la
zona inferior de la ventana vista previa: mientras las cambia la vista se actualiza para reflejar
los cambios

Una cosa que es muy obvia es que cada control tiene una caja de texto para contener el
valor, pero todos ellos no son campos de texto! Puede cambiar el tipo del control para cada
campo aquí, también, pulsando con el botón derecho sobre la caja de texto. Se le presentarán
unos pocos tipos de controles básicos: caja de texto fecha, caja de edición, contador y caja de
comprobación. En nuestro caso, queremos una caja de texto fecha para el campo servicedate,
un contador para el campo hours, una caja de edición para el campo notes y una caja de
comprobación para el campo billed. Haga todos estos cambios y entonces la vista previa
debería mostrarse ahora como la imagen de la derecha arriba.
Los controles no están todavía exactamente como queremos, pero más adelante podemos
ajustarlos. Cuando tenga la vista previa como la anterior, pulse 'Next'. Entonces verá la
página final del ayudante (abajo, izquierda). Si hemos creado ya nuestro bizobj, podríamos
decirle de nuevo al ayudante que no lo agregue, pero en este caso si queremos que él cree el
bizobj. Así que deje la opción predeterminada y pulse 'Finish'. Usted verá los controles
agregados a la mitad superior de su formulario, y su apariencia será como la de la imagen de
abajo a la derecha.

Este podría ser un excelente momento para guardar su diseño antes de proseguir. En el futuro
no le recordaré que guarde; sólo recuerde hacerlo cada pocos minutos de modo de no perder
demasiado de su trabajo.
Vamos a comprobar para asegurarnos que el bizobj fue creado correctamente. Si regresamos
a un listado de directorio del directorio biz de la aplicación, veremos ahora
[ed@Homer ~/apps/pycon_hours]$ ll biz/
total 48
-rw-r--r--@ 1 ed staff 438B Feb 6 08:03 ClientBizobj.py
-rw-r—r-- 1 ed staff 871B Feb 6 08:32 ClientBizobj.pyc
-rw-r--r-- 1 ed staff 559B Feb 6 08:32 HoursBizobj.py
-rw-r--r-- 1 ed staff 1.0K Feb 6 08:32 HoursBizobj.pyc
-rw-r--r--@ 1 ed staff 577B Feb 6 08:32 __init__.py
-rw-r--r-- 1 ed staff 221B Feb 6 08:32 __init__.pyc

Vamos a abrir HoursBizobj.py en un editor de texto. Debería verse así:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import dabo
class HoursBizobj(dabo.biz.dBizobj):
def afterInit(self):
self.DataSource = "hours"
self.KeyField = "pkid"
self.addFrom("hours")
self.addField("clientfk")
self.addField("hours")
self.addField("notes")
self.addField("billed")
self.addField("servicedate")
self.addField("pkid")
def validateRecord(self):
"""Returning anything other than an empty string from
this method will prevent the data from being saved.
"""
ret = ""
# Add your business rules here.
return ret

Seguro que se ve muy parecido al bizobj codificado a mano ¿no es así? Adicionalmente él
agrego un talón de método validateRecord() para que nosotros más adelante añadamos nuestra lógica de
negocio. Observe también que añadió la columna 'pkid', aunque nosotros no la seleccionamos
en el ayudante. La razón es que el valor PK tiene que estar presente en los datos, aun si no
see muestra en la IU. El ayudante es suficientemente inteligente como para manejar eso.
Antes de que abandonemos el editor, abra biz/__init__.py. ¡Hey, mire! ¡El ayudante fue
también suficientemente inteligente para agregar la instrucción import para la clase
HoursBizobj por nosotros!

Mejorando la Apariencia
El ayudante nos llevó hasta muy cerca de lo que queremos, pero podemos hacerlo mejor.
Primero, comencemos por cambiar la leyenda del formulario a algo mejor que el
predeterminado 'Dabo Class Designer'. Para ello, debemos seleccionar el formulario mismo,
pero eso es bastante difícil porque tenemos muchas cosas encima de él. La forma de hacerlo
es ir a la ventana Info de Objeto y presione el triángulo negro en la esquina superior derecha.
Eso ocultara la hoja de propiedades y mostrará el Árbol de Objeto. El Árbol de Objeto es una
manera práctica de visualizar la relación anidada entre la jerarquía de contenimiento de
objetos y la jerarquía de dimensionadores. De hecho, dado que los dimensionadores no son
objetos, la única forma de accederlos es en el Diseñador de Clases por medio del Árbol de
Objetos (abajo, izquierda).
Deberá ver una entrada 'DesForm' en el tope; pulse doble para seleccionar el formulario y
regrese a la hoja de propiedades. Presione sobre en la columna de la derecha de la fila
etiquetada Caption y cambie el texto a 'Horas Facturables'. Presione Intro para aceptar el
cambio, y note que la barra de título del formulario ha cambiado ahora para refleja la nueva
leyenda. Los cambios que se hagan en la hoja de propiedades son reflejados
instantáneamente reflejados en la superficie de diseño, haciendo fácil verificar que usted hizo
las cosas correctamente.
Mientras estemos aquí, cambiemos la propiedad Nombre del formulario a algo diferente al
predeterminado 'DesForm'. Esto no es algo crítico por hacer, pero hace las cosas aparezcan
un poco mejor. También, podemos estar editando múltiples clases al mismo tiempo, pero hay
una sola hoja de propiedades y esto nos ayuda a ver cual formulario está reflejando la hoja
de propiedades actual. Entonces , cambie la propiedad Name a FormaHoras', la hoja de
propiedades debe aparecer como la imagen de abajo, a la derecha.
La etiqueta que está exactamente en el tope no se refrescará inmediatamente, pero si usted
mira al árbol de objeto, ella ha sido cambiada allí (abajo, izquierda). Seleccionando cualquier
otro objeto y luego re-seleccionando el formulario refrescará la etiqueta superior.

Me gustaría tomarme un momento aquí para explicar un par de cosas acerca de la Hoja de
Propiedades, o 'prop sheet'. Ella lista todas las propiedades del control(es) seleccionado(s) y
le permite cambiar aquellas que no son sólo lectura. También, mientras usted navega a través
de las propiedades en la rejilla, la sección inferior muestra la cadena de documentación de
esa propiedad. Usted debería notar también que hay otras dos hojas disponibles: La Hoja de
Métodos y la Hoja Métodos Personalizados. Esas están más allá del propósito de este tutorial,
pero ellas proporcionan formas adicionales de personalizar sus clases IU.

Muy bien, así que hemos conseguido arreglar la barra de título del formulario. ¿Qué más
podría ser mejorado? Primero, los controles se ven aplastados contra los bordes del
formulario. Nosotros especificamos un borde de 8 píxeles en el ayudante Entorno de Datos,
pero aparentemente no fue suficiente.

Si mira en el árbol de objeto arriba, podrá observar que todos los controles que agregamos
están dentro de un Dimensionador Rejilla: este es un dimensionador que dispone sus objetos
en un diseño bidimensional parecido a una rejilla. Así que abra el árbol de objetos y pulse
doble sobre la entrada 'Grid Sizer'. Deberá ver una hoja de propiedades parecida a la imagen
de abajo a la derecha.

Observe también, que hay un grupo de propiedades que comienzan todas con 'Sizer_'; esas
no son en verdad propiedades reales, pero son un atajo práctico para cambiar las
propiedades del dimensionador que administra el diseño de este control. En este caso
queremos un borde mayor alrededor de este grupo de controles, por lo tanto, cambie la
propiedad Sizer_Border a algo más grande, como 30. Note que tan pronto usted presiona
Intro, el borde alrededor de los controles se expande apropiadamente.
Bueno, pienso que nos hemos alejado mucho sin explicar completamente esta cosa de los
dimensionadores. Esta es realmente sencilla una vez usted entiende la idea básica, pero si
usted nunca los ha usado antes, puede tomarle algún tiempo aprender a apartarse del
posicionamiento absoluto y trabajar con una aproximación basada en dimensionadores.
Volveremos a la personalización de la IU dentro de muy poco; por ahora, vamos a hablar
sobre los dimensionadores.

Dimensionadores (Sizers)
Si usted ha creado IU anteriormente, probablemente esté acostumbrado a posicionar sus
controles de forma absoluta: especifica una posición izquierda/arriba y un tamaño, y el control
siempre aparece en esa exacta posición y ese exacto tamaño. Eso funciona suficientemente
bien para algunas cosas, pero tiene una cantidad de limitaciones. Por ejemplo, si el usuario
cambia el tamaño del formulario, nosotros deberíamos esperar que los controles reflejen el
nuevo tamaño. O si un usuario tiene dificultades leyendo, aumente el tamaño de la fuente
para compensarlo. O si usted corre el formulario bajo múltiples SOs, o aun múltiples temas en
el mismo SO. Bajo todas esas circunstancias, su cuidadosamente diseñado formulario ahora
luce como una basura.
Los dimensionadores representan una forma alternativa de posicionar sus controles: ellos son
reglas que toman en cuenta el tamaño nativo del control, de otros controles dentro del
dimensionador, y del entorno dentro del cual existe el dimensionador. Hay dos tipos
principales de dimensionadores: De una dimensión que disponen los controles a lo largo de
una dimensión vertical u horizontal, y Dimensionadores de 2 dimensiones que usan un diseño
basado en una rejilla. En Dabo, estos son dSizer y dGridSizer respectivamente. Los
dimensionadores pueden ser anidados dentro de otros dimensionadores como ya hemos
visto: tuvimos un dimensionador rejilla que posicionaba a nuestros controles.
Los dimensionadores no son objetos reales, pero algunas veces es útil pensar en ellos de esa
manera con el fin tener una imagen más clara de como su formulario va a trabajar. En el
Diseñador de Clases usted puede ver esta representación en el Árbol de Objeto.
Vamos a comenzar con el más sencillo dimensionador 1-D. Los objetos agregados a un
dimensionador 1-D pueden tener varias asignaciones que afectan como el dimensionador los
trata:
• Border: el número de píxeles añadidos como un relleno alrededor de el control cuando
se calcula el tamaño y al posición del control. Predeterminado=0.
• BorderSides: ¿a cuales lados del control se aplica el borde? Predeterminado=”todos”,
pero puede ser una lista de alguno de "arriba", "abajo", "izquierda" o "derecha".
• Proportion: después que los anchos básicos de los controles hayan sido asignados,
¿cuál proporción del espacio restante este control obtiene?
• Expand: ¿se expande el control para llenar la “otra” dimensión?
• HAlign, VAlign: estas controlan donde el control es posicionado a lo largo de la “otra”
dimensión. Por esta quiero significar que si este es un dimensionador vertical, la
asignación halign sería relevante y podría ser una de "izquierda", "centro", o "derecha".
Para los dimensionadores horizontales, las asignaciones valign serían una de "arriba",
"medio", o "abajo".

Con el fin de entender como estas afectan al control, revisemos como trabajan los
dimensionadores. Primero, cuando un evento resize es recibido por el formulario, él llama el
método layout() principal del dimensionador. Usted también puede llamar al método layout()
manualmente si su código modifica los controles en el formulario para actualizar la
apariencia del formulario . El dimensionador es instruido sobre cuanto espacio tiene que
llenar y él comienza calculando el tamaño base de cada elemento que administra. Este es el
tamaño normal mostrado por un control si existiera fuera del dimensionador. Piense en una
etiqueta que contiene un texto en una tipografía determinada: esa etiqueta necesita un cierto
tamaño base para desplegar apropiadamente ese texto en esa fuente. Ese es el tamaño base
del control. El dimensionador luego añade algún valor de borde a cada control para obtener
un tamaño base total para todos sus elementos. Si el tamaño disponible es menor que eso,
los controles son recortados y sólo algunos pueden ser visibles. Pero si hay suficiente tamaño,
los controles son asignados con su tamaño base más un borde. Ahora, si hay disponible
algún otro espacio más en la orientación del dimensionador (vertical u horizontal), ese
espacio restante es dividido de acuerdo al peso relativo de la asignación de proporción del
control. Si la proporción de un control es cero, no obtiene espacio adicional. De lo contrario, el
dimensionador totaliza todas las proporciones de todos sus elementos, y dota a cada control
un porcentaje del espacio extra de acuerdo a su proporción
El uso de palabras como las anteriores puede ser muy confuso, así que veamos el siguiente
ejemplo: suponga que tenemos un formulario de 200 píxeles de ancho por 300 píxeles de
altura, que contiene tres controles, cada uno de 50 píxeles de ancho por 20 píxeles de alto. El
formulario tiene un dimensionador vertical, y estos controles son agregados a ese
dimensionador. El primero tiene una proporción de cero; el segundo tiene una proporción de 1
y el tercero tiene una proporción de 2. Todos tienen un borde de cero, de modo que el tamaño
base total en la dimensión vertical es 60px (3 controles x 20px cada uno). El espacio vertical
disponible es 200px, lo que deja 240 px libres. El total de las proporciones de los elementos
es 3 (0 + 1 + 2), luego el dimensionador haría el primer control así que el dimensionador
haría el primer control 20px alto (20px base + (0/3 * 240)).El segundo control sería ahora
(20px base + (1/3 * 240)) o 100px alto, y el tercero sería (20px base + (2/3 * 240)) o 180px
alto. Sumando todo esto obtenemos 20 + 100 + 180, o los 300px totales en la altura del
formulario.
Pero ¿qué en relación con el ancho? Bueno, eso depende de la asignación de Expand: si esta
es Verdadero, el control se ensanchará hasta ocupar todo el ancho del formulario, o 200px. Si
esta es Falso, el ancho permanecerá en sus 20px normales, y su posición será determinada
por su asignación halign.
Esto es mucho más fácil de observar visualmente. Hay una excelente demostración de
dimensionador como parte de la aplicación DaboDemo; usted puede explorarlo para obtener
una mejor opinión de como cambiando las diversas asignaciones afecta al tamaño y la
posición de los controles dentro de un dimesionador

Los dimensionadores rejilla trabajan casi igual, con unas pocas diferencias importantes. En
primer lugar, todos los controles en una fila dada tienen la misma altura, y todos los controles
de la misma columna tienen la misma anchura. Como resultado de esto, las configuraciones
para RowExpand y ColExpand serán las mismas para todos los controles en una fila o
columna determinadas, al cambiar uno se afectan todos los demás. Diferentes filas y
diferentes columnas pueden tener alturas y anchos diferentes respectivamente. La
configuración de 'proporción' no tiene ningún efecto, pero hay dos opciones disponibles que
pueden servir para diseños interesantes RowSpan y ColSpan. Lo que estas hacen es decirle
al dimensionador rejilla que el control deberá extenderse a través de múltiples filas o
columnas cuando se calcule su tamaño y posición. Esto está más allá del alcance de este
tutorial, pero pensé que debía mencionar esto aquí.

De Regreso al Formulario
Bien, aun cuando estoy seguro de que a estas altura usted no se siente enteramente
confortable con todos los conceptos del trabajo con los dimensionadores, eso debe darle
suficiente base para entender las ideas mientras avanzamos. Para refrescar su memoria, la de
abajo es la imagen de cual es la apariencia del formulario ahora mismo.
Hay varias cosas que están mal visualmente. El contador del valor de las Horas es muy
ancho; no tiene sentido que se extienda a todo lo ancho del formulario. La etiqueta Notas está
centrada verticalmente; yo preferiría tenerla incluso en el tope de su control. Y finalmente,
tener la etiqueta de la caja de chequeo dispuesta de esa forma no es normal; típicamente la
etiqueta es parte de la caja de comprobación misma. Abordaremos todo eso ahora.
Para arreglar el contador, pulse botón derecho sobre él y seleccione el ítem 'Edit Sizer
Settings' en el menú contextual. Va ver una caja de diálogo que luce como la imagen de abajo
a la izquierda. Desmarque la caja etiquetada 'Expand' y deberá ver al numerador encogerse
instantáneamente hasta un tamaño más normal. El diálogo de edición de dimensionador es
una manera práctica de probar con conceptos acerca de dimensionadores: usted puede
cambiar cualquiera de las propiedades que guste y ver los efectos de ese cambio de
inmediato, y si le gusta lo que ha hecho, sencillamente pulse Cancel' y el control regresará a
su apariencia original.

Para corregir la etiqueta Notas', pulse con botón derecho en ella y haga subir el diálogo de
edición de dimensionador. Cambie la alineación 'Valign' de 'Medio' a 'Arriba' Puede que
quiera hacer esto con las otras etiquetas, también, dado que ellas igualmente está alineadas
verticalmente al medio – esto sencillamente no es tan notorio porque los dTextBoxes son
cerca de la misma altura que las etiquetas. Para cambiar la caja de chequeo, necesitará hacer
dos cosas. Primero, presione en la caja y ponga su Leyenda a 'Billed?'. Entonces pulse con
botón derecho sobre la etiqueta y seleccione 'Delete'. Verá un hueco rectangular donde
estaba la etiqueta; este es una ranura visual que indica una posición vacía en el
dimensionador. Durante la ejecución este será reemplazado por un panel en blanco, así que
puede dejar esto así.

Tomar una Prueba de Manejo


Usted puede hacer alguna prueba visual rápida de su formulario desde el Diseñador de
Clases. Seleccione 'Correr...' en el menú Archivo, y deberá ver esto:

¿No se ve tan diferente ¿no es cierto? Pero este es un formulario vivo creado desde su diseño
de clase . Arrástrelo por allí y todavía podrá ver su superficie de diseño deba jo. Cambie de
tamaño el formulario y verá como los controles cambian su tamaño también. Aún no hay
datos dado que no le hemos dicho que cargue ninguno. Vamos a arreglar esto ahora.

Conectar los Bizobjs


Cierre el formulario vivo y diríjase a la ventana de edición de código. Esta es donde añadimos
código a nuestra clase. Deberá notar que ya hay algún código allí:

El código está en el método createBizobjs() del formulario, el cual es llamado


automáticamente por la estructura de trabajo durante la creación del formulario. El código
fue añadido por el Ayudante Entorno de Datos cuando lo corrimos para agregar los controles.
Démosle una mirada un poco más detenida al código:
Primero nosotros instanciamos el bizobj para la tabla horas, usando la referencia de clase
self.Application.biz.HoursBizobj. ¿Cómo él obtuvo la definición de clase y ese espacio de
nombres? Si usted recuerda todos los objetos Dabo puede referir al objeto principal app vía la
referencia self.Application; durante el inicio de la aplicación, el objeto aplicación poblará sus
atributos 'db', 'biz', etc. (e.d., todos los subdirectorios estándares) con los valores en el
módulo de esos directorios (por lo cual añadimos las clases a biz.__ini__.py).

A continuación, pasamos el valor de self.Connection a la creación del bizobj. ¿De donde


obtuvo el formulario el valor de 'Connection'? De nuevo, este fue proporcionado por el
Ayudante Entorno de Datos: el primer paso que dimos en ese ayudante fue seleccionar una
conexión, y el ayudante recordó eso y configuró la propiedad CxnName (usted puede
comprobar eso en la hoja de propiedades ahora). Cuando se crea el formulario, este le solicita
al objeto aplicación la conexión nombrada en la propiedad CxnName del formulario, y esa
conexión es almacenada en la propiedad Connection del formulario. Así que pasamos esa
conexión al bizobj, el cual la usa para establecer su conexión a la base de datos.

Entonces, ¿por qué no vemos ningún dato? Bien, ¡por que en ningún momento nosotros le
dijimos al bizobj que obtuviera alguno! Corrijamos esto ahora: en la ventana de edición de
código asegúrese de que todavía el Objeto es el Formulario y busque el método afterInit() en
el desplegable Método. Por favor, agregue la línea self.requery() después de la instrucción
inicial def. Después de haber hecho esto, ejecute de nuevo el formulario; esta vez deberá ver
algo como lo siguiente:

Observe que hay datos reales en los controles y la barra de estado al final dice algo como 48
registros seleccionados en 0.193 segundos. ¡Congratulaciones! Usted ha creado un formulario
que ha consultado una base de datos remota, extraído algunos datos y los ha mostrado en los
campos correctos – ¡con casi ninguna codificación! Hey, vamos a ver si en realidad hay un
puñado de registros allí.
Cierre el formulario, con botón derecho presione en la gran ranura vacía en la mitad inferior
del formulario y seleccione Interactive Controls/Add Button. Usted verá un botón agregado a
esa área , pero probablemente abajo, en el mismo fondo, dado que un botón ocupa muy poco
espacio. No nos preocupemos por la leyenda por que vamos a borrarlo en un minuto, pero
queremos agregarle un poco de código. Con botón derecho pulse en el botón y seleccione
Edit Code; la ventana de edición de código se vendrá al frente y el método onHit() del botón
estará seleccionado. Agregue la línea self.Form.Next() después de la declaración def.
Corra el formulario otra vez y observe lo que pasa cuando presione en el botón: los distintos
registros en el conjunto de datos se muestran en el control, exactamente como usted lo
esperaba. Pero hay algo también erróneo: el campo Cliente muestra un número, no el nombre
del cliente. Ocurre que el numero es el valor de la clave foránea a la tabla cliente para el
cliente asociado con ese registro, pero esto no es muy útil. Un usuario introduciendo sus
horas facturables querrá escoger un nombre, no un número de ID. Arreglemos eso ahora.

Poblar la Lista de Clientes


En primer lugar, hay una forma muy sencilla para resolver la parte de despliegue de las
cosas: podríamos simplemente añadir un join al código SQL Builder para halar la columna
nombre de cliente a nuestro dataset para hours, y luego asignar la propiedad DataField a esa
columna. Pero como este es un formulario de entrada de datos, queremos ser capaces de
extraer todos los clientes de modo que el usuario pueda seleccionar el correcto por su
nombre, y todavía tener el valor de la clave ID asociada almacenada en el campo clave
externa en la tabla hours. Así que escriba un pequeño trozo de código para hacer esto
exactamente.
Hay una excelente explicación del proceso general en la Wiki de Dabo: vea la página How To
Populate And Use List Controls. Cubriremos los puntos importantes aquí. La aproximación
básica es escribir el código en el bizobj para recuperar los nombres y claves para todos los
clientes, y luego usar eso para poblar un control lista desde donde el usuario pueda
seleccionará. Así que abramos el ClientBizobj en nuestro editor de texto favorito y
agreguemos el siguiente método:
def getNamesAndKeys(self):
"""Retorna una 2-tupla de listas de los nombres de clientes
y sus claves.
"""
crs = self.getTempCursor()
crs.execute("""select pkid, clientname
from clients
order by clientname""")
ds = crs.getDataSet()
# Crear las listas
names = [rec["clientname"] for rec in ds]
keys = [rec["pkid"] for rec in ds]
return (names, keys)

Podríamos haber usado los métodos de SQL Builder con un cursor temporal, pero vamos, ¿qué
tan difícil es escribir una sencilla instrucción sql como esta? En cualquier caso, corremos la
consulta básica para obtener las Pks y los nombres y crear dos listas: una que contiene los
nombres y la otra contentiva de las claves.
A continuación, nos aseguramos que el ClientBizobj también esté cargado dentro del
formulario. Así que regrese al Diseñador de Clases y abra el método createBizobjs en la
ventana de edición de código. Es mucho más fácil copia/pegar las líneas que crean el bizobj
hours y cambiar los nombres a client. Él debería aparecer como este:
def createBizobjs(self):
hoursBizobj = self.Application.biz.HoursBizobj(self.Connection)
self.addBizobj(hoursBizobj)
clientBizobj = self.Application.biz.ClientBizobj(self.Connection)
self.addBizobj(clientBizobj)
A continuación, vaya a la superficie de diseño y borre la caja de texto para el cliente. En este
lugar cree un desplegable por medio del usual menú de contexto botón derecho/Data
Controls/Dropdown List. Con el desplegable seleccionado, vaya a la hoja de propiedades y
ponga la propiedad DataSource en "hours", y la propiedad DataField a "clientfk". También
necesitamos asignar la propiedad ValueMode a "Key" para decirle a Dabo que no queremos
almacenar la cadena de texto mostrada, si no el valor Key asociado. ¿Cómo obtenemos las
cadenas y las claves se muestren en el control? Seleccione el control y establezca su
propiedad RegID a 'ClientList'. Como veremos más adelante, el RegID es un identificador
único dentro de un formulario que posibilita referenciar objetos de forma simple y sin
ambigüedades. Ahora abra la ventana de edición de código, seleccione el formulario en la
lista desplegable objeto y seleccione 'afterInitAll' en el desplegable de métodos. Añada el
siguiente código a ese método:
def afterInitAll(self):
clientBiz = self.getBizobj("clients")
names, keys = clientBiz.getNamesAndKeys()
self.ClientList.Choices = names
self.ClientList.Keys = keys
self.ClientList.ValueMode = "Key"
self.requery()
Vamos a ver lo que éste está haciendo: la primera línea le solicita al formulario una referencia
al bizobj client; lo hace pasándole la DataSource del bizobj que le interesa al método
getBizobj() del formulario. Esta es la forma primaria de obtener referencias a bizobj desde
un formulario. Luego, él llama al método que acabamos de crear para para obtener las listas
names y keys. Después, asigna a su propiedad Choices los nombres, y a su propiedad Keys
la lista keys. En los controles lista, la propiedad Choices contiene los elementos a mostrar;
cambiando esta propiedad se cambian las opciones disponibles para el usuario. La propiedad
Keys se usa 'bajo la manga' para asociar el valor mostrado con un valor de clave; esto se
hace mediante una asociación 1:1 entre los dos: la primera entrada de Opciones es asociada
con la primera Clave; la segunda entrada de Opciones es asociada con la segunda Clave; etc.
Ahora, como establecimos la propiedad ValueMode a Key, el control mostrará la Opción que
corresponde al valor clave ligado. Similarmente, si el usuario selecciona una Opción diferente,
el valor subyacente de Clave será cambiado.
Finalmente, llamamos a self.requery() para extraer los datos para el formulario y actualizar
todos los controles con los datos.
Necesitamos añadir ese nuevo método a biz/ClientBizobj.py, así que abra ese archivo en un
editor de texto y agregue el siguiente método:
def getNamesAndKeys(self):
"""Retorna una 2-tupla de listas de los nombres de clientes
y sus claves.
"""
crs = self.getTempCursor()
crs.execute("""select pkid, clientname
from clients
order by clientname""")
ds = crs.getDataSet()
# Crear las listas
names = [rec["clientname"] for rec in ds]
keys = [rec["pkid"] for rec in ds]
return (names, keys)
Idealmente, podríamos correr el formulario modificado, pero tenemos un problema: el bizobj
clientes fue cargado antes de que añadiéramos el método getNamesAndKeys(), y si tratamos
de ejecutar el formulario ahora, éste no encontraría ese método en la versión del bizobj
memoria, y lanzará un error. Así que guarde todo, cierre el Diseñador de Clase, y luego
inícielo de nuevo con el mismo mandato de la última vez. En este momento su clase
guardada abrirá directamente. Ahora puede tratar de ejecutar el formulario y mientras
presiona el botón para navegar por el conjunto de registros, deberá ver cambiar el nombre
del cliente. Cierre el formulario en ejecución y después borre el botón; verá regresar la gran
ranura vacía. Llenaremos esto más a continuación. Pero antes, tratemos de correr este
formulario directamente, en vez de desde el Diseñador de Clase. ¿Recuerda que cuando
creamos la aplicación con dabo.quickStart(), éste puso como la MainFormClass a la clase
predeterminada dabo.ui.dFormMain? Bien, tenemos ahora un formulario que trabaja, de modo
que cambiemos main.py para usar este formulario en su lugar. Cambie su main.py para que
se lea:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import dabo
dabo.ui.loadUI("wx")
app = dabo.dApp()
app.MainFormClass = "hours.cdxml"
app.BasePrefKey = "billing_tutorial"
app.start()

Hay unos pocos cambios que hice aquí. En primer lugar, removí el comentario de advertencia
ya que no se necesita. Segundo, establecí la propiedad MainFormClass del formulario como
la cadena hours.cdxml. Observe que los archivos .cdxml no son clases Python; son sólo
archivos XML que Dabo sabe como procesar para crear clases Python dinámicamente durante
la ejecución. Así que en lugar de asignar a la MainFormClass una clase Python real como
hicimos antes, le asignamos un archivo el nombre de un archivo cdxml que Dabo puede
convertir en una clase Python,. Podríamos haberle asignado la ruta completa al archivo
cdxml, pero como hemos seguido la estructura de directorios estándar de Dabo creada por
quickstart(), Dabo hallará ese archivo en el directorio ui.
La otra cosa que hice fue establecer la propiedad BasePrefKey. Usted puede haber notado
ya que Dabo parece recordar algunas cosas cuando usted corre una aplicación, tal como el
tamaño y posición de las ventanas. Esto es manejado automáticamente por el sistema de
preferencias de Dabo; las preferencias se mantienen bajo la propiedad BasePrefKey de cada
aplicación para conservarla distinta. Si BasePrefKey no está configurada, Dabo usa la ruta al
directorio raíz de la aplicación. Estableciendo este valor aquí estamos seguros de que las
preferencias de nuestra aplicación se mantienen, no importa si movemos la aplicación a otra
parte.
Hagamos una prueba: guarde main.py, y luego córralo. Debería ver el formulario que creamos
en el Diseñador de Clase, completo, con datos. Antes de cerrarlo, cámbielo de tamaño y
reposiciónelo. Cierre el formulario y luego ejecute main.py de nuevo. Note que el formulario
se abre con el mismo tamaño y en la misma posición en que lo dejó.

Una Nota sobre Preferencias


Las preferencias son una de esas cosas que toda aplicación debe manejar, de modo que en
Dabo tratamos de hacer el manejo de las preferencias tan fácil como sea posible. Cada
objeto tiene una propiedad llamada PreferenceManager que refiere al objeto manejo de
preferencias. Para establecer una preferencia, sencillamente asígnela a un atributo del objeto
PreferenceManager: self.PreferenceManager.meaningOfLife = 42. Puede representar las
preferencias complejas anidadas usando la notación punto; simplemente escriba algo como
self.PreferenceManager.sports.baseball.favorite = "NY Yankees", y Dabo manejará
automáticamente las preferencias anidadas; usted no tiene que predefinir los niveles
anidados.
Posteriormente cuando desee acceder al valor de una preferencia almacenada, sencillamente
referénciela usando el mismo nombre de la notación punto con que la creó.
return self.PreferenceManager.meaningOfLife. Fácil, ah?

Hay una herramienta visual para inspeccionar la base de datos preferencias; esta está en la
carpeta ide, y es denominada PrefEditor.py. No es la herramienta más pulida, pero si le
permite a usted editar o borrar cualquier preferencia para sus aplicaciones.

Crear la Rejilla de Facturación


Vamos a agregar la rejilla a la sección inferior del formulario; podemos usar el ayudante
Entorno de Datos de nuevo para hacerlo. Reabra el archivo hours.cdxml en el Diseñador de
Clase y presione con botón derecho en el panel inferior, seleccionando la opción ayudante
Entorno de Datos. Cuando usted lo corre, observará que él conoce que usted ya tiene una
conexión definida para el formulario, así que úsela y salte la primera página. Recorra las
páginas como antes, pero esta vez seleccione el diseño de Rejilla. Cuando llegue a la vista
previa de la página ejemplo. Deberá ver algo como la imagen de abajo. Como antes, usted
puede pulsar doble las leyendas de las cabecera para editarlas; también puede arrastrar las
líneas entre las columnas para cambiar el ancho de estas. Cuando termine, presione 'Next', y
esta vez dígale al ayudante que no agregue el código del bizobj, porque ya lo tenemos.
Cuando usted finalice, su formulario deberá parecerse a la imagen de abajo, derecha.

Ahora active el formulario, y deberá ver que la rejilla es poblada con todos los registros
existentes de la tabla hours. Hay varias cosas que necesitamos arreglar. Primero, tenemos el
mismo problema de antes con el cliente: se muestra el ID y no el nombre. Dado que esta área
no es de entrada de datos, no queremos usar un control lista, así que tenemos que añadir la
tabla relacionada al SQL del bizobj hours. La otra cosa notable es que estamos mostrando
todos los registros, cuando todo lo que queríamos eran los registros no facturados. Podemos
agregar esa condición al SQL también. Finalmente, los datos están desordenados aun cuando
tiene mayor sentido tenerlos ordenados por fecha. Así que agreguemos eso al SQL, también.

El código del bizobj hours deberá parecerse al siguiente; las líneas que he agregado están
marcadas con flechas. Tome en cuenta también que tuve que agregar el alias de la tabla a las
llamadas a addField(), dado que la unión con la tabla clientes introduce ahora una
ambigüedad, por que ambas tablas tienen una columna llamada pkid. Técnicamente, sólo ese
campo necesita ser calificado totalmente, pero por el interés de la consistencia los he
cambiado todos.

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import dabo

class HoursBizobj(dabo.biz.dBizobj):

def afterInit(self):
self.DataSource = "hours"
self.KeyField = "pkid"
self.addFrom("hours")
self.addJoin("clients",
"hours.clientfk = clients.pkid") # <---
self.addField("hours.clientfk")
self.addField("hours.hours")
self.addField("hours.notes")
self.addField("hours.billed")
self.addField("hours.servicedate")
self.addField("hours.pkid")
self.addField("clients.clientname") # <---
self.addWhere("billed = false") # <---
self.addOrderBy("servicedate asc") # <---

def validateRecord(self):
"""Regresar alguna otra cosa que una cadena vacía desde
este método prevendrá que los datos sean guardados.
"""
ret = ""

# Añada sus reglas de negocio aquí.


Return ret

Ahora arreglemos la columna cliente de la rejilla. Seleccione esa columna en la hoja de


propiedades y cambie la propiedad DataField a clientname en lugar de clientfk. Guarde el
diseño, luego cierre y recargue el diseño para que los cambios recientes al bizobj hours estén
disponibles. Re-ejecute el formulario y deberá ver el nombre del cliente en la rejilla en lugar
de el valor de PK (abajo, izquierda). Los datos están ordenados correctamente y sólo los
registros no facturados están en el conjunto de datos. Pero note que la columna Billed?
Muestra ceros y no False. Esto se debe a que MySQL no tiene un tipo de datos booleano; en
su lugar, almacena 1 ó 0 en un tipo llamado tinyint. Podemos corregir esto sumamente fácil
estableciendo explícitamente la propiedad DataStructure del bizobj, pero piense lo siguiente:
si estamos mostrando solamente los registros no facturados, ¿por qué necesitamos tal
columna en primer lugar? Borrémosla – la forma más fácil es abrir el Árbol de Objeto y
seleccionar Delete. Ahora, cuando corramos el formulario éste se parecerá al siguiente:
Convertirla en un Formulario Entrada de Datos
Ahora mismo, el formulario tal y como existe es excelente para mostrar los datos que ya han
sido enterados, pero dijimos al principio que queríamos hacer de esta un formulario de
entrada de datos. Eso significa que cuando corremos el formulario, nos interesa añadir
nuevos registros, no sólo ver registros viejos. Así que una de las cosas que necesitaremos
hacer es llamar el método new() del formulario tan pronto como el formulario sea creado. El
lugar para hacer esto es el método afterInitAll() del formulario, de manera que vamos a
agregar la llamada a self.new() al final de ese método. Si usted corre el formulario ahora,
probablemente obtendrá algunos mensajes de error, por que los nuevos registros tienen
valores vacíos, y por lo tanto el campo clientfk, el cual es un entero, obtendrá un valor de
cero, que no está presente en la propiedad Keys del desplegable cliente. También
obtendremos una fecha vacía que probablemente no es lo que queremos
Para resolver estos problemas , vamos a tener que hacer unos pocos ajustes. Primero,
agreguemos una opción al desplegable cliente para un cliente no especificado. En la ventana
de edición de código, seleccione el objeto formulario y modifique el código para insertar la
opción 'cliente no especificado'

Def afterInitAll(self):
clientBiz = self.Form.getBizobj("clients")
names, keys = clientBiz.getNamesAndKeys()
names.insert(0, "-Please Select a Client-")
keys.insert(0, 0)
self.Choices = names
self.Keys = keys

self.requery()
self.new()

Guarde estos cambios , y salga del Diseñador de Clase. Para la fecha, la mayoría de las veces
querremos que sea la fecha del día. Afortunadamente, Dabo provee una forma sencilla de
proporcionar valores predeterminados para registros nuevos; los bizobjs tienen una propiedad
DefaultValues, la cual es un diccionario con los nombres de columna como las claves y los
predeterminados como valores. Así que abra HoursBizobj.py en su editor de texto, y agregue
la siguiente línea:

self.DefaultValues = {"servicedate": datetime.date.today()}

Por supuesto, asegúrese de añadir la declaración import datetime arriba en el archivo. Cuando
haya guardado estos cambios, corra python main.py para ver como está trabajando la
aplicación. Si usted hizo todo correctamente, debería ver un formulario como el siguiente,
pero con la fecha del día.

Si jugamos un poco con esto, un nuevo problema se hace obvio: si presionamos las flechas
arriba o abajo del contador, los valores se incrementan en una hora cada vez mientras que
estaremos facturando en incrementos de cuartos de hora. Para corregir esto, abra el
Diseñador de Clase y seleccione el contador. En la hoja de propiedades, cambie su propiedad
Increment a 0.25. Ahora cuando presione las flechas, el valor cambiará en incrementos de
cuarto de hora. Y mientras estamos aquí, cambiemos la propiedad Max a 24;
presumiblemente no facturaremos más de 24 horas al día.

Guardar el Nuevo Registro


Aun no hemos provisto una forma para que el usuario le diga al formulario que los datos del
nuevo registro están completos y que lo guarde en la base de datos. Vamos a necesitar una
nueva ranura para añadir varios botones, así que abra el Diseñador de Clases, y vaya al Árbol
de Objeto. Busque la rejilla (no el dimensionador rejilla), y pulse el botón derecho en su nodo.
Seleccione 'Add Slot Above' en el menú contextual. Usted verá aparecer una nueva región
rectangular (abajo, izquierda) entre los controles en la sección superior y la rejilla de abajo;
probablemente ella se sobreponga a los controles de la sección superior, pero no se preocupe
por ello ahora -vamos a arreglarlo. Pulse con el botón derecho en esta nueva ranura y
seleccione 'Edit Sizer Settings'. Cambie la proporción a 0 y presione OK. La ranura será ahora
una pequeña franja delgada (abajo derecha).
Añadiremos algunos botones a esa franja; los botones serán dispuestos horizontalmente, lo
que significa que necesitaremos un dimensionador horizontal. Así que con botón derecho
presione en la delgada franja, seleccione Sizers/Add New Horizontal Sizer, y ponga el número
de ranuras en 5. Sólo por diversión, marque la opción Add Sizer Box?. Usted podría agregar
una leyenda para la caja, pero en realidad no necesitamos una, así que déjela en blanco. El
formulario lucirá ahora como la imagen de abajo a la izquierda. Agreguemos un botón en la
ranura del medio de la franja. Usted debería saber el ejercicio ahora. Cambie la leyenda del
botón a 'Save'. El formulario se debe parecer ahora a la imagen derecha de abajo.

Presione con botón derecho en el botón y seleccione 'Edit Code'. La ventana de edición de
código se viene al frente, con el botón como objeto seleccionado. Incluso seleccionó el
método onHit(), ya que ese es el método más probable que necesitará para trabajar cuando
trate con botones. Añada la línea self.Form.save() después de la línea 'def'. Eso es – todo lo
que necesita para guardar sus cambios a través de los botones del formulario es hacer esa
simple llamada.
Necesitaremos un botón 'Cancel', también, pero en lugar de agregarlo como lo hicimos con el
botón Save, ¡sencillamente copiemos y peguemos el botón! Presione con botón derecho sobre
el botón y seleccione 'Copy'. Ahora presione el botón derecho sobre la ranura extrema
derecha – verá una opción 'Paste' en el tope del menú contextual. Selecciónela y una copia
del botón Save aparecerá en esa ranura. Todo lo que tenemos que hacer es cambiar su
leyenda a 'Cancel' y editar su método onHit() para que en vez diga self.Form.cancel().
Los botones lucen bien, pero hay mucho espacio entre ellos (abajo, izquierda). Podríamos
borrar la ranura entre ellos, pero entonces ellos podrían pegarse entre sí. Por contrario,
agreguemos un Espaciador entre ellos. Del menú botón-derecho/Sizers seleccione 'Add a New
Spacer'. Cambie el ancho a 20 y el formulario} ahora tiene la apariencia como la imagen
abajo, derecha.

Guarde esto y salga. Podríamos ejecutar la aplicación, pero antes de hacerlo, hablemos de
validación de datos. Después de todo, esta es una de las funciones más importantes de un
bizobj, pero en verdad no la hemos discutido del todo. Los cambios que hemos hecho
presentan una oportunidad perfecta para validación: si ellos no extraen un cliente del
desplegable, obviamente no podemos facturar a ninguno! Entonces, agreguemos una
comprobación de validación para ello.
Abra HoursBizObj.py en su editor de texto y vaya al método validateRecord(). Modifíquelo
para que se lea:
def validateRecord(self):
"""Retgresar alguna otra cosa que una cadena vacía desde
este método prevendrá que los datos sean guardados.
"""
ret = ""
# Add your business rules here.
If self.Record.clientfk == 0:
ret = "Usted debe seleccionar un cliente"
return ret

Lo que está haciendo es chequear un valor cero para la Clave foránea para el cliente. Si
encuentra ese valor, retorna el mensaje de error. La estructura de trabajo interpretará
cualquier valor de retorno no vacío desde validateRecord() como indicador de una falla de
validación, y levantará una excepción de BusinessRuleViolation.
Guarde sus cambios de bizobj y corra main.py. Agregue algunos valores al formulario, pero de
je el desplegable cliente en el predeterminado selección no especificada. Ahora presione el
botón Save; obtendría un diálogo que despliega el mensaje de error que usted retornó del
método validateRecord().
Corrija este problema de validación seleccionando un cliente verdadero del desplegable y
luego presione 'Save' de nuevo. Esta vez no obtendrá el mensaje de error; por el contrario,
vería un mensaje de confirmación en la barra de estado del formulario.

Pasos Siguientes
Hay muchas cosas que podemos hacer para que esta aplicación sea más funcional, pero
usted puede ver que hemos logrado algo muy cercano a lo que necesitamos bastante rápido,
sin ningún esfuerzo críptico de codificación. Una cosa que sería sencilla de agregar es que la
aplicación llame automáticamente a new() después de un save() exitoso. Sería bueno poder
enterar múltiples sesiones de facturación de una sentada. Así que agreguemos un botón para
ello.

Abra el formulario en el Diseñador de Clase y copie el botón Save. Péguelo en la ranura


extrema izquierda. Cambie la propiedad Caption a 'Save && New' ¿por qué el doble
ampersand? Esto es un capricho de wxPython: él interpreta un ampersan sencillo en una
leyenda como un símbolo que hará al siguiente carácter la tecla del atajo del botón.
Realmente queremos ver un ampersan en el botón así que necesitamos duplicarlo.
Ahora copie el espaciador que creamos en la ranura restante. Humm... esto no es lo que
necesitamos (abajo, izquierda). Queremos los botones alineados a la derecha, pero ¿cómo
hacerlo? Recuerde, los botones están en un dimensionador horizontal, y este dimensionador
horizontal está en una ranura del dimensionador vertical principal. Dado que el
dimensionador horizontal no es suficientemente ancho para ocupar toda la ranura, su
configuración Expand es llenar el resto. Así que ¡corrijamos esto! Vaya al Árbol de Objeto y
localícelo; debería decir 'BorderSizer: Horizontal'. Pulse botón derecho para obtener el diálogo
'Edit Sizer Settings' ¡Guao! ¡Esto luce muy diferente! (abajo, derecha).
Aquí es donde la mayoría de las personas realmente se confunden con los dimensionadores.
Como los dimensionadores pueden ser anidados dentro de otros dimensionadores, hay
asignaciones que afectan como el dimensionador externo los trata (sección superior), y
también, como este dimensionador trata a sus miembros (sección inferior). No tenemos
problema con el último; solo es útil saber que puede cambiar cualquiera de todas esas
asignaciones en un solo lugar.

Para corregir el actual problema, en la sección superior desmarque la caja 'Expand' y cambie
HAlign a 'Derecha'. Presione 'OK' y el formulario debe aparecer en la forma que queremos.

Ahora ¿y el código? Podríamos ponerlo en el método onHit() de nuestro nuevo botón, pero
hay una pauta de diseño que va con el Patrón de Cadena de Responsabilidades que
mencionamos anteriormente: los objetos deberían conocer tan solo lo menos posible para
hacer su trabaJo. Entonces, si, pudiéramos poner un montón de código en el botón, pero se
supone que los controles son muy tontos. Ellos deberían sencillamente llamar a un método de
su Formulario para hacerle conocer que el usuario quería hacer algo, y esto es todo. En este
diseño, el Formulario es el cerebro central de la operación. De modo que agregue una sola
línea al método onHit(): self.Form.saveAndNew()

Hay un solo problema: el formulario no tiene un método con ese nombre! Realmente, esto no
es un problema del todo, ya que podemos agregarlo muy fácilmente. Vaya a la ventana de
Edición de Código y seleccione al formulario en el desplegable objeto. El desplegable de
Método contiene los nombres de los métodos más comunes y los manejadores de eventos
disponibles para el objeto seleccionado, pero si usted necesita crear un método nuevo, o hay
problema, para eso está el botón 'New'.

Presione ese botón, e introduzca el nombre de su nuevo método; en este caso este es
'saveAndNew'. Verá ese método aparecer en la ventana Edición de Código y podemos añadir
nuestro código rápidamente:

def saveAndNew(self):
if self.save():
# Un guardado fallido retornará False. Sólo queremos proceder si
# el guardado tiene éxito.
self.new()
self.serviceDate.setFocus()

Espere un segundo – ¿qué es este 'self.serviceDate' en la última línea? Parece como si fuera
una referencia al control fecha, ya que tendría sentido tener en el formulario el foco puesto en
ese cuando se introduce un nuevo registro, pero no hemos creado alguna referencia tal!.
Esto es cierto, pero es muy fácil hacerlo usando la propiedad RegID de todos los controles
Dabo. Esta es una propiedad que generalmente está vacía, pero si usted la establece, ella
debe ser única a través de todos los controles en un formulario dado, y una vez asignada, no
puede ser cambiada. Esto asegura que siempre será posible referirse a ese objeto sin
ambigüedad. Usted podría también usar una lista de nombres, separados por puntos, en la
jerarquía de contenedores del control (p.e.: self.panel1.pageframe1.page3.panel2.button1),
pero esto es muy frágil, dado que los controles son movidos con frecuencia y/o renombrados
durante el desarrollo.

Cuando un control con una RegID no vacía es instanciado, él se “registra” a sí mismo con su
formulario (esa es la parte 'reg' del nombre RegID), y ese RegID se agrega al espacio de
nombres del formulario. Desde ese punto en adelante, cualquier control puede referenciar a
ese control registrado simplemente usando self.Form.RegID, donde RegID es el RegID del
control.

Establezcamos ese RegID para el control fecha. Selecciónelo en la superficie de diseño, y


vaya a la hoja de propiedades. Busque la propiedad RegID y asígnele 'serviceDate'. ¡Eso es!
Pruébela: guarde el diseño y corra su aplicación. Agregue registros facturables y luego pulse
el botón Save & New. El registro debería ser guardado y los controles blanquearse para un
nuevo registro. El foco deberá estar en el control fecha.

Trabajar con Menús


Realmente no hemos tocado los menús en este tutorial porque la herramienta visual que
empezamos para crear menús nunca recibió la atención debida, y básicamente no funciona
del todo. Así que toda la creación de menús es hecha en el código. Pero esto en verdad no es
tan difícil y para demostrarlo, vamos a agregar opciones de menú para el botón 'Save & New'
que añadimos al formulario. Aquí está el código; lo agregamos al método afterInit() del
formulario.
def afterInit(self):
mb = self.MenuBar
fm = mb.getMenu("File")
fm.prependSeparator()
fm.prepend("Cancel", HotKey="Esc", OnHit=self.onCancel)
fm.prepend("Save and New", HotKey="Ctrl+Shift+S",
OnHit=self.saveAndNew)
fm.prepend("Save", HotKey="Ctrl+S", OnHit=self.onSave)
self.requery()

Cada formulario tiene su propia barra de menú, con una referencia a ella en su propiedad
MenuBar. Usted puede obtener referencias a cada menú pasando la leyenda de ese menú al
método getMenu de la barra de menú. Queremos que esos elementos de menú aparezcan
encima de los elementos predeterminados, así que utilizaremos el método prepend() y los
añadiremos desde el final hacia arriba. Primero añadimos un separador , luego los elementos
para las funciones del botón. Tenga en cuenta que podemos pasar la combinación de tecla
HotKey así como también el manejador para ese menú (usando el parámetro estándar
OnHit). La clase formulario viene con manejadores para onSave() y onCancel(), pero el
método saveAndNew() que agregamos anteriormente no es un manejador de evento (i.e., no
acepta un objeto evento como parámetro). Esto es bastante fácil de corregir: podemos añadir
un simple parámetro evt=None a la firma de ese método. Guarde, salga y corra la aplicación.
Verá nuestro menú personalizado (abajo, izquierda).

Observe como la cosa multiplataforma es manejada por usted: especifiqué Ctrl-S como el
atajo, y Dabo sabía que estaba corriendo en una Mac, e introdujo el símbolo Command Key;
también mostró el símbolo para Escape. Si usted corriera la aplicación sobre otra plataforma
diferente, no necesitaría hacer nada distinto del todo. Esta es una captura de la misma
aplicación corriendo sin cambios en Windows XP (abajo, derecha):
Añadir un Informe
¿Qué es una aplicación de facturación si usted no puede generar informes? Agreguemos el
informe más importante en la vida de un consultor: la factura de cliente. Añadiremos otro
elemento de menú que instanciará otro formulario y ese formulario tomará alguna entrada
del usuario para generar el cursor informe. Usaremos el Diseñador de Informes.

El resto de esta sección está en la documentación en línea.

También podría gustarte