sábado, 25 de septiembre de 2010

Los sizer de wxPython en wxFormBuilder.

Una manera de disponer los widgets de una aplicación wxPython es especificar explícitamente la posición y el tamaño de cada widget cuando se crea (con wx.Point y wx.Size). Un ejemplo de ello lo podemos ver en el diseñador de Frames de Boa Constructor. Aunque este método es razonablemente sencillo (como el usado en Microsoft Visual Studio) tiene sus defectos, más concretamente cuando el tamaño de los widgets y de las fuentes de letras difieren, puede resultar bastante complicado posicionarlos correctamente en todos los sistemas (Windows, Linux, Mac). Además, se debe cambiar explícitamente la posición de cada widget cada vez que el usuario redimensiona el contenedor padre.

Sin embargo podemos utilizar otra técnica. El mecanismo de disposición de widgets en wxPython se denomina sizer. Cada sizer maneja el tamaño y posición de sus windows basado en un conjunto de reglas.

NOTA: Se recuerda al lector que en wxPython una window no es una ventana tal como la conocemos, sino cualquier objeto en wx.

Los sizer se asignan a un contenedor window (normalmente un wx.Panel, aunque puede ser también a un wx.Frame). Así los subwindows creados dentro del padre deben de añadirse al sizer, de manera que son ellos (los sizer) son los que administran el tamaño y posición de cada widget.

Dejando algunos conceptos claros: Frame y Panel

Toda la interacción que se da en un programa wxPython tiene lugar dentro de un widget contenedor, denominado Frame, el cual es una instancia de la clase wx.Frame. Lo normal en una aplicación wxPython es crear subclases de wx.Frame y crear instancias de dichas subclases. Para aclarar mejor el concepto vamos a ver un ejemplo que utiliza posicionamiento por coordenadas de los widgets (utilizando wx.Point y wx.Size), incluyendo dichos widgets directamente en el Frame. El código es el siguiente:

# -*- coding: cp1252 -*-
# 25/09/2010
# Ejemplo de wxPython
# El Viaje del Navegante

import wx

class subclase_frame(wx.Frame):
def __init__(self):
# Llamamos al init (código del constructor) del wx.Frame del que
# heredamos.
wx.Frame.__init__(self, None, -1, 'Registro (Subclase de Frame)',
pos=wx.Point(200, 200),size=wx.Size(400,340))

# Creamos un widget wx.Button, 3 widget wx.StaticText y
# 3 widget wx.TextCtrl.
# Botón de salida de la aplicación.
self.boton = wx.Button(parent=self,id=-1,label="Salir",
pos=wx.Point(296,250),size=wx.Size(75,23))

# Nombre.
self.etiquetaNombre = wx.StaticText(id=-1,label='Nombre',
name='etiquetaNombre', parent=self,pos=wx.Point(16, 18),
size=wx.Size(54, 13), style=0)

self.textoNombre = wx.TextCtrl(id=-1, name='textoNombre',
parent=self, pos=wx.Point(80, 18), size=wx.Size(288, 21))

# Apellidos.
self.etiquetaApellidos = wx.StaticText(id=-1,label='Apellidos',
name='etiquetaApellidos', parent=self,pos=wx.Point(16, 42),
size=wx.Size(54, 13), style=0)

self.textoApellidos = wx.TextCtrl(id=-1, name='textoApellidos',
parent=self, pos=wx.Point(80, 42), size=wx.Size(288, 21))

# NIF.
self.etiquetaNIF = wx.StaticText(id=-1,label='NIF',
name='etiquetaNIF', parent=self,pos=wx.Point(16, 66),
size=wx.Size(54, 13), style=0)

self.textoNIF = wx.TextCtrl(id=-1, name='textoNIF',parent=self,
pos=wx.Point(80, 66), size=wx.Size(288, 21))

# Dirección.
self.etiquetaDireccion = wx.StaticText(id=-1,label='Dirección',
name='etiquetaDireccion', parent=self,pos=wx.Point(16, 90),
size=wx.Size(54, 13), style=0)

self.textoDireccion = wx.TextCtrl(id=-1, name='textoDireccion',
parent=self,pos=wx.Point(80, 90), size=wx.Size(288, 21))

# Código postal.
self.etiquetaCP = wx.StaticText(id=-1,label='Cód.Postal',
name='etiquetaCP', parent=self,pos=wx.Point(16, 114),
size=wx.Size(54, 13), style=0)

