Reputation: 15335
Following up on this question, I am trying to view (same as in when clicked with left mouse button) a menu,sub1
, when a button,Test
, is pressed, but I can't. In the following code button seems to instead freeze the GUI:
import tkinter as tk
root = tk.Tk()
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)
def cb(*args):
root.tk.call('::tk::TraverseToMenu', root, 'S')
tk.Button(root, text="Test", command=cb).pack()
root.mainloop()
I have also tried update_idletasks()
to no avail. How can I fix this?
Tried with:
Windows7, Python 3.6, Tkinter 8.6.
Upvotes: 3
Views: 1605
Reputation: 4482
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
}
}
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()
Upvotes: 1
Reputation: 137567
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.
Upvotes: 1