Cómo crear un menú superior con Tkinter: guía paso a paso

Cómo crear un menú superior con Tkinter: guía paso a paso

¿Te gustaría aprender a crear una interfaz gráfica de usuario (GUI) con Python? ¿Quieres darle un aspecto profesional y funcional a tus aplicaciones de escritorio? Entonces este tutorial es para ti. En este artículo te enseñaré cómo crear un menú superior con tkinter, una de las bibliotecas más populares y sencillas para crear GUIs con Python. Un menú superior es una barra que se ubica en la parte superior de la ventana y que contiene diferentes opciones y funciones que el usuario puede seleccionar. Con tkinter puedes crear menús superiores de forma fácil y rápida, siguiendo unos pocos pasos que te explicaré a continuación. Además, podrás personalizar el aspecto y el comportamiento de tus menús según tus preferencias y necesidades. ¿Estáis listos para empezar? ¡Pues vamos allá!


Importar Tkinter y crear el menú

Primero, debes importar Tkinter.

# Importación de Tkinter
import tkinter as tk

Después, hay que crear un objeto de tipo menú. Este lo instanciamos de la clase "Menu" de Tkinter.

# Se crea el menú de la ventana
menu = tk.Menu()

Crear opciones principales para el menú de Tkinter

Lo siguiente que haremos, será crear objetos para cada opción principal, las típicas de Archivo, Edición, etc.

Con el primero argumento, especificamos donde aparecen (menu) y con el segundo (tearoff con 0), hacemos que el menú sea fijo, comportamiento por defecto (luego explico la opción alternativa).

Con el nombre del objeto del menú, utilizando su método add_cascade(), vamos a añadir cada uno de los objetos al menú asociándolos a una etiqueta.

# Se crean las opciones principales
menu_archivo = tk.Menu(menu, tearoff=0)
menu_editar = tk.Menu(menu, tearoff=0)
menu_ayuda = tk.Menu(menu, tearoff=0)

# Agregar las opciones principales al menú
menu.add_cascade(label="Archivo", menu=menu_archivo)
menu.add_cascade(label="Editar", menu=menu_editar)
menu.add_cascade(label="Ayuda", menu=menu_ayuda)

Crear subopciones para el menú de Tkinter

Cuando ya tengas todas las opciones principales añadidas a tu menú, le puedes añadir subopciones para que se despliegue el menú con las que necesites.

Esto lo hacemos asociando a cada objeto de opción principal una nueva etiqueta con el método add_command:

# Se crean las subopciones para "Archivo"
menu_archivo.add_command(label="Abrir")
menu_archivo.add_command(label="Guardar")
menu_archivo.add_separator()
menu_archivo.add_command(label="Salir", command=root.quit)

# Se crean las subopciones para "Editar"
menu_editar.add_command(label="Cortar")
menu_editar.add_command(label="Copiar")
menu_editar.add_command(label="Pegar")

Si quieres que una opción principal no tenga subopciones, la dejas sin "add_command", como el caso de "menu_ayuda", que no le añado ninguna opción.

Para hacer que los botones realicen acciones, utiliza el sistema de siempre con los botones de Tkinter, el "command" con la llamada a una función o método.

En el ejemplo de "Salir", he llamado al método quit() de Tkinter para que finalice el programa si se pulsa, solo para que veas un ejemplo de uso.

Ya solo queda lo último, hemos empezado la casa por el tejado. Hay que colocar el menú en la ventana, en mi caso, se llama root:

# Se muestra la barra de menú en la ventana principal
root.config(menu=menu)

Que no te confunda "menu=menu", el primero, es el nombre del atributo de la ventana root destinado a colocar un menú. El segundo, es el nombre del objeto que hemos creado. Si crees que esto crea confusión, llámalo por ejemplo "menu_superior".

Esto quedaría así:

root.config(menu=menu_superior)

Vamos a probar esto:

tkinter menú superior

El resultado es tal y como esperamos en cualquier programa.

Añadir más opciones dentro de las subopciones

Lo siguiente que puede que necesites, es añadir más opciones anidadas dentro de las subopciones. Esto es tan fácil como utilizar de nuevo "add_command" con alguna de las subopciones. En este caso, el "add_cascade" tiene que ser aplicado con la opción principal (menu_archivo, por ejemplo).

# Se crean las subopciones para "Archivo > Abrir"
menu_preferencias = tk.Menu(menu_archivo, tearoff=0)
menu_preferencias.add_command(label="Opción 1")
menu_preferencias.add_command(label="Opción 2")
menu_preferencias.add_command(label="Opción 3")

# Se añaden las subopciones de "Abrir" al menú "Archivo"
menu_archivo.add_cascade(label="Abrir", menu=menu_preferencias)

Crear menús desacopables con Tkinter


Queda solo explicar el tema del atributo "tearoff".

El atributo "tearoff" con un valor de 0, crea un menú fijo como el que acabas de ver, sin embargo, puedes hacer que un menú se pueda desacoplar con el valor 1.

He puesto en 1 estos dos elementos, el resto a 0.

menu_editar = tk.Menu(menu, tearoff=1)
...
menu_preferencias = tk.Menu(menu_archivo, tearoff=1)

El resultado es que se pueden separar esas partes del menú en pequeñas ventanas adicionales.

Menús desplegables con tkinter

Te dejo el código completo aquí por si has tenido problemas para seguir el hilo en alguna parte:

# Importación de Tkinter
import tkinter as tk

# Se crea la ventana del programa
root = tk.Tk()
# Se crea el menú de la ventana
menu = tk.Menu()

# Se crean las opciones principales
menu_archivo = tk.Menu(menu, tearoff=0)
menu_editar = tk.Menu(menu, tearoff=0)
menu_ayuda = tk.Menu(menu, tearoff=0)

# Agregar las opciones principales al menú
menu.add_cascade(label="Archivo", menu=menu_archivo)
menu.add_cascade(label="Editar", menu=menu_editar)
menu.add_cascade(label="Ayuda", menu=menu_ayuda)

# Se crean las subopciones para "Archivo"
menu_archivo.add_command(label="Abrir")
menu_archivo.add_command(label="Guardar")
menu_archivo.add_separator()
menu_archivo.add_command(label="Salir", command=root.quit)

# Se crean las subopciones para "Editar"
menu_editar.add_command(label="Cortar")
menu_editar.add_command(label="Copiar")
menu_editar.add_command(label="Pegar")

# Se crean las subopciones para "Archivo > Preferencias"
menu_preferencias = tk.Menu(menu_archivo, tearoff=0)
menu_preferencias.add_command(label="Opción 1")
menu_preferencias.add_command(label="Opción 2")
menu_preferencias.add_command(label="Opción 3")

# Se crea la cascada de "Preferencias" al menú "Archivo"
menu_archivo.add_cascade(label="Preferencias", menu=menu_preferencias)

# Se muestra la barra de menú en la ventana principal
root.config(menu=menu)

# Bucle de ejecución del programa
root.mainloop()

Adaptar los menús de Tkinter a CustomTkinter

Para quienes utilizáis CustomTkinter, tengo una mala noticia, no tiene menús superiores, hay que utilizar los propios de Tkinter.

Pues bien, hago esta última sección para que veáis de qué forma podéis crear un menú superior de Tkinter en una ventana de tipo CTk e CustomTkinter.

La forma de hacer esto, es más engorrosa, ya que tenemos que crear los botones del menú por nuestra cuenta.

Primero, en la línea 10, creo un frame para el menú. Lo posiciono en la parte superior.

Después, en la línea 15 hago uso de una cosa nueva de Tkinter, los Menubutton, que son botones sueltos de menú.


Con estos Menubutton, podemos ir creando la barra de menú superior.

Lo bueno de usarlos, es que permiten personalizarse de manera relativamente fácil, cosa que no podemos hacer con el menú nativo de Windows (en donde estoy explicando) que utiliza Tkinter.

En la línea 21 y 22, creo menús desplegables para cada Menubutton.

Después, ya podemos ir añadiendo opciones con "add_command()" a los menús.

# Se importa Tkinter
import tkinter as tk

# Se crea la ventana con un tamaño y color de fondo
root = tk.Tk()
root.geometry("400x200")
root.config(background="gray17")

# Se crea un frame para contener el menu
menu_frame = tk.Frame(root, background='black')
# Posiciona el Frame en la parte superior de la ventana
menu_frame.pack(side='top', fill='x')

