简体   繁体   English

tkinter 应用程序添加右键单击上下文菜单?

[英]tkinter app adding a right click context menu?

I have a python-tkinter gui app that I've been trying to find some way to add in some functionality.我有一个 python-tkinter gui 应用程序,我一直试图找到一些方法来添加一些功能。 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? tkinter 能够做到这一点吗? Would I be better off looking into gtk or some other gui-toolkit?我最好研究一下 gtk 或其他一些 gui-toolkit 吗?

You would create a Menu instance and write a function that calls您将创建一个Menu实例并编写一个调用的函数
its post() or tk_popup() method.它的post()tk_popup()方法。

The tkinter documentation doesn't currently have any information about tk_popup() . tk_popup()文档目前没有关于tk_popup()任何信息。
Read the Tk documentation for a description, or the source:阅读Tk 文档以获取描述或来源:

library/menu.tcl in the Tcl/Tk source : Tcl/Tk 源代码中的library/menu.tcl

::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 : Python 源代码中的tkinter/__init__.py

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) . 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 : Tcl/Tk 源代码中的library/tk.tcl

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 .在 effbot 的一个例子中观察到了grab_release()的使用。
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:为了调整我的需求,我对上面的conext菜单代码做了一些更改,我认为分享一下会很有用:

Version 1:版本 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:版本 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()
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()

Important Caveat:重要警告:

(Assuming the event argument that contains the coordinates is called "event"): Nothing will happen or be visible when you call tk_popup(...) unless you use "event.x_root" and "event.y_root" as arguments. (假设包含坐标的事件参数称为“事件”):除非您使用“event.x_root”和“event.y_root”作为参数,否则调用 tk_popup(...) 时不会发生任何事情或不可见。 If you do the obvious of using "event.x" and "event.y", it won't work, even though the names of the coordinates are "x" and "y" and there is no mention of "x_root" and "y_root" anywhere within it.如果您明显使用“event.x”和“event.y”,即使坐标名称是“x”和“y”并且没有提到“x_root”和“ “y_root”在其中的任何地方。

As for the grab_release(..), it's not necessary, anywhere.至于grab_release(..),在任何地方都没有必要。 "tearoff=0" also isn't necessary, setting it to 1 (which is default), simply adds a dotted line entry to the context menu. “tearoff=0”也不是必需的,将其设置为 1(这是默认值),只需在上下文菜单中添加一个虚线条目即可。 If you click on it, it detaches the context menu and makes it its own top-level window with window decorators.如果单击它,它会分离上下文菜单并使其成为自己的带有窗口装饰器的顶级窗口。 tearoff=0 will hide this entry. tearoff=0 将隐藏此条目。 Moreover, it doesn't matter if you set the menu's master to any specific widget or root, or anything at all.此外,如果您将菜单的主设置为任何特定的小部件或根,或任何东西都没有关系。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM