I have a python-tkinter gui app that I've been trying to find some way to add in some functionality. I was hoping there would be a way to right-click on an item in the app's listbox area and bring up a context menu. Is tkinter able to accomplish this? Would I be better off looking into gtk or some other gui-toolkit?
Build A Paint Program With TKinter and Python Menus can be created by initializing the Menu(parent) object along with the menu items. A popup Menu can be created by initializing tk_popup(x_root,y_root, False) which ensures that the menu is visible on the screen.
mainloop() tells Python to run the Tkinter event loop. This method listens for events, such as button clicks or keypresses, and blocks any code that comes after it from running until you close the window where you called the method.
A Tearoff permits you to detach menus for the most window making floating menus. If you produce a menu you may see dotted lines at the top after you click a top menu item. To fix this tearoff needs to set to 0 at the time of menu declaration.
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 traversing the 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 3 class 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 tk from tkinter import ttk class 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: pass root = tk.Tk() app=Main(root) root.mainloop()
Version 2:
import tkinter as tk from tkinter import ttk class 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 += 1 class 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()
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With