How to access a widget in another widget event handler : Tkinter - python

I am creating a GUI in tkinter having a listbox and a Text item in a child window which appears after a button click. Listbox is displaying values of a dict which are basically names of files/directories in disk image.
I want to change the text of Text widget on <ListboxSelect> event and display type or path of selected file.
Now I cant make Text global since it has to appear on child window, so I need a way to access it in event handler of Listbox. Can I give handler reference of Textbox?
Here is my code;
def command(event):
... #Need to change the Text here, how to access it?
def display_info(dict,filename):
child_w = Tk()
listbox = Listbox(child_w)
textview = Text(child_w)
...
listbox.bind(<ListboxSelect>,command)
def upload_file():
window = Tk()
upl_button = Button(command=upload_file)
window.mainloop()
Is there a way to create a textview as global and then change its properties later to be displayed in child_window etc.

Two solutions here that I can think of is to make textview a globalized variable or to pass textview to command() as an argument.
Parameter solution:
def command(event,txtbox):
txtbox.delete(...)
def display_info(dict,filename):
child_w = Tk()
listbox = Listbox(child_w)
textview = Text(child_w)
...
listbox.bind('<ListboxSelect>',lambda event: command(event,textview))
Or simply just globalize it:
def command(event):
textview.delete(...)
def display_info(dict,filename):
global textview
child_w = Tk()
listbox = Listbox(child_w)
textview = Text(child_w)
...
listbox.bind('<ListboxSelect>',command)
While saying all this, it is good to keep in mind that creating more than one instance of Tk is almost never a good idea. Read: Why are multiple instances of Tk discouraged?

Related

Is there a way to reopen a window after closing it using destroy() in tkinter?

window6.after(1,lambda:window6.destroy())
is what I've been using to close my windows, is there any way to get them back after doing this?
basically, is there something that is the opposite of this?
ps. these are the libraries that I've imported, if it helps in any way
import tkinter as tk
from tkinter import *
import time
from tkinter import ttk
Is there a way to reopen a window after closing it using destroy() in tkinter?
The short answer is "no". Once it has been destroyed, it is impossible to get back. You should either create the window via a frame or class so that it's easy to recreate, or hide the window by calling .withdraw() rather than .destroy().
If you put the window code into a class or a function then after destroying it you can create a new instance of it by
1: creating a new instance of the class with the window code in the init function
2: call the function the has the code for the window
By doing this you are essentially creating a new instance of the program, but without initiating the script.
from tkinter impot *
from tkinter import ttk
#creating window function, not class
def main_window():
#window code here
root = Tk()
Label(root, text = "Hello World").pack()
#destroying main window
root.destroy()
root.mainloop()
main_window()
Of course, there are a few hurdles such as the window shutting down as soon as it opens, but this is to show that you can create a new instance of a window from your program.
You can wait for user input to see whether or not the window will open or close.
If you took an OOP approach, you can pass a reference to the Parent Widget as argument to the New_Window and store it in a class attribute.
You´ll have a two way reference: Parent knows child and child knows parent.
Then you can set the Parent Reference to the New_Window to None, from within the child Widget self.parent.new_window = None in a close_me() method right after you call self.destroy() on the New_Window:
1st Bonus: this code prevents the opening of more than 1 instance of a Window at a time. You won´t get more than 1 New_Window on the screen. I don´t think having two loggin windows opened or two equal options window makes sense.
2nd Bonus: It is possible to close the window from other parts of the code, as in a MVC patter, the Controller can close the window after doing some processing.
Here´s a working example:
import tkinter as tk
class Toolbar(tk.Frame):
'''Toolbar '''
def __init__(self, master, *args, **kwargs):
super().__init__(master, *args, **kwargs)
# to store the New Window reference
self.new_window = None
self.button_new_window = tk.Button(self, text = 'New Window', command = lambda : self.get_window(self))
self.configure_grid()
def configure_grid(self):
'''Configures the Grid layout'''
self.grid(row=1, column=0, columnspan=3, sticky=(tk.N,tk.S,tk.E,tk.W))
self.button_new_window.grid(row = 2, column = 2, padx=5, pady=5)
def get_window(self, parent):
''' If window exists, return it, else, create it'''
self.new_window = self.new_window if self.new_window else Window(parent)
return self.new_window
class Window(tk.Toplevel):
'''Opens a new Window.
#param parent -- tk.Widget that opens/reference this window
'''
def __init__ (self, parent : tk.Widget):
# Stores reference to the Parent Widget, so you can set parent.new_window = None
self.parent = parent
super().__init__(master = parent.master)
self.title('New Window')
self.button_dummy = tk.Button(self, text = 'Do the thing', width = 25, command = lambda : print("Button pressed on window!"))
self.button_close = tk.Button(self, text = 'Close', width = 25, command = self.close_me)
self.configure_grid()
def configure_grid(self):
'''Grid'''
self.button_dummy.grid(row = 1, column = 0)
self.button_close.grid(row = 2, column = 0)
def close_me(self):
'''Tkinter widgets are made of two parts. 1. The python Object and 2. The GUI Widget.
The destroy() method gets rid of the widget part, but leaves the object in memory.
To also destroy the object, you need to set all of its references count to ZERO on
the Parent Widget that created the new Window, so the Garbage Collector can collect it.
'''
# Destroys the Widget
self.destroy()
# Decreasses the reference count on the Parent Widget so the Garbage Collector can destroy the python object
self.parent.new_window = None
if __name__ == '__main__':
root = tk.Tk()
toolbar = Toolbar(root)
root.mainloop()
I don´t know if this: .destroy() and re-instantiate approach is more efficient than the .withdraw() and .deiconify(). Maybe if you have a program that runs for long periods of time and opens a lot of windows it can be handy to avoid stackoverflow or heapoverflow.
It sure frees up the object reference from memory, but it has the additional cost of the re-instantiation, and that is processing time.
But as David J. Malan would say on CS50, “There´s always a tradeoff”.

