Implementación de Diálogos en TinUI

Introducción

Aunque técnicamente los diálogos son combinaciones de controles y no un control individual, representan una interfaz de interacción funcional común. Por esta razón, TinUI ha incorporado una implementación nativa de diálogos.

Los diálogos de TinUI se basan en la clase TinUIDialog, diseñada de manera similar a la clase Dialog incorporada de tkinter. No está destinada a ser llamada directamente desde fuera, sino que está encapsulada a través de funciones integradas como showinfo, permitiendo a los desarrolladores utilizar estas funciones directamente. Los diálogos de TinUI se dividen en cuadros de mensaje informativos y cuadros de entrada, ambos implementados a través de la clase TinUIDialog.

Diseño de Layout

Ventana Modal

La ventana emergente es simplemente un Toplevel estándar de tkinter, pero vinculado a la ventana principal o la ventana que genera dicho diálogo.

class VentanaDialogo(Toplevel):
    """
    Clase base para diálogos de TinUI
    """

    def __init__(self,maestro,tipo_dialogo='normal',**opciones):
        super().__init__(maestro,**opciones)
        self.withdraw()

        self.tinui=BasicTinUI(self)
        self.tinui.pack(fill='both',expand=True)

        self.tipo=tipo_dialogo # Tipo de diálogo

        self.resizable(False,False)
        self.tk.call('wm', 'iconbitmap', self._w, '-default', '')

La configuración de wm_iconbitmap mediante el comando de tk se realiza para eliminar el ícono de la ventana.

A continuación, utilizaremos el cuadro de mensaje como ejemplo para ilustrar brevemente la implementación de TinUIDialog.

Inicialización

Las funciones encapsuladas de TinUIdialog invocarán a la clase base del diálogo, inicializando la ventana con los parámetros proporcionados.

    def inicializar_mensaje(self,titulo,contenido,texto_si='OK',texto_no='Cancelar'):
        """
        Inicialización de diálogo - tipo mensaje
        """
        SI=texto_si
        NO=texto_no

        self.title(titulo)
        self.protocol('WM_DELETE_WINDOW',lambda:self.retornar_mensaje(None))

El nombre inicializar_mensaje se utiliza porque corresponde a diálogos de información, mientras que el método de inicialziación para diálogos de entrada se denomina inicializar_entrada.

Tipos de Diálogo

Aquí, el tipo se refiere a la naturaleza del contenido del diálogo, como información normal, información importante, advertencia, error, consulta, etc.

Los cuadros de entrada y preguntas incluyen entradas de texto, enteros, números de punto flotante, etc.

Durante la inicialización de la ventana, se realiza una configuración básica según el tipo de diálogo.

        if self.tipo=='normal':
            pass
        elif self.tipo=='info':
            self.tinui['bg']='#ffffff'
            icon_uid=self.tinui.add_paragraph((5,5),text='\uE946',fg='#5969e0',font='{Segoe Fluent Icons} 14',anchor='w')
            self.tinui.addtag_withtag('contenido',icon_uid)
        elif self.tipo=='exito':
            self.tinui['bg']='#dff6dd'
            icon_uid=self.tinui.add_paragraph((5,5),text='\uE73E',fg='#0f7b0f',font='{Segoe Fluent Icons} 14',anchor='w')
            self.tinui.addtag_withtag('contenido',icon_uid)
        elif self.tipo=='advertencia':
            self.tinui['bg']='#fff4ce'
            icon_uid=self.tinui.add_paragraph((5,5),text='\uE7BA',fg='#9d5d00',font='{Segoe Fluent Icons} 14',anchor='w')
            self.tinui.addtag_withtag('contenido',icon_uid)
        elif self.tipo=='error':
            self.tinui['bg']='#fde7e9'
            icon_uid=self.tinui.add_paragraph((5,5),text='\uEA39',fg='#c42b1c',font='{Segoe Fluent Icons} 14',anchor='w')
            self.tinui.addtag_withtag('contenido',icon_uid)
        elif self.tipo=='pregunta':
            self.tinui['bg']='#ffffff'
            icon_uid1=self.tinui.add_paragraph((5,5),text='\uEA3A',fg='#5969e0',font='{Segoe Fluent Icons} 14',anchor='w')
            icon_uid2=self.tinui.add_paragraph((5,5),text='\uF142',fg='#5969e0',font='{Segoe Fluent Icons} 14',anchor='w')
            self.tinui.addtag_withtag('contenido',icon_uid1)
            self.tinui.addtag_withtag('contenido',icon_uid2)

Estos son los tipos de estilo de diálogo actualmente soportados por TinUIDialog.

A continuación se inicializa la información de texto y el grupo de botones. Esta parte es mucho más simple que dibujar un control individual en BasicTinUI.

        contenido_uid=self.tinui.add_paragraph((35,5),text=contenido,anchor='w')
        self.tinui.addtag_withtag('contenido',contenido_uid)
        contenido_bbox=self.tinui.bbox('contenido')
        ancho_boton=(contenido_bbox[2]-contenido_bbox[0])/2
        ancho_boton_final=ancho_boton-10 if ancho_boton>110 else 100
        final_y=self._final_y()+15
        boton_si_uid=self.tinui.add_button2(((contenido_bbox[0]+contenido_bbox[2])/2-5,final_y),text=SI,minwidth=ancho_boton_final,command=lambda e:self.retornar_mensaje(True),anchor='ne')[-1]
        boton_no_uid=self.tinui.add_button2(((contenido_bbox[0]+contenido_bbox[2])/2+5,final_y),text=NO,minwidth=ancho_boton_final,command=lambda e:self.retornar_mensaje(False),anchor='nw')[-1]
        self.tinui.add_back((),(boton_si_uid,boton_no_uid),bg='#f3f3f3',fg='#f3f3f3',linew=9)

