Python forget and destroy frame not working - python

I have a program where I check if the user has an active network connection or not. If not, the program displays a frame that says to turn on the internet connection. The the program checks to see if the state has changed, if so, the login screen is shown. But I can't get rid of the noNetworkConnectionScreen.
The loadFrame is the frame that shows the 'splashscreen'.
class AppUI(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.parent = parent
self.parent.title("Redux")
self.loadFrame = Frame(self)
self.initLogin()
def initLogin(self):
internet_is_on = False
#check 3 times to be sure
for i in range(3):
#check the internet connection by pinging to google (returns True or False)
if db.internetOn():
internet_is_on = True
if internet_is_on:
self.initLoginScreen()
else:
self.initLoadScreen()
thread1 = threading.Thread(target=self.checkNetwork)
thread1.start()
I also tried grid_forget, which crashes:
def checkNetwork(self):
internet_is_off = True
while internet_is_off:
if db.internetOn():
internet_is_off = False
self.loadFrame.pack_forget()
self.loadFrame.destroy()
self.initLoginScreen()
else:
time.sleep(2)
And here I initialize the loadscreen:
def initLoadScreen(self):
self.loadFrame.grid(row=0, column=0, pady=(150,0))
self.lblUser = Label(self.loadFrame, text="HI", font=('Arial', 60), foreground="#666666")
self.lblUser.grid(row=0, column=0)
self.canvas = Canvas(self.loadFrame, width = 121, height = 81)
self.canvas.grid(row=1, column=0)
self.loader = PhotoImage(file = 'loader.gif')
self.canvas.create_image(121, 81, image = self.loader, anchor = NW)
self.lblNetwork = Label(self.loadFrame, text="please make sure you have an active network connection", font=('Arial', 16), foreground="#666666")
self.lblNetwork.grid(row=2, column=0)
I can see that the letters from lblNetwork are cut off at the sides, but the frame doesn't disappear. Any ideas?

Destroying a widget and/or "forgetting" it are the right ways to remove a widget from the screen. Almost certainly, the problem has to do with your use of threading. Tkinter isn't thread safe, and should only be used in one thread. If you create widgets in one thread, you shouldn't try to use them from any other thread. In this case you're trying to destroy a frame from a thread other than where it was created.
What you'll need to do is set up some sort of communication between the threads -- a queue or a shared (non-Tkinter) variable. Since you are simply checking a boolean flag, a simple shared variable will work. In your main program you can check for this variable using a simple after-based loop in your main thread:
def check_network(self):
if the_network_is_down:
<display a message>
else:
<remove the message>
# check once a second
self.after(1000, self.check_network)
Also, if you call destroy on a widget, there's no need to call grid_forget or pack_forget -- once the widget is destroyed there's nothing else you need to do.

Could it be that you have to force the screen refresh ?
pyGTK sometimes needs this (but are you using pyGTK ?) :
while gtk.events_pending(): # this forces the refresh of the screen
gtk.main_iteration()

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”.

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.

How do I make a timer run in a Tkinter widget while a process, started by the widget, is running?

So, I used Tkinter to create a widget that allows the user to input some information and click the run button, which will begin running a test that is defined elsewhere. Here is the code. It is far from perfected, this is just a prototype:
from tkinter import*
import controller
root = Tk()
#create labels
label = Label(text = "text you don't need to know")
label.pack()
remind = Label(text = "more text you don't need to know")
remind.pack()
#create text fields
name = Entry(root)
name.pack()
name.insert(0, "Name")
name.focus_set()
testName = Entry(root)
testName.pack()
testName.insert(0, "Test name")
duration = Entry(root)
duration.pack()
duration.insert(0, "Duration in minutes")
def runTest():
controller.main(testName.get(), name.get(), float(duration.get()))
#create run button
run = Button(root, text = "Run", fg = "red", width = 10, command = runTest)
run.pack()
root.mainloop()
So, here is my issue. Once this project is implemented, the duration will likely be set for something like 1-4 hours. So, what I would like to do is have a countdown appear on the widget, so the users can reference that timer at any time to see how long until their data is produced. The problem is that as soon as my test is running, the widget locks up until it is complete. Everything I've tried is put on hold until it finishes running the test, then it does what I wanted. It doesn't help very much at that point.
Anybody have some experience at implementing something like this?
Thanks.
You'll need to fork off your work in runTest. The threading module will be your friend (e.g. from threading import Thread).
Then rewrite your runTest method:
def runTest():
# pack your arguments in a tuple
mainArgs = (testName.get(), name.get(), float(duration.get()))
# create a thread object armed with your function and the args to call it with
thread = Thread(target=controller.main, args=mainArgs)
# launch it
thread.start()
#and remember, never set state (directly or indirectly) from separate threads without taking appropriate precautions!

How can I stop raising event in Tkinter?

I have some code like this
from Tkinter import *
master = Tk()
def oval_mouse_click(event):
print "in oval"
def canvas_mouse_click(event):
print "in canvas"
w = Canvas(master, width = 800, height = 600)
uid = w.create_oval(390, 290, 410, 310, fill='blue')
w.tag_bind(uid, "<Button-1>", lambda x: oval_mouse_click(x))
w.bind("<Button-1>" , canvas_mouse_click)
w.pack()
mainloop()
When I click on Canvas I have "in canvas" message in console.
When I click] on Oval I have two messages "in oval" and "in canvas", but I want to have only first message. Is there any way to stop event raising?
I can do this task with some global flag but I think there should be more natural way for Tkl.
I have just posted an improved solution on the similar problem there Python tkinter: stopping event propagation in text widgets tags.
The core idea is the same as presented in the previous solutions: hijack the Canvas widget by binding it with the same event sequence as tag_bind. The improved solution I came up with enables now to simulate the expected return "break" behaviour of Tk's other bind+callback pairs. In short:
create a wrapper around the wished callback, i.e. a callable class instance
when the class instance is called, run callback and check its result.
if the result is "break", temporarily hijack the event propagation: bind the Canvas widget to the same event bound to tag_bind, with an empty callback. Then, after an idle time, unbind.
if the result is not "break": do nothing, the event will propagate to Canvas automatically
The link above lists a full working example with the Text widget, but it is immediately transferable to the Canvas widget.
Here is the simplest example to handle your issue:
import Tkinter
def oval_mouse_click(event):
print "in oval"
event.widget.tag_click = True
def canvas_mouse_click(event):
if event.widget.tag_click:
event.widget.tag_click = False
return
print "in canvas"
root = Tkinter.Tk()
canvas = Tkinter.Canvas(width=400, height=300)
oid = canvas.create_oval(400/2-10, 300/2-10, 400/2+10, 300/2+10, fill='blue')
canvas.tag_click = False
canvas.tag_bind(oid, "<Button-1>", oval_mouse_click)
canvas.bind("<Button-1>" , canvas_mouse_click)
canvas.pack()
root.mainloop()
There is no other easier way to handle this under Canvas.

