Desarrollo de interfaces gráficas en Python 3 con Tkinter

Text
0
Kritiken
Leseprobe
Als gelesen kennzeichnen
Wie Sie das Buch nach dem Kauf lesen
Schriftart:Kleiner AaGrößer Aa

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.