So I have an app in Tkinter that has a lot of buttons in the first screen and when you press one you pass into a new "Window" (basically destroying all widgets and drawing the ones that are needed for the 'window'). There is a standard function that uses some commands to destroy every child on the root. I would like to add some code that can unbind all of the bindings that are made in the root. Bindings that are on specific widgets get destroyed but those that are bind on the root stay there and cause error.
Here's the code for destroying the widgets.
#staticmethod
def clear():
for widget in guihandle.root.winfo_children():
widget.destroy()
#staticmethod
def set(db,table):
guihandle.clear()
curW = Window(db,table)
guihandle.current_Window = curW
curW.initialize()
guihandle.windows.push(curW)
(Yes, I make the base GUI from a sqlite3 database :P)
There is nothing in Tkinter to do what you want. Your app will need to keep track of the bindings it wants to remove.
That being said, depending on just how complex your real problem is, there may be other solutions. For example, instead of binding to the root window, bind to a custom binding tag (also called a bind tag or bindtag). You will then need to add that tag to every widget to enable the bindings, and remove the tag from any existing widgets to effectively disable the bindings.
I know I am VERY late but, if you go through your code and replace every widget.bind with the function below, which was taken and modified from here
def bindWidget(widget: Widget, event, all:bool=False, func=None):
# https://stackoverflow.com/a/226141/19581763
'''Set or retrieve the binding for an event on a widget'''
try:
_ = widget.__dict__['bindings']
except KeyError:
has_binding_key = False
else:
has_binding_key = True
if not has_binding_key:
setattr(widget, 'bindings', dict())
if func:
if not all:
widget.bind(event, func)
else:
widget.bind_all(event, func)
widget.bindings[event] = func
else:
return(widget.bindings.setdefault(event, None))
Than the function will keep track of every binding for you by setting a attribute called bindings which has both, the event and the function inside of it.
For example:
label.bind('<Button-1>', label.destroy)
Will become:
bindWidget(label, '<Button-1>', func=label.destroy)
After doing that, you can write a simple function which deletes all of the bindings and widgets:
def clear(self): # Self will be your Tk() instance
"""Clears everything on the window, including bindings"""
for child in self.winfo_children():
child.destroy()
if not hasattr(self, 'bindings'):
self.bindings = {}
for event, func in self.bindings.items():
self.unbind_all(event)
self.bindings = {}
There are only 2 caveats with this approach
Time
You will have to go through your code to replace every widget.bind and if you have lots of bindings, than it will take a lot of time
Readability
bindWidget(label, '<Button-1>', func=label.destroy) is less readable and less clear than label.bind('<Button-1>', label.destroy)
Edit 8/11/2022
Alternatively, you can destroy the entire window and recreate it like this:
window.destroy()
window = Tk()
However, I am not sure if creating a whole Tcl interpreter is good.
Related
I'm developing an application what have its functions set in different files.
The main file have a tkinter interface and the buttons, entrys and labels are in other file, like this:
Mainfile.py
from tkinter import *
class Program:
def __init__(self, root):
root.geometry('200x200')
self.main_frame = Frame(root)
self.main_frame.pack()
import Buttons
self.branch = Buttons.New_Button(self.main_frame)
#Here i wuold like to verify the hipotetic variable after the main_frame were destroyed
if self.branch.hipotetic_variable:
root.mainloop()
app = Program(Tk())
Buttons.py
from tkinter import *
import functools
class New_Button:
def __init__(self, using_frame):
self.button_1 = Button(using_frame, text = 'Button 1', command=functools.partial(self.Func, using_frame))
self.button_1.pack()
def Func(self, to_destroy):
to_destroy.destroy()
#Here is the hipotetic variable what i would like to verify with if statment
self.hipotetic_variable = True
The problem is that I want to keep managing the program in the main file calling the other functions and implementing it, but I cannot verify if it's time to update the screen because mainloop makes impossible to verify it using a while loop and an hipotetic variable that's created after user pressed button.
I wold like to know if there is an way to update an variable contained in the Buttons.py file on Mainfile.py to keep implementing all other canvas in this file.
Your if self.branch.hipotetic_variable: check in the Program.__init__() method is only going to be executed when the Program class instance gets created initially, which is before the button that could change the value of the variable could have been pressed. You also don't want to make the hipotetic_variable an attribute of the Button because that will be destroyed along with the Frame it is in when that's destroyed in the button callback function.
Tkinter applications are user-event driven, meaning that they're "run" by responding to events (that's what mainloop is all about). This type of programming paradigm is different from the procedural or imperative one you're probably used to.
Therefore to do what you want requires setting things up so an event that the program can respond to will be generated, which in this case to when the frame is destroyed. One way to do that is by taking advantage of tkinter Variable classes to hold this hipotetic variable you're interested in. It looks like a boolean, so I used a tkinter BooleanVar to hold its value. One interesting thing about Variables is that you can have changes to their values "traced" by defining functions to be called whenever that happens. That's what I have done in the code below, and the callback function in this case — check_hipotetic_variable() — updates a Label to display the new value of the variable when it's called.
Below is your code with the modifications necessary to use a tkinter BooleanVar and trace changes to its value.
Mainfile.py
from tkinter import *
import Buttons
class Program:
def __init__(self, root):
root.geometry('200x200')
self.main_frame = Frame(root)
self.main_frame.pack()
self.notice_lbl = Label(root, text='')
self.notice_lbl.pack(side=BOTTOM)
self.hipotetic_variable = BooleanVar(value=False)
# Set up a trace "write" callback for whenever its contents are changed.
self.hipotetic_variable.trace('w', self.check_hipotetic_variable)
self.branch = Buttons.New_Button(self.main_frame, self.hipotetic_variable)
root.mainloop()
def check_hipotetic_variable(self, *args):
"""Display value of the hipotetic variable."""
value = self.hipotetic_variable.get()
self.notice_lbl.config(text=f'hipotetic variable is: {value}')
app = Program(Tk())
Buttons.py
from tkinter import *
import functools
class New_Button:
def __init__(self, using_frame, variable):
self.button_1 = Button(using_frame, text = 'Button 1',
command=functools.partial(self.Func, using_frame))
self.button_1.pack()
self.variable = variable # Save for use in callback.
def Func(self, to_destroy):
to_destroy.destroy()
self.variable.set(True) # # Change value of the variable.
P.S. I noticed you're not following the PEP 8 - Style Guide for Python Code, which makes reading your code harder to read and follow that if you're were following them — for that reason I strongly suggest you read the guide and start following the suggestions, especially the Naming Conventions which apply to functions and variable names, as well as the names of script files.
Goal
I want to create a popup window with buttons that closes when the mouse leaves the window. (It is a submenu of a program).
To do that I have a function that is called if the popup window should be opened. It contains following code:
master = Tk()
fpy.ui.menu.submenu(master=master,[...more parameters...])
mainloop()
and a method (fpy.ui.menu.submenu(...)) to create everything else.
This function should stay as clean as possible. It's part of an API I'm building for my future projects.
def submenu(master,[...more parameters...]):
master.overrideredirect(True)
a_frame = Frame(master, bg="grey")
#[codeblock to generate buttons]
a_frame.bind("<Leave>", destroy)
a_frame.pack()
a_frame.bind("<Leave>", destroy)
calls the function 'function' when the mouse leaves the frame. And now my problem starts. the 'destroy' function should destroy my root (in this case called master).
Problem:
def destroy():
#how can I get the master-object?
master.destroy()
This will cause an error! How can I reach the Object I want to destroy from this method?
Info:
the function 'destroy' gets started
I didn't manage to transmitt the master object as parameter
please ignore the dots at the beginning of a line. the Stack Exchange editor doesn't accept indented code.
Option 1: Pass it as an argument
You could pass it to your destroy() function using a lambda statement:
a_frame.bind("<Leave>", lambda: destroy(master))
Then modify the signature of your destroy function:
def destroy(master):
master.destroy()
Option 2: Invoke the destroy method on master directly
Same lambda statement as before, except now we just call the method directly rather than handling it in another function.
a_frame.bind("<Leave>", lambda: master.destroy())
Of course, this isn't an option if you want to do other stuff in your destroy() function.
I'm working on an interface using Tkinter and the canvas widget, and so far have found answers to issues I have had from others questions and the answers posted, but I am stumped on this one.
I have several keyboard binds in the class where my GUI elements are created, and they all work fine when the program is started. The binds looks something like this:
self.canvas.get_tk_widget().bind("<Control-o>",self.flash_open)
and are within the __init__ function of the class. As of yesterday, I initialized this class
to start the program, then waited for the user to select open from a menu, which then opened (among other things) a tkmessagebox
self.specfilename =askopenfilename(filetypes=[("spec", "")],initialdir= self.pathname)
With this filename I am able to retrieve my required variable names from a certain filetype (inconsequential to the problem). Today I modified the __init__ function to call the open function when the program starts. Since nothing else can be done until this file is opened, it would make sense to open it first thing. Once the file is selected and the Tkmessagebox is closed, the root window is active, but none of the keyboard binds work. My functions still work using the menu/buttons assigned to them, just not the binds. I have tried binding the shortcuts to the root, with the same result, and am now thinking it may be an issue with the order I am calling them
def __init__(self):
...
self.openfile() #calls the tkmessagebox
self.root.mainloop() #starts gui
I had actually run into this issue before, where a toplevel() instance was closed/destroyed and disabled the binds of the parent window. There isn't any error message to speak of, the binds just don't do anything. I should also mention I have tried to focus on the root window again using
self.openfile()
self.root.mainloop()
self.root.focus_set()
I got around it before by using the wm_withdraw() and wm_deiconify() functions to simply hide the child window, then close it after the program is complete. This fix is a little more difficult to apply in this case however. If anyone can shed some light on the cause of the problem I'd appreciate it.
Edit:
I've written up a runable code segment to show exactly what my issue is.
import os
from tkFileDialog import askopenfilename
from Tkinter import *
class Start:
def __init__(self):
self.root = Tk()
self.root.title('Binding Troubles')
menubar = Menu(self.root)
#add items and their commands to the menubar
filemenu = Menu(menubar, tearoff=0)
filemenu.add_command(label="Do work", command=self.do_work)
filemenu.add_command(label="Open File",command=self.openfile)
menubar.add_cascade(label="File", menu=filemenu)
#bind control-o to perform the do work function
self.root.bind("<Control-o>",self.flash_do_work)
self.root.bind("<Control-O>",self.flash_do_work)
#add the menubar to the GUI
self.root.config(menu=menubar)
#initially open a tkdialog to open a file
self.openfile()#comment out this line to make the bind work
self.root.focus()#also tried self.root.focus_set()
self.root.mainloop()
def flash_do_work(self,event):
#indirect tie to the do_work() function, I'm don't know a
#proper way to make functions handle calls from both events and non-events
self.do_work()
def openfile(self):
#gets current path
self.pathname = os.getcwd()
#Requests filename using a tkdialog
self.filename =askopenfilename(initialdir= self.pathname)
print self.filename
def do_work(self):
#placeholder for actual function; shows whether the bind is working or not
print "work"
Start()
The bind will work if self.openfile() is removed from __init__, and used only from the menu
Another Edit: I've updated the example again, giving a menu option to run the openfile() function. I noticed that if openfile() is called in __init__, the bind will not work. But if next the openfile function is called again, this time manually from the menu, the bind will start working again. Not exactly sure what to take from this. Also, my apologies for the post getting so long.
Change
self.openfile()
to
self.root.after(1, self.openfile)
This moves the call to askopenfilename into the main event loop. Having it outside the main event loop is somehow clobbering your event bindings.
I had this kind of problem a couple of times and it took quite a while until I found a solution I was comfortable with. As #Steven Rumbalski suggests I tried with delaying the application, which works but seems shaky.
Then I found the functions for waiting until something is complete, in this case wait_visibility(widget). This will delay execution until the widget is visible, which seems to be the thing to be waiting for. Try this:
self.root.wait_visibility(self.root) # Wait for root to be displayed
self.openfile()
Now; I'm not sure why this is so, and it seems that there may be differences depending on platform: Tkinter window event . This has nevertheless worked for me on Windows10 and Python 3.10.5.
I'm proposing a continuation of the discussion in disable tkinter keyboard shortcut: I have an event handler for an event that Tkinter also uses, so that my prog & Tkinter interact badly.
Since it is a problem that I've been unable to solve I'm re-proposing here, where I tried to boil it down to the simplest form in the following code:
#!/usr/bin/env python
from Tkinter import *
import tkFont
def init():
global root,text
root = Tk()
root.geometry("500x500+0+0")
dFont=tkFont.Font(family="Arial", size=10)
text=Text(root, width=16, height=5, font=dFont)
text.pack(side=LEFT, fill=BOTH, expand = YES)
root.bind("<Control-b>", setbold)
text.tag_config("b",font=('Verdana', '10', 'bold' ))
text.tag_config("i",font=('Verdana', '10', 'italic' ))
def removeformat(event=None):
text.tag_remove('b',SEL_FIRST,SEL_LAST)
text.tag_remove('i',SEL_FIRST,SEL_LAST)
def setbold(event=None):
removeformat()
text.tag_add('b', SEL_FIRST,SEL_LAST)
text.edit_modified(True)
def main():
init()
mainloop()
if __name__ == '__main__':
main()
What it should do is simply to produce a text window where you write into.
Selecting some text and pressing Ctrl+B the program should remove any preexisting tag, then assign to it the 'b' tag that sets the text to bold.
What instead happens is an exception at the first tag_remove, telling me that text doesn't contain any characters tagged with "sel".
The suggestion to use a return 'break' is of no use, since the selection disappears before setbold() has any chance to act...
Set your binding on the text widget, not on the root. (Whole toplevel bindings are processed after widget class bindings – where the standard <Control-Key-b> binding is – and those are processed after the widget instance bindings, which is what you want to use here.) And you need to do that 'break'; it inhibits the subsequent bindings. (If you're having any problems after that, it's probably that the focus is wrong by default, but that's easy to fix.)
The only other alternative is to reconfigure the bindtags so that class bindings are processed after toplevel bindings, but the consequences of doing that are very subtle and far-reaching; you should use the simpler approach from my first paragraph instead as that's the normal way of handling these things.
Bindings are handled in a specific order, defined by the bindtags of that widget. By default this order is:
The specific widget
The widget class
The toplevel window
The special class "all"
If there are conflicting bindings -- for example, a control-b binding on both the widget and class -- they both will fire (in the described order) unless you break the chain by returning "break".
In the case of the code you posted, however, you are binding to the toplevel window (ie: the root window), and the conflicting binding is on the class. Therefore, the binding will fire for the class before it is processed by the toplevel, so even if your binding returned "break" it wouldn't matter since the class binding happens first.
The most straight-forward solution is to move your binding to the actual widget and return "break". That will guarantee your binding fires first, and the return "break" guarantees that the class binding does not fire.
If you really want your binding on the root window, you can remove the binding for the class using the bind_class method with the value of "Text" for the class.
You might find the Events and Bindings page on effbot.org to be useful.
The problem I'm running into here is that, when I click on the different file names in the Listbox, the Label changes value one click behind whatever I'm currently clicking on.
What am I missing here?
import Tkinter as tk
class TkTest:
def __init__(self, master):
self.fraMain = tk.Frame(master)
self.fraMain.pack()
# Set up a list box containing all the paths to choose from
self.lstPaths = tk.Listbox(self.fraMain)
paths = [
'/path/file1',
'/path/file2',
'/path/file3',
]
for path in paths:
self.lstPaths.insert(tk.END, path)
self.lstPaths.bind('<Button-1>', self.update_label)
self.lstPaths.pack()
self.currentpath = tk.StringVar()
self.lblCurrentPath = tk.Label(self.fraMain, textvariable=self.currentpath)
self.lblCurrentPath.pack()
def update_label(self, event):
print self.lstPaths.get(tk.ACTIVE),
print self.lstPaths.curselection()
self.currentpath.set(self.lstPaths.get(tk.ACTIVE))
root = tk.Tk()
app = TkTest(root)
root.mainloop()
The problem has to do with the fundamental design of Tk. The short version is, bindings on specific widgets fire before the default class bindings for a widget. It is in the class bindings that the selection of a listbox is changed. This is exactly what you observe -- you are seeing the selection before the current click.
The best solution is to bind to the virtual event <<ListboxSelect>> which is fired after the selection has changed. Other solutions (unique to Tk and what gives it some of its incredible power and flexibility) is to modify the order that the bindings are applied. This involves either moving the widget bindtag after the class bindtag, or adding a new bindtag after the class bindtag and binding it to that.
Since binding to <<ListboxSelect>> is the better solution I won't go into details on how to modify the bindtags, though it's straight-forward and I think fairly well documented.