domingo, 27 de marzo de 2022

Tipos de Ventanas en Tkinter y Guizero.

 

ventana de un programa











Tipos de Ventanas en Tkinter y Guizero.

Ahora que ya hemos visto los  diferentes tipos de widgets y de gestores de geometría existentes vamos a centrarnos en las ventanas.

Existen diferentes ventanas. Por una parte tenemos las ventanas de aplicación y por otra las de diálogos.

Ventanas de aplicación.

Suelen ser las que inician y finalizan las aplicaciones gráficas.

Ventanas de diálogos.

Surgen de las anteriores.

El conjunto de ambas, junto con los widgets que contienen, configuran la interfaz del usuario.

En el siguiente ejemplo desarrollaremos una ventana de aplicación con un botón abrir, que cada vez que se presione abra una ventana hija de dialogo diferente y en una posición distinta de la pantalla. 

Estas ventanas que se van generando se sitúan en un plano superior con respecto a las generadas anteriormente. Además podemos interactuar indistintamente con cada una de las ventanas creadas. Podemos cambiar sus posiciones, cerrarlas etc.

Para el ejemplo, usaremos el gestor de geometría pack, aunque podríamos haber utilizado cualquiera de los anteriormente vistos. 

Tkinter.

ventana de aplicación y de dialogo


from tkinter import *
from tkinter import ttk

class Aplicacion():
    
    # Declara una variable de clase para contar ventanas
    
    ventana = 0
    
    # Declara una variable de clase para usar en el
    # calculo de la posición de la ventana
    
    posx_y = 0
    
    # Declara una ventana de aplicación
    
    def __init__(self):
        
        self.raiz=Tk()
        
        # Define la dimensión de la ventana 300x200
        # que se situara en la coordenada x=500, y=50
        
        self.raiz.geometry('300x200+500+50')
        
        self.raiz.resizable(0,0)
        self.raiz.title("Ventana de aplicación")
        
        # Define botón 'Abrir' que se utilizará para
        # abrir las ventanas de diálogo. El botón
        # está vinculado con el método 'self.abrir'
        
        boton = ttk.Button(self.raiz, text="Abrir", command=self.abrir)
        boton.pack(side=BOTTOM, padx=20, pady=20)
        self.raiz.mainloop()
        
    def abrir(self):
        ''' Construye una ventana de dialogo '''
        
        # Define una nueva ventana de diálogo '''
        
        # Las ventanas de dialogo hijas se definen con TopLevel()
        self.dialogo = Toplevel()
        
        # incrementa en 1 el contador de ventanas
        # en la variable de clase a la que se puede acceder
        # desde cualquier método de la clase con la notación
        # del punto.
        Aplicacion.ventana+=1
        
        # Recalcula la posición de la ventana


        Aplicacion.posx_y += 50
        # el \ al final de la línea era para hacer un salto y que siga leyendo el código
        tamypos ='200x100+'+str(Aplicacion.posx_y)+ \
                  '+'+ str(Aplicacion.posx_y)
        print(tamypos)
        self.dialogo.geometry(tamypos)
        self.dialogo.resizable(0,0)
        
        # obtiene el identificador de la nueva ventana
        
        ident = self.dialogo.winfo_id()
        
        # Construye el mensaje de la barra de titulo
        
        titulo = str(Aplicacion.ventana)+": "+str(ident)
        print(titulo)
        self.dialogo.title(titulo)
        
        # Define el botón 'Cerrar' que cuando sea presionado
        # cerrara (destruirá) la ventana 'self.dialogo' llamando
        # al método 'self.dialogo.destroy'
        
        boton = ttk.Button(self.dialogo, text='Cerrar', command=self.dialogo.destroy)
        boton.pack(side=BOTTOM, padx=20,pady=20)
        
        # Cuando la ejecución del programa llega a este
        # punto se utiliza el método wait.window() para
        # esperar que la ventana 'self.dialogo' sea
        # destruida.
        
        # Mientras tanto se atiende a los eventos locales
        # que se produzcan, por lo que otras partes de la
        # aplicación seguirán funcionando con normalidad.
        # Si hay código después de esta línea se ejecutará
        # cuando la ventana 'self.dialogo' sea cerrada.
        
        self.raiz.wait_window(self.dialogo)
        
def main():
    mi_app = Aplicacion()
    return 0

if __name__=='__main__':
    main()   

