Tkinter shortcuts in context menu on Windows - python

I a have a menu that I display as a context menu in a tkinter application:
rmenu = tk.Menu(None, tearoff=0, takefocus=0)
# add some commands here
rmenu.add_command(label="Copy", copyfunction, accelerator="Shift-C")
rmenu.bind("<Shift-KeyPress-C>", copyfunction) # this doesn't work on windows!
I define a shortcut for a command in this context menu, and then, when I try to run the command by typing <Shift>+<C> on windows it doesn't work. Actually, I get a beep.
Is there a way to define shortcut in this situation?
Update:
I have even tried to generate a event in the program using:
rmenu.event_generate("<Shift-KeyPress-C>", when="tail")
But the function copyfunction wasn't called.
PS: This works on Linux

bind the keyboard shortcut to the root. Only things with focus listen for key bindings. How often does the context menu have focus? By using bind_all on the root, whatever has focus will trigger the key binding.
root.bind_all("<Shift-C>", copyfunction)

Related

Tkinter menu bindings and accelerators

I've come across a weird problem and I can't work out what is happening. I am working on a Tkinter application which utilises a menu. I have found that while creating the menu, in which all items have accelerators, some items require additional bindings to make the accelerators work (unless the menu bars are already selected) and some don't.
When an additional binding is required, I have a problem with double entry key strokes. For example, if I open a Toplevel window, I get double entries every time I type a character in an Entry box, both in the Toplevel and in the main window. This only happens if the menu item is called via the key command.
Most of the time this is not a problem, although I'd really like to know what the underlying cause is because it just seems wrong, but this particularly came to my attention recently when I implemented the built-in OS X Preferences menu, using the following code:
self.window.createcommand('::tk::mac::ShowPreferences', self._settings)
Now when I call the settings function from the build-in Preferences key command Command-,, which instantiates a new Toplevel window, this double entry is what happens. It does not happen if I navigate to the menu and open it with the mouse.
The example below recreates the problem for me. The menu bar is not strictly necessary, but the problem occurs with both the menu bar and the OS X built-in Preferences item. Interestingly, 'Settings A' which does not require a binding reproduces the problem, but 'Settings B', which does require a binding, works fine. And again, only with key commands.
import Tkinter
def settings(event = None):
top = Tkinter.Toplevel()
Tkinter.Entry(top).pack()
top.mainloop()
root = Tkinter.Tk()
root.createcommand('::tk::mac::ShowPreferences', settings)
menuBar = Tkinter.Menu(root)
fileMenu = Tkinter.Menu(menuBar)
fileMenu.add_command(label = 'Settings A', accelerator = 'Command-Shift-a', command = settings) # Does not require binding
fileMenu.add_command(label = 'Settings B', accelerator = 'Command-b', command = settings) # Requires binding
menuBar.add_cascade(label = 'File', menu = fileMenu)
root.config(menu = menuBar)
root.bind('<Command-b>', settings)
Tkinter.Entry(root).pack()
root.mainloop()
I suspected it may be a computer issue but I have tried it on another machine and I get the same result. Does anybody have any idea what is happening here and how I can prevent it?
In case anybody's interested, I think I've got to the bottom of this. I believe it was caused by the version of tkinter I had. Today I updated to Python 3 from the OS X bundled version 2.7, and the problem remained. Then I updated tkinter to ActiveTcl 8.5.18.0 and the problem seems to have disappeared.
IDLE and tkinter with Tcl/Tk on macOS

Tkinter's overrideredirect prevents certain events in Mac and Linux

