【问题标题】:tkinter app adding a right click context menu?tkinter 应用程序添加右键单击上下文菜单?
【发布时间】:2012-08-17 23:04:16
【问题描述】:

我有一个 python-tkinter gui 应用程序,我一直在尝试找到一些方法来添加一些功能。我希望有一种方法可以右键单击应用程序列表框区域中的项目并调出上下文菜单。 tkinter 能够做到这一点吗?看看 gtk 或其他一些 gui-toolkit 会更好吗?

【问题讨论】:

    标签: python user-interface tkinter contextmenu


    【解决方案1】:

    您将创建一个Menu 实例并编写一个调用
    的函数 它的post()tk_popup() 方法。

    tkinter documentation 目前没有关于tk_popup() 的任何信息。
    阅读Tk documentation 获取说明或来源:

    library/menu.tcl in the Tcl/Tk source:

    ::tk_popup -- 此过程弹出一个菜单并设置遍历 菜单及其子菜单。 论据: menu - 要弹出的菜单的名称。 x, y - 弹出菜单的根坐标。 entry - 以 (x,y) 为中心的菜单项的索引。 如果省略或指定为 {},则菜单的 左上角在 (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)
    

    您将上下文菜单调用函数与通过右键单击相关联:
    the_widget_clicked_on.bind("<Button-3>", your_function)

    但是,与右键单击相关的数字在每个平台上并不相同。

    library/tk.tcl in the Tcl/Tk source:

    在 Darwin/Aqua 上,从左到右的按钮是 1,3,2。 在以最近的 XQuartz 作为 X 服务器的 Darwin/X11 上,它们是 1、2、3; 其他 X 服务器可能会有所不同。

    这是我编写的一个示例,它向列表框添加了上下文菜单:

    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()
    

    an example on effbot中观察到grab_release()的使用。
    它对所有系统的影响可能并不相同。

    【讨论】:

    • 感谢这真的很有帮助!我不得不做一些奇怪/不同的事情来让弹出菜单出现。我最终绑定到Tk
    • @tijko 您可以使用New Mexico Tech Reference 作为额外的信息来源。
    • 我在几次学习 Tkinter 时遇到了这个链接,那里也有一些很好的教程。我开始重新思考我将如何处理这件事。起初我在想,如果我要在 Tkinter 中右键单击创建一个上下文菜单,我可以使用某种复制和粘贴功能,该功能可以转移到我的系统和浏览器/记事本中。
    • 在运行 Python 3.5.2、tkinter 8.6 的 Ubuntu 16.10 上,如果用户没有选择菜单项,语句 self.aMenu.post(event.x_root, event.y_root) 会使菜单保持打开状态。我发现用 self.aMenu.tk_popup(event.x_root, event.y_root) 替换它可以解决问题:按 ESC 或单击其他位置会按预期自动删除它。
    • grab_release 对我的影响(Ubuntu、Python 3.6.8、tkinter 8.6)正是我想要的:它使弹出菜单保持打开状态即使我点击了其他地方。我花了一些时间才发现这就是原因。希望这条评论可以节省其他人的时间:)
    【解决方案2】:

    为了调整我的需求,我对上面的conext菜单代码做了一些更改,我认为分享一下会很有用:

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

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

    【讨论】:

      【解决方案3】:
      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()
      

      【讨论】:

        【解决方案4】:

        重要提示:

        (假设包含坐标的事件参数称为“事件”):除非您使用“event.x_root”和“event.y_root”作为参数,否则调用 tk_popup(...) 时不会发生任何事情或不可见.如果你很明显地使用“event.x”和“event.y”,它不会起作用,即使坐标的名称是“x”和“y”并且没有提到“x_root”和"y_root" 中的任何位置。

        至于grab_release(..),在任何地方都没有必要。 “tearoff=0”也不是必需的,将其设置为 1(这是默认值),只需在上下文菜单中添加一个虚线条目。如果单击它,它会分离上下文菜单并使其成为带有窗口装饰器的顶级窗口。 tearoff=0 将隐藏此条目。此外,如果您将菜单的主设置为任何特定的小部件或根目录,或者任何其他内容,都没有关系。

        【讨论】:

          【解决方案5】:

          【讨论】:

          • 大约十年前我发布了这个问题:P 但我最终确实编写了一些代码来创建右键单击上下文窗口。我会查看您链接的网站,谢谢……而且我会小心地通过复制/粘贴链接来回答问题。我不会嘲笑你,但这个网站上有些人会因此谴责你。
          • 虽然此链接可能会回答问题,但最好在此处包含答案的基本部分并提供链接以供参考。如果链接页面发生更改,仅链接答案可能会失效。 - From Review
          • 我昨天检查过
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-08-19
          • 2016-02-04
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-08-25
          相关资源
          最近更新 更多