Puedes obtener el código de la aplicación en el siguiente enlace.


Guizero

ventana principal y de dialogo en Guizero

'''ejemplo de ventanas no modales porque mientras existen es posible interactuar libremente con ellas,
con ellas, sin ningún límite, excepto que si cerramos la ventana principal se cerrarán todas
las ventanas hijas abiertas.'''

from guizero import *

def dialogo():
    ''' Crea una nueva ventana de dialogo. Cada vez que se ejecuta se dibuja una
nueva ventana, con una nueva posición y un nuevo titulo de ventana'''
    global ventana, posx_y
    # Incrementa en 1 el numero de ventanas abiertas
    ventana +=1
    # Incrementa en 50 los pixeles de la variable q controla la posición
    posx_y +=50
    # variable de texto que contendrá el tamaño y posición de la ventana en geometry
    #tama_y_posc = "200x100"+"+"+str(posx_y)+"+"+str(posx_y)
    tama_y_posc = f"200x100+{posx_y}+{posx_y}"
    print(tama_y_posc)
        
    # En guizero se usa Window() para definir una ventana de dialogo.
    window = Window(raiz)
    # obtiene el identificador de la nueva ventana
    ident = window.tk.winfo_id()
    titulo = f"{ventana}:{ident}"
    print(ident)
    window.title=titulo
    window.hide()
    window.tk.geometry(tama_y_posc)
    window.tk.resizable(0,0)
    PushButton(window, text="Cancelar", align="bottom", command=window.destroy)
    window.show()

# Variable genérica para contar las ventanas.
ventana = 0
# Variable genérica para el calculo de la posición de la ventana
posx_y = 0

#---------------------------- Ventana Principal------------------------------------    
raiz = App(title="Ventana de Aplicación")
# La geometría del objeto raiz.tk es "(tamaño_x)x(tamaño_y)+posc_x+posc_y"
raiz.tk.geometry("300x200+500+50")
raiz.tk.resizable(0,0)

# En guizero el parámetro padding afecta a la distancia del texto del botón
# respecto al borde del mismo, hace mas grande o mas pequeño al boton
boton = PushButton(raiz, text="Abrir", align="bottom", padx=20, pady=5, command=dialogo)

# Sin embargo si lo pones en el método tk afecta a la distancia al siguiente widget.
# En este caso a la distancia a la parte inferior de la ventana
boton.tk.pack(padx=20, pady=20)
#-----------------------------------------------------------------------------------


raiz.display()

Puedes encontrar el código del ejemplo en el siguiente enlace.


Ventanas Modales y No modales.

A parte de que las ventanas pueden ser principales o de dialogo, también como ya hemos visto en el ejemplo anterior aunque no lo hayamos dicho, pueden ser modales y no modales. 

Las ventanas hijas creadas en los ejemplos anteriores son "no modales" porque mientras que existan es posible interactuar independientemente con cada una de ellas, sin limite alguno, excepto que si cerramos la ventana principal se cerrarán todas ellas.

El ejemplo típico es el de las ventanas de cualquier aplicación ofimática que te permiten trabajar con varios documentos mientras están abiertas.

En las ventanas con modales esto no pasa, ya que la ventana activa, bloquea a las demás hasta que se cierre el diálogo.

El ejemplo típico es el de algunas ventanas de dialogo en la que se establecen preferencias de las aplicaciones que tienen que ser cerradas antes de poder acceder a otras.


Tkinter. Ventana Modal.

ventana modal en tkinter


Aquí la peculiaridad es que se emplea el método grab_set() para crear la ventana modal y transiet() para convertir la ventana de dialogo en una ventana transitoria, haciendo que esta se oculte cuando la ventana de la aplicación se minimice.

'''Ejemplo de ventana modal. El siguiente ejemplo sólo es posible
mantener abierta sólo una ventana hija, aunque si la cerramos podremos abrir otra.

El método grab_set() se utiliza para crear la ventana modal y el método transiet()
se emplea para convertir la ventana de diálogo en ventana transitoria,
haciendo que se oculte cuando la ventana de aplicación sea minimizada.'''

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from tkinter import *
from tkinter import ttk

