Python3 Tkinter popup menu not closing automatically when clicking elsewhere - python

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

Related

Work with different tabs, of the Notebook tkinter

I have a question or problem, maybe there is an answer, but I still can't understand. I have an application, which works with several Notebook tabs
When I click on any button, it calls me a function that in turn works with a class where it creates me, the tab and the widgets:
def open_issuesDeviation (self):
global deviation deviation =
Deviation (self.root)
self.book.add (deviation, text = 'Issues DEVIATIONS')
self.book.select (deviation)
deviation.DESVfr1_entModulo.focus ()
There is even great, I do my tasks I can open,. Tabs and work on them:
My problem is that in the menu bar or in the contextual menu of the right click, when hitting search, call a simple function that does:
def search (self, event):
self.DESVfr1_entModulo.focus()
It is to activate the focus in the entry, when opening each tab and doing CTRL F, or searching in the menu, it manages to activate the focus, the problem is when I return to a previous tab, this no longer works for me, and I have seen that It is because perhaps in each tab, it will be created with a different name to the widget:
Tab 1:
.! Deviation.! Labelframe.! Entry
Tab 2:
.! Deviation2.! Labelframe.! Entry
I think I understand that the last tab is always active.
I have seen that there is a lot of talk about link tags, and I think it may be the solution but I don't understand how I can implement it in my case.

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

python tkinter Toplevel .destroy() vs .quit() not working as intended

I have a class Duplicates that checks for duplicates within 40 words.
I have a class Window that creates and runs the main window where i post the result.
I have a class popWindow that creates a Toplevel window when asking user for what to do with a possible double.
My problem is closing the popWindow once a choice is submited.
the version I have that actualy runs and posts an aswer (the text with marked duplicates) uses quit to terminate the window (meaning the popup is still there in the way) or to simply have multiple popups till you are done.
class Duplicates:
def markWord(self):
self.appendMarkedWord(self.word)
self.checked.append(self.word)
self.pop.topLevel_exit()
return ""
class popUpWindow:
temp = Button( self, font = 8,
text = "Allowed this run only",
command = app.newFile.markWord
)
temp.place( x = 178,
y = 55
)
if I instead use .destroy() the window shuts but the program stops running and that is worse.
How do i work around this so it shuts the window but still continues to run the program?
Ok, after many many hours it seemed the real problem was destroy() was not stopping my popUpWindow.mainloop() so I now have altered my exit code to first do quit() and then do destroy(). This is not what i have seen as examples at all and it seems to me that destroy() on toplevel mainloop is not terminating it (destroy() works fine on my root.mainloop).
def topLevel_exit(self):
self.pop.quit()
self.pop.destroy()
If you call destroy() on a toplevel window, it will not stop the application from running. If your application stops, there must be more to your code that what you're telling us. Without question, the right way to get rid of the popup is to call destroy on the instance of the Toplevel.
A way to hide the window and keep the program running would be to use .withdraw() on the window, and .reiconify() to get it back (if needed). Or you could use .destroy() on a Toplevel window. If you need examples just ask, hope this helps you.
The solution for me was:
def topLevel_exit(self):
self.top.quit()
self.top.destroy()
I do not know if this is common praxis but is what I had to do since destroy was not stoping my top.mainloop()
If you use a topLevel window, self.pop.destroy() should still work as you are using mainloop()
Otherwise use quit() or both but in my opinion of all of these, I prefer destroy()

Event handler for left-click on Notebook tabs in Tkinter

In Tkinter, I want to make it so when I click one or more times on an already open/selected tab of a Notebook object so that it does not take the focus (or so that it gives the focus back to the Text widget in the window).
How do I do this?
If there's an event handler for clicking on tabs, that would more than suffice.
Also, if there's an ability to make it so widgets will take events, but not take the focus, that would be great.
I already know about the virtual event <<NotebookTabChanged>>. However, I'm talking about when you click on an already selected tab. So, the tab isn't changing. I tried just binding the <Button-1> event to the Notebook widget, but it didn't do anything.
Since making Notebooks isn't common knowledge to everyone who uses Tkinter, here's an example of how to make a minimal Notebook with tabs. I don't know why the text is cut off in the final tab here, though (but it's not in my full code):
from tkinter import *;
from tkinter.ttk import *; #Notebook comes from this
class Editor:
def __init__(self):
self.tk=Tk();
self.tabs=0;
self.frame=Frame(self.tk);
self.nb=Notebook(self.frame);
self.frame.pack();
for x in range(5):
self.add_tab();
self.nb.pack();
self.tk.mainloop();
def add_tab(self):
newTabFrame=Frame(self.nb);
text=Text(newTabFrame); #Just a sample Text widget to go in each tab
text.pack();
if self.tabs==0:
self.nb.add(newTabFrame, text=str(self.tabs), compound=TOP);
else:
self.nb.add(newTabFrame, text=str(self.tabs));
self.tabs+=1;
if __name__ == '__main__':
e=Editor();
try:
e.tk.destroy();
except:
pass;
Okay, obviously I was doing something wrong, because when I tested out what I thought I already tested out on my larger code on the example code I used in my question, it actually worked. Then, I discovered that this works on my larger code, too. Huh. I think I must have been using bind_all instead of bind (whereas I thought I had attempted both), because that really doesn't work. EDIT: Actually, I was attempting to use it without return "break" and while that partially works, it doesn't work after you click on a new tab when you directly click on it again. Plus, setting the focus in my answer here with return "break" won't solve the problem (although it does answer my question), because it will set the focus on the current tab before it switches tabs. I'll figure something out, I'm sure. EDIT: Changing Button-1 to ButtonRelease-1 (or some such) fixes the problem. The Motion binding with the same handler, has potential, too, but if you have Toplevel windows popping up, this will take the focus from them if you move the mouse pointer over your tab.
Here's the working code:
from tkinter import *;
from tkinter.ttk import *; #Notebook comes from this
from tkinter.messagebox import *;
class Editor:
def __init__(self):
self.tk=Tk();
self.tabs=0;
self.frame=Frame(self.tk);
self.nb=Notebook(self.frame);
self.nb.bind("<ButtonRelease-1>", self.test);
self.frame.pack();
for x in range(5):
self.add_tab();
self.nb.pack();
self.tk.mainloop();
def add_tab(self):
newTabFrame=Frame(self.nb);
text=Text(newTabFrame);
text.pack();
if self.tabs==0:
self.nb.add(newTabFrame, text=str(self.tabs), compound=TOP);
else:
self.nb.add(newTabFrame, text=str(self.tabs));
self.tabs+=1;
def test(self, event=None):
showinfo("Success", "It works!");
#Imagine the code for selecting the text widget is here.
return "break";
if __name__ == '__main__':
e=Editor();
try:
e.tk.destroy();
except:
pass;

Determining what tkinter window is currently on top

I have written an application in python 2.7 and tkinter. I created a tool bar with several buttons that open up respective top windows that display various options. I used ttk.Checkbutton with the 'toolbutton' style as an indicator to show whether the option windows are open or closed.
The problem is that the option windows will go to the back if another window is selected. Currently, if one selects the toolbutton again, the option window will close. However, I only want to close the window if it is on top. If the option window is not on top, I want the window to moved to the front.
Some of the code I have working:
class MainWindow:
def __init__(self,application):
self.mainframe=tk.Frame(application)
application.geometry("900x600+30+30")
self.otherOptionsSelect=tk.IntVar()
self.otherOptions_Button=ttk.Checkbutton(application,style='Toolbutton',variable=self.otherOptionsSelect,
onvalue=1, offvalue=0,image=self.optionsIcon, command=self.otherOptions)
def otherOptions(self):
if self.otherOptionsSelect.get()==0:
self.otherOptions.destroy()
return
self.otherOptions=tk.Toplevel()
self.otherOptions.title("IsoSurface Options")
self.otherOptions.geometry("200x165+"+str(int(application.winfo_x())+555)+"+"+str(int(application.winfo_y())+230))
self.otherOptApply_button=ttk.Button(self.otherOptions,text="Apply",command=self.showFrame)
self.otherOptApply_button.place(x=20,y=80,width=50,height=30)
self.otherOptClose_button=ttk.Button(self.otherOptions,text="Close",command=self.otherOptionsClose)
self.otherOptClose_button.place(x=80,y=80,width=50,height=30)
def otherOptionsClose(self):
self.otherOptionsSelect.set(0)
self.otherOptions.destroy()
Here is a picture of the entire application I have written:
In the above image, each window has their respective ttk.checkbutton. At the moment, toggling the checkbutton either opens or closes the window. However, what I really want it to do is close the window if the window is in front of the application, or bring the window to the front if it is behind the application.
Hopefully this clears some things up.
Thanks in advance!
It is in fact possible to check stacking order of windows. Using Tkinter, you have to do some funny tcl evals to get at the information. I found the answer at TkDoc in the section on Windows and Dialogs, scroll down until you get to "Stacking Order". The code baffled me until I started playing around with it interactively. My test code was:
import Tkinter as tk
root = tk.Tk()
root.title('root')
one = tk.Toplevel(root)
one.title('one')
two = tk.Toplevel(root)
two.title('two')
I then manipulated the windows so that two was on top, one under that and root below them all. In that configuration, the following weirdness can tell you relative layering of windows:
root.tk.eval('wm stackorder '+str(two)+' isabove '+str(root))
returns 1, meaning "Yes, window two is above window root." While the following:
root.tk.eval('wm stackorder '+str(root)+' isabove '+str(two))
returns 0, meaning "No, window root is not above window two." You can also use the command:
root.tk.eval('wm stackorder '+str(root))
Which gives back the full window stacking order in the form of a weird string something like this:
'. .68400520L .68401032L'
Which starts to make sense when you run the commands:
str(root)
str(one)
str(two)
and figure out that root has the internal name '.', one is '.68400520L' and two is '.68401032L'. You read the output of root.tk.eval('wm stackorder '+str(root)) backwards so it's saying two is on top, one is under that and root is below both.

Categories