El control tk.Menu
permite añadir una barra de menús a la ventana principal (tk.Tk
) o a una ventana secundaria (tk.Toplevel
) en una aplicación de escritorio de Tk. Los menús de una ventana contienen un texto y/o una imagen y pueden ser asociados a funciones para responder ante la presión del usuario.
En la imagen vemos una barra de menús en la ventana principal, un menú con el título «Archivo» y un botón dentro de este menú con el texto «Nuevo», el atajo «Ctrl+N» y una imagen a modo de ícono. Para implementar una funcionalidad como esta, lo primordial es crear una barra de menús vía la clase tk.Menu
y configurarla en la ventana principal.
import tkinter as tk ventana = tk.Tk() ventana.title("Barra de menús en Tk") ventana.config(width=400, height=300) # Crear una barra de menús. barra_menus = tk.Menu() # Insertarla en la ventana principal. ventana.config(menu=barra_menus) ventana.mainloop()
Una barra de menús vacía no es de mucha utilidad: ni siquiera se ve en la ventana. Agregúemosle el primer menú de este artículo, con el título «Archivo», como suele aparecer en muchas aplicaciones de escritorio.
import tkinter as tk ventana = tk.Tk() ventana.title("Barra de menús en Tk") ventana.config(width=400, height=300) barra_menus = tk.Menu() # Crear el primer menú. menu_archivo = tk.Menu(barra_menus, tearoff=False) # Agregarlo a la barra. barra_menus.add_cascade(menu=menu_archivo, label="Archivo") ventana.config(menu=barra_menus) ventana.mainloop()
Cada menú que queramos ubicar en la barra de menús también se crea usando la clase tk.Menu
. De modo que ahora tenemos dos instancias: barra_menus
, el contenedor de todos los menús de la ventana, y menu_archivo
, menú al cual en seguida agregaremos algunos botones. Para agregar menús a una barra de menús se utiliza el método add_cascade()
, que recibe como argumento el menú (menu
) para ser insertado y el texto (label
) con que se quiere mostrar.
Nótese que en la creación de menu_archivo
(línea 8) pasamos como primer argumento la barra de menús dentro de la cual queremos ubicarlo. El segundo argumento tearoff=False
evita que Tk agregue una funcionalidad para desacoplar el menú de la ventana. Esta es una extraña opción muy rara vez necesaria y poco estimada por los usuarios, razón por la cual la desactivamos. Si te interesa ver de qué se trata, prueba iniciar el menú con el valor por defecto tearoff=True
.
Ahora ya podemos visualizar nuestra barra de menús con el único menú Archivo, pero sin botón alguno. Para añadir un botón o una opción a un menú utilizamos el método add_command()
.
import tkinter as tk def archivo_nuevo_presionado(): print("¡Has presionado para crear un nuevo archivo!") ventana = tk.Tk() ventana.title("Barra de menús en Tk") ventana.config(width=400, height=300) barra_menus = tk.Menu() menu_archivo = tk.Menu(barra_menus, tearoff=False) menu_archivo.add_command( label="Nuevo", accelerator="Ctrl+N", command=archivo_nuevo_presionado ) barra_menus.add_cascade(menu=menu_archivo, label="Archivo") ventana.config(menu=barra_menus) ventana.mainloop()
Aquí insertamos en nuestro menú un botón con texto «Nuevo» (argumento label
) y un indicador de atajo del teclado «Ctrl+N» (accelerator
). El argumento command
funciona de la misma manera que el parámetro homónimo en los botones: recibe el nombre de una función que será invocada cuando el usuario presione sobre esa opción del menú.
El parámetro accelerator
simplemente indica el atajo del teclado. Para que efectivamente el menú se active con el atajo, hay que asociar el evento correspondiente en la ventana y todos sus controles:
import tkinter as tk def archivo_nuevo_presionado(event=None): print("¡Has presionado para crear un nuevo archivo!") ventana = tk.Tk() ventana.title("Barra de menús en Tk") ventana.config(width=400, height=300) barra_menus = tk.Menu() menu_archivo = tk.Menu(barra_menus, tearoff=False) menu_archivo.add_command( label="Nuevo", accelerator="Ctrl+N", command=archivo_nuevo_presionado ) # Asociar el atajo del teclado del menú "Nuevo". ventana.bind_all("<Control-n>", archivo_nuevo_presionado) barra_menus.add_cascade(menu=menu_archivo, label="Archivo") ventana.config(menu=barra_menus) ventana.mainloop()
Además de un texto, el botón de un menú puede tener una imagen. La configuración opera de forma similar a la de una imagen en un botón. Se crea una instancia de tk.PhotoImage
con el nombre del archivo y luego se pasa como argumento al método add_command()
. El argumento compound
especifica en qué lugar debe aparecer la imagen respecto del texto.
import tkinter as tk def archivo_nuevo_presionado(event=None): print("¡Has presionado para crear un nuevo archivo!") ventana = tk.Tk() ventana.title("Barra de menús en Tk") ventana.config(width=400, height=300) barra_menus = tk.Menu() menu_archivo = tk.Menu(barra_menus, tearoff=False) img_menu_nuevo = tk.PhotoImage(file="nuevo_archivo.png") menu_archivo.add_command( label="Nuevo", accelerator="Ctrl+N", command=archivo_nuevo_presionado, image=img_menu_nuevo, # Indicar que la imagen debe aparecer a la izquierda del texto. compound=tk.LEFT ) ventana.bind_all("<Control-n>", archivo_nuevo_presionado) barra_menus.add_cascade(menu=menu_archivo, label="Archivo") ventana.config(menu=barra_menus) ventana.mainloop()
Otros valores posibles para compound
son tk.TOP
(arriba), tk.BOTTOM
(abajo) y tk.RIGHT
(derecha). Puedes descargar la imagen nuevo_archivo.png
desde este enlace. El archivo debe estar en la misma carpeta que la aplicación.
Ahora bien, de seguro un menú tendrá más de un botón, así que podemos invocar add_command()
tantas veces como sea necesario. Las opciones de un menú aparecen en la interfaz en el mismo orden en que fueron añadidas en el código. Agreguemos un nuevo botón al menu_archivo
para cerrar el programa.
import tkinter as tk def archivo_nuevo_presionado(event=None): print("¡Has presionado para crear un nuevo archivo!") ventana = tk.Tk() ventana.title("Barra de menús en Tk") ventana.config(width=400, height=300) barra_menus = tk.Menu() menu_archivo = tk.Menu(barra_menus, tearoff=False) img_menu_nuevo = tk.PhotoImage(file="nuevo_archivo.png") menu_archivo.add_command( label="Nuevo", accelerator="Ctrl+N", command=archivo_nuevo_presionado, image=img_menu_nuevo, compound=tk.LEFT ) ventana.bind_all("<Control-n>", archivo_nuevo_presionado) menu_archivo.add_separator() menu_archivo.add_command(label="Salir", command=ventana.destroy) barra_menus.add_cascade(menu=menu_archivo, label="Archivo") ventana.config(menu=barra_menus) ventana.mainloop()
El método add_separator()
introduce una línea horizontal que es útil para separar grupos de botones de menús relacionados. El resultado es el siguiente.
Del mismo modo, con sucesivas llamadas a add_cascade()
podemos agregar otros menús a la barra de menús. El siguiente código agrega un segundo menú con el título «Opciones».
import tkinter as tk def archivo_nuevo_presionado(event=None): print("¡Has presionado para crear un nuevo archivo!") ventana = tk.Tk() ventana.title("Barra de menús en Tk") ventana.config(width=400, height=300) barra_menus = tk.Menu() menu_archivo = tk.Menu(barra_menus, tearoff=False) img_menu_nuevo = tk.PhotoImage(file="nuevo_archivo.png") menu_archivo.add_command( label="Nuevo", accelerator="Ctrl+N", command=archivo_nuevo_presionado, image=img_menu_nuevo, compound=tk.LEFT ) ventana.bind_all("<Control-n>", archivo_nuevo_presionado) menu_archivo.add_separator() menu_archivo.add_command(label="Salir", command=ventana.destroy) menu_opciones = tk.Menu(barra_menus, tearoff=False) barra_menus.add_cascade(menu=menu_archivo, label="Archivo") barra_menus.add_cascade(menu=menu_opciones, label="Opciones") ventana.config(menu=barra_menus) ventana.mainloop()
Un menú puede contener botones tradicionales, como los que acabamos de usar para las opciones «Nuevo» y «Salir», o botones con casillas de verificación. El funcionamiento de estos últimos es similar al de la clase ttk.Checkbutton
. Los botones de menús con casilla de verificación se comportan igual que los botones normales, pero contienen además un valor booleano que es alterado cada vez que el usuario presiona sobre ellos. Este valor booleano está representado por la presencia de una marca a la izquierda del texto del botón.
La imagen muestra el botón con casilla de verificación «Iniciar con sistema» que indica, en una hipotética aplicación, si el programa debe iniciarse con el sistema (para una implementación real de esta funcionalidad en Windows véase este artículo). El usuario puede habilitar o deshabilitar esa opción a través del menú de opciones. La función asociada con este botón es invocada cada vez que el usuario cambia la opción. El código es el siguiente.
import tkinter as tk def archivo_nuevo_presionado(event=None): print("¡Has presionado para crear un nuevo archivo!") def menu_iniciar_con_sistema_presionado(): if iniciar_con_sistema.get(): print("Opción establecida (iniciar con el sistema).") else: print("Opción deshabilitada (no iniciar con el sistema).") ventana = tk.Tk() ventana.title("Barra de menús en Tk") ventana.config(width=400, height=300) barra_menus = tk.Menu() menu_archivo = tk.Menu(barra_menus, tearoff=False) img_menu_nuevo = tk.PhotoImage(file="nuevo_archivo.png") menu_archivo.add_command( label="Nuevo", accelerator="Ctrl+N", command=archivo_nuevo_presionado, image=img_menu_nuevo, compound=tk.LEFT ) ventana.bind_all("<Control-n>", archivo_nuevo_presionado) menu_archivo.add_separator() menu_archivo.add_command(label="Salir", command=ventana.destroy) menu_opciones = tk.Menu(barra_menus, tearoff=False) iniciar_con_sistema = tk.BooleanVar() menu_opciones.add_checkbutton( label="Iniciar con sistema", command=menu_iniciar_con_sistema_presionado, variable=iniciar_con_sistema ) barra_menus.add_cascade(menu=menu_archivo, label="Archivo") barra_menus.add_cascade(menu=menu_opciones, label="Opciones") ventana.config(menu=barra_menus) ventana.mainloop()
Como se observa en la línea 30, para crear un botón con casilla de verificación dentro de un menú se utiliza add_checkbutton()
en lugar de add_command()
. A este método es necesario pasarle una variable booleana de Tk creada vía la clase tk.BooleanVar()
(líneas 29 y 33). Cada vez que el usuario presiona el botón, Tk cambia el valor booleano de la instancia iniciar_con_sistema
y además invoca la función menu_iniciar_con_sistema_presionado()
. El valor booleano de iniciar_con_sistema
puede ser leído o establecido vía código a través de los método get()
(tal como ocurre en la línea 7) y set()
.
Esta misma lógica tiene el método add_radiobutton()
, empleado para añadir varios botones con casilla de verificación dentro de un menú, pero que tienen una relación entre sí de tal modo que cuando uno de ellos está activado, el resto se desactiva. Por ejemplo, si queremos permitirle al usuario elegir a través de los menús de nuestra aplicación el color del tema de la interfaz entre las opciones «Claro» y «Oscuro», sería conveniente emplear un par de botones de este estilo.
import tkinter as tk def archivo_nuevo_presionado(event=None): print("¡Has presionado para crear un nuevo archivo!") def menu_iniciar_con_sistema_presionado(): if iniciar_con_sistema.get(): print("Opción establecida (iniciar con el sistema).") else: print("Opción deshabilitada (no iniciar con el sistema).") def menu_tema_presionado(): valor_tema = tema_elegido.get() if valor_tema == 1: print("Tema claro establecido.") elif valor_tema == 2: print("Tema oscuro establecido.") ventana = tk.Tk() ventana.title("Barra de menús en Tk") ventana.config(width=400, height=300) barra_menus = tk.Menu() menu_archivo = tk.Menu(barra_menus, tearoff=False) img_menu_nuevo = tk.PhotoImage(file="nuevo_archivo.png") menu_archivo.add_command( label="Nuevo", accelerator="Ctrl+N", command=archivo_nuevo_presionado, image=img_menu_nuevo, compound=tk.LEFT ) ventana.bind_all("<Control-n>", archivo_nuevo_presionado) menu_archivo.add_separator() menu_archivo.add_command(label="Salir", command=ventana.destroy) menu_opciones = tk.Menu(barra_menus, tearoff=False) iniciar_con_sistema = tk.BooleanVar() menu_opciones.add_checkbutton( label="Iniciar con sistema", command=menu_iniciar_con_sistema_presionado, variable=iniciar_con_sistema ) menu_tema = tk.Menu(barra_menus, tearoff=False) tema_elegido = tk.IntVar() tema_elegido.set(1) # Opción seleccionada por defecto ("Claro"). menu_tema.add_radiobutton( label="Claro", variable=tema_elegido, value=1, command=menu_tema_presionado ) menu_tema.add_radiobutton( label="Oscuro", value=2, variable=tema_elegido, command=menu_tema_presionado ) menu_opciones.add_cascade(menu=menu_tema, label="Tema") barra_menus.add_cascade(menu=menu_archivo, label="Archivo") barra_menus.add_cascade(menu=menu_opciones, label="Opciones") ventana.config(menu=barra_menus) ventana.mainloop()
Hay varias cosas para notar en este código. En primer lugar, creamos un nuevo menú llamado menu_tema
que fue a su vez añadido al menú de opciones vía add_cascade()
(líneas 42 y 57). Como el mismo nombre lo indica (cascade, «cascada»), puede haber menús dentro de otros menús que se despliegan en forma de cascada tal como se observa en la imagen. Segundo, dentro del menu_tema
insertamos dos botones vía los métodos add_radiobutton()
, pero a diferencia de la opción anterior insertada con add_checkbutton()
, aquí ambas opciones hacen referencia a la misma variable entera tema_elegido
. El hecho de que refieran a la misma variable le indica a Tk que esas dos opciones (aunque podrían ser más de dos) son incompatibles: cuando el usuario presiona el botón «Claro», se remueve la selección del tema «Oscuro» y viceversa. Por último, nótese que cada llamada a add_radiobutton()
incluye un argumento value
que indica el valor numérico (pues la variable creada es tk.IntVar
) que representa la opción que se está agregando al menú. Este valor será retornado por menu_tema.get()
cuando la opción esté seleccionada, como vemos en la función menu_tema_presionado()
.
Estados
El botón de un menú puede estar habilitado o deshabilitado, igual que un botón. Cuando está desactivado, el texto y la imagen se muestran con un color diferente y el usuario no puede presionar sobre él. Los métodos add_command()
, add_checkbutton()
y add_radiobutton()
soportan el argumento state
, que denota el estado del botón.
import tkinter as tk ventana = tk.Tk() ventana.title("Barra de menús en Tk") ventana.config(width=400, height=300) barra_menus = tk.Menu() menu_archivo = tk.Menu(barra_menus, tearoff=False) menu_archivo.add_command( label="Nuevo", accelerator="Ctrl+N", state=tk.DISABLED ) barra_menus.add_cascade(menu=menu_archivo, label="Archivo") ventana.config(menu=barra_menus) ventana.mainloop()
La constante tk.DISABLED
indica que el menú está deshabilitado. El menú puede ser rehabilitado cambiando el valor de state
por tk.NORMAL
.
# En algún otro lugar del código o en respuesta a un evento. menu_archivo.entryconfig(0, state=tk.NORMAL)
El método entryconfig()
es empleado para alterar cualquiera de las opciones pasadas como argumentos a las tres funciones disponibles para agregar botones a un menú. Cada botón es identificado por un índice, que representa su posición en el menú y que se pasa como primer argumento. Aquí el menú «Nuevo» tiene el índice 0
. Otras opciones, tales como label
, accelerator
, command
, etc., pueden asimismo alterarse vía entryconfig()
en cualquier parte del código.
Estilos
La apariencia de los botones de los menús se personaliza pasando argumentos a las funciones add_command()
, add_checkbutton()
y add_radiobutton()
. Las tres soportan cinco propiedades que establecen el tipo de letra y los colores de los botones.
import tkinter as tk from tkinter import font ventana = tk.Tk() ventana.title("Barra de menús en Tk") ventana.config(width=400, height=300) barra_menus = tk.Menu() menu_archivo = tk.Menu(barra_menus, tearoff=False) menu_archivo.add_command( label="Nuevo", accelerator="Ctrl+N", # Tipo de fuente. font=font.Font(family="Times", size=14), # Color de fondo. background="#ADD8E6", # Color del texto. foreground="#FF0000", # Color de fondo cuanto el botón tiene el foco. activebackground="#32CDFF", # Color del texto cuando el botón tiene el foco. activeforeground="#FFFF00" ) barra_menus.add_cascade(menu=menu_archivo, label="Archivo") ventana.config(menu=barra_menus) ventana.mainloop()
Para una explicación completa de la clase Font
y las opciones que acepta, véase el apartado Tipo de letra en nuestro artículo sobre las cajas de texto.
Curso online 👨💻
¡Ya lanzamos el curso oficial de Recursos Python en Udemy!
Un curso moderno para aprender Python desde cero con programación orientada a objetos, SQL y tkinter
en 2024.
Consultoría 💡
Ofrecemos servicios profesionales de desarrollo y capacitación en Python a personas y empresas. Consultanos por tu proyecto.
Alexis says:
Hola; como hago para escoger las opciones de los Formularios??, es decir, para resolver cada opción del MENU hice un formulario; archivos separados; como hago para llamar cada archivo separado desde el MENU ???, Gracias
Recursos Python says:
Hola. Al usar
.add_command(command=...)
podés pasar una función que esté en otro archivo, siempre y cuando hayas importado el módulo. Si tenés problemas con el código, creá un tema en el foro para verlo.Saludos
Fede says:
hola, tengo un menu en cascada, llamado menu principal, que tiene
varias opciones, entre ellas (que son formularios con captura de datos) Menu-Mantencion Alumnos.
Menu-Mantencion Asignaturas. Menu-Mantencion-historico Alumnos.
Menu-Mantencion Malla. Menu-Mantencion Profesores….. Que
al llamar al formulario mantencion de alumnos, solo puedo
mostrar los datos en un grid, pero no me deja guardar.
btnNuevo=Button(marco2, text=»Guardar», command=Lambda:nuevo())
btnNuevo.grid(column=3, row=10)
Que puedo hacer??????
es en python y mysql80
Recursos Python says:
Hola. No entiendo bien el problema. Si lo querés explicar con más detalle y el código en cuestión, creá un tema en el foro y lo vemos allí.
Saludos
Wommker says:
Hola, Estoy desarrollando un proyecto y requiero actualizar en tiempo real el menu, cosa que logro gracias a un bucle for que añade cosas al menu desplegable, el problema está en que en un punto requiero borrar cosas que ya están en el menu
Recursos Python says:
Hola. Podés borrar botones del menú usando el método
delete()
, al cual le pasas el índice como argumento. Por ejemplo, para borrar el botón «Nuevo», harías:menu_archivo.delete(0)
Y para borrar todo el contenido del menú:
menu_archivo.delete(0, tk.END)
Saludos
ajgutierrez says:
he probado tu primer código pero no sale la barra de menú
Recursos Python says:
Hola. Si la barra de menú no tiene ningún menú, ni siquiera aparece. Está explicado justo debajo del primer código. Te invito a que leas todo el artículo para entender cómo funciona.
Saludos
aless says:
comprension lectora porfa