I am writing a program in Python with a Tkinter UI. I want to have a small window with no title bar. This window must receive keyboard input. I am not picky whether this is in the form of an Entry widget or just binding to KeyPress. overrideredirect(True) is typically how the title bar is disabled. Unfortunately, (except in Windows), this seems to prevent many events from being received. I wrote this code to illustrate the problem:
#!/usr/bin/env python
from __future__ import print_function
import Tkinter
class AppWindow(Tkinter.Tk):
def __init__(self, *args, **kwargs):
Tkinter.Tk.__init__(self, *args, **kwargs)
self.overrideredirect(True)
self.geometry("400x25+100+300")
titleBar = Tkinter.Frame(self)
titleBar.pack(expand = 1, fill = Tkinter.BOTH)
closeButton = Tkinter.Label(titleBar, text = "x")
closeButton.pack(side = Tkinter.RIGHT)
closeButton.bind("<Button-1>", lambda event: self.destroy())
self.bind("<KeyPress>", lambda event: print("<KeyPress %s>" % event.char))
self.bind("<Button-1>", lambda event: print("<Button-1>"))
self.bind("<Enter>", lambda event: print("<Enter>"))
self.bind("<Leave>", lambda event: print("<Leave>"))
self.bind("<FocusIn>", lambda event: print("<FocusIn>"))
self.bind("<FocusOut>", lambda event: print("<FocusOut>"))
if __name__ == "__main__":
app = AppWindow()
app.mainloop()
This creates a little window (with no title bar) that prints the name of common events when it receives them. I have run this script on Windows 7, Mac OSX (El Capitan), and Ubuntu 14.04.1. I ran only Ubuntu in a virtual machine (VMWare).
In Windows, this seems to work as expected. All events that my code tests for can be received.
In Ubuntu, the Tkinter window receives <Enter>, <Leave>, and <Button-1> events as expected, but <KeyPress>, <FocusIn>, and <FocusOut> are never received. In fact, even after the window has been clicked on, the last window with focus continues to receive the key presses.
In OSX, the Tkinter window receives <Button-1> events as expected, but <KeyPress>, <FocusIn>, and <FocusOut> are never received. The last window with focus does not continue to receive key presses like in Ubuntu. The <Enter> and <Leave> events behave a little oddly. The <Enter> event is not received until the window is clicked. Then, once the <Leave> event occurs, the window needs to be clicked again to receive another <Enter> event.
I have also tried self.focus_force() just before the end of the __init__ function. This causes the window to receive a <FocusIn> event when the program starts, but no further <KeyPress>, <FocusIn>, or <FocusOut> events are never received.
Ultimately, my question is this: is there any way to hide the title bar but continue to receive keyboard input in OSX and Linux?
I am aware of some other questions dealing with this same problem. In these three questions:
python tkinter overrideredirect; cannot receive keystrokes (Linux)
root.overrideredirect and <Any-KeyPress> binding
How to bind Tkinter destroy() to a key in Debian?
The accepted answer is to use self.attributes('-fullscreen', True), which will not work for me as I want a tiny little window, not a fullscreen application.
There is one other question: Tkinter overrideredirect no longer receiving event bindings. This seems very close to my question, but provided less detail and has no answer.
Update: I have been attempting to investigate the underlying mechanism of my problem. I know that Tkinter is a wrapper around Tcl/Tk, so I thought I would try rewriting my code in Tcl. I don't really know Tcl, but I think I managed to (more or less) translate my Python:
#!/usr/bin/env wish
wm overrideredirect . True
wm geometry . "400x25+100+300"
bind . <KeyPress> {puts "<KeyPress %K>"}
bind . <Button-1> {puts "<Button-1>"}
bind . <Enter> {puts "<Enter>"}
bind . <Leave> {puts "<Leave>"}
bind . <FocusIn> {puts "<FocusIn>"}
bind . <FocusOut> {puts "<FocusOut>"}
I tried the resulting program in Windows and Mac OSX. In Windows I received <KeyPress> events, but in OSX I did not. Without the wm overrideredirect . True line, OSX does receive the <KeyPress> events. Therefore it looks like this problem is not with Python, but with Tcl/Tk.
I have submitted a bug report to Tk for this situation.
You can use the devilspie program to remove the decorations from your window. Use the wm title . myname command to give your window a specific name and use that name in the devilspie configuration fragment below. Remove the overrideredirect command from your program.
I have tested this (as a Tk program), and the undecorated window will still receive the keypress &etc. bindings.
Note that devilspie is written as a daemon process and stays active. The daemon can be killed after it is started and the window changes it made will still be in effect. Or it can be left running, and any time your window is activated, the devilspie configuration will be applied.
(if (is (application_name) "t.tcl")
(begin (undecorate)))

Python Tkinter Menu Command Not Working

I am trying to execute the following code in Python 2.6.5. What I want to do is show a main window with an 'Applications' menu. I want the menu to have a series of commands which should correspond to the keys of the Apps dictionary. When I click the command, I would like the default web browser to open and navigate to the url in the Apps dictionary for that particular key. Instead, when I execute the code the browser is opening to the first url in the Apps dictionary without any clicking. Help please!
from Tkinter import *
import webbrowser
#Real links are to pages on Intranet.
Apps={
'Google':'http://www.google.com/',
'Yahoo':'http://www.yahoo.com/'
}
def openApp(appURL):
webbrowser.open(appURL, new=1, autoraise=1)
return None
root=Tk()
menubar=Menu(root)
root.config(menu=menubar)
appsMenu=Menu(menubar)
for app in Apps:
appsMenu.add_command(label=app, command=openApp(Apps[app]))
menubar.add_cascade(label='Apps', menu=appsMenu)
root.mainloop()
appsMenu.add_command(label=app, command=openApp(Apps[app]))
Command parameters that call functions need to be wrapped in a lambda, to prevent them from being called right away. Additionally, commands bound within a for loop need the looping variable as a default argument, in order for it to bind the right value each time.
appsMenu.add_command(label=app, command=lambda app=app: openApp(Apps[app]))

