Desarrollo de interfaces gráficas en Python 3 con Tkinter

Tekst
Loe katkendit
Märgi loetuks
Kuidas lugeda raamatut pärast ostmist
Šrift:Väiksem АаSuurem Aa

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:



ancho

x

alto

±

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 l