python - 如何在 python tkinter 中以编程方式打开菜单?

标签 python tkinter

我有一个带有菜单栏的图形用户界面。我希望能够以编程方式打开这些菜单,就好像用户点击了它们一样。

我的第一个猜测是 invoke 但这没有明显的效果。 我知道我可以使用 tk_popup 打开菜单,但我不知道坐标。 yposition 函数的返回值看起来没有帮助。 奇怪的是,我什至无法获得菜单栏的宽度 - 它始终为 1。

我知道我可以使用 underline 将菜单按钮绑定(bind)到按键事件,并且我可能可以通过编程方式创建这样的事件,但我真的不想那样做。

import Tkinter as tk

class MenuBar(tk.Menu):
     def __init__(self, root):
         tk.Menu.__init__(self, root)
         self.root = root
         self.menu_file = tk.Menu(m, tearoff=False)
         self.menu_file.label = 'File'
         self.menu_file.add_command(label='save')
         self.menu_file.add_command(label='open')

         self.menu_edit = tk.Menu(m, tearoff=False)
         self.menu_edit.label = 'Edit'
         self.menu_edit.add_command(label='add')
         self.menu_edit.add_command(label='remove')

         self.menus = (
             self.menu_file,
             self.menu_edit,
         )
         for menu in self.menus:
             self.add_cascade(label=menu.label, menu=menu, underline=0)

     def invoke(self, menu):
         if menu in self.menus:
             index = self.index(menu.label)
         else:
             index = menu
         print("invoke({!r})".format(index))
         tk.Menu.invoke(self, index)

     def open_menu(self, menu):
         x = self.root.winfo_rootx()
         y = self.root.winfo_rooty()
         print("yposition: {}".format(self.yposition(self.index(menu.label))))
         print("mb.width : {}".format(self.winfo_width()))
         print("mb.geometry: {}".format(self.winfo_geometry()))
         print("tk_popup({x},{y})".format(x=x, y=y))
         menu.tk_popup(x,y)
         pass

m = tk.Tk()
mb = MenuBar(m)
m.config(menu=mb)
m.update()
m.bind('f', lambda e: mb.invoke(mb.menu_file))
m.bind('e', lambda e: mb.invoke(mb.menu_edit))
m.bind('<Control-f>', lambda e: mb.open_menu(mb.menu_file))
m.bind('<Control-e>', lambda e: mb.open_menu(mb.menu_edit))
m.mainloop()

提前致谢。

编辑: Jonathan,我假设您指的是 mb.menu_file.invoke(0)。如果我将 tearoff 设置为 True,那会起作用,是的,但这不是我想要的。因为这会在某个单独的窗口中打开菜单(在我的例子中是在屏幕的左上角 - 远离窗口)并且必须通过明确单击窗口右上角的关闭按钮来关闭它。

即使 tearoff=True mb.invoke(mb.menu_file) 仍然没有效果(除了打印“invoke(1)”)。

我对 postcascade 做了一些研究,它听起来正是我要找的东西。除了 - 正如你已经指出的那样 - 它不起作用。 tcl documentation就此事说:“如果未发布 pathName,则该命令除了取消发布任何当前发布的子菜单外没有任何效果”。事实上,如果我将 m.config(menu=mb) 替换为 mb.update(); mb.post(m.winfo_rootx(), m.winfo_rooty()) 它有效。 (我在这里使用 post 而不是 tk_popup 因为在这种情况下它应该保持打开状态。)它仍然不完美,因为我无法用键盘控制子菜单;但是,无论如何,我想要一个菜单​​栏,而不是张贴的菜单。

你知道“伪造”菜单会有什么弊端吗?我没有考虑这样做,因为 effbot声明“由于此小部件尽可能使用 native 代码,因此您不应尝试使用按钮和其他 Tkinter 小部件来伪造菜单。” (另一方面,本文还建议使用 post 打开菜单,我从 this answer 了解到这不是最好的方法 - tk_popup 更好。)解决方案肯定会提供所需的灵 active 。我目前看到的一个缺点是,当将鼠标光标移动到下一个菜单时,该菜单不会打开。但应该可以处理它。我需要考虑更多细节吗?

关于原因:我希望用户能够完全自定义键盘快捷键。因此我需要一个可以绑定(bind)到用户选择的事件的函数。

另一个用例可能是实现帮助功能,它不仅会告诉用户在哪里可以找到命令,还会打开正确的菜单并选择该命令。这将使用户找到它比自己搜索正确的菜单快得多。

最佳答案

invoke命令相当于tearoff。如果您允许撕下,您会看到该作品。

您要查找的命令是“postcascade”。此命令没有 tkinter 绑定(bind),如果您手动调用它 ( root.tk.eval(str(mb)+' postcascade 1') 什么也不会发生,这可能就是没有绑定(bind)的原因。

我尝试了很多其他的东西,但我无法让它工作。

但是,tk 也有一个 Menubutton 小部件,它响应 <<Invoke>>事件。所以(如果你真的想要这个功能)你可以制作自己的菜单栏:

import Tkinter as tk
import ttk

def log(command):
    print 'running {} command'.format(command)

class MenuBar(tk.Frame):
    def __init__(self, master=None):
        tk.Frame.__init__(self, master, bd=1, relief=tk.RAISED)

        file_btn = tk.Menubutton(self, text='File')
        menu_file = tk.Menu(file_btn, tearoff=False)
        menu_file.add_command(label='save', command=lambda: log('save'))
        menu_file.add_command(label='open', command=lambda: log('open'))
        file_btn.config(menu=menu_file)
        file_btn.pack(side=tk.LEFT)
        master.bind('f', lambda e: file_btn.event_generate('<<Invoke>>'))

        edit_btn = tk.Menubutton(self, text='Edit')
        menu_edit = tk.Menu(edit_btn, tearoff=False)
        menu_edit.add_command(label='add', command=lambda: log('add'))
        menu_edit.add_command(label='remove', command=lambda: log('remove'))
        edit_btn.config(menu=menu_edit)
        edit_btn.pack(side=tk.LEFT)
        master.bind('e', lambda e: edit_btn.event_generate('<<Invoke>>'))

m = tk.Tk()
m.geometry('300x300')
mb = MenuBar(m)
mb.pack(side=tk.TOP, fill=tk.X)
m.mainloop()

使用热键打开菜单后,可以使用箭头键进行导航,可以使用回车键运行所选选项,或使用退出键关闭。

关于python - 如何在 python tkinter 中以编程方式打开菜单?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43355901/

相关文章:

python - 迭代并追加列表

python - %d 和 %s 的目的是什么

python - UserWarning : Matplotlib is currently using agg, 所以无法显示数字

python - tkinter Canvas 不绘制顶部和左侧的线

python - 将项目添加到 ttk.Treeview 时出现“不匹配的左括号”错误

python - 在 Tkinter 中使用 askdirectory() 选择文件夹时显示所有文件

python - 在 Python 中将 str 数据转换为文件对象

python - 根据可能重复的管道分隔列制作新的 Pandas 列

python - 在 Python 中,单个字符串是否保证相同?

python - 如何使 Python Tkinter 中的按钮保持按下状态直到按下另一个按钮