Python tkinter label won't change at beginning of function

I'm using tkinter with Python to create a user interface for a program that converts Excel files to CSV.
I created a label to act as a status bar, and set statusBarText as a StringVar() as the textvariable. inputFileEntry and outputFileEntry are textvariables that contain the input and output file paths.
def convertButtonClick():
statusBarText.set('Converting...')
if inputFileEntry.get() == '' or outputFileEntry.get() == '':
statusBarText.set('Invalid Parameters.')
return
retcode = subprocess.('Program.exe' ,shell=true)
if retcode == 0:
statusBarText.set('Conversion Successful!')
else:
statusBarText.set('Conversion Failed!')
This function gets called when you click the convert button, and everything is working fine EXCEPT that the status bar never changes to say 'Converting...'.
The status bar text will get changed to invalid parameters if either the input or output are empty, and it will change to success or failure depending on the return code. The problem is it never changes to 'Converting...'
I've copied and pasted that exact line into the if statements and it works fine, but for some reason it just never changes before the subprocess runs when its at the top of the function. Any help would be greatly appreciated.
Since you're doing all of this in a single method call, the GUI never gets a chance to update before you start your sub process. Check out update_idletasks() call...
from http://infohost.nmt.edu/tcc/help/pubs/tkinter/universal.html
w.update_idletasks()
Some tasks in updating the display, such as resizing and redrawing widgets, are called idle tasks because they are usually deferred until the application has finished handling events and has gone back to the main loop to wait for new events.
If you want to force the display to be updated before the application next idles, call the w.update_idletasks() method on any widget.
How are you creating your Label?
I have this little test setup:
from Tkinter import *
class LabelTest:
def __init__(self, master):
self.test = StringVar()
self.button = Button(master, text="Change Label", command=self.change)
self.button.grid(row=0, column=0, sticky=W)
self.test.set("spam")
self.testlabel = Label(master, textvariable = self.test).grid(row = 0,column = 1)
def change(self):
self.test.set("eggs")
root = Tk()
root.title("Label tester")
calc = LabelTest(root)
root.mainloop()
And it works.
Did you make sure to use "textvariable = StatusBarText" instead of "text=StatusBarText.get()"?

Categories