La barra de menús, los menús y sus ítems, tk_popup
Toda la funcionalidad de una aplicación visual debería ser accesible desde el menú de su ventana principal. Incluso en algunas ventanas de la aplicación puede ser interesante el uso de un menú contextual (o emergente).
El widget tkinter.Menu
es la base de los menús que se pueden mostrar en una aplicación visual desarrollada con tkinter. Su prototipo es
Menu(master=None, **options)
Un menú tiene una estructura arbórea, donde sus ramas son menús y sus hojas actúan como botones (muestran un texto y/o una imagen y cuando son pulsados ejecutan cierta función). En el menú principal de una aplicación, las ramas que surgen de la raíz forman lo que llamamos barra de menús, cada una de ellas es un menú que agrupa diversos ítems, que a su vez pueden ser menús.
En algunos casos, los ítems de un menú pueden aparecer marcados con una marca de verificación, también pueden aparecer en un tono grisáceo que indica al usuario que esa opción no está disponible.
Para crear una barra de menús se usa la orden tkinter.Menu(master)
, donde master
es la ventana de alto nivel que la contiene. Los menús que aparecerán en la barra de menús se han de crear con la misma orden, esta vez indicando que su master
es el identificador usado para la barra de menús.
Una aplicación puede tener más de una barra de menús, pero sólo puede usar una de ellas, lo que se consigue con su método config
.
root.config(menu=barra_de_menús)
Métodos
Para añadir ítems a un menú se pueden usar las órdenes
add(itemType, cnf={}, **kw)
add_cascade(cnf={}, **kw)
add_checkbutton(cnf={}, **kw)
add_command(cnf={}, **kw)
add_radiobutton(cnf={}, **kw)
Se pueden separar los ítems con
add_separator(cnf={}, **kw)
Para cambiar las opciones del widget se puede usar
config = configure(self, cnf=None, **kw)
Si queremos borrar alguna entrada del menú usaremos
delete(index1, index2=None)
`
Con
entrycget(index, option)
obtenemos la opción pedida del ítem indicado.
entryconfig(index, cnf=None, **kw)
modifica las opciones especificadas del ítem indicado (entryconfigure
es un sinónimo).
index(index)
convierte el índice (de cualquier tipo) en un número entero.
insert(index, itemType, cnf={}, **kw)
inserta un nuevo ítem en la posición que se indique, similar a add()
.
insert_cascade(index, cnf={}, **kw)
insert_checkbutton(index, cnf={}, **kw)
insert_command(index, cnf={}, **kw)
insert_radiobutton(index, cnf={}, **kw)
insert_separator(index, cnf={}, **kw)
son funciones similares a sus correspondientes add_X()
.
invoke(index)
invoca el comando del ítem indicado.
post(x, y)
muestra el menú en la posición indicada (en referencia a la ventana principal).
Para mostrar menús emergentes, con la entrada indicada:
tk_popup(self, x, y, entry='')
type(index)
devuelve el tipo de ítem indicado.
unpost()
elimina el menú mostrado con post()
.
xposition(index)
devuelve la posición del píxel más a la izquierda del ítem indicado.
xyposition(index)
devuelve la posición del píxel más arriba del ítem indicado.
Propiedades de los ítems
label
es una cadena de caracteres. Se usa como tal, encerrada entre comillas simples o dobles, para identificar el ítem.
underline
indica que se colocará una marca de subrayado en la posición indicada de la etiqueta del ítem, comenzando por 0. Permite al usuario navegar por un menú usando el teclado, tras activarlo con la tecla 'Alt'.
image
permite mostrar una imagen junto al texto de la etiqueta del ítem. Con compound
indicamos en qué posición queremos que se muestre la imagen, con las opciones "bottom", "center", "left", "right", "top" o "none".
variable
permite asociar al ítem una variable de tipo BoolanVar
, StringVar
, IntVar
o DoubleVar
.
value
Estructura
En realidad la barra de menús es un menú más. Los menús se pueden anidar unos dentro de otros. Su uso básico es el siguiente:
- Crear la barra de menús con
barra_de_menús = tkinter.Menu(ventana_principal)
. Una aplicación visual sólo muestra una barra de menús, sin embargo se puede definir más de una para mostrar, por ejemplo, opciones básicas o avanzadas. - Crear los menús con
nombre_del_menú = tkinter.Menu(barra_de_menús)
. Generalmente habrá más de un menú, los más típicos sonArchivo
,Edición
,Vista
,Ventana
yAyuda
. Los menús no ejecutan comandos, están ahí para ocultar o mostrar una serie de ítems que sí ejecutarán comandos. - Añadir ítems a los menús con
nombre_del_menú.add_X(label="Etiqueta del ítem"[, command=nombre_función_a_ejecutar] [,var=variable_asociada][, ...])
. Cada menú recoge una serie de ítems con los comandos que se pueden aplicar en su contexto o variables asociadas al ítem. Incluso uno de estos ítems puede ser un menú que tenga a su vez una colección de ítems asociado. - Añadir los menús a la barra de menús con
barra_de_menús.add_cascade(label="Título de menú", menu=nombre_del_menú)
. Así se construye la barra de menús, un menú en el que los (sub)menús añadidos son sus ítems. - Asignar la barra de menús a la ventana principal con
root.config(menu=barra_de_menús)
. Si tuviéramos más de una barra de menús, para alternar entre una y otra (no pueden mostrarse dos simultáneamente) se usaríaroot.config(menu=otra_barra_de_menús)
.
Comandos
Cuando queremos que un ítem de menú realice una acción, se define primero la función que contiene la acción y se añade el ítem a su menú indicando su etiqueta y el nombre del comando a ejecutar cuando se pulse en el ítem.
menu.add_command(label='Etiqueta', command=comando)
Checkbuttons
Los ítems que queremos puedan ser seleccionados/deseleccionados los añadimos con
menu.add_checkbutton(label='Etiqueta', variable=var, command=comando)
donde la variable var
se ha de asociar a una variable de tipo tkinter.BooleanVar()
, y su valor (True
o False
) determinará si aparece o no la marca de selección junto a la etiqueta del ítem. Al hacer clic en esta opción, la variable asociada cambia su estado (si era True
pasa a ser False
y viceversa).
Radiobuttons
Cuando queremos que el usuario pueda elegir uno y sólo uno de una serie de ítems, añadimos cada uno de ellos con
menu.add_radiobutton(label='Etiqueta', variable=var, command=comando)
menu.add_radiobutton(label='Etiqueta 1', variable=var1, command=comando1)
En este caso la variable var
es
Ejemplo 1
from tkinter import *
def donothing():
filewin = Toplevel(root)
button = Button(filewin, text="Do nothing button")
button.pack()
root = Tk()
menubar = Menu(root)
filemenu = Menu(menubar, tearoff=0)
filemenu.add_command(label="New", command=donothing)
filemenu.add_command(label="Open", command=donothing)
filemenu.add_command(label="Save", command=donothing)
filemenu.add_command(label="Save as...", command=donothing)
filemenu.add_command(label="Close", command=donothing)
filemenu.add_separator()
filemenu.add_command(label="Exit", command=root.quit)
menubar.add_cascade(label="File", menu=filemenu)
editmenu = Menu(menubar, tearoff=0)
editmenu.add_command(label="Undo", command=donothing)
editmenu.add_separator()
editmenu.add_command(label="Cut", command=donothing)
editmenu.add_command(label="Copy", command=donothing)
editmenu.add_command(label="Paste", command=donothing)
editmenu.add_command(label="Delete", command=donothing)
editmenu.add_command(label="Select All", command=donothing)
menubar.add_cascade(label="Edit", menu=editmenu)
helpmenu = Menu(menubar, tearoff=0)
helpmenu.add_command(label="Help Index", command=donothing)
helpmenu.add_command(label="About...", command=donothing)
menubar.add_cascade(label="Help", menu=helpmenu)
root.config(menu=menubar)
root.mainloop()
Menú emergente/contextual
Para mostrar un menú cuando el usuario hace click con el botón secundario (<Button-3>
en Linux y MS-Windows, <Button-2>
en macOS) se debe usar su método tk_popup(posición-x, posición-y)
.
Ejemplo 2
from tkinter import *
def siguiente():
root.title('Siguiente')
def anterior():
root.title('Anterior')
def casa():
root.title('Casa')
root = Tk()
w = Label(root, text="Right-click to display menu", width=40, height=20)
w.pack()
# create a menu
popup = Menu(root, tearoff=0)
popup.add_command(label="Next", command=siguiente) # , command=next) etc...
popup.add_command(label="Previous", command=anterior)
popup.add_separator()
popup.add_command(label="Home", command=casa)
def do_popup(event):
# display the popup menu
try:
popup.tk_popup(event.x_root, event.y_root, 0)
finally:
# make sure to release the grab (Tk 8.0a1 only)
popup.grab_release()
# return None
pass
#TODO:Detectar el SO, el original usaba "<Button-3>", que no funciona en macOS.
w.bind("<Button-2>", do_popup)
b = Button(root, text="Quit", command=root.destroy)
b.pack()
root.mainloop()
Menubutton
Un Menubutton
tiene la apariencia de un botón con una marca de drop-down lateral que indica al usuario que al pulsar el botón se mostrará un menú. El widgets Menubutton
es realmente la parte visible del menú que tiene asociado.
Si le asociamos un menú con ítems del tipo checkbutton
, haciendo clic en cualquiera de sus opciones se selecciona/deselecciona y se guarda de nuevo el menú, mostrando sólo el botón con su texto y marca lateral.
Al consultar la documentación oficial de Python, help(tkinter.Menubutton)
nos informa de que este widget está obsoleto desde la versión Tk8.0. Su versión tematizada, help(tkinter.ttk.Menubutton)
, no tiene esta advertencia por lo que en esta asignatura usaremos siempre la versión tematizada del widget.
El widget Menubutton
posee su propio menú, que se despliega al hacer click sobre él. puede ser un ítem de un menú, permitiendo seleccionar al usuario cada uno de sus ítems.
OptionMenu
Un OptionMenu
es parecido visualmente al Menubutton
, sin embargo sólo permite seleccionar una de las opciones del menú que despliega y la muestra en el botón.
Ejercicios
- Refactoriza el ejemplo 1 utilizando PEP8.
Observa que en el ejemplo 1, en macOS aparece una opción de menú que no hemos escrito nosotros,
Help / Search
. Esta es una de las peculiaridades de las APIs, que se adaptan a comportamientos genéricos de las aplicaciones de cada sistema operativo. Si cambias el códigomenubar.add_cascade(label="Help", menu=helpmenu)
pormenubar.add_cascade(label="Ayuda", menu=helpmenu)
observarás que esa opción extra ya no aparece.Con la ayuda de las referencias bibliográficas de este tema, reproduce las aplicaciones mostradas como ejemplos visuales de
Menubutton
yOptionMenu
.En https://stackoverflow.com/questions/23442792/tkinter-enable-disable-menu encontramos el siguiente ejemplo.
import Tkinter as tk
class Example(tk.Frame):
def __init__(self, root):
tk.Frame.__init__(self, root)
self.menubar = tk.Menu()
self.test1Menu = tk.Menu()
self.test2Menu = tk.Menu()
self.menubar.add_cascade(label="Test1", menu=self.test1Menu)
self.menubar.add_cascade(label="Test2", menu=self.test2Menu)
self.test1Menu.add_command(label="Enable Test2", command=self.enable_menu)
self.test1Menu.add_command(label="Disable Test2", command=self.disable_menu)
self.test2Menu.add_command(label="One")
self.test2Menu.add_command(label="Two")
self.test2Menu.add_command(label="Three")
self.test2Menu.add_separator()
self.test2Menu.add_command(label="Four")
self.test2Menu.add_command(label="Five")
root.configure(menu=self.menubar)
def enable_menu(self):
self.menubar.entryconfig("Test2", state="normal")
def disable_menu(self):
self.menubar.entryconfig("Test2", state="disabled")
if __name__ == "__main__":
root = tk.Tk()
root.geometry("500x500")
app = Example(root)
app.pack(fill="both", expand=True)
root.mainloop()
Conviértelo a Python 3 y analiza cómo construye el menú y cómo conseguir que una opción del menú esté activa o deshabilitada.
- Al arrancar la aplicación anterior el menú
Test 2
está habilitado, por lo que no tiene sentido que la opciónTest 1 / Enable Test 2
también lo esté. Cuando se inhabilita el test 2, la opciónTest 1 / Enable Test 2
debería habilitarse, y la opciónTest 1 / Disable Test 2
deshabilitarse. Cuando se vuelve a habilitar deberían intercambiar sus estados.
- Añade el menú
Test 3
, con la misma funcionalidad queTest 1
pero sólo un ítem.
Recetas
La sentencia pass
pass
es una sentencia de Python que no hace nada, pero está ahí. Es útil cuando se está desarrollando un menú con mucha funcionalidad y no se quiere escribir el código de todas las funciones apuntadas por los ítems del menú.
def AlPulsarEstaOpcion():
pass
def AlPulsarEstaOtraOpcion():
pass