tkinter app adding a right click context menu?
You would create a Menu instance and write a function that calls
its post()
or tk_popup()
method.
The tkinter documentation doesn't currently have any information about tk_popup()
.
Read the Tk documentation for a description, or the source:
library/menu.tcl
in the Tcl/Tk source:
::tk_popup --This procedure pops up a menu and sets things up for traversingthe menu and its submenus.Arguments:menu - Name of the menu to be popped up.x, y - Root coordinates at which to pop up the menu. entry - Index of a menu entry to center over (x,y). If omitted or specified as {}, then menu's upper-left corner goes at (x,y).
tkinter/__init__.py
in the Python source:
def tk_popup(self, x, y, entry=""): """Post the menu at position X,Y with entry ENTRY.""" self.tk.call('tk_popup', self._w, x, y, entry)
You associate your context menu invoking function with right-click via:the_widget_clicked_on.bind("<Button-3>", your_function)
.
However, the number associated with right-click is not the same on every platform.
library/tk.tcl
in the Tcl/Tk source:
On Darwin/Aqua, buttons from left to right are 1,3,2. On Darwin/X11 with recent XQuartz as the X server, they are 1,2,3; other X servers may differ.
Here is an example I wrote that adds a context menu to a Listbox:
import tkinter # Tkinter -> tkinter in Python 3class FancyListbox(tkinter.Listbox): def __init__(self, parent, *args, **kwargs): tkinter.Listbox.__init__(self, parent, *args, **kwargs) self.popup_menu = tkinter.Menu(self, tearoff=0) self.popup_menu.add_command(label="Delete", command=self.delete_selected) self.popup_menu.add_command(label="Select All", command=self.select_all) self.bind("<Button-3>", self.popup) # Button-2 on Aqua def popup(self, event): try: self.popup_menu.tk_popup(event.x_root, event.y_root, 0) finally: self.popup_menu.grab_release() def delete_selected(self): for i in self.curselection()[::-1]: self.delete(i) def select_all(self): self.selection_set(0, 'end')root = tkinter.Tk()flb = FancyListbox(root, selectmode='multiple')for n in range(10): flb.insert('end', n)flb.pack()root.mainloop()
The use of grab_release()
was observed in an example on effbot.
Its effect might not be the same on all systems.
I made some changes to the conext menu code above in order to adjust my demand and I think it would be useful to share:
Version 1:
import tkinter as tkfrom tkinter import ttkclass Main(tk.Frame): def __init__(self, master): tk.Frame.__init__(self, master) master.geometry('500x350') self.master = master self.tree = ttk.Treeview(self.master, height=15) self.tree.pack(fill='x') self.btn = tk.Button(master, text='click', command=self.clickbtn) self.btn.pack() self.aMenu = tk.Menu(master, tearoff=0) self.aMenu.add_command(label='Delete', command=self.delete) self.aMenu.add_command(label='Say Hello', command=self.hello) self.num = 0 # attach popup to treeview widget self.tree.bind("<Button-3>", self.popup) def clickbtn(self): text = 'Hello ' + str(self.num) self.tree.insert('', 'end', text=text) self.num += 1 def delete(self): print(self.tree.focus()) if self.iid: self.tree.delete(self.iid) def hello(self): print ('hello!') def popup(self, event): self.iid = self.tree.identify_row(event.y) if self.iid: # mouse pointer over item self.tree.selection_set(self.iid) self.aMenu.post(event.x_root, event.y_root) else: passroot = tk.Tk()app=Main(root)root.mainloop()
Version 2:
import tkinter as tkfrom tkinter import ttkclass Main(tk.Frame): def __init__(self, master): master.geometry('500x350') self.master = master tk.Frame.__init__(self, master) self.tree = ttk.Treeview(self.master, height=15) self.tree.pack(fill='x') self.btn = tk.Button(master, text='click', command=self.clickbtn) self.btn.pack() self.rclick = RightClick(self.master) self.num = 0 # attach popup to treeview widget self.tree.bind('<Button-3>', self.rclick.popup) def clickbtn(self): text = 'Hello ' + str(self.num) self.tree.insert('', 'end', text=text) self.num += 1class RightClick: def __init__(self, master): # create a popup menu self.aMenu = tk.Menu(master, tearoff=0) self.aMenu.add_command(label='Delete', command=self.delete) self.aMenu.add_command(label='Say Hello', command=self.hello) self.tree_item = '' def delete(self): if self.tree_item: app.tree.delete(self.tree_item) def hello(self): print ('hello!') def popup(self, event): self.aMenu.post(event.x_root, event.y_root) self.tree_item = app.tree.focus()root = tk.Tk()app=Main(root)root.mainloop()
from tkinter import *root=Tk()root.geometry("500x400+200+100")class Menu_Entry(Entry): def __init__(self,perant,*args,**kwargs): Entry.__init__(self,perant,*args,**kwargs) self.popup_menu=Menu(self,tearoff=0,background='#1c1b1a',fg='white', activebackground='#534c5c', activeforeground='Yellow') self.popup_menu.add_command(label="Cut ",command=self.Cut, accelerator='Ctrl+V') self.popup_menu.add_command(label="Copy ",command=self.Copy,compound=LEFT, accelerator='Ctrl+C') self.popup_menu.add_command(label="Paste ",command=self.Paste,accelerator='Ctrl+V') self.popup_menu.add_separator() self.popup_menu.add_command(label="Select all",command=self.select_all,accelerator="Ctrl+A") self.popup_menu.add_command(label="Delete",command=self.delete_only,accelerator=" Delete") self.popup_menu.add_command(label="Delete all",command=self.delete_selected,accelerator="Ctrl+D") self.bind('<Button-3>',self.popup) self.bind("<Control-d>",self.delete_selected_with_e1) self.bind('<App>',self.popup) self.context_menu = Menu(self, tearoff=0) self.context_menu.add_command(label="Cut") self.context_menu.add_command(label="Copy") self.context_menu.add_command(label="Paste") def popup(self, event): try: self.popup_menu.tk_popup(event.x_root, event.y_root, 0) finally: self.popup_menu.grab_release() def Copy(self): self.event_generate('<<Copy>>') def Paste(self): self.event_generate('<<Paste>>') def Cut(self): self.event_generate('<<Cut>>') def delete_selected_with_e1(self,event): self.select_range(0, END) self.focus() self.event_generate("<Delete>") def delete_selected(self): self.select_range(0, END) self.focus() self.event_generate("<Delete>") def delete_only(self): self.event_generate("<BackSpace>") def select_all(self): self.select_range(0, END) self.focus()ent=Menu_Entry(root)ent.pack()root.mainloop()