self.textoCP = wx.TextCtrl(id=-1, name='textoCP',
parent=self,pos=wx.Point(80, 114), size=wx.Size(50, 21))

# Población.
self.etiquetaPoblacion = wx.StaticText(id=-1,label='Población',
name='etiquetaPoblacion', parent=self,pos=wx.Point(140, 114),
size=wx.Size(54, 13), style=0)

self.textoPoblacion = wx.TextCtrl(id=-1, name='textoPoblacion',
parent=self,pos=wx.Point(200, 114), size=wx.Size(170, 21))

# Provincia.
self.etiquetaProvincia = wx.StaticText(id=-1,label='Provincia',
name='etiquetaProvincia', parent=self,pos=wx.Point(16, 138),
size=wx.Size(54, 13), style=0)

self.textoProvincia = wx.TextCtrl(id=-1, name='textoProvincia',
parent=self,pos=wx.Point(80, 138), size=wx.Size(288, 21))

# País.
self.etiquetaPais = wx.StaticText(id=-1,label='País',
name='etiquetaPais', parent=self,pos=wx.Point(16, 162),
size=wx.Size(54, 13), style=0)

self.textoPais = wx.TextCtrl(id=-1, name='textoPais',
parent=self,pos=wx.Point(80, 162), size=wx.Size(288, 21))

# Correo electrónico.
self.etiquetaEmail = wx.StaticText(id=-1,label='e-mail',
name='etiquetaEmail', parent=self,pos=wx.Point(16, 186),
size=wx.Size(54, 13), style=0)

self.textoEmail = wx.TextCtrl(id=-1, name='textoEmail',
parent=self,pos=wx.Point(80, 186), size=wx.Size(288, 21))

# Teléfono.
self.etiquetaTelefono = wx.StaticText(id=-1,label='Teléfono',
name='etiquetaTelefono', parent=self,pos=wx.Point(16, 210),
size=wx.Size(54, 13), style=0)

self.textoTelefono = wx.TextCtrl(id=-1, name='textoTelefono',
parent=self,pos=wx.Point(80, 210), size=wx.Size(288, 21))

# Creamos los manejadores de eventos, ligando los eventos a
# los métodos que tendrán el código asociado.
self.Bind(wx.EVT_BUTTON, self.OnBotonSalir)
self.Bind(wx.EVT_CLOSE, self.OnSalir)

# Definimos los métodos que contienen el código que se ejecutará
# cuando sean llamados a petición de los eventos definidos anteriormente.

def OnBotonSalir(self, event):
# Cerramos la ventana.
self.Close(True)

def OnSalir(self, event):
# Destruimos el widget.
self.Destroy()

# Creamos una aplicación simple wx.
aplicacion = wx.PySimpleApp()

# Creamos el objeto frame, fruto de la instanciación de la clase
# subclase_frame.
frame = subclase_frame()

# Mostramos la instanciación de la clase subclase_frame.
frame.Show()

# Lanzamos el MainLoop, para escuchar peticiones de eventos.
aplicacion.MainLoop()


Dando como resultado:

La aplicación expuesta está lo suficientemente comentada para comprender el funcionamiento de la misma, por lo que no me pararé en este punto. Sin embargo podemos darle algo más de funcionalidad a este mantenimiento, incluyendo características extras mediante la utilización de paneles.

Un panel es una instancia de la clase wx.Panel, siendo este un contenedor simple para otros widgets con poca funcionalidad. Se debería usar casi siempre el wx.Panel como subwidget de alto nivel para un Frame, por diversas razones:

1) Se puede reusar el código, ya que un mismo panel se puede utilizar en varios Frames.
2) Las instancias de wx.Panel tienen un color de background por defecto en Microsoft Windows de blanco, en vez de gris.
3) Cuando se pulsa la tecla Enter el Panel responde con eventos de teclado de tabulación (para pasar de un widget a otro, como si fuera pulsando la tecla TAB).

Así pues, en el ejemplo anterior, podríamos incluir una instancia de wx.Panel y modificar todos los widget de modo que ahora el padre sea el panel que actuará como subwidget de alto nivel. Esto es, habría que crear el panel justo después de llamar al constructor del wx.Frame en el __init__:

# Creamos un panel.
self.panel = wx.Panel(self, -1)

Y en cada widget cambiar el parent, tal que así:

# Botón de salida de la aplicación.
self.boton = wx.Button(parent=self.panel,id=-1,label="Salir",
pos=wx.Point(296,250),size=wx.Size(75,23))

Aquí he incluido solo el widget botón, pero hay que hacerlo en todos.

El resultado sería:

No solo cambia el color del background, sino que podemos "saltar" de una caja de texto a otra simplemente pulsando la tecla Enter (ó Intro), dando una funcionalidad de la tecla de tabulación TAB.

¿Qué es en realidad un sizer?

Un sizer es un algoritmo automatizado para disponer ó enmarcar un grupo de widgets. Un sizer se adjunta a un contenedor, normalmente un frame ó un panel, como se ha comentado anteriormente. Los subwidgets que se crean dentro del contenedor padre se deben de añadir por separado al sizer. Dicho sizer administra la disposición de los widgets que están dentro de él en el momento en el que se adjunta al contenedor.

NOTA: A los widgets que están dentro de un sizer normalmente se les llama los hijos del sizer.

Las ventajas de utilizar un sizer son sustanciales. El sizer recalculará la disposición de sus widgets hijos cuando cambie el tamaño del contenedor en el que se encuentra. De igual manera, si un widget hijo cambia de tamaño, el sizer puede refrescar automáticamente la disposición de sus hijos. Además, los sizer son fáciles de administrar cuando se quiere cambiar la disposición de los widgets hijos. La principal desventaja del uso de sizers es que pueden llegar a ser restrictivos en algunas ocasiones.

Tipos de sizer

Un sizer de wxPython en un objeto cuyo único propósito es administrar el posicionamiento de un conjunto de widgets dentro de un contenedor (un Frame ó un Panel). Hay que tener claro que el sizer no es un contenedor ó un widget propiamente dicho. Un sizer es la representación de un algoritmo de posicionamiento en pantalla. Todos los sizer son instancias de una subclase de la clase abstracta wx.Sizer. Por último hay que tener en cuenta que un sizer puede estar incluido dentro de otro sizer.

Hay 5 tipos (los más utilizados) de sizer en wxPython, a saber:
  • wx.BoxSizer
  • wx.FlexGridSizer
  • wx.GridSizer
  • wx.GridBagSizer
  • wx.StaticBoxSizer
¿Cómo crear un sizer?

Los pasos para crear un sizer son los siguientes:
  • Crear el panel ó frame (un contenedor) donde se quiere que se automatice el dimensionado.
  • Crear el sizer.
  • Crear los subwindows (widgets ó cualquier otra cosa) como se haría normalmente.
  • Añadir cada subwindow al sizer utilizando su método Add(). Utilizando este método se le pasa al sizer información adicional, incluyendo la cantidad de espacio que rodea al window, cómo alinear el window dentro del espacio asignado que es administrado por el sizer y cómo ampliar el window cuando el contenedor se redimensiona.
  • Los sizer pueden anidarse, es decir, se pueden añadir sizers dentro de un sizer padre como objetos window. Además se puede dejar a un lado un cierta cantidad de espacios en blanco que actúe como separador.
  • Llamar al método SetSizer(sizer) del contenedor.
Utilizando sizers con wxFormBuilder

Decir que las aplicaciones gráficas podemos diseñarlas con herramientas alternativas, como wxGlade (del que tanto se ha hablado en este blog), las cuales nos permiten crear frames visualmente, de manera que veamos in situ como quedarán nuestras ventanas. Cuando se tiene una gran experiencia se llega al punto en el cual no se necesitan de estos diseñadores gráficos, y normalmente el programador directamente lo programa por código, más aún si tiene una jerarquía de clases de donde heredar componentes gráficos, que sería lo deseable para proyectos grandes, esto es, tener frames tipo, menús tipo, paneles tipo, ya diseñados, e ir intercambiándolos según nos convengan mediante herencia y creación de nuevas clases.

NOTA: Se recuerda al lector que para la utilización de wxPython no es necesario ningún diseñador gráfico, todo se puede programar por código. Sin embargo estos programas nos ayudan a visualizar como quedará realmente nuestra aplicación de la capa gráfica, mostrando los atributos y métodos de los componentes que se nos ofrecen.