Python3 Tkinter popup menu not closing automatically when clicking elsewhere

I'm running Python 3.3.3 (and right now I'm on Ubuntu but I also develop on Mac and Windows, which I haven't yet tested). I have a Treeview object that responds to right click on items and shows a context menu depending on what you click... but I've noticed that if you right click somewhere else while the original menu is up, it just opens another one.
In fact, normal clicking doesn't hide them either. Even when I close the window the menus still stay floating. The only way to get them to go away is to click one of the options.
The end result is this:
My code for the menu is as follows:
def rightclick_listitem(self, event):
rowitem = self.sources.identify('item', event.x, event.y)
if rowitem == '':
print('Right clicked an empty space.')
return
# user right clicked something.
self.sources.selection_set(rowitem)
rcmenu = Menu(self.root, tearoff=0)
plugin_disabled=self.sources.item(rowitem, 'values')[0] == 'Disabled'
if plugin_disabled:
rcmenu.add_command(label='Plugin is disabled...',
command=self.plugin_disabled_click)
rcmenu.add_command(label='Plugin options',state='disabled' if plugin_disabled else 'active')
rcmenu.add_command(label='Uninstall plugin')
rcmenu.post(event.x_root, event.y_root)
The code that calls this code is located here:
#RIGHTMOUSE is a variable that changes based on OS due to the way Mac OSX works
#sources is the treeview object
self.sources.bind(RIGHTMOUSE, self.rightclick_listitem)
I googled around and only got some people asking the same question with no answers. I'm still somewhat new to tkinter and python in general, and didn't see anything about this. I bind other actions to the treeview as well.
If you need more sourcecode my project is here: https://github.com/Mgamerz/Fresh-Set-of-Images (freshsetofimages.py)
Any help is appreciated.
And the plugins required to make this appear: https://github.com/Mgamerz/fsoi_plugins
Try calling the method tk_popup rather than post.
Also, your code has a memory leak, in that each time you right-click you're creating a new menu but never destroying the old one. You only ever need to create one, and the reconfigure it before popping it up.
To close the popup menu when click elsewhere, you can add
rcmenu.bind("<FocusOut>",popupFocusOut)
and call unpost in popupFocusOut.
def popupFocusOut(self,event=None):
rcmenu.unpost()

tkinter menu accelerators and modal dialog boxes

I want to have a menu that shows a modal dialog box. Everything is fine, until I add an accelerator. If I do this and use the accelerator to access to dialog, it hangs. I suspect that wait_window, used inside the modal dialog box is somehow in conflict with the mainloop, when called from a "bind". Here is an example:
import tkinter
from tkinter import simpledialog
class App(tkinter.Tk):
def __init__(self):
tkinter.Tk.__init__(self)
self.bind_all("<Control-f>", lambda event: self.menu_file())
menubar = tkinter.Menu(self)
fileMenu = tkinter.Menu(menubar, tearoff=False)
fileMenu.add_command(label="File", underline=0,
command=self.menu_file, accelerator="Control+f")
# fileMenu.add_command(label="File", underline=0,
# command=self.menu_file)
menubar.add_cascade(label="File",underline=0, menu=fileMenu)
self.config(menu=menubar)
def menu_file(self):
simpledialog.Dialog(self,"Message")
app=App()
app.mainloop()
If in the above code I comment out the line that adds the accelerator and uncomment the subsequent line, there is no hangup (I can of course still use Ctrl+F to access the dialog box). The only problem is that the accelerator string is not shown next to the File menu. According to the tkinter documentations on the web that I have found, adding the accelerator should only change how the menu is shown and nothing else, so I am really puzzled. Anyone any ideas? (I could of course emulate accelerators by modifying the strings to be displayed, but I would not consider this as an elegant solution.)
As I have noticed, this is a Mac-specific bug.
Following the workaround suggested for a known Tk bug on Mac (see link), changing the line which binds the menu method to the keystroke to the following:
self.bind_all("<Command-f>", lambda event: self.after(100,self.menu_file))
is "fixing" the bug. They also suggest to increase 100 to 150 on slower systems. Hmm..

Categories