class Aplicacion():
    
    #variables de la clase
    ventana = 0
    posx_y = 0
    
    def __init__(self):
        self.raiz=Tk()
        self.raiz.geometry('300x200+500+50')
        self.raiz.resizable(0,0)
        self.raiz.title("Ventana de aplicación")
        boton = ttk.Button(self.raiz, text="Abrir", command=self.abrir)
        boton.pack(side=BOTTOM, padx=20, pady=20)
        self.raiz.mainloop()
        
    def abrir(self):
        '''Construye una ventana de dialogo'''
        
        self.dialogo = Toplevel() #equivale a Window en guizero
        Aplicacion.ventana+=1
        Aplicacion.posx_y+=50
        tamypos = f'200x100+{Aplicacion.posx_y}+{Aplicacion.posx_y}'
        self.dialogo.geometry(tamypos)
        self.dialogo.resizable(0,0)
        ident = self.dialogo.winfo_id()
        titulo = str(Aplicacion.ventana)+": "+str(ident)
        self.dialogo.title(titulo)
        boton=ttk.Button(self.dialogo,text='Cerrar', command=self.dialogo.destroy)
        boton.pack(side=BOTTOM, padx=20, pady=20)
        
        # Convierte la ventana ''self.dialogo' en
        # transitoria con respecto a su ventana maestra
        # 'self.raiz'.
        # Una ventana transitoria siempre se dibuja sobre
        # su maestra y se ocultará cuando la maestra sea
        # minimizada. Si el argumento 'master' es
        # omitido el valor, por defecto, será la ventana
        # madre
        
        self.dialogo.transient(master=self.raiz)
        
        # El método grab_set() asegura que no haya eventos
        # de ratón o teclado que se envien a otra ventana
        # diferente a 'self.dialogo'. Se utilizará para
        # crear una ventana de tipo modal que será
        # necesario cerrar para poder trabajar con otra
        # diferente. Con ello, también se impide que la
        # misma ventana se abra varias veces.
        
        self.dialogo.grab_set()

        # Para esperar a que la ventana self.dialogo sea cerrada.
        self.raiz.wait_window(self.dialogo)
        
def main():
    mi_app = Aplicacion()
    return 0

if __name__=='__main__':
    main()        

El código de este ejemplo se encuentra en el siguiente enlace.

Guizero. Ventana Modal.

ventana modal en guizero



Por defecto en Guizero las ventanas añadidas de dialogo que se creen son no modales, funcionan de forma independiente, así que para transformarlas en modales hay que usar componentes de Tkinter.

from guizero import *
# Las listas de un solo elemento tienen que tener una coma ,
# después del elemento.
contador=[0,]
posx_y=[0,]

def dialogo():
    # En vez de variables globales (enteros, tuplas) vamos a utilizar listas que se modifican
    # por si solas a nivel global de la aplicación.
    contador[0]=contador[0]+1
    posx_y[0]=posx_y[0]+50
    ventana=Window(raiz)
    ident = ventana.tk.winfo_id()
    ventana.title=f"{contador[0]} :{ident}"
    ventana.hide()
    ventana.tk.resizable(0,0)
    dimension = f"200x100+{posx_y[0]}+{posx_y[0]}"
    ventana.tk.geometry(dimension)
    #-----------------------------------------------------------------------
    PushButton(ventana, text="Cancelar", align="bottom", command=ventana.destroy)
        # Convierte la ventana ''self.dialogo' en
        # transitoria con respecto a su ventana maestra
        # 'self.raiz'.
        # Una ventana transitoria siempre se dibuja sobre
        # su maestra y se ocultará cuando la maestra sea
        # minimizada. Si el argumento 'master' es
        # omitido el valor, por defecto, será la ventana
        # madre
    ventana.tk.transient(master=raiz.tk)
        # El método grab_set() asegura que no haya eventos
        # de ratón o teclado que se envíen a otra ventana
        # diferente a 'self.dialogo'. Se utilizará para
        # crear una ventana de tipo modal que será
        # necesario cerrar para poder trabajar con otra
        # diferente. Con ello, también se impide que la
        # misma ventana se abra varias veces.
    ventana.tk.grab_set()
    ventana.show()

raiz=App(title="Ventana de Aplicación")
raiz.tk.geometry("300x200+500+50")
raiz.tk.resizable(0,0)

boton=PushButton(raiz, text="Abrir", padx=20, pady=5, align="bottom", command=dialogo)
boton.tk.pack(pady=20)

raiz.display()

Puedes encontrar en este enlace el código del ejemplo anterior.


No hay comentarios:

Publicar un comentario