wxFormBuilder es un diseñador gráfico para las wxWidgets. Esta herramienta está pensada
para ser utilizada en aplicaciones C++, pero también puede generar código Python utilizando en consecuencia wxPython. Este tipo de aplicación, al igual que wxGlade, sirve únicamente para el diseño y construcción de interfaces gráficas, no es un RAD propiamente dicho.

Pequeña introducción a wxFormBuilder

Pasos para utilizar wxFormBuilder:

1) Descargar la aplicación, según nuestra plataforma de trabajo, desde su sitio web, e instalarla.

2) Ejecutar la aplicación, y configurar un proyecto para que genere código Python, en vez de C++, que es el predeterminado. La primera vez que abrimos wxFormBuilder:

A la derecha de la pantalla, en el Object Properties, tenemos las características del proyecto que queremos generar. Como se puede observar está preparado para generar código C++. Esto hay que cambiarlo. Además hay que darle un nombre al proyecto, así como la ruta en donde se generarán los ficheros .py con el código wxPython. Por ejemplo, tal que así:

Hemos creado un proyecto llamado sizers, que generará un fichero sizers.py, en wxPython, puesto que hemos elegido como code_generation a Python.

OBSERVACIÓN: wxFormBuilder tiene multitud de widgets, más que wxGlade y más actualizados, aunque no todos, claro. Así podemos encontrar los formularios (Form) wxFrame, wxPanel y wxDialog; los tipos de sizers wxBoxSizer, wxStaticBoxSizer, wxGridSizer, wxFlexGridSizer, wxGridBagSizer, wxStdDialogButtonSizer y spacer; los contenedores avanzados wxSplitterWindow, wxScrolledWindow, wxNotebook, wxAuiNotebook, wxListBook y wxChoiceBook; los widgets comunes wxButton, wxBitmapButton,wxStaticText, wxTextCtrl, wxStaticBitmap, wxComboBox, wxChoice, wxListBox, wxListCtrl, wxCheckBox, wxRadioBox, wxRadioButton, wxStaticLine, wxSlider y wxGauge; los widgets avanzados wxTreeCtrl, wxHtmlWindow, wxRichTextCtrl, wxCheckListBox, wxGrid, wxToggleButton, wxColourPickerCtrl, wxFontPickerCtrl, wxFilePickerCtrl, wxDirPickerCtrl, wxDatePickerCtrl, wxCalendarCtrl, wxScrollBar, wxSpinCtrl, wxSpinButton, wxHyperlinkCtrl, wxGenericDirCtr y CustomControl (se puede incluir un widget en la interfaz de usuario aunque todavía no esté soportado por wxFormBuilder); así como los widgets de menú y barra de herramientas que se incluyen en los Frames, a saber wxStatusBar, wxMenuBar, wxMenu, wxMenuItem, Sub Menus, Menu Separators, wxToolBar, ToolBar Tools y ToolBar Separators. Todos ellos los podemos encontrar en la Component Pallete de wxFormBuilder:

Decir que según se vaya construyendo la interfaz gráfica, wxFormBuilder genera automáticamente el código apropiado. Esto lo podemos encontrar en la parte inferior de la aplicación, donde podemos ver las pestañas Designer (muestra como quedarán los widgets gráficamente y Python (el código generado a partir de los widgets que se encuentran en la pestaña Designer):

Así, si incluimos un widget Frame (haciendo click en la pestaña Forms del Component Pallete) se nos presenta en el Designer el widget gráfico y en la pestaña Python el código generado automáticamente.

Se puede observar que el código generado es wxPython. Si hacemos click en el botón de generar código (F8) se nos crea el fichero .py que hemos configurado anteriormente.

Como se ha comentado anteriormente a la hora de crear un mantenimiento se recomienda el uso de wx.Panel para incluir en ellos los sizers que a su vez incluirán los widgets, puesto que le dan una funcionalidad extra. Ahora bien, con wxFormBuilder podemos crear componentes Frame y Panel por separado, creando clases independientes, de manera que podemos crear componentes y a partir de ellos crear nuevos mantenimientos. Anteriormente hemos insertado en nuestro proyecto un Frame. Vamos a insertar ahora un Panel, que NO esté incluido en el Frame, sino que será un componente independiente. El widget está en la pestaña Form/Panel:


En el Object Tree (árbol de objetos):

Lo que tenemos ahora es un árbol de componentes independientes, esto es, el proyecto está formado por dos componentes que se pueden utilizar mediante herencia. Vamos a crear un segundo Frame, con características diferentes al anterior e incluir un sizer StaticBoxSizer en el Panel (con esto podremos llegar a entender a lo que me refiero con proyecto de componentes). Para hacer esto último hay que seleccionar el Panel en el Object Tree e ir a la pestaña Layout/wx.StaticBoxSizer del Component Pallete:

Darse cuenta que el sizer se encuentra dentro del Panel, tal como aparece en el árbol de objetos. Incluimos ahora dentro del sizer un botón y una caja de texto, que se encuentran en la pestaña Common (widgets comunes).

Si nos situamos encima del wxBoxSizer en el Object Tree podemos ver las propiedades del sizer. Hay una especialmente importante, que es la orientación (orient) de los widgets dentro del sizer. Viendo su valor vemos que es wx.Vertical. Esto quiere decir que conforme incluyamos widgets dentro de este sizer se irán posicionando uno debajo de otro, en vertical (fijarse en el botón y en la caja de texto, uno debajo de otro). Si cambiamos el valor de orient a wx.Horizontal, lo que hace es posicionar los widgets que se vayan incluyendo en los sizer de izquierda a derecha, en horizontal, tal que así:

De esta manera le dejamos al sizer que se encargue del posicionamiento automático de los widgets que están contenidos en él. Generalizando tendremos que:

Imaginemos que ahora queremos incluir en este panel dos cajas de texto más, una debajo de otra, en vertical. Evidentemente si seguimos incluyendo este tipo de widget dentro del sizer actual nos pasará esto:

Evidentemente esto no es correcto. Si queremos cambiar la orientación lo que podemos hacer es incluir otro sizer, dentro del anterior (sizers anidados), y cambiarle su orientación. En este caso he utilizado un wx.BoxSizer que podemos encontrar en la pestaña Layout/wxBoxSizer). Esto es:

¡Vaya! Hemos incluido dentro de wxStaticBoxSizer un wxBoxSizer. Tal como aparece en el Object Tree se añade al sizer contenedor en una posición horizontal, pero claro, tampoco es lo que queremos. ¿Cómo solucionarlo? La respuesta está en crear un sizer con orientación vertical que contenga a los sizer que hemos creado. ¿Cómo? Lo primero moviendo el wxStaticBoxSizer dentro de un nuevo wxBoxSizer (click botón derecho sobre el contenedor al cual queremos crearle un sizer padre), tal que así:


Darse cuenta de cómo queda el Object Tree. Todo lo que hemos hecho está incluido en un nuevo wxBoxSizer, y su componente hijo es un sizer wxStaticBoxSizer. Ahora solo falta decirle a wxFormBuilder que el wxBoxSizer padre (con orientación vertical) está compuesto por un wxStaticBoxSizer y justo debajo por un wxBoxSizer. Esto es tan sencillo como hacer Drag & Drop, seleccionando y arrastrando el wxBoxSizer (de abajo) y soltándolo sobre el wxBoxSizer padre, quedando:

De esta manera, tenemos un wxBoxSizer padre con orientación vertical, que tiene como hijos un wxStaticBoxSizer, el cual tiene orientación horizontal y en su interior dos widgets (un botón y una caja de texto), y un wxBoxSizer al cual queremos incluirle dos cajas de texto, una debajo de la otra. Lo que falta es justamente eso, asñadirlas y decirle la orientación deseada.

Analizando el árbol de objetos vemos que en realidad lo que hacemos es anidar sizers que contienen widgets que pueden estar a su vez orientados de maneras diferentes dentro de dichos sizers.

La forma en cómo se comportan los sizers a partir de la redimensión de los contenedores padres (en este caso un Panel) se controla mediante los flags de sizeritembase en la pestaña Properties:

En este caso se ha incluido el flag wx.Expand a las cajas de texto, de manera que cuando se redimensione el Panel las cajas también lo hagan.

Una propiedad muy importante es la proportion en sizeritem, que debería de tener valor 0, para controlar la estrechez de los sizer. Si la modificamos en wxStaticBoxSizer mira como queda ahora:

Es más, si también lo modificamos en el wxBoxSizer hijo tenemos que (fijarse en el margen de color rojo que identifica al sizer):

