How to view a menu when a button is pressed?

徘徊边缘 提交于 2019-12-06 13:56:49

Trivia

This trick works within the X Window System (read as Unix), because "Alt-keying" is handled by tk itself via tk::TraverseToMenu function, wich is binded to the all bind-tag in that case.

In your case tk detects, that it works in Win environment, and binds tk::TraverseToMenu function only to the Menubutton bind-tag, because in such circumstances "Alt-keying" is handled by native Win wm.

What was said is represented by the source code in menu.tcl:

if {[tk windowingsystem] eq "x11"} {
    bind all <Alt-KeyPress> {
    tk::TraverseToMenu %W %A
    }

    bind all <F10> {
    tk::FirstMenu %W
    }
} else {
    bind Menubutton <Alt-KeyPress> {
    tk::TraverseToMenu %W %A
    }

    bind Menubutton <F10> {
    tk::FirstMenu %W
    }
}

Solution

When you press Alt key, Windows sends a message, which signaling that the Alt-key is pressed down, and waits for another message, which contains specified character as ANSI-code. After a specified character is received, wm is trying to find a menu to open.

In same time tk::TraverseToMenu works well - try to pass an empty string or any arbitrary char as a char parameter, with wich menu cannot be found. The problem only occurs when you're trying to play on the lawn near the Windows house.

Your best bets in this situation: SendMessage or keybd_event.

So a complete hack (as @Donal Fellows said) is this:

import tkinter as tk

root = tk.Tk()

if root._windowingsystem == 'win32':
    import ctypes

    keybd_event = ctypes.windll.user32.keybd_event
    alt_key = 0x12
    key_up = 0x0002

    def traverse_to_menu(key=''):
        if key:
            ansi_key = ord(key.upper())
            #   press alt + key
            keybd_event(alt_key, 0, 0, 0)
            keybd_event(ansi_key, 0, 0, 0)

            #   release alt + key
            keybd_event(ansi_key, 0, key_up, 0)
            keybd_event(alt_key, 0, key_up, 0)

else:
    #   root._windowingsystem == 'x11'
    def traverse_to_menu(key=''):
        root.tk.call('tk::TraverseToMenu', root, key)

menubar = tk.Menu(root)

sub1 = tk.Menu(menubar, tearoff=0)
sub1.add_command(label='Item 1', command=lambda: print('item 1'))
sub1.add_command(label='Item 2', command=lambda: print('item 2'))

menubar.add_cascade(menu=sub1, label='Sub1', underline=0)
root.config(menu=menubar)

traverse_button = tk.Button(root, text='Test', command=lambda: traverse_to_menu('S'))
traverse_button.pack()

root.mainloop()

The Tkinter buttons aren't supposed to work that way; that's what a menubutton is for. But if you're going to continue to hack on a button, you will need to bind to events on the button and not just use the command callback (which is fired off on mouse-button-1 release over the button when the button is armed; arming happens when the mouse-button-1 is pressed over the button).

I really advise using a menubutton (tk.Menubutton) instead. It's easier for users as it is designed to look like it will post a menu when pressed.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!