Propiedades Anchor y Dock
IronPython - Diseño WinForms: Propiedades Anchor
y Dock
En las secciones anteriores hemos hablado sobre Location
y Size
, las propiedades básicas de los controles winforms para el diseño de la interfaz de nuestras aplicaciones.
También hemos hablado de cómo adaptar la posición y tamaño de los controles cuando cambia el tamaño del formulario. Lo hacemos manejando el evento Resize
.
Bien, ha llegado el momento de hablar de las propiedades Anchor
y Dock
, que en muchos casos pueden facilitarnos la vida enormemente.
Propiedad Anchor
La propiedad Anchor
(anclaje) de un control define a que direcciones está anclado el control. El comportamiento que aporta el valor de la propiedad Anchor
al control se pone de manifiesto cuando cambia el tamaño del formulario.
El valor de la propiedad Anchor
debe ser uno de los definidos en la enumaración System.Windows.Forms.AnchorStyles
, o una combinación de estos:
AnchorStyles.Left
- El control permanece anclado a la izquierda.AnchorStyles.Right
- El control permanece anclado a la derecha.AnchorStyles.Top
- El control permanece anclado a la parte superior.AnchorStyles.Bottom
- El control permanece anclado a la parte inferior.
Que un control esté anclado en una dirección significa que si el formulario se redimensiona en esa dirección la posición del control se ajusta para que la distancia entre este y el límite en dicha dirección permanezca constante. Es decir, la propiedad Anchor
puede cambiar el valor de la propiedad Location
durante un Resize
.
El valor de la propiedad Anchor
puede ser una combinación de los valores citados anteriormente, unidos a través del operador de disyunción |
. De hecho, el valor por defecto de Anchor
para todos los controles es AnchorStyles.Top | AnchorStyles.Left
, lo que significa que los controles están anclados arriba y a la izquierda por defecto.
Si el valor del Anchor
de un control es AnchorStyles.Top | AnchorStyles.Bottom
o AnchorStyles.Left | AnchorStyles.Right
, el anclaje forzará que cambie el valor de la propiedad Size
del control.
Para comprender el funcionamiento de Anchor
vamos a crear una aplicación de ejemplo a continuación.
Uso práctico de Anchor
¡¡Vamos a crear un editor de código python!!
Necesitamos un formulario con un RichTextBox
en el que escribir código y un Button
para ejecutar dicho código. El botón estará en el pie del formulario, en la parte inferior, y la caja de texto ocupará el resto del espacio disponible. ¡¡Manos a la obra!!
El esqueleto de la aplicació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 *
if sys.implementation.name == 'cpython': # Ajustes para que funcione en PythonNET (no necesarios para IronPython)
_Size, _Font = Size, Font
Size = lambda x, y: _Size(int(x), int(y))
Font = lambda fontfamily, fontsize, fontstyle: _Font(fontfamily, float(fontsize), fontstyle, GraphicsUnit.Pixel)
class MainForm(Form):
def __init__(self):
Form.__init__(self)
self.Text = 'Editor IronPython' if sys.implementation.name == 'ironpython' else 'Editor PythonNET'
self.Icon = Icon.ExtractAssociatedIcon(sys.executable)
self.ClientSize = Size(500, 300)
self.Load += self.set_size_limits
self.add_controls()
def set_size_limits(self, *args):
self.MinimumSize = self.Size
self.MaximumSize = Size(self.Size.Width*2, self.Size.Height*2)
def add_controls(self):
pass
def ejecutar_codigo(self, *args):
pass
if __name__ == '__main__':
Application.EnableVisualStyles()
Application.Run(MainForm())
Por ahora hemos creado un formulario y hemos definido su texto, icono y tamaño en el método __init__
. Cuando el formulario sea cargado se ejecutará el método set_size_limits
, donde se asigna un tamaño mínimo y un tamaño máximo para el formulario.
Hemos definido dos métodos vacíos que todavía no hacen nada. En el método add_controls
crearemos y añadiremos los controles, y el método ejecutar_codigo
será el controlador/manejador del evento Click
del botón que vamos a añadir.
Método add_controls
Añadiremos una instancia de RichTextBox
que almacenaremos en la variable self.rch
:
self.rch = RichTextBox()
self.rch.Location = Point(0, 0)
self.rch.Size = Size(self.ClientSize.Width, self.ClientSize.Height*0.9)
self.rch.Font = Font(FontFamily('Consolas'), 12, FontStyle.Regular)
self.rch.AcceptsTab = True # Permite introducir tabulaciones
self.rch.ForeColor = Color.Blue
self.rch.Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Top | AnchorStyles.Bottom
self.Controls.Add(self.rch)
Lo mas importante aquí es lo siguiente:
self.rch.Location = Point(0, 0)
self.rch.Size = Size(self.ClientSize.Width, self.ClientSize.Height*0.9)
self.rch.Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Top | AnchorStyles.Bottom
La caja de texto está posicionada en la esquina superior izquierda. Su ancho es igual al del formulario, y su altura es un 90% la del formulario.
Gracias a la asignación de la propiedad Anchor
:
self.rch.Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Top | AnchorStyles.Bottom
Si cambia el tamaño del formulario también lo hará el de la caja de texto, ya que los 4 límites del formulario están tirando de ella.
Ahora tenemos que añadir un botón que nos permita ejecutar el código escrito en self.rch
:
self.btn = Button()
self.btn.Text = 'Ejecutar'
self.btn.Location = Point(0, self.rch.Size.Height)
self.btn.Size = Size(self.ClientSize.Width, self.ClientSize.Height*0.1)
self.btn.Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Bottom
self.btn.Click += self.ejecutar_codigo
self.Controls.Add(self.btn)
Lo mas importante aquí es lo siguiente:
self.btn.Location = Point(0, self.rch.Size.Height)
self.btn.Size = Size(self.ClientSize.Width, self.ClientSize.Height*0.1)
self.btn.Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Bottom
El botón almacenado en self.btn
está posicionado totalmente a la izquierda en horizontal. Su posición en vertical coincide con la altura de self.rch
. El botón ocupa todo el ancho disponible, y su altura es un 10% de la altura del formulario.
Gracias a la asignación de la propiedad Anchor
:
self.btn.Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Bottom
Si cambia el tamaño del formulario también lo hará el del botón, ya que el límite de la izquierda, derecha e inferior tiran de él. El botón está anclado en esas 3 direcciones.
Ya hemos registrado el controlador/manejador del evento Click
del botón. Ahora tenemos que definir lo que ocurre en él
Método ejecutar_codigo
El código de este método es muy sencillo:
def ejecutar_codigo(self, *args):
try:
exec(self.rch.Text, {})
except Exception as e:
MessageBox.Show(str(e), 'ERROR', MessageBoxButtons.OK, MessageBoxIcon.Error)
Dado que el funcionamiento de este método no es la finalidad de este artículo voy a omitir su explicación. En cualquier caso, seguramente lo comprendes perfectamente.
Este es el código completo del Editor IronPython:
# -*- coding: utf-8 -*-
import sys, clr
clr.AddReference('System.Windows.Forms')
clr.AddReference('System.Drawing')
from System.Windows.Forms import *
from System.Drawing import *
if sys.implementation.name == 'cpython': # Ajustes para que funcione en PythonNET (no necesarios para IronPython)
_Size, _Font = Size, Font
Size = lambda x, y: _Size(int(x), int(y))
Font = lambda fontfamily, fontsize, fontstyle: _Font(fontfamily, float(fontsize), fontstyle, GraphicsUnit.Pixel)
class MainForm(Form):
def __init__(self):
Form.__init__(self)
self.Text = 'Editor IronPython' if sys.implementation.name == 'ironpython' else 'Editor PythonNET'
self.Icon = Icon.ExtractAssociatedIcon(sys.executable)
self.ClientSize = Size(500, 300)
self.Load += self.set_size_limits
self.add_controls()
def set_size_limits(self, *args):
self.MinimumSize = self.Size
self.MaximumSize = Size(self.Size.Width*2, self.Size.Height*2)
def add_controls(self):
self.rch = RichTextBox()
self.rch.Location = Point(0, 0)
self.rch.Size = Size(self.ClientSize.Width, self.ClientSize.Height*0.9)
self.rch.Font = Font(FontFamily('Consolas'), 12, FontStyle.Regular)
self.rch.AcceptsTab = True
self.rch.ForeColor = Color.Blue
self.rch.Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Top | AnchorStyles.Bottom
self.Controls.Add(self.rch)
self.btn = Button()
self.btn.Text = 'Ejecutar'
self.btn.Location = Point(0, self.rch.Size.Height)
self.btn.Size = Size(self.ClientSize.Width, self.ClientSize.Height*0.1)
self.btn.Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Bottom
self.btn.Click += self.ejecutar_codigo
self.Controls.Add(self.btn)
def ejecutar_codigo(self, *args):
try:
exec(self.rch.Text, {})
except Exception as e:
MessageBox.Show(str(e), 'ERROR', MessageBoxButtons.OK, MessageBoxIcon.Error)
if __name__ == '__main__':
Application.EnableVisualStyles()
Application.Run(MainForm())
Propiedad Dock
La propiedad Dock
(acoplamiento) especifica a que borde/s se acopla un control.
El valor de la propiedad Dock
debe ser uno de los definidos en la enumaración System.Windows.Forms.DockStyle
:
DockStyle.None
- No se aplica acoplamiento. Es el valor por defecto.DockStyle.Top
- Acoplamiento al borde superior.DockStyle.Bottom
- Acoplamiento al botde inferior.DockStyle.Left
- Acoplamiento a la izquierda.DockStyle.Right
- Acoplamiento a la derecha.DockStyle.Fill
- Acoplamiento en todas las direcciones. El control ocupa todo el espacio disponible, rellena a su contenedor.
Diferencias entre Anchor
y Dock
:
- El valor de
Dock
no puede ser una combinación de los valores definidosSystem.Windows.Forms.DockStyle
. - Ambas propiedades pueden modificar el
Location
de un control, pero mientras queAnchor
la modifica para mantener la distancia con la dirección de anclaje definida,Dock
anula completamente a la propiedadLocation
para enviar al control al borde de acoplamiento definido. En el caso deDockStyle.Fill
, el control se expande en todas las direcciones rellenando todo el espacio disponible.
DockStyle.None
Es el valor por defecto. El control no es acoplado en ninguna dirección, y su posición y tamaño están definidos por su Location
y Size
.
DockStyle.Left
y DockStyle.Right
El control se acopla a la izquierda o a la derecha. Anula a las propiedades Location
y Height
.
DockStyle.Top
y DockStyle.Bottom
El control se acopla arriba o abajo. Anula a las propiedades Location
y Width
.
DockStyle.Fill
El control se acopla en todas las direcciones. Anula a las propiedades Location
y Size
(tanto Width
como Height
).
En el apartado anterior hemos creado un Editor IronPython para ver el funcionamiento de Anchor
. Podemos simplificar el método add_controls
utilizando Dock
y que el programa se comporte exactamente igual.
Nuestro código para add_controls
era el siguiente:
def add_controls(self):
self.rch = RichTextBox()
self.rch.Location = Point(0, 0)
self.rch.Size = Size(self.ClientSize.Width, self.ClientSize.Height*0.9)
self.rch.Font = Font(FontFamily('Consolas'), 12, FontStyle.Regular)
self.rch.AcceptsTab = True
self.rch.ForeColor = Color.Blue
self.rch.Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Top | AnchorStyles.Bottom
self.Controls.Add(self.rch)
self.btn = Button()
self.btn.Text = 'Ejecutar'
self.btn.Location = Point(0, self.rch.Size.Height)
self.btn.Size = Size(self.ClientSize.Width, self.ClientSize.Height*0.1)
self.btn.Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Bottom
self.btn.Click += self.ejecutar_codigo
self.Controls.Add(self.btn)
Y podemos simplificarlo utilizando Dock
en lugar d Anchor
:
def add_controls(self):
self.rch = RichTextBox()
self.rch.Font = Font(FontFamily('Consolas'), 12, FontStyle.Regular)
self.rch.AcceptsTab = True
self.rch.ForeColor = Color.Blue
self.rch.Dock = DockStyle.Fill
self.Controls.Add(self.rch)
self.btn = Button()
self.btn.Text = 'Ejecutar'
self.btn.Height = self.ClientSize.Height*0.1
self.btn.Dock = DockStyle.Bottom
self.btn.Click += self.ejecutar_codigo
self.Controls.Add(self.btn)
Al hacer esta modificación, y una vez compruebes que el programa se comporta exactamente igual, fíjate en lo siguiente:
- Al usar
Dock
no hemos tenido que indicar unLocation
para los controles. - Tampoco hemos indicado el
Size
de los controles. Solamente hemos asignado una altura del 10% la del formulario para el botón:self.btn.Height = self.ClientSize.Height*0.1
, que estará enDockStyle.Bottom
. - La caja de texto ocupará todo el espacio disponible:
DockStyle.Fill
. Pero como hemos asignado unHeight
para el botón que se encuentra en la parte inferior, el borde inferior para la caja de texto es el botón.
Evidentemente hay situaciones en las que hay que utilizar Anchor
y no podemos optar por Dock
. Por ejemplo, cuando un control debe anclarse a la izquierda pero necesitamos un Location.X != 0
, o cuando necesitamos anclar en dos direcciones, por ejemplo: AnchorStyles.Left | AnchorStyles.Top
.
Espero que te haya quedado claro cómo deben utilizarse estas dos propiedades, que son tremendamente útiles.
Un saludo!