Para que quede mejor el panel podemos decirle a wxFormBuilder que la caja de texto del wxStaticBoxSizer se expanda y se redimensione a todo el tamaño del contenedor. El problema radica en que no podemos hacer eso ahí, puesto que estamos dentro de un sizer con orientación horizontal. ¿Cómo se resuelve? Igual que antes. Este widget lo incluyo dentro de un sizer y hago lo que necesite. Esto es:

E incluimos el flag wx.Expand:

Redimensionamos un poco el panel e incluimos una nuevo texto en el botón así como en texto del wxStaticBoxSizer, quedando:

Bien, la operativa para trabajar con sizers (y sus tipos) es siempre la misma. Lo único que difiere es que hay tipos de sizers con unas características propias que los distinguen, evidentemente (un wxFlexGridSizer se comporta diferente a un wxBoxSizer, pero son sizers).

Si vemos el código generado en la pestaña de Python podemos observar que aparecen los pasos de creación y utilización de sizers que hemos comentado más arriba. Lo único que nos hemos ahorrado ha sido la codificación, creando la interfaz en un entorno gráfico WYSIWYG (lo que ves es lo que obtienes).

Creando una interfaz real con wxFormBuilder

Llegados a este punto vamos a crear el mantenimiento de Registros visto anteriormente, esta vez con sizers. Decir que la operativa se ha seguido es la misma que hemos visto en el apartado anterior, por lo que solamente muestro el árbol de objetos y la interfaz terminada.

Como se puede observar el proyecto se compone de dos widgets, un Frame y un Panel, independientes entre ellos. Una vez tenemos creada la interfaz tenemos que crear la clase que herede del Frame e incluir en él el Panel que hemos diseñado. Darse cuenta que en el Frame no hay nada, todos los componentes widgets están dentro del Panel. Esto es especialmente aconsejable ya que podemos tener varias clases de Frames y Paneles, con lo que podemos tener varias clases de interfaces, únicamente mezclando componentes. Esto es lo que se llama reutilización de componentes, se construyen objetos nuevos a partir de trozos de código ya creado, para formar nuevos elementos con diversas funcionalidades.

NOTA: wxFormBuilder da la posibilidad de crear el código para los manejadores de eventos y las funciones que se ejecutarán cuando dichos eventos sean una realidad. Para ello tan fácil como seleccionar el widget al cual se le quiere asignar un manejador de eventos e irse a los Events del Object Properties. Por ejemplo, para incluir la función que se disparará cuando se haga click en el botón de "Salir":

Cuando ocurra el evento OnButtonClick (hacer click en el botón) del widget wxButton botonSalida se disparará la función OnSalir. Si nos vamos a la pestaña de Python vemos el código generado automáticamente:



En este ejemplo el código generado por el diseñador wxFormBuilder se guarda en sizers.py. En él se han creado las clases frame_generico (el Frame) y panel_registro (el Panel). Un código para utilizar estos dos componentes sería el siguiente:

(El entorno en el que he desarrollado la programación de este artículo es PyScripter, una alternativa muy interesante para desarrollos profesionales, muy potente y más ligera que NetBeans. De lo mejor que me he encontrado en IDE's no comerciales para desarrollar en Python. Recomendado!!!)

Dando el siguiente resultado:


CONCLUSIONES

El sizer es la herramienta que nos brinda wxPython para poder disponer los widgets dentro de contenedores padre. De esta manera se ahorra tiempo, dejando la administración del posicionamiento y respuesta de redimensionado en sus manos. Es aconsejable utilizar el componente wx.Panel para el diseño de interfaces, ya que nos brinda un funcionamiento extra, así como la realización de una jerarquía de componentes.

wxFormBuilder es un entorno WYSIWYG para el diseño gráfico de interfaces para wxWidgets, que nos da la posibilidad de generar código Python con la plataforma wxPython. Una de las grandes ventajas de wxFormBuilder es que contiene bastantes widgets, así como la posibilidad de ampliarlos. Además, trabajar con sizers en wxFormBuilder es muy fácil e intuitivo, ya que nos da la posibilidad de ir cambiando las características de nuestra interfaz, viendo los resultado in situ. Hay varias aplicaciones de este tipo, como wxGlade (del que tanto se ha hablado en este blog) ó wxDesigner (excelente, pero de pago). Se aconseja al lector profundizar en estos entornos de diseño de interfaces. En siguientes post veremos los diferentes tipos de sizers de los que dispone wxPython, estudiando su comportamiento en profundidad, mediante wxFormBuilder.

Un saludo.

9 comentarios:

  1. Muy interesante el tutorial, una vez leido me estoy empezando a considerar un tipo raro.
    Para empezar la primera vez que entre en tu web me estuve mirando el tutorial que hiciste sobre la agenda y vi que usaste un configurador visual (creo q tambien fue wx.glade) yo de hecho lo estuve mirando y toqueteando pero me parecia bastante raro de usar y lo peor el código que genera (que obviamente lei y estudie) no lo entendía así que opté por usar el método antiguo... leerme un libro e ir encontrándome con problemas y superarlos poco a poco.
    Vale es un método mucho mas dificil y tortuoso pero para mi forma de pensar (como digo creo q soy raro) es mejor.
    Lo que me preocupa y por eso escribo éstas líneas es que si tu que tienes muchísima más experiencia que yo utilizas esas herramientas porque tengo q ser tan cabezota para usar yo el método clásico? ... lo dicho soy un tipo raro.
    Saludos !

    ResponderEliminar
  2. Hola Héctor. Yo, cuando empecé con wxPython, pensaba exactamente como tú, y la verdad que no entendía el mecanismo de sizers, ni su utilidad. Además, yo vengo de programar en Visual Basic y PowerBuilder, donde todo se hace colocando componentes mediante el posicionamiento manual dentro de un contenedor (Frame). Al principio cuesta bastante, pero si te das cuenta, si empiezas a jugar con ellos (sobretodo con el FlexGrid) y con sus propiedades (Expand, Proportion, etc) el mecanismo se vuelve brutalmente poderoso, descargándote de gran cantidad de trabajo en el posicionamiento de widgets. De verdad te digo que el utilizar sizers al principio cuesta, y mucho, no eres para nada raro, porque por ahí pasé yo (¡me considero freak, pero no raro!). Con wxGlade se pueden hacer muchas cosas, pero quizás no es tan intuitivo como wxFormBuilder, y lo digo tanto en generación de código como en el diseño del propio IDE, que a mi parecer wxFormBuilder le gana la partida a wxGlade. Mi consejo es que intentes practicar con wxFormBuilder y el tema de sizers, fijándote mucho en los atributos para su configuración. Te doy mi palabra que no te va a defraudar.

    Saludos Héctor!

    ResponderEliminar
  3. Lo de los sizers con el libraco que tengo lo tengo mas o menos controlado ... lo que no he conseguido es poder meter los textos en sizers de una forma mas elegante que uno a uno, es decir:

    sizer6col.Add(self.txt_PS1_existe, 0, wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL)
    sizer6col.Add(self.ventana.entra_PS1_numero, 0, wx.EXPAND)
    sizer6col.Add(self.atencionps1, 0, wx.ALIGN_LEFT|wx.ALIGN_CENTER_VERTICAL)
    sizer6col.Add(self.txt_PS1_altura, 0, wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL)
    sizer6col.Add(self.ventana.entra_PS1_altura, 0, wx.EXPAND)
    sizer6col.Add((10,1))

    sizer6col.Add((10,1))
    sizer6col.Add(self.txt_PS1_uso_principal, 0, wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL)
    sizer6col.Add(self.ventana.entra_PS1_uso_principal, 0, wx.EXPAND)
    sizer6col.Add(self.txt_PS1_sup_uso_principal, 0, wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL)
    sizer6col.Add(self.ventana.entra_PS1_sup_uso_principal, 0, wx.EXPAND)
    sizer6col.Add(self.ventana.utiles_ps1u1)

    sizer6col.Add((10,1))
    sizer6col.Add(self.txt_PS1_uso_secundario, 0, wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL)
    sizer6col.Add(self.ventana.entra_PS1_uso_secundario, 0, wx.EXPAND)
    sizer6col.Add(self.txt_PS1_sup_uso_secundario, 0, wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL)
    sizer6col.Add(self.ventana.entra_PS1_sup_uso_secundario, 0, wx.EXPAND)
    sizer6col.Add(self.ventana.utiles_ps1u2)

    sizer6col.Add((10,1))
    sizer6col.Add(self.txt_PS1_uso_terciario, 0, wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL)
    sizer6col.Add(self.ventana.entra_PS1_uso_terciario, 0, wx.EXPAND)
    sizer6col.Add(self.txt_PS1_sup_uso_terciario, 0, wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL)
    sizer6col.Add(self.ventana.entra_PS1_sup_uso_terciario, 0, wx.EXPAND)
    sizer6col.Add(self.ventana.utiles_ps1u3)

    al final se convierte en un mareo de objetos y posiciones, vale lo hace muy rapido y flexible ademas el resposicionado/redimensionado lo hace perfecto pero lo encuentro "chavacano" ... estuve intentándolo mediante introspección pero no lo logré.

    ResponderEliminar
  4. Bueno, es que no hay otra forma. Lo de chavacano es suave comparado con lo que dije yo la primera vez que lo utilicé. De todas formas no es obligatorio para usar wxPython la utilización de sizers, como bien sabes, aunque sí lo es en wxGlade y wxFormBuilder. El único que te permite posicionamiento por coordenadas es BOA Constructor, creo recordar (no se si habrá alguno más que lo permita).

    Saludos.

    ResponderEliminar
  5. mi profe me dejo una tarea pero no encuentro definiciones exactas.. haber si me das una mano

    Investigar en Internet el uso de los siguientes componetes del entorno visual del DEVC++:

    wxScrollBar

    wxSlider

    espero respondas gracias

    ResponderEliminar
  6. Hola Anónimo. No he usado nunca DEV C++. Si necesitas documentación sobre esto, yo de tí miraría las wxWidgets, que puedes encontrar en http://www.wxwidgets.org/.

    Espero haberte ayudado.
    Un saludo.

    ResponderEliminar
  7. Buen tutorial, felicitaciones.... Como hago para que algunos sizer (donde voy a colocar unas imagenes con matplotlib) pudan ser antecedidos por el self, es decir en lugar que en la clase que me entrega el wxformuilder aparecen los sizer como bSizer1 = wx.BoxSizer( wx.VERTICAL ) ... etc quiero que sea global es decir self.bSizer_spec = wx.BoxSizer( wx.VERTICAL ), por ahora lo tengo que hacer de manera manual pero me gustaria saber si colocando alguna opcion del sizer en el wxformbuilder pueda hacerlo de manera automatiza, gracias...

    ResponderEliminar
  8. Hola Rubeng. A ver, puedes crear atributos de sizers, está permitido, pero te he de recordar que un sizer es un algoritmo de posicionamiento, no un widget, por lo tanto no tiene mucho sentido hacer self.sizer (donde sizer = wx.BoxSizer(wx.VERTICAL), por ejemplo). En vez de eso puedes hacer 2 cosas. O bien utilizar el widget customControl, lo configuras para el widget que necesites y listo (en este blog hay un post sobre su uso). La otra opción es crear el interfaz gráfico en wxFormBuilder, y luego crear clases que hereden de ellas e incluir tu widget adicional. Por ejemplo, imagina que wxFB genera esto:

    class MyFrame1(wx.Frame):
    def __init__(self, parent):
    wx.Frame.__init__(self, parent)
    self.panel = wx.Panel(self, -1)

    Pues tú puedes crear un módulo que tenga una clase que herede de la que creas en wxFB, de esta forma:

    from modulo import MyFrame1

    class frame(MyFrame1):
    def __init__(self, parent):
    MyFrame1.__init__(self, parent)
    sizer = wx.BoxSizer(wx.VERTICAL)
    self.matplot = matplot(self.panel)
    sizer.Add(self.matplot, 1, wx.EXPAND,5)
    self.panel.SetSizer(sizer)

    De esta manera incluyes tu componente matplot dentro del panel de la clase generada por wxFB.

    Espero haberte ayudado. Un saludo.

    ResponderEliminar
  9. Gracias por la pronta respuesta me ayudo de mucho, estuve leyendo el taller de wxformBuilder y no sabia de la existencia del custom control, magnifico¡¡¡¡¡¡.
    Respecto a heredar la clase de la interface, asi como con f8 genero el codigo con f6 se genera un archivo que automaticamente hereda la interface y te provee de los eventos a usar (excelente)no necesito crear un archivo para realizar la herencia, gracias excelente blog......

    ResponderEliminar