Copy Entry widget text and paste into another Entry widget in the same window

How do I copy Entry widget text and paste into another Entry widget in the same window.
i.e. Let's imagine you are completing a joint credit application but you and the co-applicant have the same mailing address. Instead of re-typing the same address over again, there should be a checkbutton on the application that when it's checked it will auto-populate the co-applicant's mailing address with the main applicant's address. How do I get this done in tkinter? (I'm a tkinter and python rookie)
thanks in advance DP​
There can probably a couple more ways of achieving the behavior you want. I think using the textvariable option and Variable Classes fits well here. Together, they let a widget's text to be the same as another at all times. With the Checkbutton the user decides whether or not to do that:
try: # In order to be able to import tkinter for
import tkinter as tk # either in python 2 or in python 3
except ImportError:
import Tkinter as tk
def on_selection(copy_widget, paste_widget, condition_widget):
if condition_widget.var.get():
paste_widget['textvariable'] = copy_widget['textvariable']
else:
paste_widget['textvariable'] = ''
def create_entry_widgets(master):
entries = list()
for i in range(2):
entries.append(tk.Entry(master))
entries[-1].pack()
entries[0].var = tk.StringVar()
entries[0]['textvariable'] = entries[0].var
return entries
def create_checkbutton(master, entries):
checkbutton = tk.Checkbutton(master, text="Copy?")
checkbutton.var = tk.BooleanVar(value=False)
checkbutton.config(variable=checkbutton.var, onvalue=True, offvalue=False)
checkbutton['command'] = lambda cw=entries[0], pw=entries[1], \
cdw=checkbutton: on_selection(cw, pw, cdw)
checkbutton.pack()
return checkbutton
def main():
root = tk.Tk()
entries = create_entry_widgets(root)
checkbutton = create_checkbutton(root, entries)
tk.mainloop()
if __name__ == '__main__':
main()

Python Tkinter: how to get text from ScrolledText widget on <Destroy> event callback?

I understand ScrolledText is constructed as a Text object (but has a scrollbar attached together in a frame). But the following code throws an error when the window is closed and the printText() method is called:
import Tkinter as tk
import ttk
import ScrolledText as st
class tkGui(object):
def printText(self, event):
print "It works!"
self.mText.get("1.0", 'end-1c')
def __init__(self, window):
# create widgets
self.frame=tk.Frame(window)
self.mText = st.ScrolledText(self.frame)
self.mText.bind('<Destroy>',self.printText)
# place widgets
self.frame.pack()
self.mText.pack()
window = tk.Tk()
app = tkGui(window)
window.mainloop()
The error:
[...]
File "/usr/lib/python2.7/lib-tk/Tkinter.py", line 3077, in get
return self.tk.call(self._w, 'get', index1, index2)
TclError: invalid command name ".140506094171344.140506094172280.140506094172496"
What am I doing wrong?
Once you destroy the main window, all its sub-widgets are destroyed, and you can't access them. (This is not technically correct, as Bryan Oakley's answer points out, but it's how I think about it. Trying to work with the widgets after destruction is problematical, at best.)
You need to use wm_protocol
http://nullege.com/codes/search/Tkinter.Tk.wm_protocol
import Tkinter as tk
import ttk
import ScrolledText as st
class tkGui(object):
def printText(self):
print "It works!"
self.mText.get("1.0", 'end-1c')
self.window.destroy()
def __init__(self, window):
# create widgets
self.window = window
self.frame=tk.Frame(window)
self.mText = st.ScrolledText(self.frame)
#self.mText.bind('<Destroy>',self.printText)
window.wm_protocol("WM_DELETE_WINDOW", self.printText)
# place widgets
self.frame.pack()
self.mText.pack()
window = tk.Tk()
app = tkGui(window)
window.mainloop()
There are a couple of changes here. I removed the event parameter from printText and added a call to self.window.destroy. Note that I had to add the self.window attribute to make this call. The basic change is using wm_protocol instead of binding to the event; the others are necessary consequences.
You should not assume you can get data out of a widget when handling the <Destroy> event for that widget.
From the official documentation:
When the Destroy event is delivered to a widget, it is in a
“half-dead” state: the widget still exists, but most operations on it
will fail.

Prevent multiple toplevels from opening?

I have a object oriented tkinter program set up.
I have initialized a variable to store Toplevel() in as
self.toplevel = None
Then when I create the actual Toplevel window I simply assign it to the variable:
self.toplevel = Toplevel()
The thing is...when the Toplevel() window is closed, the value still remains in the variable self.toplevel. How can I reset the variable back to None after closing the window so that I can perform a check:
if (self.toplevel == None):
self.toplevel = Toplevel()
Or are there any other methods to prevent multiple Toplevel Windows from opening?
Check this How do I handle the window close event in Tkinter?
Assign the value None to self.toplevel after the Toplevelcloses usign a callback function TopCloses. For this, write a method within the GUI class to access the toplevel attribute and set it's value to None inside the callback function.
In your main program,
def TopCloses():
top.destroy()
#Call the setTopLevel method and assign the attribute toplevel value None
guiObject.setTopLevel(None)
top.protocol("WM_DELETE_WINDOW", TopCloses)
root.mainloop()
Here is my solution:
#somewhere in __init__ make
self.window = None
#I took this piece of code from my bigger app and I have a function
#self.makevariables(), which is called in init, which contains the line above.
def instructions(self):
if self.window == None: #here I check whether it exists, if not make it, else give focus to ok button which can close it
self.window = Toplevel(takefocus = True)
#some optional options lol
self.window.geometry("200x200")
self.window.resizable(0, 0)
#widgets in the toplevel
Label(self.window, text = "NOPE").pack()
self.window.protocol("WM_DELETE_WINDOW", self.windowclosed) #this overrides the default behavior when you press the X in windows and calls a function
self.okbutton = Button(self.window, text = "Ok", command = self.windowclosed, padx = 25, pady = 5)
self.okbutton.pack()
self.okbutton.focus()
self.okbutton.bind("<Return>", lambda event = None:self.windowclosed())
else:
self.okbutton.focus() #You dont need to give focus to a widget in the TopLevel, you can give the focus to the TopLevel, depending how you want it
#self.window.focus() works too
def windowclosed(self): #function to call when TopLevel is removed
self.window.destroy()
self.window = None
These are all overly complicated solutions imo.
I just use win32gui as such:
toplevel_hwid = win32gui.FindWindow(None, '<Top Level Window Title>')
if toplevel_hwid:
print(f'Top level window already open with HWID: {toplevel_hwid}')
win32gui.SetForegroundWindow(toplevel_hwid)
return
else:
<create new top level>
Simple, easy, and gives you the flexibility to close, move, focus, etc.

TKInter checkbox variable is always 0

I'm using Python's TkInter module for a GUI. Below is a simple checkbox code.
def getCheckVal():
print cbVar.get()
windowTime=Tk.Tk()
cbVar = Tk.IntVar()
btnC = Tk.Checkbutton(windowTime, text="Save", variable = cbVar, command=getCheckVal)
btnC.grid()
windowTime.mainloop()
This code works fine. Each time I tick the checkbox, I get 1, else 0.
However, when I run the same code in a function that is called from another TkInter command (when a button is pressed), it stops working. I always get 0 as the value.
class GUIMainClass:
def __init__(self):
'''Create the main window'''
self.window = Tk.Tk()
def askUser(self):
def getCheckVal():
print cbVar.get()
windowTime=Tk.Tk()
cbVar = Tk.IntVar()
btnC = Tk.Checkbutton(windowTime, text="Save", variable = cbVar,
command=getCheckVal)
btnC.grid()
windowTime.mainloop()
def cmdWindow(self):
frameShow=Tk.Frame(self.window)
frameShow.grid()
btnSwitch = Tk.Button(frameShow, text='Show Plots', command=self.askUser)
btnSwitch.grid()
self.window.mainloop()
GUIObj=GUIMainClass()
GUIObj.cmdWindow()
This is very unusual. What could be going wrong?
EDIT: I've used 2 mainloops because I want a separate window (windowTime) to open up when I click "Show Plots" button. This new window should have the checkbox in it.
Your windowTime, cbVar, etc. variables are defined in the function's local scope. When askUser() completes execution, those values are thrown away. Prepend self. to them to save them as instance variables.
There should only be one mainloop() in your program, to run the main Tkinter root object. Try putting it as the very last line in the program. I recommend doing some reading on Effbot for how to set up a Tkinter application.
I'm not sure what all you're trying to do, but one problem is that the TK.IntVar called cbVar that you create in your askUser() method will be deleted when the function returns, so you need to attach it to something that will still exist after that happens. While you could make it a global variable, a better choice would be to make it an attribute of something more persistent and has a longer "lifespan".
Another likely issue is that generally there should only be one call to mainloop() in a single Tkinter application. It appears what you want to do is display what is commonly known as a Dialog Window, which Tkinter also supports. There's some standard ones built-in, plus some more generic classes to simplify creating custom ones. Here's some documentation I found which describes them in some detail. You may also find it helpful to look at their source code.
In Python 2 it's in the /Lib/lib-tk/tkSimpleDialog.py file and
in Python 3 the code's in a file named /Lib/tkinter/simpledialog.py.
Below is code that takes the latter approach and derives a custom dialog class named GUIButtonDialog from the generic one included the Tkinter library which is simply named Dialog.
try:
import Tkinter as Tk # Python 2
from tkSimpleDialog import Dialog
except ModuleNotFoundError:
import tkinter as Tk # Python 3
from tkinter.simpledialog import Dialog
class GUIButtonDialog(Dialog):
"""Custom one Button dialog box."""
def __init__(self, btnText, parent=None, title=None):
self.btnText = btnText
Dialog.__init__(self, parent, title)
def getCheckVal(self):
print(self.cbVar.get())
def body(self, master):
"""Create dialog body."""
self.cbVar = Tk.IntVar()
self.btnC = Tk.Checkbutton(master, text=self.btnText, variable=self.cbVar,
command=self.getCheckVal)
self.btnC.grid()
return self.btnC # Return the widget to get inital focus.
def buttonbox(self):
# Overridden to suppress default "OK" and "Cancel" buttons.
pass
class GUIMainClass:
def __init__(self):
"""Create the main window."""
self.window = Tk.Tk()
def askUser(self):
"""Display custom dialog window (until user closes it)."""
GUIButtonDialog("Save", parent=self.window)
def cmdWindow(self):
frameShow = Tk.Frame(self.window)
frameShow.grid()
btnSwitch = Tk.Button(frameShow, text='Show Plots', command=self.askUser)
btnSwitch.grid()
self.window.mainloop()
GUIObj = GUIMainClass()
GUIObj.cmdWindow()

Categories