70 - tkinter : ventanas de diálogos

Vimos en el concepto anterior que tkinter nos provee una serie de diálogos para mostrar mensajes de información y error. En muchas situaciones podemos necesitar crear otras ventanas con más funcionalidades que los simples ventanas de mensajes.

Para crear diálogos en tkinter debemos crear un objeto de la clase TopLevel y pasar como parámetro la referencia de la ventana principal.

Un diálogo se asemeja mucho a lo que es la ventana principal, podemos disponer dentro de la misma objetos de la clase Label, Button, Entry etc.

Lo más común es que un diálogo tenga por objetivo la entrada de datos para luego ser utilizados en la ventana principal.

Problema:

Confeccionar una aplicación que muestre un diálogo cuando se seleccione una opción de un menú.
El dialogo debe solicitar el ingreso de dos enteros que se utilizarán en la ventana principal para redimensionarla.

La representación visual de la ventana principal es muy sencilla:

ventana principal

Cuando se abre el diálogo tenemos la siguiente imagen:

dialogo

Programa: ejercicio251.py

Ver video

import tkinter as tk
from tkinter import ttk

class Aplicacion:

    def __init__(self):
        self.ventana1=tk.Tk()
        self.agregar_menu()
        self.ventana1.mainloop()

    def agregar_menu(self):
        self.menubar1 = tk.Menu(self.ventana1)
        self.ventana1.config(menu=self.menubar1)
        self.opciones1 = tk.Menu(self.menubar1, tearoff=0)
        self.opciones1.add_command(label="Configurar ventana", command=self.configurar)
        self.menubar1.add_cascade(label="Opciones", menu=self.opciones1)    

    def configurar(self):
        dialogo1 = DialogoTamano(self.ventana1)
        s=dialogo1.mostrar()
        self.ventana1.geometry(s[0]+"x"+s[1])
        

class DialogoTamano:

    def __init__(self, ventanaprincipal):
        self.dialogo=tk.Toplevel(ventanaprincipal)
        self.label1=ttk.Label(self.dialogo, text="Ingrese ancho:")
        self.label1.grid(column=0, row=0, padx=5, pady=5)
        self.dato1=tk.StringVar()
        self.entry1=ttk.Entry(self.dialogo, textvariable=self.dato1)
        self.entry1.grid(column=1, row=0, padx=5, pady=5)
        self.entry1.focus()
        self.label2=ttk.Label(self.dialogo, text="Ingrese alto:")
        self.label2.grid(column=0, row=1, padx=5, pady=5)
        self.dato2=tk.StringVar()
        self.entry2=ttk.Entry(self.dialogo, textvariable=self.dato2)
        self.entry2.grid(column=1, row=1, padx=5, pady=5)
        self.boton1=ttk.Button(self.dialogo, text="Confirmar", command=self.confirmar)
        self.boton1.grid(column=1, row=2, padx=5, pady=5)
        self.dialogo.protocol("WM_DELETE_WINDOW", self.confirmar)
        self.dialogo.resizable(0,0)
        self.dialogo.grab_set()

    def mostrar(self):
        self.dialogo.wait_window()
        return (self.dato1.get(), self.dato2.get())

    def confirmar(self):
        self.dialogo.destroy()


aplicacion1=Aplicacion() 

Para resolver este problema hemos declarado dos clases, la primer que muestra la ventana principal:

import tkinter as tk
from tkinter import ttk

class Aplicacion:
    def __init__(self):
        self.ventana1=tk.Tk()
        self.agregar_menu()
        self.ventana1.mainloop()

Separamos la creación del menú en otro método:

    def agregar_menu(self):
        self.menubar1 = tk.Menu(self.ventana1)
        self.ventana1.config(menu=self.menubar1)
        self.opciones1 = tk.Menu(self.menubar1, tearoff=0)
        self.opciones1.add_command(label="Configurar ventana", command=self.configurar)
        self.menubar1.add_cascade(label="Opciones", menu=self.opciones1)    

Cuando se selecciona la opción del menú "Configurar ventana" se dispara el método "configurar".
Creamos un objeto de la clase DialogoTamano y le pasamos como parámetro la referencia de la ventana principal:

    def configurar(self):
        dialogo1 = DialogoTamano(self.ventana1)

Inmediatamente luego de crear el diálogo llamamos al método 'mostrar' donde se quedará ejecutando la aplicación hasta que el operador cierre el diálogo:

        s=dialogo1.mostrar()

Una vez que el operador cierra el diálogo el método 'mostrar' retorna una tupla con los dos valores ingresados por teclado, estos los usamos para dimensionar la ventana principal:

        self.ventana1.geometry(s[0]+"x"+s[1])

Ahora analicemos la clase DialogoTamano, en el método __init__ recibimos como parámetro la referencia de la ventana principal:

class DialogoTamano:

    def __init__(self, ventanaprincipal):

Creamos un diálogo mediante la clase TopLevel que requiera la referencia de la ventana principal:

        self.dialogo=tk.Toplevel(ventanaprincipal)

Creamos seguidamente todos los controles visuales que tendrá el diálogo de la misma manera que hacemos con la ventana principal:

        self.label1=ttk.Label(self.dialogo, text="Ingrese ancho:")
        self.label1.grid(column=0, row=0, padx=5, pady=5)
        self.dato1=tk.StringVar()
        self.entry1=ttk.Entry(self.dialogo, textvariable=self.dato1)
        self.entry1.grid(column=1, row=0, padx=5, pady=5)
        self.entry1.focus()
        self.label2=ttk.Label(self.dialogo, text="Ingrese alto:")
        self.label2.grid(column=0, row=1, padx=5, pady=5)
        self.dato2=tk.StringVar()
        self.entry2=ttk.Entry(self.dialogo, textvariable=self.dato2)
        self.entry2.grid(column=1, row=1, padx=5, pady=5)
        self.boton1=ttk.Button(self.dialogo, text="Confirmar", command=self.confirmar)
        self.boton1.grid(column=1, row=2, padx=5, pady=5)

Con la siguiente línea indicamos que método debe ejecutarse si el operador presiona la 'x' de cerrado del diálogo:

        self.dialogo.protocol("WM_DELETE_WINDOW", self.confirmar)

Es común no permitir el cambio de tamaño del diálogo con las flechas del mouse:

        self.dialogo.resizable(0,0)

Mediante la llamada al método 'grab_set' desactivamos los eventos en la ventana principal, es decir que hasta que el operador no cierre el diálogo la ventana principal aparecerá inactiva:

        self.dialogo.grab_set()

El método 'mostrar' hace visible el diálogo y cuando se cierra retorna la tupla:

    def mostrar(self):
        self.dialogo.wait_window()
        return (self.dato1.get(), self.dato2.get())

Finalmente el método 'confirmar' cierra el diálogo y permite que finalice el método 'mostrar' retornando los dos enteros:

    def confirmar(self):
        self.dialogo.destroy()