# Crea un Menubutton dentro del Frame
archivo = tk.Menubutton(menu_frame, 
                        text='Archivo', 
                        background='black', 
                        foreground='white', 
                        activeforeground='black', 
                        activebackground='gray52')

# Crea un Menubutton dentro del Frame
edicion = tk.Menubutton(menu_frame, text='Edición', 
                        background='black', 
                        foreground='white',
                        activeforeground='black', 
                        activebackground='gray52')

# Crea un menú desplegable para el Menubutton
menu_archivo = tk.Menu(archivo, tearoff=0)
menu_edicion = tk.Menu(edicion, tearoff=0)

# Agrega una opción al menú desplegable
menu_archivo.add_command(label='Imprimir', 
                         command=lambda: print('Hello PC Master!'), 
                         background='black', 
                         foreground='white', 
                         activeforeground='black', 
                         activebackground='gray52')

# Asigna el menú desplegable al Menubutton
archivo.config(menu=menu_archivo)
# Posiciona el Menubutton dentro del Frame
archivo.pack(side='left')

root.mainloop()

Para quienes seguís el proyecto que estoy realizando en este curso, os dejo una adaptación de un menú en la interfaz de este.

Si no estás siguiendo este curso, ignora este código.

El color es para el modo oscuro. Aquí ya hay que añadir trabajo extra al utilizar CustomTkinter, ya que deberíamos crear dos menús solo para los dos modos de color.

En el vídeo de arriba explico mucho más:

class VentanaOpciones:
    # Diccionario para los botones
    botones = {'Consulta SQL': objeto_funciones.ventana_consultas, 
               'Mostrar Bases de Datos': objeto_funciones.ventana_mostrar_bases_datos,
               'Eliminar Bases de Datos': objeto_funciones.ventana_eliminar_bases_datos,
               'Crear Bases de Datos': objeto_funciones.ventana_crear_bases_datos, 
               'Crear Respaldos': objeto_funciones.ventana_crear_respaldos,
               'Crear Tablas': objeto_funciones.ventana_crear_tablas,
               'Eliminar Tablas': objeto_funciones.ventana_eliminar_tablas,
               'Mostrar Tablas': objeto_funciones.ventana_mostrar_tablas,
               'Mostrar Columnas': objeto_funciones.ventana_mostrar_columnas,
               'Insertar Registros': objeto_funciones.ventana_insertar_registros,
               'Eliminar Registros': objeto_funciones.ventana_eliminar_registros,
               'Vaciar Tablas': objeto_funciones.ventana_vaciar_tablas,
               'Actualizar Registros': objeto_funciones.ventana_actualizar_tablas
               }
    def __init__(self):
        # Se crea la ventana de CustomTkinter
        self.root = ctk.CTk()
        # Se le da un título
        self.root.title("Opciones para trabajar con bases de datos.")
    
        # Marco para contener el menú superior
        menu_frame = ctk.CTkFrame(self.root)
        menu_frame.pack(side='top', fill='x')

        # Se crea el botón de Menú
        archivo = tk.Menubutton(menu_frame, 
                                text='Archivo', 
                                background='#2b2b2b', 
                                foreground='white', 
                                activeforeground='black', 
                                activebackground='gray52')
        
        # Se crea el botón de Menú
        edicion = tk.Menubutton(menu_frame, 
                                text='Edición', 
                                background='#2b2b2b', 
                                foreground='white', 
                                activeforeground='black', 
                                activebackground='gray52')
        
        # Se crea el menú
        menu_archivo = tk.Menu(archivo, tearoff=0)
        # Se crea el menú
        menu_edicion = tk.Menu(edicion, tearoff=0)

        # Añade una opción al menú desplegable
        menu_archivo.add_command(label='Imprimir Saludo', 
                                 command=lambda: print('Hello PC Master!'), 
                                 background='#2b2b2b', 
                                 foreground='white', 
                                 activeforeground='black', 
                                 activebackground='gray52')
        
        
        # Crea un nuevo menú para la cascada
        cascada = tk.Menubutton(menu_edicion, 
                                text='Cascada', 
                                background='black', 
                                foreground='white', 
                                activeforeground='black', 
                                activebackground='gray52')
        
        # Se crea el menú
        menu_cascada = tk.Menu(cascada, tearoff=0)
        cascada.config(menu=menu_cascada)
        
        # Se crea una cascada dentro del menu de edición
        menu_edicion.add_cascade(label="Opciones", menu=menu_cascada, 
                                 background='#2b2b2b', 
                                 foreground='white', 
                                 activeforeground='black', 
                                 activebackground='gray52')
    
        # Agrega opciones a la cascada
        menu_cascada.add_command(label="Opción 1", 
                                 command=lambda: print("Opción 1 seleccionada"), 
                                 background='#2b2b2b', 
                                 foreground='white', 
                                 activeforeground='black', 
                                 activebackground='gray52')
        
        menu_cascada.add_command(label="Opción 2", 
                                 command=lambda: print("Opción 2 seleccionada"), 
                                 background='#2b2b2b', 
                                 foreground='white', 
                                 activeforeground='black', 
                                 activebackground='gray52')
        
        menu_cascada.add_command(label="Opción 3", 
                                 command=lambda: print("Opción 3 seleccionada"), 
                                 background='#2b2b2b', 
                                 foreground='white', 
                                 activeforeground='black', 
                                 activebackground='gray52')
        
        # Asigna el menú desplegable al Menubutton
        archivo.config(menu=menu_archivo)
        # Posiciona el Menubutton dentro del Frame
        archivo.pack(side='left')
        
        # Asigna el menú desplegable al Menubutton
        edicion.config(menu=menu_edicion)
        # Posiciona el Menubutton dentro del Frame
        edicion.pack(side='left')
        
        # Asigna el menú desplegable al Menubutton
        cascada.config(menu=menu_cascada)
        
        # Crea un Frame para contener los botones de la ventana
        frame_botones = ctk.CTkFrame(self.root)
        # Posiciona el Frame debajo del menú
        frame_botones.pack(side='top', fill='x')

        # Contador para la posición de los botones
        contador = 0

        # Valor de elementos por fila
        elementos_fila = 3

        # Crea los botones y establece su texto
        for texto_boton in self.botones:
            boton = ctk.CTkButton(
                master=frame_botones, #Se le indica en que frame aparecer
                text=texto_boton,
                height=25,
                width=200,
                command=self.botones[texto_boton]
            )
            boton.grid(row=contador//elementos_fila, column=contador%elementos_fila, padx=5, pady=5)

            # Incrementa el contador
            contador += 1

        self.root.mainloop()

No te pierdas nada del curso Máster en Python.


5 comentarios en «0»

  1. Buenas, estoy atascado en la ventana dude eliminar bd, me da un error de «‘ser’ object has no atribuye ‘conexion_cerrada'». A continuación dejo el código de la ventana:

    def ventana_eliminar_bases_datos(self):
    ventana = ctk.CTkToplevel()
    ventana.title(«Ventana para eliminar bases de datos»)
    #tamaño
    ventana.geometry(«550×450″)
    #se evita redimensión
    ventana.resizable(0,0)

    ventana.grab_set()

    #creamos marco
    marco = ctk.CTkFrame(ventana)
    marco.pack(padx=10, pady=10)

    #agregar campo de entrada para la actualización
    self.busqueda_control = tk.StringVar()

    #se crea la entrada de texto de búsqeuda
    self.entrada = ctk.CTkEntry(marco,
    font=fuente_widgets,
    textvariable=self.busqueda_control,
    width=300)

    self.entrada.grid(row=0, column=0, columnspan=2)

    #creamos etiqueta informativa
    ctk.CTkLabel(marco, text=»Listado base de datos en el servidor», font=fuente_widgets).grid(row=1, column=1)

    #añadimos caja resultados
    self.texto = ctk.CTkTextbox(marco,
    font=fuente_widgets,
    width=300,
    height=300)
    self.texto.grid(row=2, column=0, columnspan=2)

    #se crea etiqueta resultados
    self.resultados_label = ctk.CTkLabel(marco,
    text=»»,
    font=fuente_widgets)
    self.resultados_label.grid(row=3, column=1)

    def eliminar():
    try:
    #borra el contenido de «texto»
    self.texto.delete(‘1.0’, ‘end’)
    # obtiene el contenido del entry
    base = self.entrada.get()
    # llama al método base_datos.consulta() con los datos como argumento
    numero_bases = base_datos.consulta(«SHOW DATABASES»)
    # Actualiza el contador de bases de datos
    numero_registros = len(numero_bases)
    self.resultados_label.configure(text=f»Hay {numero_registros} bases de datos»)
    sqlbd.BaseDatos.eliminar_bd(base)
    except Exception:
    self.resultados_label.configure(text=f»Hay un error en tu consulta SQL. Por favor, revísela.»)
    msg.Mensajes.mesaje_error(«¡Hay un error en tu consulta SQL! El nombre de la base de datos no ha sido encontrado. Por favor, revise su sintaxis») #(este mensaje de error es uno personalizado, ya que la biblioteca de CustomTkinterMessages me da problemas)
    actualizar()

    boton_eliminar = ctk.CTkButton(marco,
    text=»Eliminar base de datos»,
    command= lambda : eliminar())
    boton_eliminar.grid(row=0, column=2)

    Aquí dejo la parte correspondiente a base_datos.py:
    # Decorador para el cierre del cursor y la base de datos
    def conexion(funcion_parametro):
    def interno(self, *args, **kwargs):
    try:
    if self.conexion_cerrada:
    self.conector = mysql.connector.connect(
    host = self.host,
    user = self.usuario,
    password = self.contrasena
    )
    self.cursor = self.conector.cursor()
    self.conexion_cerrada = False
    print(«Se abrió la conexión con el servidor.»)
    # Se llama a la función externa
    funcion_parametro(self, *args, **kwargs)
    except Exception as e:
    # Se informa de un error en la llamada
    print(f»Ocurrió un error {e}»)
    #propaga la excepción
    raise e
    finally:
    if self.conexion_cerrada:
    pass
    else:
    # Cerramos el cursor y la conexión
    self.cursor.close()
    self.conector.close()
    print(«Se cerró la conexión con el servidor.»)
    self.conexion_cerrada = True
    return self.resultado
    return interno

    # Eliminar bases de datos
    @conexion
    @reporte_bd
    @comprueba_bd
    def eliminar_bd(self, nombre_bd):
    # Realiza la consulta para eliminar la base de datos
    self.cursor.execute(f»DROP DATABASE {nombre_bd}»)

    Agradecería mucho cualquier tipo de ayuda, gracias

      1. ya he podido solucionarlo, en la parte de sqlbd.BaseDatos.eliminar_bd(nombre_bd) me faltaba poner un argumento, quedaría así una vez corregido: sqlbd.BaseDatos.eliminar_bd(base_datos, nombre_bd)

  2. Hola Enrique, estoy desarrollando las demas ventanas graficas y todo va bien. Estoy detenido en mostrar tablas, porque la variable no me esta retornando los resultados correctos cuando retorna de base_datos.py, en vez de mostrarme las tablas me muestra las bases de datos. ya he investigado, hecho muchas pruebas diferentes, pero no llego a la solucion. Te agradeceria me heche una mano please.

    base_datos.py
    def mostrar_tablas(self, nombre_bd):
    resultado en consola muestra las tablas correctamente desde base_datos.py, Sin embargo no asi en interfaz_grafica.py
    -test1.
    -usuario2.
    -usuarios1.

    En interfaz_grafica.py app:
    def nombrebd():
    nombre_bd = self.nombrebd_control.get().lower()
    self.texto.delete(‘1.0’, ‘end’)
    reusultado = base_datos.mostrar_tablas(nombre_bd)
    print(reusultado)

    resultado en consola desde interfaz_grafica.py
    [(‘information_schema’,), (‘mysql’,), (‘performance_schema’,), (‘prueba1’,), (‘prueba4’,), (‘sakila’,), (‘sys’,), (‘world’,)]

  3. Buenas, estoy intentando terminar las ventanas del proyecto de MySQL y me está dando problemas, estoy atascado en la ventana de reporte de BD, ya que siempre me crea la copia de la BD, exista o no y en el caso de que no exista se me bloquea las otras ventanas, puesto que no me carga las BD del servidor.
    ¿Podrías pasar el código de las ventanas del proyecto?

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

curso sql Entrada anterior Ejercicios resueltos para practicar SQL
protocolo http Entrada siguiente Protocolo HTTP: ¿Cómo se comunican los clientes web y los servidores?