¿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:

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.

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.
Hola buenas tardes.
Tienes la bondad de decirme como hago para que teniendo un widget cualquiera y al cambiar de modo ya sea oscuro a claro o viceversa este cambie de color, porque no cambia automáticamente si no tengo que cerrar la ventana y volverla abrir mi código es este
import customtkinter as ctk
root = ctk.CTk()
ctk.set_appearance_mode(«System»)
modo_color = ctk.get_appearance_mode()
print(modo_color)
ctk.set_default_color_theme(«blue»)
#root = ctk.CTk()
root.geometry(«300×300+800+150»)
if modo_color == «Light»:
ctk.CTkLabel(root,text=»Esto es una prueba»,bg_color=»yellow»).pack()
else:
ctk.CTkLabel(root, text=»Esto es una prueba», bg_color=»green»).pack()
root.mainloop()
P.D.: Es algo escueto pero estoy aprendiendo, gracias
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
las sangrías se me han quitado, pero las tengo bien estructuradas en mi archivo
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)
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’,)]
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?