Es importante destacar que deseamos que el grupo de botones ocupe todo el fondo, por lo que se realizan los cálculos de longitud y posición en las líneas 2-6.

Mostrado de Ventana Modal y Congelación

Dado que es una ventana emergente, debe tener el aspecto de una. Esta funcionalidad es implementada por el método TinUIDialog.cargar_ventana:

Centrado de la ventana:

    def cargar_ventana(self):
        # Obtener bbox de todos los controles de la ventana, centrar el layout
        bboxall=self.tinui.bbox('all')
        w,h=bboxall[2]-bboxall[0],bboxall[3]-bboxall[1]+1
        ancho_pantalla=self.winfo_screenwidth()
        alto_pantalla=self.winfo_screenheight()
        x,y=(ancho_pantalla-w)/2,(alto_pantalla-h)/2
        self.geometry(f'{w}x{h}+{int(x)}+{int(y)-10}')
        self.deiconify()

        self.tinui.config(scrollregion=bboxall)

Bloquear la ventana principal, esperar la respuesta del diálogo, obtener el foco y respuesta a nivel de ventana completa (tcl/tk), y esperar a que el diálogo desaparezca. Esto constituye la congelación de la ventana. Una vez que el contenido interactivo es confirmado, se devuelve el contenido, el diálogo se destruye y se libera automáticamente el foco de la ventana completa (a nivel tcl/tk) para la respuesta centralizada.

        self.transient(self.maestro)
        self.focus_set()
        self.wait_visibility()
        self.grab_set()
        self.wait_window(self)

        return self.resultado

La determinación de self.resultado es la siguiente:

    def retornar_mensaje(self,val):
        # Devolver mensaje
        self.resultado=val
        self.destroy()
        self.maestro.focus_set()

Retorno de Contenido

Por limitaciones de espacio, no se proporcionará código para esta sección.

Mensaje Informativo

Al hacer clic en "Confirmar", devuelve True.

Al hacer clic en "Cancelar", devuelve False.

Al cerrar la ventana, devuelve None.

Diálogo de Entrada

Al hacer clic en "Confirmar", si la entrada cumple las reglas, devuelve el contenido de entrada, de lo contrario continúa la entrada.

Al hacer clic en "Cancelar" o cerrar la ventana, devuelve None.

Encapsulación de Funciones

Tomando mostrar_informacion como ejemplo:

def mostrar_informacion(maestro,titulo,contenido):
    """
    Mostrar cuadro de diálogo de información
    """
    dialogo=VentanaDialogo(maestro,'info')
    return dialogo.inicializar_mensaje(titulo,contenido)

Efectos

Código de Prueba

if __name__=='__main__':
    raiz=Tk()
    raiz.iconbitmap('LOGO.ico')
    a=mostrar_mensaje(raiz,'prueba','¡hola mundo!')
    # print(a)
    mostrar_informacion(raiz,'prueba','mostrar información\n¡hola mundo!')
    mostrar_exito(raiz,'prueba','¡Éxito!\n¡hola mundo! ¡hola mundo! ¡hola mundo! ¡hola mundo!')
    mostrar_advertencia(raiz,'prueba','esto es una advertencia\n¡hola mundo!')
    mostrar_error(raiz,'prueba','algo está mal\n¡hola mundo! ¡hola mundo! ¡hola mundo! ¡hola mundo!')
    mostrar_pregunta(raiz,'prueba','¿Desea continuar?')
    b=solicitar_cadena(raiz,'prueba','ingrese algo ingrese algo ingrese algo ingrese algo')
    solicitar_entero(raiz,'prueba','ingrese entero')
    solicitar_flotante(raiz,'prueba','ingrese flotante')
    # print(b)
    raiz.mainloop()

Efecto Final

La información normal, la información importante, el recordatorio de éxito, la advertencia, el recordatorio de error y el recordatorio de pregunta muestran todos el mismo diseño básico.

Los diálogos de entrada estándar, entrada de enteros y entrada de flotantes utilizan la misma plantilla, pero el método de procesamiento del contenido después de hacer clic en "Confirmar" es ligeramente diferente.

Diálogo de Selección

Nuevo Parámetro 2025-3-4

Se añadió el parámetro de estilo tema, con un valor predeterminado de 'claro' que produce el efecto mostrado anteriormente. También se puede seleccionar 'oscuro' con el siguiente efecto:

Nueva Funcionalidad 2025-7-7

Los botones inferiores pueden obtener el foco (simulado) y activarse mediante Entrar o Espacio.

Los diálogos de entrada solo pueden activar el botón "OK" presionando Entrar.

Diálogos Personalizados basados en TinUIXml

Ver introducción.

Proyecto GitHub

La dirección del proyecto GitHub de TinUI

Descarga mediante pip

pip install tinui

Conclusión

Al importar las funciones del módulo TinUIDialog, se pueden utilizar directamente los diálogos.

from tinui import mostrar_informacion,mostrar_error,...

En TinUI-5.5, se pueden importar clases y funciones de TinUI y TinUIDialog mediante from tinui import ....

Innovación en tkinter🔆

Etiquetas: Python tkinter diálogos interfaz gui

Publicado el 7-22 15:05