jueves, 26 de marzo de 2020

Tkinter - de marciano a humano - sección 4 - gestión del diseño


Sección 4 - Gestión del diseño

Más adelante discutiremos sobre los widgets, los componentes básicos de su aplicación GUI.

4.0 ¿Cómo se organizan los widgets en una ventana?

Aunque hay tres diferentes "administradores de geometría" en Tkinter (pack, grid y place), el autor prefiere el administrador de geometría .grid() para casi todo (aunque se verán todos, el que más utilizaremos será .grid()). Este administrador trata cada ventana o marco como una tabla, una cuadrícula de filas y columnas.
  • Una celda es el área en la intersección de una fila y una columna.
  • El ancho de cada columna es el ancho de la celda más ancha en esa columna.
  • La altura de cada fila es la altura de la celda más grande de esa fila.
  • Para los widgets que no llenan toda la celda, puede especificar qué sucede con el espacio extra, por ejemplo puede dejar el espacio extra fuera del widget o bién estirar el widget para que se ajuste, ya sea en la dimensión horizontal como en la vertical.
  • Puede combinar varias celdas en un área más grande, este es un proceso llamado expansión.
Cuando crea un widget, no aparece hasta que lo registra en un administrador de geometría. Por lo tanto, la construcción y colocación de un widget es un proceso de dos pasos que va más o menos así:

self.thing=tk.Constructor(parent, ...)
self.thing.grid(...)

Donde Constructor es una de las clases de widgets como "Button", "Frame", etc., y "parent" es el widget principal en el que se está construyendo este widget secundario. Todos los widgets tienen un método .grid() que puede usar para indicarle al administrador de geometría dónde colocarlo.

4.1. El método .grid()

Para mostrar un widget "w" en la pantalla de su aplicación:

w.grid(option=value, ...)

Este método registra un widget "w" con el administrador de geometría de cuadrícula, si no hace esto, el widget existirá internamente, pero no será visible en la pantalla.
Para explicar mejor lo anteriormente dicho, veamos este fragmento del código de nuestra aplicación de ejemplo:

46 self.botonSalida.grid(row=0, column=0, pady=5, padx=5)

Como obsevaremos a continuación, si comentamos esta línea, agregando un "#" en la parte izquierda al inicio del renglón, el botón estará en el programa, pero NO se mostrará en la ventana, el colocar un "hash" delante del renglón es lo que se llama "comentar una línea", es útil a la hora de testear y es una practica bastante común, veamos este renglón comentado y el resultado obtenido:

46 # self.botonSalida.grid(row=0, column=0, pady=5, padx=5)



Como podemos ver, ahora no aparece el botón, pero si "descomentamos" la línea quitando el "#" entonces volverá a verse la ventana y el botón como se muestra en la sección 2.

4.1.1 Argumentos del administrador de geometría .grid()

 

Tabla 1: Argumentos del administrador de geometría .grid()
column El número de columna donde desea que se cuadricule el widget, contando desde cero. El valor por defecto es cero.
columnspan Normalmente, un widget ocupa solo una celda en la cuadrícula, sin embargo, puede tomar varias celdas de una fila y fusionarlas en una celda más grande configurando la opción de la columna en el número de celdas, por ejemplo:

w.grid(row=0, column=2, columnspan=3)

colocaría el widget w en una celda que abarca las columnas 2, 3 y 4 de la fila 0.
_in Para registrar "w" como hijo de algún widget "w2", debemos usar in_=w2. El nuevo padre "w2" debe ser un descendiente del widget principal utilizado cuando se creó "w".
ipadx Espacio interno "x". Esta dimensión se agrega dentro del widget dentro de su lado izquierdo y derecho.
ipady Espacio interno "y". Esta dimensión se agrega dentro del widget en su parte superior e inferior.
padx Espacio externo "x". Esta dimensión se agrega a la izquierda y a la derecha "fuera del widget".
pady Espacio externo "y". Esta dimensión se agrega arriba y debajo "fuera del widget".
row El número de fila en el que desea insertar el widget, contando desde 0. El valor predeterminado es el de la siguiente fila desocupada con un número más alto.
rowspan Normalmente un widget ocupa solo una celda en la cuadrícula, pero puedes agarrar múltiples adyacentes. Sin embargo, las celdas de una columna establecen la opción de la "fila de filas" en el número de celdas que se desea capturar. Esta opción se puede usar en combinación con la opción de columnas para tomar un bloque de células. Por ejemplo:

w.grid(row=3, column=2, rowspan=4, columnspan=5)

lo anterior colocaría el widget "w" en un área formada al fusionar 20 celdas, con los números de fila 3–6 y números de columna 2–6.
sticky Esta opción determina cómo distribuir cualquier espacio extra dentro de la celda que no se tome arriba por el widget en su tamaño natural, lo que causa este efecto es el de "estirar" el widget en todo el espacio posible de acuerdo a como estiremos la ventana, por ejemplo, si estiramos la ventana hacia abajo el botón se estirará hacia abajo, si estiramos la ventana hacia un lado se estirará de lado, todo esto depende de las siguientes configuraciones:
  • Si no proporciona un atributo fijo, el comportamiento predeterminado es centrar el widget en la celda.
  • Puede colocar el widget en una esquina de la celda usando:
    • sticky=tk.NE (arriba a la derecha)
    • sticky=tk.SE (abajo a la derecha)
    • sticky=tk.SW (abajo a la izquierda)
    • sticky=tk.NW (arriba a la izquierda)
  • Puede colocar el widget centrado contra un lado de la celda usando:
    • sticky=tk.N (centro superior)
    • sticky=tk.E (centro derecho)
    • sticky=tk.S (centro inferior)
    • sticky=tk.W (centro izquierdo)

Podemos usar sticky=tk.N+tk.S para estirar el widget verticalmente pero déjelo centrado horizontalmente.

Podemos usar sticky=tk.E+tk.W para estirarlo horizontalmente pero déjelo centrado verticalmente.

podemos usar sticky=tk.N+tk.E+tk.S+tk.W para estirar el widget tanto horizontal como verticalmente para llenar la celda.

Las otras combinaciones también funcionarán. Por ejemplo, sticky=tk.N+tk.S+tk.W estirará el widget verticalmente y lo colocará contra la pared oeste (izquierda).

Si al script le agregamos un padx=0, pady=0 (que sería el default) no va a a notarse diferencia en el diseño, pero si colocamos, por ejemplo, padx=5, pady=5 entonces notamos una mejora ya que “despega” nuestro botón de los bordes en 5 pixeles, en este cuadro se pueden comparar ambas imágenes:

.grid(row=0, column=0, pady=0, padx=0)

.grid(row=0, column=0, pady=5, padx=5)

 

 

 4.2. Otros métodos para gestionar .grid()

Estos métodos relacionados con la cuadrícula se definen en todos los widgets:

 

w.grid_bbox(column=None, row=None, col2=None, row2=None)

Devuelve una tupla de 4 valores que describe el cuadro delimitador de algunos o todos los sistemas de cuadrícula en el widget w, en este caso column y row son las coordenadas x e y de la esquina superior izquierda del área, col2 y row2 son el ancho y la altura. Si pasa argumentos de columna y fila, el cuadro delimitador devuelto describe el área de la celda en esa columna y fila. Si también pasa los argumentos col2 y row2, el cuadro delimitador devuelto describe el área de la cuadrícula de columnas column a col2 inclusive, y de las filas row a row2 inclusive. Por ejemplo, w.grid_bbox(0, 0, 1, 1) devuelve el cuadro delimitador de cuatro celdas, no una.

w.grid_forget()

Este método hace que el widget w desaparezca de la pantalla. Todavía existe, simplemente no es visible. Puede usar .grid() para que aparezca de nuevo, pero no recordará sus opciones de cuadrícula.

w.grid_info()

Devuelve un diccionario cuyas claves son nombres de opciones de w, con los valores correspondientes de esas opciones.

w.grid_location(x, y)

Dadas las coordenadas (x, y) relativas al widget que contiene, este método devuelve una tupla (column, row) que describe qué celda del sistema de cuadrícula de w contiene esa coordenada de pantalla.

w.grid_propagate()

Normalmente, todos los widgets propagan sus dimensiones, lo que significa que se ajustan para adaptarse a los contenidos. Sin embargo, a veces desea forzar que un widget tenga un tamaño determinado, independientemente del tamaño de su contenido. Para hacer esto se llama a w.grid_propagate(0) donde w es el widget cuyo tamaño desea forzar.

w.grid_remove()

Este método es como .grid_forget(), pero sus opciones de cuadrícula se recuerdan, por lo que si .grid() lo vuelve a usar, usará las mismas opciones de configuración de cuadrícula.

w.grid_size()

Devuelve una tupla de 2 valores que contiene el número de columnas y el número de filas respectivamente, en el sistema de cuadrícula de w.

w.grid_slaves (row=None, column=None)

Devuelve una lista de los widgets administrados por el widget w. Si no se proporcionan argumentos, obtendrá una lista de todos los widgets administrados. Use el argumento row= para seleccionar solo los widgets en una fila, o el argumento column= para seleccionar solo los widgets en una columna.

4.3. Configurar tamaños de columna y fila

A menos que tome ciertas medidas, el ancho de una columna de cuadrícula dentro de un widget dado será igual al ancho de su celda más ancha, y el alto de una fila de cuadrícula será el alto de su celda más alta. El atributo adhesivo (sticky) en un widget controla solo dónde se colocará si no llena completamente la celda. Si desea anular este tamaño automático de columnas y filas, use estos métodos en el widget principal w que contiene el diseño de cuadrícula:

w.columnconfigure(N, option=value, ...)

En el diseño de cuadrícula dentro del widget w, configure la columna N para que la opción dada tenga el valor dado.

w.rowconfigure(N, option=value, ...)

En el diseño de cuadrícula dentro del widget w, configure la fila N para que la opción dada tenga el valor dado.


