如何在 python tkinter 中以编程方式打开菜单? [英] how to open a menu programmatically in python tkinter?

查看:24
本文介绍了如何在 python tkinter 中以编程方式打开菜单?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have a graphical user interface with a menubar. I would like to be able to open those menus programmatically as if a user had clicked on them.

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

My first guess was invoke but that has no visible effect. I know that I can open the menu using tk_popup but I can not figure out the coordinates. The return value of the yposition function does not look helpful. Strangely I can not even get the width of the menubar - it's always 1.

我知道我可以使用 underline 将 menubutton 绑定到一个关键事件,并且我可能可以通过编程方式创建这样一个事件,但我真的不想这样做.

I know that I can bind a menubutton to a key event with underline and that I could probably create such an event programmatically but I really wouldn't want to do that.

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

提前致谢.

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

I am assuming you, Jonathan, are referring to mb.menu_file.invoke(0). That works if I set tearoff to True, yes, but that is not what I am looking for. Because that opens the menu in a separate window somewhere (in my case in the top left corner of the screen - far away from the window) and it must be closed with an explicit click on the close button in the top right of the window.

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

Even with tearoff=True mb.invoke(mb.menu_file) has still no effect (other than printing "invoke(1)").

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

I have done some research on postcascade and it sounds exactly like what I am looking for. Except that - as you have pointed out already - it does not work. The tcl documentation says on that matter: "if pathName is not posted, the command has no effect except to unpost any currently posted submenu". And indeed if I replace m.config(menu=mb) by mb.update(); mb.post(m.winfo_rootx(), m.winfo_rooty()) it works. (I am using here post instead of tk_popup because in this case it is supposed to stay open.) It's still not perfect because I can not control the submenu with the keyboard; but, anyway, I want a menubar, not a posted menu.

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

Do you know what drawbacks it would have to "fake" the menu? I did not consider doing that because effbot states "Since this widget uses native code where possible, you shouldn’t try to fake menus using buttons and other Tkinter widgets." (On the other hand this article also suggests to open menus with post, which I have learned from this answer to not be the best way - tk_popup is better.) That solution would certainly provide the desired flexibility. One drawback I am seeing currently is that when moving the mouse cursor to the next menu that menu does not open. But it should be possible to handle that. Are there more details that I need to consider?

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

Regarding the why: I would like the user to be able to fully customize the keyboard shortcuts. Therefore I need a function which I can bind to the event which the user chooses.

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

Another usecase might be implementing a help functionality which does not only tell the user where to find a command but opens the correct menu and selects that command. Which would make it a lot faster for the user to find it than searching for the right menu himself.

推荐答案

invoke 命令相当于tearoff.如果您允许撕下,您会看到这项工作.

The invoke command is equivalent to tearoff. If you allowed tearoff you would see that work.

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

The command you are looking for is 'postcascade'. There is no tkinter binding to this command, and if you call it manually (root.tk.eval(str(mb)+' postcascade 1') nothing happens, which is probably why there is no binding.

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

I tried many other things but I could not get this to work.

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

However, tk has a Menubutton widget too, and that responds to the <<Invoke>> event. So (if you really really want this functionality) you can make your own menubar:

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

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

Once the menu is opened with the hotkey, it can be navigated with the arrow keys, the selected option can be run with the enter key, or closed with the escape key.

这篇关于如何在 python tkinter 中以编程方式打开菜单?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