Kitabı oxu: «Desarrollo de interfaces gráficas en Python 3 con Tkinter»


Desarrollo de interfaces gráficas en Python 3 con Tkinter
Primera edición, 2022
© 2022 Tomás Domínguez Mínguez
© 2022 MARCOMBO, S. L.
www.marcombo.com
Cubierta: ENEDENÚ DISEÑO GRÁFICO
Maquetación: cuantofalta.es
Correctores: Anna Alberola y Mónica Muñoz
Revisor técnico: Ferran Fàbregas
Directora de producción: M. Rosa Castillo
«Cualquier forma de reproducción, distribución, comunicación pública o transformación de esta obra solo puede ser realizada con la autorización de sus titulares, salvo excepción prevista por la ley. Diríjase a Cedro (Centro Español de Derechos Reprográficos, www.cedro.org) si necesita fotocopiar o escanear algún fragmento de esta obra».
ISBN: 978-84-267-3401-3
Producción del ePub: booqlab
A mi hijo
TABLA DE CONTENIDO
1. INTRODUCCIÓN
1.1 Qué es Tkinter
1.2 Su primera interfaz de usuario con Tkinter
2. LA VENTANA PRINCIPAL
3. POSICIONAMIENTO Y DISEÑO
3.1 El gestor de geometría pack
3.2 El gestor de geometría grid
3.3 El gestor de geometría place
4. OPCIONES COMUNES
4.1 Dimensiones
4.2 Color
4.3 Fuente
4.4 Foco
4.5 Posición
4.6 Relieve
4.7 Imágenes
4.8 Cursor del ratón
5. MÉTODOS COMUNES
5.1 Asignación y obtención de valores de atributos
5.2 Temporizadores
5.3 Gestión del foco
5.4 Manejo de dimensiones y posiciones
6. VARIABLES DE CONTROL
7. WIDGETS
7.1 Label y Message
7.1.1 Opciones y métodos
7.1.2 Práctica
7.2 Button
7.2.1 Opciones y métodos
7.2.2 Práctica
7.3 Entry
7.3.1 Opciones y métodos
7.3.2 Práctica
7.4 Spinbox
7.4.1 Opciones y métodos
7.4.2 Práctica
7.5 Listbox
7.5.1 Opciones y métodos
7.5.2 Práctica
7.6 Menús
7.6.1 Menú
7.6.2 OptionMenu
7.6.3 Menubutton
7.7 Cuadros de diálogo
7.7.1 colorchooser
7.7.2 messagebox
7.7.3 filedialog
7.8 Text
7.8.1 Opciones y métodos
7.8.2 Práctica
7.9 Scrollbar
7.9.1 Opciones y métodos
7.9.2 Práctica
7.10 Toplevel
7.10.1 Opciones y métodos
7.10.2 Práctica
7.11 Frame y LabelFrame
7.11.1 Opciones y métodos
7.11.2 Práctica
7.12 PanedWindow
7.12.1 Opciones y métodos
7.12.2 Práctica
7.13 Checkbutton y RadioButton
7.13.1 Opciones y métodos
7.13.2 Prácticas
7.14 Canvas
7.14.1 Opciones y métodos
7.14.2 Objetos gráficos
7.15 Scale
7.15.1 Opciones y métodos
7.15.2 Práctica
8. VALIDACIÓN DE ENTRADAS DE DATOS
9. EVENTOS
9.1 Vinculación de eventos con widgets
9.2 Secuencias de eventos
9.3 Controladores de eventos
9.4 Prácticas
9.4.1 Control del tamaño de la ventana
9.4.2 Atajos de teclado
9.4.3 Editor gráfico
10. EL MÓDULO TTK
10.1 Temas y estilos
10.2 Estado
10.3 Opciones comunes
10.4 Métodos comunes
10.5 Adaptación de una aplicación Tkinter a ttk
10.6 Widgets específicos de ttk
10.6.1 Combobox
10.6.2 Notebook
10.6.3 Progressbar
10.6.4 Separator
10.6.5 Sizegrip
11. ANEXO A. FUNDAMENTOS DE PYTHON
11.1 Entorno de desarrollo
11.1.1 Instalación
11.1.2 Descripción general
11.2 Sintaxis básica de Python
11.3 Variables
11.4 Tipos de datos básicos
11.4.1 Números
11.4.2 Cadenas de caracteres
11.4.3 Booleanos
11.4.4 Conversión de tipos
11.5 Operadores
11.6 Estructuras de control
11.6.1 if…else
11.6.2 while
11.6.3 for
11.7 Estructuras de datos
11.7.1 Listas
11.7.2 Tuplas
11.7.3 Conjuntos
11.7.4 Diccionarios
11.8 Entrada de datos de usuario
11.9 El depurador de código de Python
11.10 Funciones
11.11 Alcance de las variables
11.12 Clases y objetos
11.12.1 Herencia
11.13 Módulos
11.14 Threads
11.15 Ficheros
11.16 Excepciones
11.17 Práctica. Aplicación de gestión de clientes
12. ANEXO B. UNA ÚLTIMA PRÁCTICA
12.1 Módulo gestión de clientes
12.2 Interfaz de usuario
12.2.1 Funciones del menú “Archivo”
12.2.2 Funciones del menú “Operaciones”
12.2.3 Funciones del menú “Ayuda”
Unidad 1
INTRODUCCIÓN
Las interfaces de usuario son el medio de interacción entre las aplicaciones y las personas. Dependiendo de la forma en la que se lleve a cabo esta comunicación, se distinguen diferentes tipos: desde las de línea de comandos (en las que el intercambio de información se realiza utilizando únicamente texto), pasando por las interfaces gráficas, hasta las sofisticadas interfaces de voz (en las que el usuario ni siquiera necesita usar las manos).
Si conoce Python, sabrá que la forma más sencilla de intercambio de datos con un programa escrito en este lenguaje es mediante la shell de su entorno de desarrollo o la línea de comandos proporcionada por una ventana de símbolo del sistema en Windows, la aplicación Terminal en macOS o una consola de Linux (también llamada “terminal” o shell). Se trata, por lo tanto, de una interfaz textual.

Si no conoce Python, al final del libro dispone de un amplio anexo en el que se describen los fundamentos de este lenguaje. Allí se dan todos los conocimientos necesarios para poder seguir las prácticas propuestas.
Por sus características, este tipo de interfaces son muy limitadas, por lo que su utilización puede resultar difícil y engorrosa, aparte de poco atractiva, lo que puede provocar el rechazo de la aplicación. Por ese motivo, salvo casos excepcionales, la mayoría disponen de una interfaz gráfica. Los programas escritos en Python no iban a ser una excepción, y existen diversos paquetes para su desarrollo con los que podrá usar todo tipo de controles gráficos. Entre ellos, destacan:
•Tkinter. Librería estándar de Python. Viene integrada en el propio entorno.
•PyQT. Creada por Riverbank, proporciona un puente de acceso al conocido framework QT de desarrollo de interfaces gráficas de usuario. Incluye un entorno gráfico (PyQt5 designer) con el que incluso se podrían construir de forma sencilla, mediante el método de arrastrar y soltar (drag and drop) los controles gráficos. Se distribuye con una licencia GPL (GNU General Public License – Licencia Pública General de GNU) y otra comercial.
•PySide. Similar a la anterior, también proporciona un puente de acceso a QT; pero, a diferencia de PyQT, está disponible bajo la licencia LGPL (Lesser General Public License – Licencia Pública General Reducida de GNU). Surge como respuesta de Nokia (propietaria de QT) a la negativa de Riverbank de liberar PyQt bajo esta licencia, al requerir el pago de la comercial en caso de que la aplicación también lo sea (algo que no sucede con la licencia LGPL).
•Kivy. Se trata de un paquete de código abierto con licencia MIT (Massachusetts Institute of Technology – Instituto Tecnológico de Massachusetts), para el desarrollo rápido de interfaces gráficas multiplataforma. No solo funciona en Windows, Linux o macOS, sino también en Android e iOS. Los gráficos se procesan a través de OpenGL ES 2, en lugar de controles gráficos nativos, lo que hace que su apariencia sea similar, independientemente del sistema operativo en el que se ejecute.

El nombre del paquete QT juega con la forma en la que se pronuncia en inglés, similar a la palabra cute (“bonito” o “lindo”).

El término framework se puede traducir como “marco de trabajo”. En el contexto de QT, representa un entorno de desarrollo específico para interfaces de usuario.

OpenGL (Open Graphics Library) es un estándar que define una API multilenguaje y multiplataforma para el desarrollo de gráficos 2D y 3D.
Cada uno de estos paquetes tiene sus puntos fuertes, por lo que decidirse por uno u otro dependerá de las necesidades del proyecto en el que se vaya a emplear, la plataforma donde se ejecute y/o las preferencias del programador. De todos ellos, se ha elegido Tkinter, por ser el estándar de Python.
1.1 QUÉ ES TKINTER
Como se acaba de indicar, Tkinter es el paquete estándar de Python para el desarrollo de interfaces gráficas de usuario. Por ese motivo, se incluye al instalar el propio entorno, sin que sea necesario importar nada adicional para empezar a utilizarlo. Fue escrito por Fredrik Lundh y su nombre procede de la interfaz Tk en la que está basado. En realidad, se trata de una API (Application Programming Interface – interfaz de programación de aplicaciones) creada específicamente para utilizar Tk desde Python.
Tk es un framework multiplataforma de software libre, que proporciona una biblioteca de controles gráficos para el desarrollo de interfaces de usuario en Tcl. Al igual que Python, este sencillo lenguaje de propósito general es interpretado y soporta múltiples paradigmas de programación, incluyendo la orientada a objetos y la imperativa o tradicional.
Por lo tanto, Tkinter es un recubrimiento de Tcl que permite usar Tk desde Python, motivo por el que el intérprete de Tcl está incrustado en el de Python.
Tkinter ofrece una gran variedad de componente gráficos (a los que se conoce como widgets) con los que construir las interfaces de usuario. Para su diseño, se emplea una estructura jerárquica, en cuya parte superior está la ventana principal, donde se sitúan tanto los widgets sencillos (por ejemplo, botones, menús, campos de texto, etc.) como aquellos que a su vez contienen otros widgets (por ejemplo, frames, paneles, etc.).
Los widgets disponen de multitud de opciones con las que se determina el aspecto del componente gráfico (como, por ejemplo, el tamaño, el color, etc.), además de métodos que sirven para configurar dinámicamente dicha apariencia, situarlos en la posición que deban tener en pantalla, establecer su comportamiento, etc.

A lo largo del libro, no se describe la lista completa de atributos o métodos de todos los widgets. La documentación de esta librería se encuentra en https://docs.python.org/3/library/tkinter.html.
Cuando un usuario hace algo con alguno de estos controles gráficos (como, por ejemplo, pulsar un botón, seleccionar la opción de un menú, etc.), se genera un evento. Dichos eventos son capturados por la aplicación, lo cual provoca la ejecución de las funciones encargadas de responder de la forma deseada a cada uno de ellos. A estas funciones se las conoce como “controladores de eventos”, y son las que realmente permiten la interacción (intercambio de información) entre la aplicación y el usuario.

Algunos eventos también podrían dispararse por sucesos que no hayan sido causados por el usuario como, por ejemplo, un temporizador.
En el viaje que emprenda con la lectura de este libro, aprenderá a usar las clases que representan cada uno de los widgets que necesite, así como a gestionar los eventos que se produzcan cuando el usuario interactúe con ellos, creando interfaces gráficas atractivas y fáciles de manejar. Comencemos este recorrido con la más sencilla de todas.
1.2 SU PRIMERA INTERFAZ DE USUARIO CON TKINTER
Como no podía ser de otra manera, la primera interfaz de usuario que desarrollará será una que muestre el mensaje “¡Hola Mundo!” en una ventana. Su código es el siguiente:

En primer lugar, se importan las clases Tk y Label del paquete Tkinter. Con la primera se mostrará la ventana principal y, con la segunda, la etiqueta que contiene el texto “Hola Mundo”:

A continuación, se crea la instancia de la clase Tk, que representa la ventana principal (root). Una aplicación solo puede tener una ventana principal, a la que se la suele llamar “raíz”, porque es la que está en el nivel superior de la jerarquía de widgets; es decir, la que los agrupa a todos (en el siguiente apartado, se explicará en detalle este aspecto). En cualquier caso, siempre tendrá la posibilidad de crear otras ventanas independientes de la principal:

Después, se crea la etiqueta con el texto correspondiente:


Al texto se le ha añadido un retorno de carro y un espacio al principio y al final, para separarlo de los bordes de la ventana. Más adelante aprenderá cómo hacer esto mismo de una forma más adecuada.
La última sentencia sitúa la etiqueta en la ventana:

Ejecute este programa. Obtendrá el resultado que puede ver a continuación:

A título informativo, otra forma de programar una interfaz de usuario sería tratarla como un objeto. De esta forma, el código anterior también se podía haber escrito así:

Como puede observar, se declara la clase Interfaz, que representa a la interfaz de usuario de la aplicación. Su constructor será el encargado de situar la etiqueta en la ventana principal.
Las sentencias que siguen a la declaración de la clase anterior son las encargadas de crear la ventana principal (root) y, a continuación, la instancia de la clase que representa la interfaz (mi_interfaz).

El estilo de programación seguido en todas las prácticas de este libro será el primero. Se ha incluido este segundo estilo (deliberadamente simplificado) porque lo encontrará también con frecuencia en los foros y webs dedicados a Tkinter.
Unidad 2
LA VENTANA PRINCIPAL
Todos los widgets de una interfaz gráfica se organizan en una estructura jerárquica, cuyo nivel superior está ocupado por la ventana principal (main window en inglés), en la que se encuentran todos ellos. En Tkinter, esta se representa mediante la clase Tk, cuyo constructor es:


La forma más común de invocar este constructor es sin argumentos, aunque dispone de screenName, baseName, className y useTk.
La ventana principal contendrá tanto widgets elementales (por ejemplo, etiquetas, botones, etc.) como widgets contenedores (por ejemplo, frames, paneles, etc.). Estos últimos podrán incluir, a su vez, otros widgets básicos y/o contenedores, en cuyo caso se añadiría un nuevo nivel a la jerarquía.
La siguiente imagen muestra, de forma esquemática, una interfaz compuesta de una etiqueta y un frame que, a su vez, contiene otra etiqueta y un botón:

El diseño de esta interfaz se representa como una jerarquía de widgets con una estructura en árbol, tal como puede ver en la siguiente imagen:


Curiosamente, en informática las estructuras en árbol tienen la raíz en la parte superior y las ramas en la inferior (crece hacia abajo).

Solo puede haber una instancia de la clase Tk, es decir, una ventana principal. Si su aplicación requiriese varias ventanas, tendría que hacer uso de la clase Toplevel (se estudia más adelante).
El método principal de la clase Tk es:

Este método es el encargado de arrancar el bucle que atiende los eventos que se producen en la interfaz de la aplicación (como, por ejemplo, la pulsación de un botón, la selección de la opción de un menú, etc.).

El método mainloop() hace que la aplicación entre en un bucle infinito de espera y atención de eventos. Hasta que no se cierre la ventana principal, no se saldrá de él y, por lo tanto, no se ejecutará el código que hay a continuación de la sentencia que lo llama. Por ese motivo, se suele poner al final del programa. Si en un momento dado quisiera forzar la salida de este bucle, debería invocar el método quit().
Para poner un texto en la barra de título de la ventana, la clase Tk ofrece el método:

Si desea que la ventana tenga un tamaño determinado, use el método:

Tanto el ancho como el alto vendrán dados en píxeles; por ejemplo, para que una ventana tenga un tamaño de 200 píxeles de ancho y otros tantos de alto, el argumento de este método tendría el valor “200x200”.

Si no se indica un tamaño concreto, este se adapta al de los widgets que contiene.
Dicho tamaño podrá ser fijo e inmutable, o podrá ser modificado por el usuario a su gusto (comportamiento predeterminado). En este último caso, se podrá establecer que solo sea posible cambiar el ancho de la ventana, el alto o ambas dimensiones. La forma de hacerlo será invocando el método:

Ambos argumentos son de tipo booleano; uno permite modificar el ancho (primer argumento) y otro el alto (segundo argumento). Si su valor fuera True, podría hacerse; en caso contrario, no. Por lo tanto, si no quiere que el usuario redimensione la ventana, ambos argumentos deberán tomar el valor False.
Si se pudiera redimensionar la ventana, con los siguientes métodos se limitaría el tamaño mínimo y el máximo en cualquiera de sus dimensiones:

También en el caso de que sea posible cambiar el tamaño de la ventana, cuando la lógica del programa requiera saber sus dimensiones con objeto de ajustar su contenido, la forma de conocer el ancho y alto que tiene en un determinado momento sería mediante los métodos:

Para mostrar la ventana a pantalla completa, el método empleado sería:

Por el contrario, si lo que quiere es hacerla desaparecer de la pantalla (sin cerrarla), tendría que emplear alguno de estos métodos:

La diferencia entre ambos es que el primero hace desaparecer cualquier rastro de la ventana en la pantalla, mientras que el segundo la minimiza (se puede acceder a ella, por ejemplo, desde la barra de tareas de Windows).
Para volver a mostrarla, se usaría el método:

Cuando en la lógica de la aplicación fuera necesario conocer el estado de la ventana, el método empleado sería:

El valor devuelto por este método es:
•"normal ". La venta se muestra en pantalla de la forma habitual.
•"iconic". La ventana se muestra como un icono en la barra de tareas.
•"withdrawn". La ventana ha desaparecido de la pantalla, aunque no se ha cerrado.
•"zoomed". La ventana ocupa toda la pantalla.
Este método también se podría usar para establecer el estado de la ventana si se incluyera la opción newstate como argumento (sus valores serían los indicados anteriormente); por ejemplo, para maximizar la ventana de la aplicación, el método al que tendría que llamar de dicha ventana es:


Como habrá podido deducir, los métodos withdraw() e iconify() son equivalentes a state(newstate="withdrawn") y state(newstate="iconic"). Por la misma razón, deiconify() es equivalente a state(newstate="normal").
Si lo que quiere es cerrar la ventana (por ejemplo, cuando se ha pulsado un botón o la opción de salir de la aplicación), se llamaría al método:

De todos modos, la forma más común de cerrar una ventana es pulsando en el icono con forma de aspa que hay en su parte superior derecha. Para modificar ese comportamiento estándar (por ejemplo, avisando al usuario de que no se han guardado los cambios realizados en un fichero que se estaba editando), puede capturar el evento y realizar las tareas requeridas con el método:

En ese caso, en vez de cerrarse la ventana directamente, se ejecutaría la función cuyo nombre ha indicado en el segundo argumento.

El método protocol() se utiliza para la interacción entre la aplicación y el gestor de ventanas de su ordenador. Proporciona un mecanismo que intercepta eventos del sistema (primer argumento) para procesarlos de forma personalizada mediante una función, a la que se denomina “controlador de protocolo” (segundo argumento). De todos los posibles eventos que podrían capturarse, en los foros se indica que WM_SAVE_YOURSELF está obsoleto (evento que se lanza cuando la aplicación necesita guardar datos) y que tampoco es posible manejar correctamente WM_TAKE_FOCUS (la ventana coge el foco). Por lo tanto, solo se aconseja usar WM_DELETE_WINDOW.
A modo de ejemplo, el siguiente programa modifica su primera interfaz, con el fin de que la ventana principal tenga un tamaño inicial, que no podrá ser redimensionado a lo alto. Aunque sí podrá hacerlo a lo ancho; solo tendrá un margen entre 50 y 400 píxeles:

Como se ha comentado, el programa se basa en el desarrollado anteriormente (¡Hola Mundo!), por lo que solo se explicarán los cambios realizados sobre este. El primero es la incorporación de la sentencia que llama al método geometry() para establecer el tamaño inicial de la ventana en 200 × 50 píxeles:

A continuación, se invoca el método resizable(), cuyo segundo argumento tiene el valor False, lo que indica que la ventana no se puede redimensionar en el eje Y (a lo alto):

Las dos últimas sentencias que se han añadido establecen el ancho y el alto máximo y mínimo que puede tener la ventana principal. Puesto que con la sentencia anterior solo se permite redimensionarla a lo ancho, con estas limitará el tamaño entre 50 y 400 píxeles:

Ejecute el programa y pruebe que, efectivamente, el comportamiento de la ventana principal es el esperado.
Unidad 3
POSICIONAMIENTO Y DISEÑO
En el programa anterior, la interfaz se componía de un único widget. Pero, en la práctica, hasta la aplicación más sencilla que desarrolle requerirá más de uno. En ese caso, ¿cómo se indicaría la posición que debe ocupar cada uno de ellos en la ventana principal? La respuesta es haciendo uso de alguno de los tres gestores de geometría proporcionados por Tkinter:
•pack. Organiza los widgets horizontal o verticalmente (por defecto, de arriba hacia abajo).
•grid. Distribuye los widgets en cuadrícula, con una estructura similar a una tabla. Es el recomendado en interfaces gráficas complejas.
•place. Coloca los widgets en coordenadas concretas.

Los gestores de geometría se utilizan tanto para distribuir los widgets en la ventana principal como dentro de otros widgets contenedores (por ejemplo, los frames y los paneles que conocerá más adelante).
Veamos en detalle cómo funciona cada uno de ellos.
3.1 EL GESTOR DE GEOMETRÍA PACK
Este gestor de geometría distribuye los widgets en horizontal o en vertical. Para utilizarlo, se tiene que invocar el siguiente método, común a todos ellos:

Las opciones disponibles son las siguientes:
•expand. Establece el espacio que el gestor de geometría asignaría al widget dentro de la ventana principal (o widget contenedor). Si su valor fuera True, sería el máximo disponible.
•fill. Determina si el widget debe ocupar todo el espacio asignado por el gestor de geometría o mantiene sus propias dimensiones. En el último caso, el valor que tomaría esta opción sería NONE (predeterminado). Por el contrario, si pudiera crecer/disminuir para ocupar el área máxima disponible, se debería indicar si lo haría a lo ancho (X), a lo alto (Y), o en ambas direcciones (BOTH).
•side. De forma predeterminada, este gestor de geometría ubica los widgets de arriba hacia abajo (su valor es TOP). Aquí se puede cambiar esta forma de organizarlos para que se coloquen de abajo hacia arriba (BOTTOM), de izquierda a derecha (LEFT) o de derecha a izquierda (RIGHT).

Recuerde que, para usar cualquier constante, previamente deberá haberla importado del módulo Tkinter. También puede usar directamente el valor que tiene cada una de ellas (por ejemplo, el de BOTTOM es "bottom", a TOP le correspondería "top", etc.).
Para que pueda entender el funcionamiento conjunto de estas opciones, en las siguientes imágenes (tomadas de https://stackoverflow.com/questions/28089942/difference-between-fill-and-expandoptions-for-tkinter-pack-method) se puede apreciar el efecto producido por estas opciones sobre una etiqueta, cuyo texto indica los valores asignados a cada una de ellas.
En la primera, el valor de la opción expand es False:

En esta segunda, el valor de la opción expand es True:

Como puede observar, muchas de las combinaciones de las tres opciones dan los mismos resultados, lo que indica que podrían obviarse. Así, por ejemplo, el resultado de la primera fila, en el que la etiqueta aparece centrada en la ventana, se podría conseguir utilizando únicamente la opción:

Además de las opciones anteriores, las siguientes opciones permiten separar un widget de los límites de la ventana principal (o widget contenedor) y del resto de los widgets:
•ipadx. Número de píxeles con los que se rellena el widget horizontalmente, dentro de sus bordes.
•ipady. Número de píxeles con los que se rellena el widget verticalmente, dentro de sus bordes.
•padx. Número de píxeles con los que se rellena el widget horizontalmente, fuera de sus límites.
•pady. Número de píxeles con los que se rellena el widget verticalmente, fuera de sus límites.
En la siguiente imagen, puede ver gráficamente cómo afectan estas opciones a la posición de una etiqueta (situada en la esquina superior izquierda de la ventana principal) y al texto que contiene (justificado a la izquierda). Se supone que, al crear tanto la ventana como la etiqueta, se han fijado sus dimensiones de forma explícita:

Si no se hubieran indicado las dimensiones de la ventana, esta se ajustaría a las de los widgets que contuviera (en este caso, a las de la etiqueta). Por el mismo motivo, si no se hubiera especificado el ancho y el alto de la etiqueta, su tamaño correspondería al del texto que contuviera. La imagen de más abajo muestra gráficamente el resultado obtenido en ese caso:

Por último, para dejar de mostrar un widget en la ventana principal (o widget contenedor), se debe ejecutar el método:

Si quisiera volver a mostrar dicho widget, seguramente que no lo haría en la misma posición donde se encontraba antes de desaparecer, sino en aquella que le asigne el gestor de geometría una vez invocado el método pack() del resto de widgets.
Para probar el funcionamiento de este gestor de geometría, puesto que el único widget que conoce por ahora son las etiquetas, creará cuatro etiquetas que distribuirá de dos formas diferentes. En primer lugar, lo hará según el comportamiento por defecto, es decir, de arriba hacia abajo:

Lo primero que se debe hacer es importar las clases que se van a utilizar del paquete Tkinter (Tk y Label):

Luego, se crea la ventana principal:

Acto seguido, se hace lo mismo con las cuatro etiquetas:

Finalmente, se muestran en la ventana con el método pack(). Estas se rodean con un borde de un grosor de 10 píxeles de ancho y 5 de alto. Eso hace que la primera etiqueta se aleje del límite superior de la ventana 5 píxeles (tal como se indicó en la opción pady), al igual que la última etiqueta del borde superior. Sin embargo, la separación vertical entre etiquetas es de 10 píxeles (5 del superior y 5 del inferior). Con los límites derecho e izquierdo de la ventana también se mantiene un espacio de 10 píxeles, tal como se ha establecido en la opción padx:

El resultado, como cabía esperar, son las cuatro etiquetas colocadas unas debajo de otras. La imagen de la izquierda muestra el resultado de la ejecución del programa, mientras que la imagen de la derecha muestra gráficamente el efecto de las opciones padx y pady, con las que se han separado las etiquetas de los límites de la ventana principal y entre sí:


Al utilizar las opciones padx y pady, ya no es necesario incluir los retornos de carro (carácter ‘\n’) ni los espacios en el texto de la etiqueta.

Para que el espacio que se establezca a la derecha de un widget sea diferente que el de la izquierda, asigne una tupla con dos valores a la opción padx. Esto mismo es igualmente válido para la opción pady.
En el siguiente programa, se utiliza la opción side para situar las etiquetas en cada uno de los lados de la ventana:

De nuevo, se importan las clases Tk y Label de la librería Tkinter, pero, en este caso, también se hace lo mismo con las constantes TOP, BOTTOM, LEFT y RIGHT, que determinarán la posición de cada una de las etiquetas:

Para que el efecto sea más visible, una vez creada la ventana principal, esta se configura con un tamaño de 200 × 200 píxeles:

Las etiquetas se crean de la misma forma que en el programa anterior, por lo que no se van a dar explicaciones adicionales.
El principal cambio se produce al mostrarlas en la ventana principal con el método pack(), ya que ahora se añade la opción side para fijar la posición en la que se va a situar cada una de ellas. Además, dependiendo del lado de la ventana en el que se coloquen, se utilizarán las opciones padx o pady para separarlas del borde:

Ejecute el programa. Obtendrá el resultado mostrado en la siguiente imagen:

3.2 EL GESTOR DE GEOMETRÍA GRID
Cuando el diseño de una interfaz es complejo, puede resultar difícil y engorroso trabajar con el gestor de geometría pack. Por este motivo, es preferible utilizar el gestor grid, y dejar el anterior para las más sencillas. Con grid, la distribución de los widgets se realiza de una manera más flexible, utilizando un diseño de cuadrícula (de ahí su nombre, ya que grid se puede traducir por “cuadrícula”). De esta manera, cada widget se sitúa en la celda determinada por la intersección de una fila y una columna.

Nunca use los gestores pack() y grid() dentro de la misma ventana, porque los algoritmos que utilizan para calcular las posiciones de los widgets no son compatibles.
La forma de utilizarlo es invocando el siguiente método, presente en todos los widgets:
grid(opciones)
Las opciones posibles son:
•column. Columna en la que se va a colocar el widget (por defecto, la 0).
•columnspan. Número de columnas que ocupa el widget (por defecto, una).
•row. Fila en la que se va a colocar el widget (por defecto, la 0).
•rowspan. Número de filas que ocupa el widget (por defecto, una).
•ipadx. Número de píxeles con los que se rellena el widget horizontalmente, dentro de sus bordes.
•ipady. Número de píxeles con los que se rellena el widget verticalmente, dentro de sus bordes.
•padx. Número de píxeles con los que se rellena el widget horizontalmente, fuera de sus límites.
•pady. Número de píxeles con los que se rellena el widget verticalmente, fuera de sus límites.
•sticky. Determina dónde se ubica el widget dentro de la celda (por defecto, estaría centrado). Como si de los puntos cardinales de una brújula se tratara, el valor de esta opción permite situarlo en una esquina (NE, SE, SW o NW) o centrado en cada uno de los lados de la celda (N, E, S y W). La siguiente imagen trata de mostrarlo gráficamente. El cuadrado exterior representa la celda, y los más pequeños representan widgets situados en su interior, según el valor de esta opción.


El efecto de dichos valores solo sería apreciable si el tamaño del widget fuera menor que el de la celda.
Si quisiera adaptar el ancho del widget para que coincida con el de la celda, el valor de esta última opción debería ser EW. Si lo que desea es que tenga el mismo alto, asígnele el valor NS. Para que el tamaño del widget corresponda con el de la celda en ambas dimensiones, el valor utilizado sería NSEW.
La siguiente imagen muestra gráficamente cómo cambia el tamaño del widget con el de la celda en función del valor de esta opción:

Cuando se utiliza este gestor de geometría, el ancho de una columna será el de su celda más ancha. De la misma manera, la altura de una fila será la correspondiente a la celda más alta. Si desea cambiar este comportamiento, use los siguientes métodos de la ventana principal (o widget contenedor):

Con ellos, podrá configurar aspectos del tamaño de una columna o de una fila concreta empleando diferentes opciones, entre la que se encuentran:
•minsize. Tamaño mínimo de la columna o de la fila indicada, en píxeles. Si no hubiera nada dentro, esta no aparecería.
•weight. Permite que el tamaño de una columna o de una fila se adapte al de la ventana principal (o widget contenedor). El valor proporcionado establece el peso relativo de esta columna o fila respecto de las demás a la hora de distribuir el espacio existente.
Para entender esta última opción, imagine que la ventana principal (representada por el objeto ventana_principal) se divide en una cuadrícula de dos filas y dos columnas, tal como se muestra en la siguiente imagen:

Observe el resultado obtenido al añadir estas sentencias:

Ahora, el espacio se distribuye en la proporción de tres cuartos para la primera columna y un cuarto para la segunda. Además, al haber utilizado esta opción, el ancho de las columnas se adaptará al de la ventana cuando esta se modifique (en la proporción indicada).
El mismo razonamiento serviría para las filas. En ese caso, las sentencias que tendrían que añadirse son:

Por último, para dejar de mostrar un widget situado previamente en la ventana principal (o widget contenedor), se debe ejecutar el método:

Si volviera a mostrar dicho widget, lo haría en la misma posición donde se encontraba antes de desaparecer.
Con el fin de practicar con este nuevo gestor de geometría, va a desarrollar un programa que muestre una etiqueta en cada una de las celdas de una cuadrícula, cuyo texto indique el número de la fila y la columna en la que está situada; por ejemplo, el texto de la etiqueta situada en la fila 1 y la columna 2 será Etiqueta12, tal como se muestra a continuación:

El código del programa es el siguiente:

En primer lugar, se importan las conocidas clases Tk y Label:

Luego, se declaran las constantes que definen el número de filas y columnas que va a tener la rejilla en la que se van a distribuir las etiquetas:

A continuación, se crea la ventana principal:

El código principal de este programa está en los bucles for, mediante los que se crean y se sitúan las etiquetas en las posiciones correspondientes. El bucle exterior recorre las filas, mientras que el interior recorre las columnas:

Las etiquetas se crean con un texto que contiene el número de la fila y de la columna donde se van a colocar. La opción bg determina que el color de fondo sea amarillo:


Más adelante, estudiará todas las opciones de configuración de las etiquetas.
La siguiente sentencia sitúa cada etiqueta en la ventana principal con el método grid(). En este método se utilizan cuatro opciones: las que identifican la fila y la columna en la que deben ubicarse (row y column) y las que establecen los márgenes alrededor de cada una de ellas para separarlas entre sí (padx y pady):

El resultado obtenido al ejecutar este programa lo puede ver a continuación:

Ahora, redimensione la pantalla. Como podrá observar, las etiquetas se mantienen con el mismo tamaño y en la misma posición:

Si quiere que las celdas se adapten al tamaño de la ventana, deberá utilizar los métodos columnconfigure() y rowconfigure(), tal como aparece en la nueva versión de este mismo programa:

Ejecútelo de nuevo. En este caso, al aumentar el tamaño de la ventana, las celdas crecen para ocupar todo el espacio libre, pero no las etiquetas:

Para que estas ocupen todo el espacio de las celdas, deberá utilizar la opción sticky del método grid(). En el programa anterior, sustituya la sentencia

por esta otra:

El resultado que obtendrá ahora demuestra que tanto las celdas como las etiquetas que hay en su interior se ajustan al tamaño de la ventana en todas las circunstancias:

3.3 EL GESTOR DE GEOMETRÍA PLACE
Este gestor, a diferencia de los dos anteriores, permite colocar los widgets en coordenadas específicas de la ventana principal (o widget contenedor). Para utilizarlo, se debe llamar al siguiente método, disponible en todos los widgets:
place(opciones)
Las opciones de las que dispone son:
•anchor. Indica cómo situar el widget en la ventana, tomando como referencia las coordenadas (x, y) contenidas en las opciones x e y (se describen más abajo). Su valor puede ser N, S, E, W, NE, NW (predeterminado, esquina superior izquierda del widget), SE, SW o CENTER. La siguiente imagen lo muestra gráficamente (el cuadrado representa el widget tal como quedaría ubicado respecto del punto de referencia, dependiendo del valor de esta opción):

•bordermode. Determina si las coordenadas especificadas usan como referencia el interior del widget contenedor (INSIDE, valor por defecto) o el exterior (OUTSIDE).
•height, width. Alto y ancho del widget en píxeles.
•relheight, relwidth. Ancho y alto del widget, tomado de forma relativa al ancho y alto de la ventana principal (o widget contenedor). Por lo tanto, determina sus dimensiones como una fracción de la ventana. Su valor estará comprendido entre 0,0 y 1,0; por ejemplo, si el alto de la ventana principal fuera de 800 píxeles y el valor de la opción relheight de uno de los widgets fuera de 0,25, su alto sería de 200 píxeles. La utilidad de este método es ajustar el tamaño de los widgets al de la ventana (o widget contenedor), cuando esta se modifique.
•x. Coordenada x del punto utilizado como referencia para situar el widget en la ventana principal (o widget contenedor). Su valor viene dado en píxeles. Las coordenadas (0, 0) corresponden a la esquina superior izquierda de la ventana principal (o widget contenedor).
•y. Coordenada y del punto utilizado como referencia para situar el widget en la ventana principal (o widget contenedor).
•relx, rely. Posición relativa, tanto horizontal como vertical, representada como una fracción del alto y ancho de la ventana principal (o widget contenedor). Su valor, de nuevo, estará comprendido entre 0,0 y 1,0; por ejemplo, si el ancho de una ventana fuera de 500 píxeles y relx tuviera el valor 0,5, la coordenada x del widget sería 250. La utilidad de esta opción es mantener la posición de los widgets, aunque cambie el tamaño de la ventana.
Por último, para dejar de mostrar un widget situado previamente en la ventana principal (o widget contenedor), se debe ejecutar el método:

A continuación, se describe el código de un programa que muestra una etiqueta centrada en la ventana principal:

Tras importar las clases Tk y Label del paquete Tkinter, se crea la ventana principal, a la que se asigna un tamaño de 200 × 200 píxeles.
Luego, se crea la etiqueta y se muestra en el centro de la ventana con el método place(). Para que esta quede realmente centrada en dicha posición, se asigna el valor "center" al atributo anchor:

Ejecute el programa y observe el resultado obtenido:

El problema viene cuando se cambia el tamaño de la ventana. En ese caso, la etiqueta se mantendrá fija en las mismas coordenadas (100, 100), y dejará de estar centrada:

Para resolverlo, puede evitar que el usuario modifique las dimensiones de la ventana con el método resizable(), o utilizar las opciones relx y rely (en vez de x e y) en el método place(). De esa forma, la posición de la etiqueta será relativa al tamaño de la ventana (no absoluta). Así, cuando la ventana se redimensione, la etiqueta seguirá estando centrada (en la misma posición relativa).
Por lo tanto, sustituya la sentencia

por:

Vuelva a ejecutar el programa. Tal como se acaba de comentar, ahora la etiqueta aparecerá centrada, independientemente de las dimensiones de la ventana:


Se desaconseja el uso de este gestor de geometría (salvo en casos excepcionales), ya que requiere especificar la posición absoluta de cada elemento. Cualquier cambio posterior supondría rehacer de nuevo toda la interfaz.
Unidad 4
OPCIONES COMUNES
En Tkinter, el aspecto de un widget se describe a través de una serie de características; algunas son específicas y otras son compartidas por muchos widgets. Estas características comunes son las siguientes:
•Dimensiones: height, width, bd, borderwidth, border, padx, pady y highlightthickness.
•Color: background (bg), activebackground, activeforeground, disabledbackground, disabledforeground, foreground (fg), highlightbackground y highlightcolor.
•Fuente del texto: font.
•Foco: takefocus.
•Posición: anchor.
•Relieve: relief.
•Imágenes: bitmap, image y compound.
•Cursor del ratón: cursor.
Para asignar un valor a cualquiera de estas características, se utilizan keyword arguments. Como seguramente sepa, los argumentos de una función pueden ser de tipo positional o keyword. En el primer caso (el que estará acostumbrado a usar), cada argumento se identifica por su posición al invocar la función (o método); es decir, son posicionales.
Los argumentos de tipo keyword van precedidos de su nombre, por lo que podrán ocupar cualquier posición. Esto es especialmente útil cuando hay muchos argumentos opcionales, como en el caso de la mayoría de los widgets.

A las características también se las llama “opciones”, incluso “atributos”. Esta última acepción debe entenderse en el sentido general del término, es decir, como un rasgo o cualidad del widget (y no como lo que se entiende en Python como atributo de una clase).
Veamos en detalle cada uno de estos grupos.
4.1 DIMENSIONES
Las primeras opciones que estudiará son las relacionadas con las dimensiones de diversas características de un widget, entre las que destacan:
•bd, borderwidth o border. Ancho del borde utilizado para dar un aspecto tridimensional al widget.
•height. Altura del widget. Dependiendo de su tipo, podrá ser un número de píxeles o líneas.
•highlightthickness. Ancho del rectángulo que aparece alrededor del widget cuando tiene el foco.
•padx. Espacio adicional, interno a los límites del widget, que se le añade horizontalmente.
•pady. Espacio adicional, interno a los límites del widget, que se le añade verticalmente.
•width. Ancho del widget. Dependiendo de su tipo, podrá ser un número de píxeles o caracteres.

Cuando una opción tiene varios nombres, tenga en cuenta que puede haber widgets que solo admitan uno de ellos.

Si el valor de una dimensión se especifica como un número seguido del carácter ‘c’, se estará especificando en centímetros; si se usa el carácter ‘m’, en milímetros; mientras que ‘i’ hará referencia a pulgadas (inches en inglés).
En la primera práctica que realizará con este tipo de atributos comunes, modificará el programa ¡Hola Mundo! inicial para no tener que añadir los retornos de carro ni los espacios antes y después del texto, con el fin de evitar que este quede pegado a los bordes de la ventana. Su código ahora es el siguiente:

Como puede observar, el único cambio está en el constructor de la clase Label, ya que ahora se utilizan los atributos padx y pady para añadir un espacio adicional de 10 píxeles alrededor de la etiqueta:

Ejecute el programa y observe que el resultado es similar al utilizado de base.
4.2 COLOR
El segundo bloque de atributos tiene que ver con el color. Tkinter representa los colores con cadenas. Hay dos formas de especificarlos:
•Por su nombre en inglés: "white", "black", "red", "green", "blue", "cyan", “yellow", “orange", y "magenta". Dependiendo de su instalación, podría disponer de otros adicionales.
•Indicando la intensidad de los colores rojo, verde y azul en dígitos hexadecimales.
Para entender esta última forma de expresar los colores, primero debe saber que al rojo, al verde y al azul se los considera primarios porque el resto de colores se pueden obtener a partir de una composición de estos tres colores primarios. El valor de intensidad mínimo que pueden tener es 0 y el máximo es 255.
Ejemplos de especificación de colores utilizando la nomenclatura RGB:
•Blanco: (255, 255, 255)
•Negro: (0, 0, 0)
•Rojo: (255, 0, 0)
•Verde: (0, 255, 0)
•Azul: (0, 0, 255)
•Amarillo: (255, 255, 0)
•Púrpura: (255, 0, 255)
•Cian: (0, 255, 255)
Dicha notación es la decimal. En hexadecimal, un color se representa con una almohadilla #, seguida de seis caracteres hexadecimales (0-9 y A-F). Estos caracteres se dividen en tres parejas de dos caracteres cada una para representar los colores R, G y B (en dicho orden). La intensidad máxima de un color es FF (255 en decimal) y la mínima es 00 (0 en decimal).
Así, por ejemplo, el color rojo se expresaría como “#FF0000” porque los dos primeros caracteres representan la intensidad máxima de este color (FF, la máxima), los dos siguientes la del verde (00, la mínima) y los dos últimos la del azul (00, la mínima). Por el mismo motivo, el color verde se especificaría como “#00FF00” y el azul como “#0000FF”. El blanco se representaría como “#FFFFFF” (es la suma de los tres colores básicos) y el negro como “#000000” (no hay ningún color).
Los principales atributos (opciones) relacionados con el color son:
•background o bg. Color de fondo con el que se muestra el widget en su estado habitual.
•activebackground. Color de fondo cuando el widget está activo, es decir, cuando el cursor del ratón se encuentra sobre él (y no está inactivo).
•activeforeground. Color de primer plano cuando el widget está activo.
•disabledbackground. Color de fondo del widget cuando está inactivo, es decir, cuando no se puede actuar sobre él.
•disabledforeground. Color de primer plano cuando el widget está inactivo.
•foreground o fg. Color de primer plano con el que se muestra el widget en su estado habitual.
•highlightbackground. Color del rectángulo que rodea el widget cuando no tiene el foco. Debe utilizarse junto con highlightthickness.
•highlightcolor. Color del rectángulo que rodea el widget cuando tiene el foco. Debe utilizarse junto con highlightthickness.

Al igual que se indicó en las opciones anteriores, cuando alguna tenga varios nombres, puede haber widgets que solo admitan uno de ellos. El nombre más largo suele ser el más admitido comúnmente (background en vez de bg, foreground en vez de fg, borderwidth en vez de bd, etc.).
Modifique la sentencia del programa anterior, donde se creaba la etiqueta:

Debe quedar como sigue:

Se ha asignado un color de fondo amarillo y otro azul de primer plano (el del texto) con los atributos bg y fg, respectivamente. El resultado lo puede ver a continuación:

Ahora quite las opciones padx y pady del constructor de la etiqueta y páselas al método pack(). El programa quedaría así:

En este caso, ahora ambas opciones hacen referencia al interior de los bordes de la ventana principal, por lo que la etiqueta se separa de estos 10 píxeles:

Si quisiera que dentro de la etiqueta también hubiera ese margen interno, debería añadir de nuevo las opciones padx y pady a su constructor, o incluir las opciones ipadx e ipady al método pack(). Quedaría así:

Como puede apreciar en esta nueva imagen, ahora existe un espacio de 10 píxeles tanto en el interior como en el exterior de la etiqueta:


A diferencia del método pack(), el constructor de la etiqueta no tiene las opciones ipadx e ipady. Además, recuerde que las opciones padx y pady de dicho método hacen referencia al borde interno de la ventana principal (o widget contenedor), no al de la etiqueta. Por eso, las opciones ipadx e ipady del método pack() tienen el mismo efecto que padx y pady en los widgets que contienen.
4.3 FUENTE
Una de las características compartidas por los controles gráficos que muestran o permiten la entrada de textos es la fuente (tipo de letra). Una fuente se describe como una tupla con el siguiente formato:
(familia, tamaño)
Opcionalmente, se puede añadir un elemento más, consistente en una cadena que contiene uno o más modificadores de estilo para indicar si el texto va en negrita, cursiva, subrayado o tachado (bold, italic, underline u overstrike); por ejemplo, para que un texto use la fuente Times New Roman con un tamaño de 24 píxeles en itálica y negrita, la tupla que debería utilizar es:

Pruébelo, sustituyendo la sentencia donde se creaba la etiqueta del programa anterior

por:

El resultado será el mostrado más abajo:

Para conocer la lista de fuentes disponibles en su sistema, utilice este sencillo programa:

Le aparecerá una pequeña ventana y, en la shell, verá el botón sobre el que deberá hacer doble clic para obtener la lista completa de fuentes:

Dicha lista la puede ver parcialmente en la siguiente imagen:

4.4 FOCO
Muchos de los widgets de una aplicación, especialmente aquellos con los que se realiza la entrada de datos, son susceptibles de tener el foco en un momento determinado; es decir, que lo que se escriba con el teclado vaya dirigido hacia ellos. Una forma de hacer que un widget tenga el foco es pulsarlo con el ratón. Pero también se podría usar el tabulador, haciendo que este vaya pasando de uno a otro, en lo que se conoce como “enfoque transversal” (focus traversal). El orden en el que se mueve sigue las siguientes reglas:
•En el caso de los widgets contenidos en la ventana principal (o un widget contenedor), el foco los va recorriendo en el mismo orden en que se crearon.
•Si el valor del atributo takefocus de un widget contenedor fuera True (por ejemplo, un frame o un panel), el foco iría primero a este y, luego, a todos los que contenga en el orden en que fueron creados, de forma recursiva.
•Para evitar que un widget pueda llegar a tener el foco, solo tiene que asignar el valor False al atributo takefocus, lo que haría que este pasara de largo en su recorrido al ir pulsando el tabulador.

Por defecto, el foco no pasará por las etiquetas, frames o menús al pulsar el tabulador. Para que lo haga, tendrá que asignar el valor True a dicho atributo.

Como ya sabe, los atributos highlightthickness y highlightcolor permiten dibujar un marco del ancho y color especificado alrededor del widget que tenga el foco.
Cada widget se comporta de una forma diferente cuando tiene el foco:
•Campo de entrada de texto. Cualquier carácter se agregará a su texto.
•Botón. Podrá presionarse con la barra espaciadora.
•Checkbuttons. Cambia de estado (seleccionado o no seleccionado) al pulsar la barra espaciadora.
•Radiobuttons. Se selecciona (si todavía no lo estuviera) con la barra espaciadora.
•Barra de scroll. Las teclas PageUp y PageDown desplazan el contenido de página en página. Las teclas ↑ y ↓ lo mueven en sentido vertical, y las teclas ← y → en sentido horizontal.
•Barra de desplazamiento. Si su orientación fuera horizontal, se movería con las teclas ← y →. Si fuera vertical, se movería con las teclas ↑ y ↓.

Tendrá ocasión de estudiar cada uno de estos widgets más adelante.
Para probar el funcionamiento de esta opción, realizará una nueva práctica utilizando campos de entrada de texto. Es habitual que los formularios le permitan pasar de un campo a otro pulsando el tabulador. Aunque este widget se describirá en detalle más adelante, las opciones con las que se va a utilizar ya las conoce, por lo que no será impedimento para entender el código mostrado a continuación:


En la primera sentencia, además de Tk, se importa también la clase Entry, que representa el campo de entrada de texto:

A continuación, se crea la ventana principal (root):

Después, se crean los campos de entrada de texto. Todo lo que se escriba en ellos aparecerá en color azul sobre un fondo amarillo (opciones bg y fg), con un tipo de letra Times New Roman de 24 píxeles, en cursiva y negrita (opción font). Además, se añaden los atributos highlightcolor y highlightthickness para que, cuando el campo tenga el foco (se pueda escribir en él), muestre un marco de color rojo con un grosor de línea de dos píxeles. Lo más interesante en esta práctica es que el tercer campo incluye también la opción takefocus, cuyo valor False hará que no pueda obtener el foco al pulsar el tabulador:

Las últimas sentencias sitúan los campos de texto en la ventana principal, uno debajo del otro. Para separarlos de los bordes de esta y entre sí, se utilizan las opciones padx y pady:

Ejecute el programa y pulse sobre el primer campo. Observe que aparece un marco rojo alrededor, que indica que ya puede empezar a escribir en él. Ahora pulse el tabulador; comprobará que el foco salta al campo inferior. Vuelva a pulsar de nuevo el tabulador. Esta vez, el foco irá al último campo, saltando el tercero, ya que se asignó el valor False a su opción takefocus. Si vuelve a pulsar el tabulador, el foco regresará de nuevo al primer campo, repitiéndose el ciclo.
La siguiente secuencia de imágenes muestra este recorrido del foco:

4.5 POSICIÓN
El atributo anchor especifica dónde se sitúa el texto o la imagen de un widget en su interior. Sus posibles valores y significado son los mismos que en la opción anchor, ya estudiada en el método place(): CENTER (predeterminada), N, S, W, E, NE, NW, SE y SW. Por ejemplo, si usara el valor W, el texto o la imagen se ubicaría en el lado izquierdo del widget. En cambio, con NW se colocaría en la esquina superior izquierda.
Para probar este nuevo atributo, se utilizará el siguiente código, que será una adaptación del conocido ¡Hola Mundo!:


En este caso, una vez creada la ventana principal, se le asigna un tamaño de 200 × 100 píxeles con el método geometry():

Lo realmente interesante reside en el constructor de la etiqueta, ya que ahora se le da el mismo tamaño de la ventana con las opciones width y height:

Con esto se consigue que la etiqueta quede centrada, incluso aunque modifique el tamaño de la ventana:

Ahora cambie dicha sentencia por:

El resultado obtenido sería el mostrado a continuación:

Realice un último cambio, sustituyendo la sentencia anterior por:

En esta ocasión, el texto se vería en la esquina inferior derecha:

En ambos casos, aunque cambie el tamaño de la ventana, la posición del texto seguirá siendo la misma.
4.6 RELIEVE
El relieve determina el efecto 3D mostrado por un widget. Para aplicarlo, se utiliza el atributo relief, cuyos valores pueden ser: FLAT, RAISED, SUNKEN (por defecto), GROOVE y RIDGE. Es importante que el widget tenga un borde con cierta anchura (opción bd); si fuera muy estrecho, el efecto apenas sería perceptible.

Recuerde que, para hacer uso de cualquier constante, antes deberá importarla. También podrá usar directamente sus valores; por ejemplo, la constante FLAT tiene el valor “flat”, el de RAISED es “raised”, etc.
Para demostrar el efecto que tiene este atributo, se utilizará el siguiente programa:

En primer lugar, se importan las clases del paquete Tkinter que se van a utilizar; en concreto, Tk para crear la ventana principal y Label y Entry para mostrar el típico campo de entrada de datos de un formulario, precedido por una etiqueta que indica la información que se debe introducir:

Una vez creada la ventana principal, se evita que pueda ser redimensionada:

A la etiqueta se la asigna el texto genérico “Campo:”. El campo de texto se crea con un efecto SUNKEN (es el predeterminado, por lo que realmente no habría sido necesario incluir esta opción) y un borde de cinco píxeles de grosor:

Las últimas sentencias sitúan la etiqueta a la izquierda del campo, con la separación indicada en las opciones padx y pady:

El resultado de su ejecución es el siguiente:

Ahora, cambie el valor del atributo relief por FLAT, RAISED, GROOVE o RIDGE, y ejecute de nuevo el programa cada vez que lo haga. El resultado será el mostrado en las siguientes imágenes. Se ha añadido un título a la ventana indicando el valor asignado a dicho atributo:

4.7 IMÁGENES
En Tkinter, las imágenes se pueden representar mediante dos clases:
•BitmapImage. Contiene imágenes de dos colores en formato xbm.
•PhotoImage. Contiene imágenes a todo color en formato gif, pgm o ppm.
El constructor de ambas clases tiene como argumento el nombre del archivo que contiene la imagen:
BitmapImage (archivo)
PhotoImage (archivo)
Para asociar una imagen a un widget, se pueden utilizar los siguientes atributos:
•bitmap. Solo admite objetos de la clase BitmapImage.
•image. En este caso, su valor podrá ser un objeto de la clase BitmapImage o PhotoImage.
Normalmente, los píxeles del primer plano de un bitmap se muestran negros y los del fondo se muestran transparentes. Para asignarles otros colores, use las opciones foreground y background.
Tkinter dispone de una serie de bitmaps prediseñados que puede utilizar para expresar advertencias, errores, preguntas, etc. Se muestran a continuación.

Sus nombres son, de izquierda a derecha: "error", "gray75", "gray50", "gray25", "gray12", "hourglass", "info", "questhead", "question" y "warning". También podría crear sus propios bitmaps con archivos en formato xbm.

Para mostrar una imagen, además de utilizar objetos de la clase BitmapImage o PhotoImage, también puede hacer uso de ImageTk, que pertenece a la librería PIL (Python Imaging Library), y que soporta una amplia variedad de formatos de imágenes.
Relacionado con los atributos bitmap e image, se encuentra el atributo compound, que se utiliza cuando la imagen aparece junto a un texto (por ejemplo, en una etiqueta o un botón), indicando su posición respecto de este. Sus valores son: NONE, TOP, BOTTOM, LEFT, RIGHT y CENTER, dependiendo de si quiere que la imagen no aparezca o aparezca encima, debajo, a la izquierda, a la derecha o centrada con el texto, respectivamente.
El siguiente código permite practicar con algunos de los últimos atributos descritos:

En este nuevo programa, tras importar las clases Tk y Label del paquete Tkinter, se crea una ventana a la que se le asigna un tamaño de 200 × 100 píxeles con el método geometry():

A continuación, se crea la etiqueta, en cuyo constructor se establecen los valores de diversos atributos. En primer lugar, se le da un aspecto 3D con la opción relief. Luego, se le añade un texto y un icono con las opciones text y bitmap. Además, para que la imagen quede a la izquierda, se utiliza el atributo compound:

Por último, la etiqueta se muestra centrada horizontal y verticalmente en la ventana con el método place(), utilizando los atributos y valores que ya conoce de una práctica anterior:

Ejecute el programa y observe el resultado. Tal como se aprecia, ahora la etiqueta tiene un borde que da la sensación 3D pretendida, dentro de la que se muestra tanto el texto como el bitmap del reloj a su izquierda:


Para resaltar el efecto de relieve en 3D, amplíe el grosor del borde de la etiqueta con la opción bd.
4.8 CURSOR DEL RATÓN
El último atributo común que se va a estudiar es cursor. Con él, podrá modificar el icono que señala la posición del ratón dentro de la ventana principal. Tkinter tiene predefinidos los siguientes: "arrow", "circle", "clock", "cross", "dotbox", "exchange", "fleur", "heart", "man", "mouse", "pirate", "plus", "shuttle", "sizing", "spider", "spraycan", "star", "target", "tcross", "trek" y "watch".
Para probar esta opción, añada la siguiente sentencia al programa anterior, después de la utilizada para asignar las dimensiones de la ventana principal con el método geometry(). Así, cuando mueva el ratón dentro de la ventana, el cursor tomará la forma de una araña:

Vuelva a ejecutar el programa y sitúe el cursor dentro de la ventana. Compruebe cómo ahora su aspecto cambia para tomar la forma de ese temido animal:

Unidad 5
MÉTODOS COMUNES
Al igual que sucede con las opciones, existen una serie de métodos compartidos por muchos controles gráficos. Estos se pueden agrupar en las siguientes categorías:
•Asignación y obtención de valores de atributos. Compuesto por los métodos cget() y configure().
•Temporizadores. Dentro de este grupo, estudiará los métodos after() y after_cancel().
•Gestión del foco. Está formada por focus_get(), focus_set(), tk_focusFollowsMouse(), tk_focusNext() y tk_focusPrev().
•Obtención de dimensiones y posiciones. Agrupa los métodos winfo_geometry(), winfo_x(), winfo_y(), winfo_width(), winfo_height(), winfo_rootx(), winfo_rooty(), winfo_ screenwidth(), winfo_screenheight(), winfo_pointerxy(), winfo_pointerx() y winfo_pointery().
Además de los grupos anteriores, también existen los siguientes:
•Gestión de eventos. Con los métodos bind(), bind_class() y bind_all(), se puede asociar un evento (o secuencia de eventos) a un widget concreto, a los pertenecientes a una misma clase o a todos los de una aplicación, respectivamente. Cuando se produzca dicho evento (o secuencia de eventos), se ejecutará la función designada como controlador. Por el contrario, los métodos unbind(), unbind_class() y unbind_all() realizarán la operación contraria, desvinculando eventos de widgets. Los estudiará posteriormente, en un capítulo dedicado exclusivamente a este tema.
•Gestores de geometría. Este grupo lo componen los métodos grid(), pack() y place(), que ya conoce.
•Gestión de portapapeles. Lo forman dos métodos relacionados con el contenido del portapapeles (utilizado en las operaciones copy&paste). Con clipboard_append(texto) se le añade un texto, mientras que con clipboard_clear() se vacía.
•Eliminación de un widget. Al invocar el método destroy(), se provocaría la destrucción del widget. No lo confunda con los métodos pack_forget(), grid_forget() y place_forget(), que únicamente dejan de mostrarlo en pantalla.
A continuación, se describe en detalle cada uno de los grupos de la primera lista.
5.1 ASIGNACIÓN Y OBTENCIÓN DE VALORES DE ATRIBUTOS
Dentro de esta categoría hay dos métodos. El primero se utiliza para obtener el valor de un atributo:
cget("opción”)
El segundo permite modificar el valor de una serie de opciones (atributos):
configure(opción = valor, …)

Este método es equivalente a config().
Si se invocara únicamente con el nombre de una opción, devolvería su valor. Si se llamara sin argumentos, daría como resultado un diccionario con los valores de todas las opciones del widget. Para probarlo, añada la siguiente sentencia al final del último programa desarrollado en la práctica anterior, que mostraba una etiqueta centrada en la ventana con una imagen y un texto:

Esta vez, además de abrirse la ventana de la aplicación, en la shell verá un diccionario con el valor de todas las opciones de la etiqueta. En la siguiente imagen puede comprobar que relief, text, bitmap y compound tienen el valor establecido en el constructor de widget:

Otra forma de cambiar el valor de un atributo es mediante la expresión:
widget["atributo"] = valor
Por el mismo motivo, para asignar el valor de la opción de un widget a una variable, la expresión sería:
variable = widget["atributo"]
5.2 TEMPORIZADORES
En este grupo se encuentran los métodos relacionados con la invocación de funciones una vez transcurrido un intervalo de tiempo determinado. El principal es aquel con el que se establece el temporizador:
after(intervalo, función)
Dicho método llamará a la función indicada en el segundo argumento cuando haya pasado el número de milisegundos establecido en el primero. De forma opcional, este método puede tener más argumentos, que serían con los que se invocaría la función.

Si no se incluyera una función, el comportamiento sería similar al de la función sleep() del módulo time de Python.
Siempre que sea necesario aplazar la ejecución de una función, deberá llamar a este método para no bloquear la aplicación en el punto donde se realice la temporización. Eso provocaría que la interfaz dejara de atender los eventos del usuario, como si hubiera dejado de funcionar. Por ese motivo, se debe evitar el uso de la función sleep() del módulo time de Python, que tampoco podría llamarse dentro de un thread, porque Tkinter no funciona adecuadamente con dicho módulo. Aunque existen formas de hacer que trabajen conjuntamente, lo más sencillo es usar el método after().
El resultado devuelto por este método es un identificador, que podría usarse para cancelar posteriormente el temporizador con este otro método:
after_cancel(id temporizador)
Para practicar con estos métodos, va a desarrollar un programa en el que aparezca una frase que aumente de tamaño continuamente hasta alcanzar un máximo, a partir del cual empezará a disminuir; así, hasta que alcance un tamaño mínimo, momento en el que comenzará a crecer otra vez, repitiéndose de nuevo el ciclo.
El código del programa es el siguiente:


Lo primero que se hace es importar las clases necesarias; en este caso, Tk y Label. Con la primera, se creará la ventana principal y, con la segunda, el texto que irá cambiando de tamaño:

A continuación, se declaran las variables utilizadas para parametrizar el comportamiento del programa. Con incremento, se establece el cambio de tamaño experimentado por el texto (en píxeles) cada vez que transcurra el número de milisegundos almacenado en periodo. Cuanto más grande sea el valor de incremento y/o más pequeño el del de periodo, mayor será la velocidad a la que crezca o disminuya el tamaño del texto. Las variables tamanio_max y tamanio_min contienen el tamaño máximo y mínimo que puede tener la fuente, cuyo valor actual se guardará en tamanio:

De momento, salte la función modifica_tamanio() para continuar con las sentencias en las que se crea la ventana principal (root), a la que se asigna un tamaño de 400 × 200 píxeles. A continuación, se crea la etiqueta con una fuente de tipo Arial y en negrita, cuyo tamaño será el contenido en la variable tamanio:

Una vez creada la etiqueta, se muestra centrada en la ventana principal, para lo cual se asigna el valor True a la opción expand:

Por último, se llama a la función modifica_tamanio(), encargada de cambiar el tamaño del texto (su fuente) de forma continuada:

Por lo tanto, ha llegado el momento de describir el código que contiene. En primer lugar, se declaran como globales las variables tamanio e incremento, porque son compartidas en todas las invocaciones de esta función:

La primera sentencia de esta función determina el signo del incremento, y lo cambia cuando se alcanza el valor máximo o mínimo. De esta forma, se consigue el efecto de crecimiento y decrecimiento continuo del tamaño del texto, cuyo valor se calcula en la segunda sentencia:

Dicho valor se traslada a la etiqueta con el método configure() a través de la opción font, con el fin de mostrar en pantalla el texto con el nuevo tamaño:

Por último —y lo más importante a efectos de esta práctica—, se invoca el método after(), para volver a llamarse a sí misma transcurrido el tiempo indicado por la variable periodo. La ejecución recursiva de esta función será la que consiga que el tamaño del texto cambie continuamente:

El resultado obtenido al ejecutar este programa puede verse reflejado en la siguiente secuencia de imágenes:

5.3 GESTIÓN DEL FOCO
El siguiente grupo de métodos está relacionado con el foco de un widget, es decir, con la entrada de datos por teclado:
•focus_get(). Devuelve el widget que tiene el foco en una aplicación. Si no lo tuviera ninguno, devolvería None.
•focus_set(). Pone el foco en un widget determinado.
•tk_focusFollowsMouse(). Fuerza que el foco esté donde se encuentre el ratón.
•tk_focusNext(). Devuelve el widget que sigue en la secuencia transversal del foco.
•tk_focusPrev(). Devuelve el widget que precede en la secuencia transversal del foco.

Recuerde que la secuencia transversal del foco está formada por los widgets por los que va pasando cuando se pulsa el tabulador.
La práctica que va a realizar en esta ocasión es un juego donde se muestra una ventana con cuatro campos. Al iniciar el programa, el foco se encuentra en el primer campo y, a partir de ese momento, cambia de forma aleatoria entre el de arriba y el de abajo. El objetivo es ser capaz de escribir correctamente una determinada palabra en todos ellos. Si se confundiera al añadir alguno de sus caracteres, el juego finalizaría, deshabilitándolos y mostrando un mensaje en color rojo que indicaría que el reto no ha sido conseguido. En caso contrario, el mensaje de acierto se mostraría en color verde.
El código del programa es el siguiente:



En primer lugar, se importa todo aquello que se va a utilizar. En concreto, de Tkinter se va a necesitar la clase Tk para crear la ventana principal, Entry para los campos de texto y Label para los mensajes de éxito o fracaso. De la librería random se importa la función randint(), para generar números aleatorios:

Luego, se declaran las variables de configuración. La primera (texto) almacena el texto que deberá escribirse correctamente en todos los campos para completar el juego. La segunda (periodo) es el tiempo que permanecerá el foco en un campo antes de cambiar al siguiente. Cuanto menor sea su valor, en milisegundos, más dificultad entrañará el juego:

Salte la declaración de la función, que se describirá más adelante, hasta encontrar las sentencias en las que se crea la ventana principal (root); se le asigna un texto a su barra de título y se impide que pueda redimensionarse:

A continuación, se crean los campos de texto (campo1, campo2, campo3 y campo4). En ellos se establece la misma fuente (opción font), el mismo borde (opción bd) y, sobre todo, un marco de color rojo de dos píxeles de grosor para indicar cuándo tienen el foco (opciones highlightcolor y highlightthickness):


Una vez creados los campos, se colocan verticalmente en la ventana principal con el método pack(). El espacio entre ellos se fija con las opciones padx

La siguiente sentencia establece el foco en el primer campo (campo1), para lo que se hace uso de uno de los métodos descritos en esta sección: focus_set():

Por último, se comienza el juego invocando la función modifica_foco(), en cuyo único argumento se debe indicar el campo activo en ese momento, inicialmente campo1:

Ha llegado el momento de conocer el contenido de esta función. En primer lugar, se declara la variable estado, cuyos valores indicarán si el juego está en curso (“EN CURSO”), o si se ha finalizado con éxito (“OK”) o con fracaso (“KO”). Cuando se invoca la función es porque, de momento, está en curso; de ahí que se asigne inicialmente dicho estado:

Después, se obtiene el valor del campo actual (el pasado como argumento), utilizando el método get():

Las siguientes sentencias determinan si el juego ha terminado (podrá ser con éxito o con fracaso). Para ello, si los caracteres que hay escritos en dicho campo (texto_campo_actual) no coincidieran con los del texto de referencia (texto), el juego pasaría a un estado “KO” (más tarde se verá lo que se hace en este estado). En caso contrario, se comprobaría si el contenido de todos los campos coincide con el texto completo de referencia, en cuyo caso el juego pasaría a un estado “OK” (a continuación, descubrirá qué se hace en este otro estado):

Si el juego no hubiera terminado (no se ha cumplido ninguna de las condiciones de los if anteriores), su estado seguiría siendo en “EN CURSO”, por lo que se cumpliría la condición del siguiente if, en el que se decide el campo al que tendría que pasar el foco. Para saber cuál es, se usa la función randint(), que devuelve aleatoriamente el número 0 o el 1 (equivalente a False y True). En función de dicho valor, se invocaría a la función tk_focusNext() o tk_focusPrev() para identificar el campo siguiente o el anterior al que tiene el foco actualmente. Una vez conocido dicho campo (nuevo_campo), se le asigna el foco con el método focus_set(). Por último, se invoca de nuevo a esta función con el método after() para continuar jugando:

Si el juego ha finalizado (el estado no es “en curso”), se ejecutan las sentencias del bloque else. Dentro, hay otra sentencia if…else que determina si el juego ha finalizado con éxito (estado “OK”) o no. Si lo hubiera hecho con éxito, se crearía la etiqueta que muestra el mensaje de felicitación. La fuente del texto sería la misma de los campos de entrada (opción font), pero de color verde (opción fg). En caso contrario, la etiqueta tendría otro texto menos afortunado, con la misma fuente pero en color rojo. En ambos casos, se deshabilitan todos los campos de texto utilizando el método configure(), con el que se asigna el valor "disabled" al atributo. Por último, se llama al método pack(), para presentar en pantalla la etiqueta con el mensaje correspondiente de finalización del juego:


El atributo state, compartido por muchos widgets, determina el estado del widget en el que se encuentra. Se describirá más adelante, cuando estudie cada uno de ellos.
Seguro que estará deseando empezar a jugar. Espero que tenga mejor suerte que yo.


Si quiere mejorar la jugabilidad del programa, y también su dificultad, obligue a introducir algún carácter al cambiar el foco de campo.
5.4 MANEJO DE DIMENSIONES Y POSICIONES
En este apartado, se estudian todos los métodos relacionados con la obtención de las dimensiones de un widget, la ventana principal e, incluso, la pantalla. También conocerá los métodos necesarios para saber la posición de un widget en la ventana principal (o widget contenedor), así como la de la ventana o el ratón en la pantalla.
El primero que veremos devuelve las dimensiones y la posición de un widget en la ventana principal (o widget contenedor). Si, en vez de un widget, se tratara de la propia ventana principal, la posición devuelta sería la que ocupa en la pantalla:

El valor devuelto es una cadena con el siguiente formato:
anchoxalto±x±y
Los valores +x determinan la distancia entre el lado izquierdo del widget y el de la ventana, en píxeles. Si el signo fuera negativo, sería la distancia entre el lado derecho del widget y el de la ventana. Los valores +y indican la distancia entre el límite superior de la ventana y el del widget. Si el signo fuera negativo, sería la distancia entre el límite inferior de la ventana y el del widget; por ejemplo, un widget de 50 píxeles de ancho y 20 de alto, cuya esquina superior izquierda estuviera situada en la posición (200, 100), se describiría como:


Hasta que el gestor de geometría no asigne los tamaños y las posiciones correspondientes a todos los widgets que se muestran en la ventana principal, su valor inicial es "1x1+0+0". Para asegurarse de que el valor obtenido esté actualizado, invoque antes update_idletasks().
Métodos relacionados con el método anterior:
•winfo_rootx(). Devuelve la coordenada x del lado izquierdo de la ventana principal respecto de la pantalla del ordenador. Si tuviera un borde, haría referencia a su parte exterior.
•winfo_rooty(). Devuelve la coordenada y del lado superior de la ventana principal respecto de la pantalla del ordenador. Si tuviera un borde, haría referencia a su parte exterior.
•winfo_x(). Devuelve la coordenada x del lado izquierdo del widget respecto de la ventana principal (o widget contenedor). Si tuviera un borde, haría referencia a su parte exterior.
•winfo_y(). Devuelve la coordenada y del lado superior del widget respecto de la ventana principal (o widget contenedor). Si tuviera un borde, haría referencia a su parte exterior.
•winfo_width(). Devuelve el ancho del widget. Si el objeto sobre el que se invoca es de la clase Tk, corresponderá al ancho de la ventana principal.
•winfo_height(). Devuelve el alto del widget. Si el objeto sobre el que se invoca es de la clase Tk, corresponderá al alto de la ventana principal.

En caso de querer obtener el ancho y el alto establecido con las opciones width y height de un widget, deberá usar los métodos winfo_ reqwidth() y winfo_reqheight(), respectivamente. Como el gestor de geometría lo ajustará para encajarlo con el resto de los widgets que se muestran en la ventana principal (o widget contenedor), dichos valores no tienen por qué ser los mismos que los asignados a dichas opciones.
Si lo que desea conocer es el tamaño de la pantalla del ordenador, llame a los métodos:
•winfo_screenwidth(). Devuelve el ancho de la pantalla en píxeles.
•winfo_screenheight(). Devuelve el alto de la pantalla en píxeles.
El último grupo de métodos devuelve las coordenadas del ratón:
•winfo_pointerxy(). Devuelve una tupla que contiene las coordenadas del ratón respecto de la pantalla de su ordenador (no respecto de la ventana principal).
•winfo_pointerx(). Devuelve el valor x de la tupla anterior.
•winfo_pointery(). Devuelve el valor y de la tupla anterior.
Para practicar con algunos de estos métodos, especialmente los relacionados con la posición del ratón, en esta última práctica va a mostrar las coordenadas de su posición dentro de la ventana principal. Fuera de ella, aparecerá el valor −1, −1.
El código del programa es el siguiente:

En la primera sentencia, se importa la clase Tk para crear la ventana principal y Label para mostrar las coordenadas del ratón:

Las siguientes sentencias, de sobra conocidas por usted, crean la ventana principal (root), a la que se asigna un tamaño de 400 × 200 píxeles, además de la etiqueta (etiqueta), con la que se mostrarán las coordenadas del ratón en el centro de la ventana:

Por último, se llama a la función responsable de presentar la posición del ratón en todo momento:

En ella, lo primero que se hace es obtener la información necesaria para calcular las coordenadas del ratón relativas a la ventana principal (no hay ninguna función que la proporcione directamente). Para ello, en las variables x e y se almacenan las coordenadas del ratón respecto de la pantalla del ordenador. Por su parte, root_x y root_y contendrán las coordenadas de la esquina superior izquierda de la ventana principal, también respecto de la pantalla del ordenador:

Con la información anterior, las coordenadas x, y del ratón relativas a la ventana principal se calculan como:

Por último, las variables ancho_pantalla y alto_pantalla guardarán el ancho y alto de la ventana principal (recuerde que puede redimensionarse):

La sentencia if que viene a continuación se utiliza para mostrar el valor “−1, −1” cuando el ratón se encuentre fuera de la ventana, en cuya condición se utiliza el ancho y alto de la ventana principal, obtenido anteriormente:

Una vez conocidas las coordenadas del ratón relativas a la ventana principal (mouse_x y mouse_y), se muestran en la etiqueta modificando la opción text con el método configure():

Por último, transcurridos 10 milisegundos, se vuelve a ejecutar esta función para mostrar la nueva posición en la que pudiera encontrarse el ratón. Para ello, se hace uso del conocido método after():

Observe un ejemplo de lo que vería cuando ejecutara el programa, situando el ratón en la parte inferior derecha de la ventana:

Aunque mueva o modifique el tamaño de la ventana principal, el programa seguirá funcionando correctamente.
Unidad 6
VARIABLES DE CONTROL
Las variables de control son un tipo especial de variables, que se asocian a ciertos widgets para almacenar valores de entrada o visualización de información. Un ejemplo del primer caso serían los datos introducidos por el usuario en un campo de entrada de texto. Dichos datos quedarían a disposición del programa cuando fueran necesarios. Otro ejemplo, en este caso de visualización de información, sería el texto mostrado por una etiqueta, que cambiaría al modificarse el valor de la variable de control que tuviera asociada.

En el caso de las etiquetas, otra forma de modificar el texto que contienen es asignando el nuevo texto a la opción text con el método configure(), tal como ha podido comprobar en una práctica anterior.
La forma de asociar una variable de control a un widget es mediante la opción textvariable.
Una variable de control es un objeto que puede almacenar un número (entero o de coma flotante), una cadena o un valor booleano. Dependiendo del tipo de datos que contenga, dicho objeto será de la clase IntVar, DoubleVar, StringVar o BooleanVar. Sus constructores son:


Para poder usar alguna de estas clases, primero deberá importarla. Además, solo podrán crearse variables de control (objetos de estas clases) después de la ventana principal. De lo contrario, obtendría el siguiente error:
AttributeError: ‘NoneType’ object has no attribute ‘_root’
Si quisiera asignar un valor a la variable de control en el momento de crearla, estos constructores pueden invocarse con la opción value.
Otra forma de asignar un valor a una variable de control, una vez creada, es invocando el método:
set(valor)
Para obtener el valor que contiene, el método al que tendría que llamar es:
get()
Por último, si lo que desea es saber cuándo es leída, escrita o borrada, Tkinter ofrece el método:
trace(suceso, función)
El suceso puede tomar los valores "r", "w" y "u", dependiendo de si está interesado en saber cuándo se lee, se escribe o se borra su contenido, respectivamente. El segundo argumento es la función que se invocaría al producirse dicho evento.
En la siguiente práctica va a utilizar una variable de control para modificar el texto de una etiqueta, lo cual provocará un efecto de desplazamiento hacia la derecha:

El código utilizado para ello es el siguiente:


En primer lugar, se importan las clases necesarias, que son las utilizadas para crear la ventana principal (Tk), la etiqueta (Label) y, en especial, la variable de control con el texto que se mostrará en cada momento, StringVar():

Luego, se declaran las variables de configuración necesarias para el funcionamiento del programa. La primera (texto) es el texto que se quiere mostrar. La segunda (intervalo) establece la velocidad a la que se desplazará; en este caso, un carácter cada 200 milisegundos:

La siguiente variable (texto_auxiliar) contiene un texto auxiliar, al que se han añadido a la derecha tantos espacios como número de caracteres tenga el texto que mostrar. Su necesidad la entenderá más adelante:

Saltando la declaración de la función que realizará el desplazamiento horizontal del texto, se encuentran las sentencias que crean la ventana principal (root) y establecen el nombre que se verá en la barra de título:

Luego, se crea la variable de control que contendrá la parte del texto que se muestra en cada momento (variable_control) para dar sensación de movimiento. Será un objeto de la clase StringVar porque su contenido será un texto:

A continuación, se crea la etiqueta. Ya conoce todas las opciones utilizadas, excepto textvariable, que será la que asocie la variable de control que se acaba de crear (variable_control) con la etiqueta. Cada vez que asigne un nuevo texto a dicha variable, la etiqueta lo mostrará en pantalla de forma automática. Por ese motivo, en el constructor no se ha utilizado la opción text. Además, es importante destacar que se hace coincidir el tamaño de la etiqueta (atributo width) con la longitud del texto que mostrar. Más adelante entenderá el motivo:

Una vez creada la etiqueta, se sitúa en la ventana principal con el método pack():

Solo queda llamar a la función que comenzará el desplazamiento del texto:

La última sentencia invoca el método mainloop() de la ventana principal. Aunque no se generen eventos de usuario, es necesario para el correcto funcionamiento del método after(), utilizado dentro de la función anterior para provocar el movimiento del texto:

Tras describir el flujo principal del programa, llegó el momento de estudiar la función avanza(), encargada de dotar de movimiento al texto de la etiqueta. Dicha función declara la variable texto_auxiliar como global, porque su valor será modificado en cada una de las ejecuciones de la función, que será compartida en todas ellas:

Las tres variables siguientes quitan el último carácter del texto auxiliar (texto_auxiliar) y lo insertan al principio. Como al iniciar el programa este se compuso añadiendo al texto del mensaje el mismo número de espacios en blanco que su tamaño, provocará un efecto de desplazamiento hacia la derecha. Eso es debido a que estos espacios en blanco se van colocando al principio, y van desplazando el texto en dicho sentido. Cuando ya no haya más caracteres en blanco, el mensaje volverá a aparecer por la izquierda, siguiendo el mismo movimiento:

La siguiente sentencia es la más importante desde el punto de vista de esta práctica, ya que asigna el texto auxiliar que se acaba de obtener a la variable de control asociada a la etiqueta con el método set(). Pero no se copiarán todos sus caracteres, sino únicamente los correspondientes al tamaño de la etiqueta, que coincide con el número de caracteres del texto original (atributo width):

Finalmente, se llama al método after(), para que este proceso se repita de forma regular, provocando así un desplazamiento continuo del texto:

Solo queda ejecutar el programa y comprobar que se produce el efecto deseado.
Unidad 7
WIDGETS
Como sabe, los widgets son los componentes gráficos proporcionados por Tkinter para el desarrollo de interfaces de usuario. Cada uno de ellos se representa mediante una clase:
•Label. Es un widget que muestra un texto corto y/o una imagen.
•Message. Similar al widget anterior, pero empleado en textos más largos, compuestos por varias líneas que pueden justificarse.
•Button. Es un botón.
•Entry. Permite introducir una línea de texto.
•Spinbox. Semejante al anterior. Se añaden dos iconos con forma de punta de flecha hacia arriba y hacia abajo, con los que el usuario también podrá seleccionar el valor de un rango.
•Listbox. Ofrece la posibilidad de elegir la opción de una lista.
•Menu, OptionMenu y Menubutton. Se utilizan para crear menús desplegables.
•Colorchooser, Messagebox y Filedialog. No se trata realmente de widgets, sino de módulos que ofrecen una serie de funciones con las que se crean ventanas de diálogo emergentes (pop-up dialogs), donde se pueden elegir colores, exponer mensajes informativos o preguntas, o abrir y guardar archivos.
•Text. Área en la que es posible editar texto con formato.
•Scrollbar. Habilita el desplazamiento por el contenido de un widget cuando este no puede verse completamente.
•Checkbutton, Radiobutton. Permiten elegir una opción de un conjunto predeterminado. Cada una de ellas es, en realidad, un botón que se puede activar o desactivar.
•Frame. Se trata de un widget contenedor, usado para agrupar otros widgets. Facilita el desarrollo de interfaces con diseños complejos.
•Panel. Son similares a los frames, pero, a diferencia de estos, es posible mover los límites entre ellos y cambiar su tamaño.
•Toplevel. También es un widget contenedor, pero, a diferencia de los anteriores, se muestra en otra ventana diferente a la principal.
•Canvas. Área donde es posible dibujar cualquier tipo de gráfico, que puede ser interactivo.
•Scale. Proporciona una barra de desplazamiento para seleccionar gráficamente un valor dentro de una escala.
En los siguientes apartados tendrá ocasión de conocer y practicar con todos estos widgets, desarrollando interfaces gráficas totalmente operativas.
7.1 LABEL Y MESSAGE
Una etiqueta es un widget que exhibe una o más líneas de texto (y/o una imagen). Un mensaje es muy similar a una etiqueta. Su principal diferencia es que el texto se divide automáticamente en líneas y se justifica, manteniendo un ancho o relación de aspecto determinado.

7.1.1 Opciones y métodos
En Tkinter, una etiqueta es un objeto de la clase Label. Su constructor es el siguiente:
Label(widget contenedor, opción, …)
El primer argumento es el widget contenedor. Si no se incluyera, se consideraría que se trata de la ventana principal. Como opciones, además de las comunes activebackground, activeforeground, anchor, bg, bitmap, bd, compound, cursor, disabledforeground, font, fg, height, highlightbackground, highlightcolor, highlightthickness, image, padx, pady, relief, takefocus y width, destacan:
•justify. Cuando la etiqueta tenga varias líneas, esta opción determina cómo se alinean. Sus valores pueden ser: LEFT (a la izquierda), CENTER (centradas, es el valor por defecto) o RIGHT (a la derecha). Si se trata de una sola línea, y esta ocupa menos espacio del disponible en la etiqueta, deberá utilizar la opción anchor para establecer su posición.
•state. De forma predeterminada, el valor de esta opción es NORMAL. Si no quiere que atienda eventos, asígnele el valor DISABLED. Cuando el ratón se sitúe sobre la etiqueta, tomará el valor ACTIVE (a no ser que esté desactivada).
•text. Cadena de texto que muestra la etiqueta. Si quiere que tenga más de una línea, utilice la secuencia de escape ‘\n’.
•textvariable. Nombre de una variable de control. El texto mostrado por la etiqueta será el correspondiente al valor de dicha variable.
•underline. Fija el carácter del texto que quiera que aparezca subrayado. Su valor es la posición que ocupa en dicho texto, empezando a contar desde 0. Se utiliza para indicar la existencia de atajos de teclado. El valor por defecto es −1, lo que significa que no habrá ningún texto subrayado.
•wraplength. Limita el ancho del texto de la etiqueta en cada línea (en píxeles). El valor por defecto es 0, lo que significa que solo se pueden añadir nuevas líneas de forma explícita.

Las listas de opciones se describirán normalmente en orden alfabético.
La forma de modificar el texto mostrado por una etiqueta es cambiando el valor del atributo text mediante el método configure() o asignando una variable de control al atributo textvariable. En este último caso, la etiqueta mostraría el valor que tuviera dicha variable en todo momento.
Por su parte, los mensajes son objetos de la clase Message, cuyo constructor es:

Además de las opciones comunes bg, bd, cursor, font, fg, highlightbackground, highlightcolor, highlightthickness, padx, pady, relief, takefocus y width, podrán utilizarse:
•aspect. Determina la relación entre el ancho y el alto como un porcentaje (aunque este widget dispone de la opción width para establecer su ancho, no tiene la opción height para hacer lo mismo con el alto); por ejemplo, un valor de 100 haría que el mensaje quedara encajado en un cuadrado. Si fuera 200, tendría el doble de ancho que de alto. El valor predeterminado es 150; es decir, el texto se muestra dentro de un rectángulo un 50 % más ancho que alto.
•justify, text, texvariable. Su significado es el mismo descrito para las etiquetas.
7.1.2 Práctica
En la siguiente práctica, desarrollará un reloj digital que muestra la hora actual:

En primer lugar, se importan las clases Tk, Label y StringVar de la librería Tkinter. Con la primera se crea la ventana principal, con la segunda se crea la etiqueta y, con la última, se crea la variable de control que contendrá la hora mostrada por dicha etiqueta. También se importa la librería time, con la que se obtendrá la hora actual:

Saltando la declaración de la función actualizar_hora(), que verá más adelante, se procede a crear la ventana principal, impidiendo que se pueda redimensionar:

Luego, se crea la variable de control que contendrá la hora actual:
variable_control = StringVar()
A continuación, se crea la etiqueta a la que se asocia la variable de control anterior con la opción textvariable. La hora se mostrará en color rojo con un tipo de fuente “Arial” de 18 píxeles (opciones fg y font). Alrededor de la etiqueta se deja un margen de 20 píxeles (opciones padx y pady). Una vez creada, se añade a la ventana principal con el método pack():

Por último, se llama a la función que actualiza el texto de la etiqueta para que muestre la hora actual:

Es el momento de describir el código de dicha función. Dentro, lo primero que se hace es llamar a la función strftime(), que devuelve la hora actual en el formato hh:mm:ss y la almacena en la variable hora:

Puesto que la etiqueta reloj muestra en todo momento el contenido de la variable de control que tiene asociada (variable_control), se le asigna dicho valor con el método set():

Una vez actualizada la hora, se invoca el método after() para que, transcurrido un segundo, se vuelva a llamar a esta misma función, que actualizará de nuevo la hora en pantalla:

El resultado de la ejecución de este programa lo puede ver a continuación:

Si quisiera mostrar la imagen de un reloj de arena junto a la hora, podría hacer uso de la que viene con Tkinter. Para ello, sustituya la sentencia

por:

Lo que se ha hecho es añadir al constructor de la etiqueta la opción bitmap, para cargar la imagen del reloj, y compound, para situarla a la izquierda del texto.
El resultado obtenido en esta ocasión será el siguiente:

7.2 BUTTON
Este widget representa un botón. Habitualmente muestran un texto, aunque también se les pueden añadir una imagen que sirva para indicar su propósito de forma gráfica:

7.2.1 Opciones y métodos
Los botones se representan como objetos de la clase Button, cuyo constructor es:
Button(widget contenedor, opción, …)
El primer argumento es el widget contenedor. Si no se incluyera, se tomaría por defecto la ventana principal. Como opciones, además de las comunes activebackground, activeforeground, anchor, bd, bg, bitmap, compound, cursor, disabledforeground, fg, font, height, highlightbackground, highlightcolor, highlightthickness, image, padx, pady, relief, takefocus, underline, width y wraplength, destacan:
•command. Función que se ejecutará cuando se pulse el botón.
•justify. Cuando el texto mostrado por el botón tenga varias líneas, esta opción determina cómo se alinean. Sus valores pueden ser: LEFT (a la izquierda), CENTER (centradas, es el valor por defecto) o RIGHT (a la derecha). Si se trata de una sola línea, y esta ocupa menos espacio del disponible en la etiqueta, deberá utilizar la opción anchor.
•default. Asigne el valor DISBALED a esta opción si quiere que el botón se cree deshabilitado (que no se pueda pulsar). Por defecto, tiene el valor NORMAL.
•overrelief. Estilo de relieve que presenta el botón cuando sitúa el ratón sobre él. Al igual que el atributo relief, puede tener los valores: FLAT, RAISED, SUNKEN, GROOVE y RIDGE. El valor predeterminado es RAISED.
•repeatdelay y repeatinterval. Normalmente, la función asociada a un botón (opción command) se ejecuta solo una vez cuando se deja de pulsar el ratón. Si desea que lo haga a intervalos regulares mientras se mantenga presionado, asigne a repeatinterval el número de milisegundos entre repeticiones, y a repeatdelay la cantidad de milisegundos que deben transcurrir antes de empezar a invocarse; por ejemplo, si el valor de repeatdelay es 500 y el de repeatinterval es 100, la función asociada al botón se ejecutará al cabo de medio segundo y, luego, cada décima de segundo, hasta que se deje de pulsar. Si no se mantuviera presionado el ratón al menos el tiempo indicado en la opción repeatdelay, la función se ejecutaría una única vez (comportamiento habitual).
•state. El valor predeterminado es NORMAL, lo que permite pulsarlo. Si quiere evitarlo, asigne a esta opción el valor DISABLED. Cuando el ratón esté sobre el botón, su valor será ACTIVE (a no ser que esté desactivado).
•text. Texto que se muestra dentro del botón.
•textvariable. Nombre de una variable de control. El texto mostrado por el botón será el correspondiente al valor de dicha variable en todo momento.
•underline. Fija el carácter del texto que quiera que aparezca subrayado. Su valor es la posición que ocupa en dicho texto, empezando a contar desde 0. Se utiliza para indicar la existencia de atajos de teclado. El valor por defecto es −1, lo que significa que no habrá ninguno subrayado.
•wraplength. Limita el ancho del texto del botón en cada línea (en píxeles). El valor por defecto es 0, lo que significa que solo se pueden añadir nuevas líneas de forma explícita.
Esta clase tiene dos métodos. El primero le hace parpadear varias veces entre los colores que tenga definidos para los estados activo y normal (establecidos en las opciones activebackground y activeforeground en el primer caso, y background y foreground en el segundo):

El segundo permite invocar la función que tenga asociada (como si se hubiera pulsado):

7.2.2 Práctica
En esta nueva práctica, va a desarrollar un cronómetro. Para ello, utilizará una etiqueta que muestre el tiempo transcurrido y dos botones: uno para ponerlo en marcha —empezando a contar a partir de cero— y otro que le permita pararlo.
El código del programa es el siguiente:



En primer lugar, se importan las clases necesarias del módulo Tkinter. Las primeras son las utilizadas para crear la ventana principal (Tk), la etiqueta que marca el tiempo transcurrido (Label) y los botones de inicio y parada (Button). Por último, la variable de control asociada a la etiqueta será un objeto de la clase StringVar:

A continuación, se declaran las variables empleadas a lo largo del programa. Las primeras (hora, minuto y segundo) serán las que lleven la cuenta de las horas, minutos y segundos transcurridos. La última (inicio) indica si el cronómetro está en marcha o no:

Saltando la declaración de las funciones, se encontrará con las sentencias que crean la ventana principal y evitan que pueda redimensionarse:

Luego, se crea la variable de control que se vinculará a la etiqueta. Será la que contenga el tiempo transcurrido en todo momento. Se le asigna inicialmente el valor “00:00:00” al arrancar el programa, utilizando la opción value:

La siguiente sentencia crea la etiqueta que muestra el tiempo transcurrido. Su código es similar al de la práctica del reloj vista anteriormente, por lo que no se dará ninguna explicación adicional:

Después, se crean los botones de arranque (boton_start) y parada (boton_ stop). El nombre de cada uno de ellos se asigna con el atributo text. Para que sean más anchos, se establece un margen interior de 10 píxeles con el atributo padx. Los colores del texto y del fondo los determina el valor de los atributos fg y bg. Por último, con la opción command, se establece la función que se llamará cuando se pulsen; en concreto, start() para el de arranque y stop() para el de parada (su código se describirá más adelante):

Una vez creados los widgets, estos se muestran en la ventana principal. La etiqueta se situará la primera y, por lo tanto, en la parte superior. En el caso de los botones, se utilizará la opción side para colocar el de la puesta en marcha del cronómetro a la izquierda y el de parada a la derecha. Además, se establece una separación de 10 píxeles alrededor de cada uno con padx y pady:

La última sentencia invoca el método mainloop(), momento a partir del cual se podrán capturar los eventos producidos cuando se pulse cualquiera de los botones, invocando la función que tengan asociada:

Pasemos a ver la declaración de estas funciones, empezando por la que actualiza el tiempo transcurrido desde que se pulsó el botón “Start”. En ella, lo primero que se hace es declarar como globales las variables que contienen el tiempo que ha pasado desde ese momento (hora, minuto y segundo), ya que serán las mismas utilizadas en las invocaciones que se realicen cada segundo a esta función. Además, como solo se actualizará el tiempo cuando el cronómetro esté en marcha, la lógica de esta función solo se llevará a cabo si la variable inicio tiene el valor True:

El primer bloque de sentencias que se ejecuta cuando se cumple la condición del if anterior actualiza el valor de la hora, minutos y segundos transcurridos. Solo se podrá llegar a contar un periodo de tiempo de 99 horas, momento a partir del cual el cronómetro se reseteará:

Las siguientes sentencias lo único que hacen es añadir un 0 delante de los números menores de 10, para que siempre se muestren con dos dígitos:

Una vez obtenido el tiempo transcurrido desde que se pulsó el botón “Start”, se muestra en la etiqueta en formato hh:mm:ss. Para ello, se modifica el valor de la variable de control que tiene asociada (variable_control) con el método set():

Por último, se invoca el método after para volver a ejecutar esta función al cabo de un segundo:

Las dos funciones que quedan por describir son las de los botones “Start” y “Stop”. Empecemos por la primera.
La función que inicia el cronómetro también declara como globales las variables hora, minuto y segundo, porque podrían modificar su valor, al igual que inicio. Como serán utilizadas fuera de esta función, se quiere evitar que se traten como locales:

Puesto que el tiempo empezará a contar únicamente cuando el cronómetro esté parado, las tareas necesarias para iniciar dicha cuenta solo se llevarán a cabo si la variable inicio tiene el valor False:

Si se cumpliera la condición anterior, se pondría a cero el cronómetro y se asignaría el valor “00:00:00” a la variable de control variable_control asociada a la etiqueta con el método set(), se iniciarían a 0 las variables con las horas, minutos y segundos transcurridos y se asignaría a la variable inicio el valor True para empezar la cuenta:

Por último, se pondría en marcha el cronómetro invocando la función actualizar_tiempo() transcurrido un segundo, con el método after():

La última función detiene la cuenta del tiempo cuando se pulsa el botón “Stop”. Para ello, asigna el valor False a la variable inicio con el fin de que deje de invocarse recursivamente la función actualizar_tiempo(), es decir, que deje de actualizarse el tiempo. Naturalmente, dicha variable deberá haber sido declarada como global, ya que será utilizada por el resto de funciones del programa:

El resultado de la ejecución del programa se puede ver a continuación. Como puede apreciar, el tiempo transcurrido se muestra en color azul, mientras que el texto de los botones aparece en blanco sobre fondo verde (“Start”) o rojo (“Stop”). Al pulsar el botón “Start”, empezaría a contar el tiempo, mientras que, pulsando “Stop”, se pararía. Si volviera a pulsar “Start”, el cronómetro se resetearía, empezando la cuenta desde cero.

7.3 ENTRY
Este widget se utiliza para mostrar un campo de entrada de texto:


Si quisiera que el usuario pudiera escribir más de una línea, el widget más adecuado sería Text (lo estudiará más adelante).
7.3.1 Opciones y métodos
En Tkinter, un campo de entrada de texto se representa por la clase Entry, cuyo constructor es el siguiente:
Entry(widget contenedor, opción, …)
El primer argumento es el widget contenedor (si no indicara este argumento, se supondría que es la ventana principal). Como opciones, además de las comunes bg, bd, cursor, disabledbackground, disabledforeground, fg, font, highlightbackground, highlightcolor, highlightthickness, relief, takefocus y width, destacan:
•exportselection. Por defecto, cuando se selecciona el texto de este campo, se copia en el portapapeles. Para evitar este comportamiento, asigne el valor False a esta opción.
•justify. Cuando el texto mostrado en el campo tiene varias líneas, esta opción determina cómo se alinean. Sus valores pueden ser: LEFT (a la izquierda), CENTER (centradas, es el valor por defecto) o RIGHT (a la derecha).
•readonlybackground. Color de fondo del campo cuando su estado es de solo lectura.
•show. En vez de los caracteres introducidos por el usuario, se muestra el asignado a esta opción; por ejemplo, si un campo de entrada de texto sirviera para introducir una contraseña y esta opción tuviera el valor ‘*’, solo se verían asteriscos.
•state. El valor predeterminado es NORMAL, lo que permite escribir en él. Si quiere evitarlo, haciendo que sea de solo lectura, asigne a esta opción el valor DISABLED. Cuando el ratón esté sobre el campo, su valor será ACTIVE (a no ser que esté desactivado).
•textvariable. Nombre de una variable de control cuyo valor es el contenido del campo en todo momento.
•validate y validatecommand. El valor de la primera opción determina cuándo se debe llamar a la función indicada en la segunda. Para conocer cómo usarlas, consulte el capítulo de validación de entradas de datos.
•xscrollcommand. Permite hacer scroll del texto introducido en el campo cuando este supera su tamaño. Toma como valor un objeto de la clase Scrollbar, que representa una barra de scroll. Cuando estudie este widget, sabrá cómo utilizarlo.
Además de las opciones descritas anteriormente, existen otras relacionadas con el cursor de inserción:
•insertbackground. Color del cursor de inserción del texto. Por defecto, es negro.
•insertborderwidth. Ancho del relieve alrededor del cursor de inserción. Si quiere que tenga un aspecto 3D con efecto RAISED, asigne a esta opción un valor que, al menos, duplique el de la opción insertwidth.
•insertofftime. De forma predeterminada, el cursor de inserción parpadea. Con esta opción puede establecer el número de milisegundos entre cada parpadeo. El valor predeterminado es 300. Si le asignara el valor 0, no parpadearía.
•insertontime. En este caso, lo que se establece es el número de milisegundos en los que el cursor de inserción se ve en cada parpadeo. El valor predeterminado es 600.
•insertwidth. Ancho del cursor de inserción. Por defecto, es de dos píxeles de grosor.
Por último, existe otro grupo de opciones relativas al texto seleccionado dentro del campo:
•selectbackground. Color de fondo del texto seleccionado.
•selectborderwidth. Ancho del borde que hay alrededor del texto seleccionado. El valor predeterminado es de un píxel.
•selectforeground. Color de primer plano del texto seleccionado (el del propio texto).
En cuanto a los métodos, únicamente se describirán tres de los que dispone este widget. El primero devuelve el texto contenido en el campo:
get()
El siguiente añade el texto incluido en el segundo argumento antes del carácter situado en la posición indicada en el primero:
insert(index, texto)
La posición dentro de un campo de texto se puede especificar de las siguientes formas:
•Un número, en el que la primera posición es el 0.
•END. Posición siguiente a la del texto existente.
•INSERT. Posición en la que se encuentra el cursor de inserción (el que indica dónde se empezaría a escribir el nuevo texto).
•ANCHOR. Primer carácter del texto seleccionado (si lo hubiera).
Por ejemplo, si quiere añadir un texto al final del existente en el campo, este método se llamaría así:

El último método permite borrar parte del texto o todo el texto del campo:

Este método elimina los caracteres contenidos entre la posición inicial y la final (sin incluir el último). Si se omitiera el segundo argumento, solo se eliminaría el carácter situado en la posición inicial.
Por ejemplo, para borrar todo el texto de un campo, la forma de invocar este método sería:


Como viene siendo habitual, también podría sustituir la constante END por la cadena "end". Si prefiere usar la constante, no se olvide de importarla.
7.3.2 Práctica
Para practicar con este nuevo widget, va a realizar un programa que muestre una ventana donde se solicite un usuario y una contraseña. Además de los campos de entrada correspondientes, habrá dos botones: “ACEPTAR” y “CANCELAR”. El primero cerrará la ventana y el segundo borrará el contenido de ambos campos.
El código del programa es el siguiente:


Lo primero que se hace es importar las clases del paquete Tkinter que se van a utilizar; en concreto, las que permitirán crear la ventana principal (Tk), las etiquetas situadas a la izquierda de cada campo de entrada (Label), los propios campos de texto (Entry) y los botones (Button):

Saltando la declaración de las funciones a las que se llamará cuando se pulsen los botones, lo primero que se hace es crear la ventana principal (root), que no podrá redimensionarse. Además, la barra de título exhibirá el nombre “Login”:

Luego, se crea la etiqueta con la que se indica que, en el campo de entrada, hay que introducir el nombre del usuario y, a continuación, dicho campo de texto. Con la opción bd se establece que el borde tenga cinco píxeles de grosor. Las otras dos opciones determinan el color (highlightcolor) y el ancho del rectángulo (highlightthickness) que rodeará el campo cuando se pulse sobre él (tenga el foco):


Por defecto, el ancho del rectángulo que aparece cuando el campo tiene el foco es de 0 píxeles, por lo que, para que se vea, siempre tendrá que asignarle un valor mayor con la opción highlightthickness.
El código que crea la etiqueta y el campo de la contraseña es similar. La única diferencia remarcable es que en el campo de texto se usa la opción show, por lo que los caracteres mostrados en pantalla cuando se escriba una contraseña serán únicamente asteriscos:

Respecto a los botones, el de aceptar tiene asociada la función aceptar() y el otro tiene asociada la función cancelar():

Una vez creados los widgets, se colocan en la ventana principal con el gestor de geometría grid.
En primer lugar, se sitúa la etiqueta “USUARIO” en la celda correspondiente a la fila 0, columna 0. El texto que contiene se ubica a la izquierda asignando el valor "W" a la opción sticky. También se establece un margen de 10 píxeles a su alrededor con las opciones padx y pady:

El campo de entrada de texto se coloca a la derecha de la etiqueta, es decir, en la misma fila pero en la siguiente columna. Además, se lleva al extremo derecho de la celda, dando el valor "E" a la opción sticky. En este caso, solo se establece un margen horizontal de 10 píxeles:

El código que ubica la etiqueta y el campo de la contraseña es similar (lo único que cambia es el número de la fila, para situarlos debajo), por lo que no se van a dar explicaciones adicionales:

El botón “ACEPTAR” se ubica en la parte izquierda de la celda para alinearlo con las etiquetas. Por eso, se asigna el valor "W" a la opción sticky:

El botón “CANCELAR” se pone a la derecha de la celda, alindándolo con los campos que tiene encima (la opción sticky toma el valor "E"):

La última sentencia permite atender los eventos generados en la interfaz; en concreto, los provocados al pulsar cualquiera de los botones:

Solo queda describir las funciones a las que se llamará cuando se pulsen dichos botones. Si fuera el de “ACEPTAR”, se ejecutaría la función aceptar(), que únicamente cerraría la ventana con el método destroy:

Si se pulsara el botón “CANCELAR”, se ejecutaría la función cancelar(), que borraría el contenido de ambos campos con el método delete():

Ejecute el programa. En la siguiente imagen puede ver el aspecto que tendría el formulario una vez introducido un usuario y una contraseña (aparece con asteriscos). Como puede observar, el foco se encuentra en este último campo, que aparece rodeado por un rectángulo de color rojo:

Si pulsara el botón “CANCELAR”, su contenido desaparecería. Con el botón “ACEPTAR”, se cerraría la ventana.
7.4 SPINBOX
Este widget también se usa para la entrada de datos, pero, a diferencia del anterior, el usuario podrá seleccionar el valor de un rango (o lista de valores), en el que podrá desplazarse con los dos iconos con forma de punta de flecha hacia arriba y hacia abajo que tiene a su derecha:

7.4.1 Opciones y métodos
Este widget se representa con la clase Spinbox, cuyo constructor es:
Spinbox(widget contenedor, opción, …)
El primer argumento es el widget contenedor. Si no lo incluyera, se consideraría que se trata de la ventana principal. En el segundo argumento, además de las opciones comunes activebackground, bg, bd, cursor, disabledbackground, disabledforeground, fg, font, highlightbackground, highlightcolor, highlightthickness, relief, takefocus y width, también dispone de las siguientes:
•command. Nombre de la función a la que se llamará cuando el usuario haga clic en una de las puntas de flecha. Dicha función no se invocará cuando el usuario edite directamente el valor en el propio campo de texto.
•exportselection. Por defecto, cuando se selecciona texto dentro de un widget de entrada de datos, se copia automáticamente en el portapapeles. Para evitarlo, asigne el valor False a esta opción.
•format. Expresión que determina el formato de los valores numéricos de las opciones from_ y to (los verá a continuación); por ejemplo, el valor:
‘%4.2f’
mostraría los valores con cuatro dígitos, dos de los cuales serían decimales.
•from_. Valor inicial del rango. Será el que se muestre inicialmente en el campo de entrada.
•increment. Cantidad en la que aumentan o disminuyen los valores del rango establecido entre from_ y to, cuando se pulsan las puntas de flecha hacia arriba o hacia abajo.
•justify. Controla la posición del texto en el campo de entrada del widget. Por defecto, este se justifica a la izquierda (LEFT). Si deseara otro comportamiento, podría cambiar el valor de esta opción por CENTER (centrado) o RIGHT (a la derecha).
•readonlybackground. Color de fondo del botón cuando su estado es de solo lectura (‘readonly’).
•repeatdelay y repeatinterval. La primera opción (repeatdelay) especifica cuánto tiempo debe mantenerse pulsado el botón del ratón antes de que empiecen a cambiar los valores del campo de forma automática, mientras que la segunda (repeatinterval) determina la frecuencia con la que estos se incrementan/decrementan. Los tiempos predeterminados son 400 y 100 milisegundos, respectivamente.
•state. Puede tomar los valores NORMAL (por defecto) o DISABLED, en cuyo caso el campo no será editable. Si una vez introducida la información en el campo, quisiera evitar que pudiera modificarse, asigne el valor ‘readonly’ (la constante READONLY equivalente no existe). El valor de este atributo será ACTIVE cuando se sitúe el ratón sobre el campo (a no ser que esté desactivado).
•textvariable. Variable de control asociada al campo.
•to. Valor final del rango (el inicial se estableció con la opción from_).
•validate y validatecommand. El valor de la primera opción determina cuándo se debe llamar a la función indicada en la segunda. La forma de utilizarlas se explicará en el capítulo dedicado a la validación de entrada de datos.
•values. Hay dos formas posibles de especificar los valores de este widget: mediante un rango de valores (opciones from_ y to) o mediante una tupla de strings, que es la que se daría a esta opción. En ese caso, al pulsar las flechas superior e inferior, se irían mostrando los elementos de dicha tupla.
•wrap. Normalmente, si se pulsa la punta de flecha hacia arriba cuando el campo contiene el valor más alto del rango, no tendrá ningún efecto. Y, al revés, si tuviera el valor más bajo, sería la punta de flecha hacia abajo la que no funcionaría. Para cambiar este comportamiento, asigne el valor True a esta opción. De esa forma, al llegar al valor más alto, si siguiera pulsando la punta de la flecha hacia arriba, aparecería de nuevo el valor más bajo. Igualmente, al alcanzar el valor inferior del rango, si presionara la punta de la flecha hacia abajo, vería el valor más alto. Por lo tanto, esta opción permite seleccionar los valores de un rango de forma cíclica.
•xscrollcommand. Permite hacer scroll del texto introducido en el campo cuando este supera su tamaño. Su valor es un objeto del tipo Scrollbar (lo verá más adelante, junto con la manera de utilizar esta opción).
Además, relacionados con el cursor de inserción, esta clase dispone de los atributos insertbackground, insertborderwidth, insertofftime, insertontime, e insertwidth, cuyo significado es el mismo descrito en el campo de entrada de texto.
Con dicho widget también comparte las opciones selectbackground, selectborderwidth y selectforeground.
Otro grupo de atributos que podría resultarle interesante es aquel relativo a los botones con los que se puede mover hacia arriba/abajo entre los valores del rango establecido con las opciones from_ y to, o en la tupla de strings asignada al atributo values. Se trata de los siguientes:
•buttonbackground. Color de fondo de las puntas de flecha (gris, por defecto).
•buttoncursor. Imagen que muestra el cursor cuando el ratón está sobre las puntas de flecha. Su valor puede ser cualquiera de los utilizados para el atributo estándar cursor.
•buttondownrelief. Estilo del relieve asignado a la punta de flecha hacia abajo. Puede tomar cualquiera de los valores del atributo estándar relief (por defecto, RAISED).
•buttonuprelief. Similar al anterior, pero referente al botón con la punta de flecha hacia arriba (por defecto, RAISED).
Aunque este widget tiene sus propios métodos, los más habituales son los descritos anteriormente para los campos de texto (Entry), estudiados en el apartado anterior.
7.4.2 Práctica
En esta ocasión, va a crear un formulario para solicitar al usuario su nombre, dirección y edad. Este último campo será un spinbox en el que se pueda seleccionar un valor entre 18 y 65 años.
El formulario tendrá dos botones. El botón “CANCELAR” borrará la información introducida en los campos “nombre” y “dirección”, volviendo a reflejar la edad inicial (18). El botón “CONFIRMAR” mostrará, en la parte inferior de la ventana, una etiqueta con el valor introducido en cada uno de ellos.
Más abajo se muestra el aspecto que tendrá el formulario. Como puede ver, el tamaño del campo “edad” es inferior a los dos anteriores. Además, al modificar el ancho de la ventana, los campos “nombre” y “dirección” crecerán en tamaño —cosa que no sucederá con el campo “edad”, que mantendrá el que tiene—. No tendría sentido que cambiara su tamaño, porque el margen de edades posibles solo puede contener dos dígitos (a diferencia del nombre o la dirección, cuya longitud es arbitraria).

El código utilizado para desarrollar este formulario es el siguiente:


En primer lugar, se importan las clases de la librería Tkinter necesarias, que en este caso serán las correspondientes a la ventana principal (Tk), las etiquetas (Label), los campos de texto (Entry), el spinbox (Spinbox) y los botones (Button):

Las siguientes sentencias establecen el rango de edades que se podrán seleccionar en el spinbox:

Las funciones que se ejecutan al pulsar los botones se describirán más adelante. Por lo tanto, salte a las sentencias que crean la ventana principal, establecen el texto que aparecerá en su barra de título (“Formulario”), su tamaño mínimo (300 × 100) y la posibilidad de modificarlo únicamente en sentido horizontal:

Luego, se encuentran las sentencias encargadas de crear todos los widgets que conformarán el formulario. En primer lugar, la etiqueta y el campo en el que se introducirá el nombre y, a continuación, los correspondientes para la dirección. Las opciones utilizadas en los constructores de cada uno de ellos ya las conoce de la práctica anterior (la del usuario y contraseña), por lo que no se darán explicaciones adicionales:

Donde sí nos vamos a detener es en la sentencia con la que se crea el spinbox, después de la etiqueta que se situará a su izquierda. El constructor de dicho widget, además de las opciones ya conocidas, comunes a los campos de entrada anteriores, incluye las que fijan el valor mínimo y máximo del rango de edades (opciones from_ y to), así como la que establece su ancho en tres caracteres (width). Como verá más adelante, será el que tenga siempre, aunque modifique el tamaño de la ventana:

Por último, se crea la etiqueta donde se mostrará el nombre, la dirección y la edad introducidos en cada uno de estos campos cuando se pulse el botón “CONFIRMAR”. Inicialmente estará vacía (no se utiliza la opción text). Como el texto que muestre tendrá varias líneas, con la opción justify se indica que todas empiecen a la misma altura por la izquierda:

Una vez creados los widgets, se sitúan en la ventana principal utilizando un gestor de geometría grid. Las celdas en las que irán situados se establecen con las opciones row y column. Para mantener una distancia de separación entre ellos, se utilizan las opciones padx y pady. Por último, con sticky se indica la posición que tendrán dentro de cada celda. En este sentido, todas las etiquetas y el spinbox lo hacen en la parte izquierda (valor "w"), manteniendo sus dimensiones, independientemente de las que tenga la celda. Por el contrario, como se quiere que los campos de entrada del nombre y la dirección cambien de tamaño con el de la celda, estos deberán ocupar todo el ancho que tenga, por lo que el valor de la opción sticky deberá ser "ew":

Precisamente, la siguiente sentencia es la que permite que el ancho de la segunda columna —y, por lo tanto, el de todas sus celdas— siga el de la ventana cuando se modifique. El valor que ponga en la opción weight es indiferente, al tratarse de la única columna que puede cambiar de tamaño. Sin embargo, es imprescindible incluirlo en el método:

La sentencia que hay a continuación añade la etiqueta donde se mostrará el contenido de cada campo (inicialmente vacía). La única opción que puede llamar la atención es columnspan, que asigna dos columnas a este widget. Esto se hace así para que ocupe todo el ancho de la ventana:

Las dos sentencias que le siguen son las que añaden los botones “ACEPTAR” y “CANCELAR” a ambos lados de la parte inferior de la ventana. Su código es el mismo que el de la práctica anterior, por lo que no se ofrecerá ninguna aclaración adicional:

Como siempre, la última sentencia es necesaria para empezar a atender los eventos que se produzcan en la interfaz:

Ha llegado el momento de describir las funciones que se ejecutarán cuando se pulsen los botones, empezando por aceptar(), que es la vinculada al botón “ACEPTAR”. En ella, primero se inicializa la variable en la que se almacenará un mensaje con la información introducida en los campos del formulario:

La manera de obtener el contenido de dichos campos es mediante el método get():

Las siguientes sentencias componen la cadena que se mostrará en la etiqueta de resultados (selecciones) a partir de los valores anteriores, añadiendo un texto de inicio previo a cada uno de ellos (si se hubiera introducido alguno) y separándolos en líneas independientes con el carácter de escape "\n":

Por último, el texto que se acaba de componer se asigna a la opción text de la etiqueta (selecciones_label) utilizando el método genérico config(), con el fin de mostrarlo:


Otra forma de hacer esto mismo es mediante una variable de control.
Por último, la función cancelar() se ejecutará cuando se pulse el botón “CANCELAR”. En ella, se borra el contenido de todos los campos con el método delete():

Además, al spinbox se le asigna de nuevo el valor inicial que tenía al arrancar la aplicación con el método insert():

Por último, se borra el contenido de la etiqueta que mostraba la información introducida por el usuario al pulsar el botón “ACEPTAR”. Para ello, se emplea de nuevo el método config(), con el fin de asignar la cadena vacía a la opción text:

Ejecute el programa para verificar que su aspecto coincide con el esperado. Compruebe que el tamaño de la ventana solo se puede modificar en sentido horizontal y que los únicos campos que se ajustan a dicho tamaño son el del nombre y el de la dirección. Luego, rellene los campos con los datos que quiera y pulse el botón “ACEPTAR”. La información que acaba de introducir debe aparecer en la parte inferior.

Si pulsara el botón “CANCELAR”, los campos con el nombre y la dirección quedarían vacíos, mientras que el de la edad mostraría el valor inicial (18).
Pulsuz fraqment bitdi.