Tabla 2: Opciones de configuración de columna y fila para el administrador de geometría .grid()
minimize El tamaño mínimo de la columna o fila en píxeles. Si no hay nada en la columna o fila dada, no aparecerá, incluso si usa esta opción.
pad Una cantidad de píxeles que se agregarán a la columna o fila dada, más allá de la celda más grande en la columna o fila.
weight para hacer que una columna o fila se pueda estirar, use esta opción y proporcione un valor que le proporcione “peso relativo” a esta columna o fila al distribuir el espacio extra. Por ejemplo, si un widget w contiene un diseño de cuadrícula, estas líneas distribuirán tres cuartas partes del espacio extra para la primera columna y un cuarto para la segunda columna, si no se usa esta opción, la columna o fila no se estirará.

w.columnconfigure (0, weight=3) w.columnconfigure (1, weight=1)

4.4. Hacer que la ventana raíz sea redimensionable

¿Desea que el usuario cambie el tamaño de toda la ventana de su aplicación y distribuya el espacio adicional entre sus widgets internos? Esto requiere algunas operaciones que no son obvias.
Es necesario utilizar las técnicas para la gestión del tamaño de fila y columna, descritas en la Sección 4.3 para hacer que la cuadrícula del widget de la Aplicación se pueda estirar, pero eso por sí solo no es suficiente.
Considere la aplicación básica discutida en la Sección 2, que contiene solo un botón SALIR. Si ejecuta esta aplicación y cambia el tamaño de la ventana, el botón permanece del mismo tamaño y en el mismo lugar (fila 0 columna 0, o sea arriba a la izquierda).
Aquí hay una nueva versión del ejemplo 001 que reemplaza al método .__createWidgets(), en esta versión, el botón "SALIR" siempre llena todo el espacio disponible.

#!/user/bin/env python
#-------------------------------------------------------------------------------
# Name: tkinter ejemplo 002
# Purpose: El proposito de este script es demostrar características básicas
# de tkinter, es parte de la sección 4.4 de la guía de tkinter 8.5
#
# Author: Mauricio José Tobares
#
# Created: 25/01/2020
# Copyright: (c) SplinterStudio 2020
# Licence:
#-------------------------------------------------------------------------------

'de entrada esta importación no es correcta en python 3.x'
# import Tkinter as tk
'la forma correcta de importar tkinter es esta:'
import tkinter as tk

class Prototipo2(tk.Frame):
    def __init__(self, parent):
        super(Application, self).__init__(parent)
        'se trae el contenido de la función crearboton'
        self.crearboton()
    def crearboton(self):
        'ventana de nivel superior.'
        top=self.winfo_toplevel()
        'hace que la fila 0 de la ventana de nivel superior pueda expandirse.'
        top.rowconfigure (0, weight=1)
        'hace que la columna 0 de la ventana de nivel superior pueda expandirse.'
        top.columnconfigure (0, weight=1)
        'hace que la fila 0 del widget pueda expandirse'
        self.rowconfigure (0, weight=1)
        'hace que la columna 0 del widget pueda expandirse'
        self.columnconfigure (0, weight=1)
        'esto es así porque se destruye a root que es el padre o raiz.'
        self.botonSalida=tk.Button(self, text='SALIR', command=root.destroy)
        self.botonSalida.grid(row=0, column=0, sticky=tk.N+tk.S+tk.E+tk.W)

if __name__ == "__main__":
    'ventana root o raiz'
    root=tk.Tk()
    'con esto ejecutamos la aplicación'
    main=Prototipo2(root)
    main.pack(fill="both", expand=True)
    root.mainloop()


La "ventana de nivel superior" es la ventana más externa de la pantalla, sin embargo, esta no es la ventana de su aplicación, es la principal de la instancia de la aplicación.
Para obtener la ventana de nivel superior deberá llamar al método .winfo_toplevel() en cualquier widget de su aplicación (no quiero apurarme mucho pero puede consultar la Sección 26).
La línea top.rowconfigure(0, weight=1) hace que la fila "0" de la cuadrícula de la ventana del nivel superior se pueda estirar, la línea que sigue, top.columnconfigure(0, weight=1) hace lo mismo que la anterior pero con la columna "0".
Los siguientes dos renglones, self.rowconfigure(0, weight=1) y self.columnconfigure(0, weight=1) hacen que la fila "0" y la columna "0" de la cuadrícula del widget Prototipo2 se puedan estirar.
El argumento sticky=tk.N+tk.S+tk.E+tk.W hace que el botón se expanda para llenar su celda de la cuadrícula.
Si notamos en este ejemplo quitamos la parte en que la ventana inicia maximizada para que el ejemplo sea más claro.

Hasta el próximo jueves, salud y saludos!
Mauricio José Tobares

No hay comentarios:

Publicar un comentario

TkInter - de marciano a humano - sección 13 - El Widget LabelFrame

  13 El widget LabelFrame El widget LabelFrame al igual que el widget Frame es un contenedor espacial, un área rectangular que puede...