Controles con posición y tamaño adaptativo



IronPython - Diseño WinForms: Manejar el evento Resize


En la sección anterior hemos visto como posicionar y dar tamaño a los controles. Habíamos creado un script que muestra un formulario con un botón centrado:

# -*- coding: utf-8 -*-

import sys, clr

clr.AddReference('System.Windows.Forms')
clr.AddReference('System.Drawing')

from System.Windows.Forms import *
from System.Drawing import *

class MainForm(Form):
    def __init__(self):
        Form.__init__(self)
        self.Text = 'Mi App'
        self.Icon = Icon.ExtractAssociatedIcon(sys.executable)
        self.ClientSize = Size(300, 100)
        self.MaximizeBox = False
        self.FormBorderStyle = FormBorderStyle.FixedSingle
        self.add_controls()

    def add_controls(self):
        button = Button()
        button.Text = 'Un botón'

        button.Size = Size(100, 50)

        x = (self.ClientSize.Width - button.Size.Width) / 2
        y = (self.ClientSize.Height - button.Size.Height) / 2

        button.Location = Point(x, y)

        self.Controls.Add(button)


if __name__ == '__main__':

    Application.EnableVisualStyles()
    Application.Run(MainForm())

El resultado fue el siguiente:

Formulario con botón centrado

El problema era que al cambiar el tamaño del formulario el botón ya no estaría centrado, por lo que para impedir que el usuario pueda cambiar el tamaño del formulario añadimos lo siguiente en el método __init__:

self.MaximizeBox = False
self.FormBorderStyle = FormBorderStyle.FixedSingle

Pero si queremos que la aplicación muestre una ventana de tamaño variable y que el botón permanezca siempre centrado hay que buscar otra solución.



Evento Resize del formulario


Los objetos de la clase System.Windows.Forms.Form tienen definido un evento Resize que nos permite ejecutar una función (manejador de eventos) cuando se produce dicho evento.

El evento Resize se dispara cuando cambian las dimensiones del formulario, bien porque el usuario hace click en una esquina y arrastra para aumentar y disminuir su tamaño, o bien porque se maximiza o minimiza el formulario haciendo click en los correspondientes botones de la caja de control.

En primer lugar vamos a hacer que nuestro formulario pueda redimensionarse:

def __init__(self):
    Form.__init__(self)
    self.Text = 'Mi App'
    self.Icon = Icon.ExtractAssociatedIcon(sys.executable)
    self.ClientSize = Size(300, 100)
    #self.MaximizeBox = False
    #self.FormBorderStyle = FormBorderStyle.FixedSingle
    self.add_controls()

A continuación vamos a eliminar el Location del botón dentro del método add_controls, y además vamos a hacer que el botón sea un atributo de MainForm (cambiando button por self.button):

def add_controls(self):
    self.button = Button()
    self.button.Text = 'Un botón'
    self.button.Size = Size(100, 50)

    #x = (self.ClientSize.Width - button.Size.Width) / 2
    #y = (self.ClientSize.Height - button.Size.Height) / 2

    #button.Location = Point(x, y)

    self.Controls.Add(self.button)

Hemos cambiado button por self.button porque ahora vamos a crear un método llamado relocate_controls en el que tenemos que utilizar el botón. En este método es donde vamos a asignar el Location del botón:

def relocate_controls(self, *args):

    x = (self.ClientSize.Width - self.button.Size.Width) / 2
    y = (self.ClientSize.Height - self.button.Size.Height) / 2

    self.button.Location = Point(x, y)

Fíjare en que este método recibe un número arbitrario de argumentos (*args). Esto es así porque a continuación utilizaremos este método como un manejador de eventos.

Ahora, en el método __init__ invoca al método que acabamos de crear y registralo como manejador del evento Resize:

def __init__(self):
    Form.__init__(self)
    self.Text = 'Mi App'
    self.Icon = Icon.ExtractAssociatedIcon(sys.executable)
    self.ClientSize = Size(300, 100)
    self.add_controls()
    self.relocate_controls()                    # Invocamos al método para que se centre al inicio
    self.Resize += self.relocate_controls       # Registramos el método como manejador del evento Resize

Al registrar el método relocate_controls como manejador del evento Resize, la posición del botón se ajustará siempre que cambien las dimensiones del formulario, permaneciendo el botón centrado en todo momento:

Formulario con botón centrado y posición adaptable

Cuando el tamaño del formulario cambia se modifica el valor de self.ClientSize y se dispara el evento Resize, de manera que se ejecuta el método relocate_controls, en el que se calcula el valor de las variables x e y en función del valor actual de self.ClientSize. Finalmente, se asigna un nuevo valor para la propiedad Location del botón: self.button.Location = Point(x, y).



Código final


Ya sabes como adaptar dinámicamente la posición de los controles en función del tamaño actual del formulario manejando el evento Resize. El código final de esta sección es el siguiente:

# -*- coding: utf-8 -*-

import sys, clr

clr.AddReference('System.Windows.Forms')
clr.AddReference('System.Drawing')

from System.Windows.Forms import *
from System.Drawing import *

class MainForm(Form):
    def __init__(self):
        Form.__init__(self)
        self.Text = 'Mi App'
        self.Icon = Icon.ExtractAssociatedIcon(sys.executable)
        self.ClientSize = Size(300, 100)
        self.add_controls()
        self.relocate_controls()
        self.Resize += self.relocate_controls

    def add_controls(self):
        self.button = Button()
        self.button.Text = 'Un botón'
        self.button.Size = Size(100, 50)
        self.Controls.Add(self.button)

    def relocate_controls(self, *args):

        x = (self.ClientSize.Width - self.button.Size.Width) / 2
        y = (self.ClientSize.Height - self.button.Size.Height) / 2

        self.button.Location = Point(x, y)


if __name__ == '__main__':

    Application.EnableVisualStyles()
    Application.Run(MainForm())

Como ejercicio intenta que el tamaño del botón también se ajuste proporcionalmente al tamaño del formulario, ¿cómo lo harías? tendrías que reasignar un nuevo valor a la propiedad Size del botón siempre que las dimensiones del formulario cambien.


En la siguiente sección veremos un ejemplo con 6 botones con tamaño y posición adaptativos.

Un saludo!