How to test to see if a top-level window is open? - python

I'm feeling somewhat like Python programming may not be my thing....
I have created a tkinter GUI that uses a button callback to open another window (other searches say this window should be a top-level window) and it works pretty good, how-ever each time the button is pressed it opens another identical (as far as I can tell) window.
Question: how can I test to see if a window (opened with the button) already exists and thus prevent duplicates from being generated???
NOTE: I am not (yet) a OOP programmer so please avoid that paradigm if possible...
regards,
Bill W.

I am not (yet) a OOP programmer so please avoid that paradigm if possible...
Sooner or later, you'll have to understand object-oriented programming if you want to program in Python successfully. The alternative (global variables and functions everywhere) is not definitely a good approach. Even Tkinter has lots of classes with its respective methods, so it looks like it is necessary for your purpose.
Back to your question, a solution could be setting the Toplevel window as an attribute of a class where you wrap all your application, and only open a new window if this attribute is None. To set this attribute to None when you close the window, you can use the protocol method to set a callback.
This is a small working example where you can see how it would work:
import Tkinter as tk
class App(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.button = tk.Button(self, text="Open a new window", command=self.openwindow)
self.button.pack()
self.toplevel = None
def openwindow(self):
if self.toplevel is None:
self.toplevel = tk.Toplevel(self)
self.toplevel.protocol('WM_DELETE_WINDOW', self.removewindow)
def removewindow(self):
self.toplevel.destroy()
self.toplevel = None
app = App()
app.mainloop()

You can use this method winfo_exists() to check if the window exists.
if the return value is 0, it means the window doesn't exist.
if the return value is 1, it means the window exists.
Sample Code...
from tkinter import *
mainWindow = Tk()
mainWindow.title("This is main Window")
# create a top-level window
newWindow = Toplevel(mainWindow)
newWindow.title("This is Toplevel window")
print("Before destroying window = " + str(newWindow.winfo_exists()))
newWindow.destroy()
print("After destroying window = " + str(newWindow.winfo_exists()))
mainloop()
Output is...
# Before destroying window = 1
# After destroying window = 0

Related

Delete all from TkInter Canvas and put new items while in mainloop

The goal is to achieve different "screens" in TkInter and change between them. The easiest to imagine this is to think of a mobile app, where one clicks on the icon, for example "Add new", and new screen opens. The application has total 7 screens and it should be able to change screens according to user actions.
Setup is on Raspberry Pi with LCD+touchscreen attached. I am using tkinter in Python3. Canvas is used to show elements on the screen.
Since I am coming from embedded hardware world and have very little experience in Python, and generally high-level languages, I approached this with switch-case logic. In Python this is if-elif-elif...
I have tried various things:
Making global canvas object. Having a variable programState which determines which screen is currently shown. This obviously didn't work because it would just run once and get stuck at the mainloop below.
from tkinter import *
import time
root = Tk()
programState = 0
canvas = Canvas(width=320, height=480, bg='black')
canvas.pack(expand=YES, fill=BOTH)
if(programState == 0):
backgroundImage = PhotoImage(file="image.gif")
canvas.create_image(0,0, image=backgroundImage, anchor=NW);
time.sleep(2)
canvas.delete(ALL) #delete all objects from canvas
programState = 1
elif(programState == 1):
....
....
....
root.mainloop()
Using root.after function but this failed and wouldn't show anything on the screen, it would only create canvas. I probably didn't use it at the right place.
Trying making another thread for changing screens, just to test threading option. It gets stuck at first image and never moves to second one.
from tkinter import *
from threading import Thread
from time import sleep
def threadFun():
while True:
backgroundImage = PhotoImage(file="image1.gif")
backgroundImage2 = PhotoImage(file="image2.gif")
canvas.create_image(0,0,image=backgroundImage, anchor=NW)
sleep(2)
canvas.delete(ALL)
canvas.create_image(0,0,image=backgroundImage2, anchor=NW)
root = Tk()
canvas = Canvas(width=320, height=480, bg='black')
canvas.pack(expand=YES, fill=BOTH)
# daemon=True kills the thread when you close the GUI, otherwise it would continue to run and raise an error.
Thread(target=threadFun, daemon=True).start()
root.mainloop()
I expect this app could change screens using a special thread which would call a function which redraws elements on the canvas, but this has been failing so far. As much as I understand now, threads might be the best option. They are closest to my way of thinking with infinite loop (while True) and closest to my logic.
What are options here? How deleting whole screen and redrawing it (what I call making a new "screen") can be achieved?
Tkinter, like most GUI toolkits, is event driven. You simply need to create a function that deletes the old screen and creates the new, and then does this in response to an event (button click, timer, whatever).
Using your first canvas example
In your first example you want to automatically switch pages after two seconds. That can be done by using after to schedule a function to run after the timeout. Then it's just a matter of moving your redraw logic into a function.
For example:
def set_programState(new_state):
global programState
programState = new_state
refresh()
def refresh():
canvas.delete("all")
if(programState == 0):
backgroundImage = PhotoImage(file="image.gif")
canvas.create_image(0,0, image=backgroundImage, anchor=NW);
canvas.after(2000, set_programState, 1)
elif(programState == 1):
...
Using python objects
Arguably a better solution is to make each page be a class based off of a widget. Doing so makes it easy to add or remove everything at once by adding or removing that one widget (because destroying a widget also destroys all of its children)
Then it's just a matter of deleting the old object and instantiating the new. You can create a mapping of state number to class name if you like the state-driven concept, and use that mapping to determine which class to instantiate.
For example:
class ThisPage(tk.Frame):
def __init__(self):
<code to create everything for this page>
class ThatPage(tk.Frame):
def __init__(self):
<code to create everything for this page>
page_map = {0: ThisPage, 1: ThatPage}
current_page = None
...
def refresh():
global current_page
if current_page:
current_page.destroy()
new_page_class = page_map[programstate]
current_page = new_page_class()
current_page.pack(fill="both", expand=True)
The above code is somewhat ham-fisted, but hopefully it illustrates the basic technique.
Just like with the first example, you can call update() from any sort of event: a button click, a timer, or any other sort of event supported by tkinter. For example, to bind the escape key to always take you to the initial state you could do something like this:
def reset_state(event):
global programState
programState = 0
refresh()
root.bind("<Escape>", reset_state)

Python Tkinter how to hide a widget without removing it

I know similar things have been asked a lot, but I've tried to figure this out for two hours now and I'm not getting anywhere. I want to have a button in a Tkinter window that is only visible on mouseover. So far I failed at making the button invisible in the first place (I'm familiar with events and stuff, that's not what this question is about) pack_forget() won't work, because I want the widget to stay in place. I'd like some way to do it like I indicated in the code below:
import tkinter as tki
class MyApp(object):
def __init__(self, root_win):
self.root_win = root_win
self.create_widgets()
def create_widgets(self):
self.frame1 = tki.Frame(self.root_win)
self.frame1.pack()
self.btn1 = tki.Button(self.frame1, text='I\'m a button')
self.btn1.pack()
self.btn1.visible=False #This doesnt't work
def main():
root_win = tki.Tk()
my_app = MyApp(root_win)
root_win.mainloop()
if __name__ == '__main__':
main()
Is there any way to set the visibility of widgets directly? If not, what other options are there?
Use grid as geometry manager and use:
self.btn1.grid_remove()
which will remember its place.
You can try using event to call function.
If "Enter" occurs for button then call a function that calls pack()
and if "Leave" occurs for button then call a function that calls pack_forget().
Check this link for event description:List of All Tkinter Events
If you wish your button to stay at a defined place then you can use place(x,y) instead of pack()

Tk(), Toplevel() and winfo_toplevel(). Difference between them and how and when to use effectively?

I am trying to understand how a widget is being created. And I found above three functions are getting used in creating a widget, yet I couldn't come up with the difference and the advantage of one over the other. Even though, I had taken a look on this answer that still leaves me with confusion (and it hadn't said anything about winfo_toplevel too).
Here is my code.
from tkinter import *
root = Tk()
root.title("Root widget")
root.mainloop()
window = Toplevel()
window.title("Window widget")
window.mainloop()
On running above code "Root" widget is getting created. On closing "Root", two widgets are created with one titled "Window widget" and other being unwanted. On closing unwanted widget, "Window widget" is also getting destroyed.
What is actually happening here and how to overcome?
Another sample:
class ldo(Frame):
def __init__(self, master = None):
Frame.__init__(self,master)
self.grid()
self.appOutline()
def appOutline(self):
top = self.winfo_toplevel()
self.menuBar = Menu(top)
top["menu"] = self.menuBar
self.subMenu1 = Menu(self.menuBar)
self.menuBar.add_cascade(label = "File", menu = self.subMenu1)
app = ldo()
app.master.title("Sample UI")
app.mainloop()
On the other hand, this code is using winfo_toplevel() where the widget looks perfectly fine. Here, my assumption is, Frame plays a role of creating widget and winfo_toplevel() is an enhancing tool to other tkinter items. But would like to know what it does actually.
However, below snippet is not working:
winf = winfo_Toplevel()
winf.title("Winfo Widget")
winf.mainloop()
And returning such error:
winf = winfo_Toplevel()
NameError: name 'winfo_Toplevel' is not defined
What is the exact difference between Tk(), Toplevel() and winfo_Toplevel(). What and when should one be used effectively. Looking for really a better understanding.
On running above code "Root" widget is getting created. On closing "Root", two widgets are created with one titled "Window widget" and
other being unwanted. On closing unwanted widget, "Window widget" is
also getting destroyed. What is actually happening here and how to
overcome?
When you create any widget with the absence of an actual Tk() instance, a Tk() instance automatically gets created, thus resulting in an unwanted Toplevel-like widget when the second part of the first code snippet runs. Additionally, when a widget gets created with the absence of master option, it is assumed that the instance is a child of one of the Tk instances, in above case, there's only one, and that's the one that got automatically created. When a parent gets destroyed all widgets that are under it also gets destroyed, so when you close the unwanted widget that is an instance of Tk, the Toplevel instance also gets destroyed as its parent is destroyed.
On the second part, winfo_toplevel refers to the automatically created Tk instance again and creates other children with that automatically created Tk as the parent, which should be technically fine but would be harder to maintain as a code, than the standard ways of creating the same GUI I'd presume.
winf = winfo_Toplevel()
winf.title("Winfo Widget")
winf.mainloop()
In the code piece above, unless imported or otherwise defined winfo_Toplevel has no meaning, first of all, it's not as same as winfo_toplevel as python is case sensitive. Secondly, even if python wasn't case sensitive, it would still throw an error as it is a method and it lacks the first positional argument, which is the object instance to the class of which the winfo_toplevel method is also defined for.
Essentially, you're trying to use a case-insensitive spelling of a method, as if it is a class name such as Toplevel or Tk, which winfo_toplevel has almost nothing to do with.
Examine the following code:
import tkinter as tk
root = tk.Tk()
root.title("This is the actual Tk instance, root")
toplevel = tk.Toplevel(root)
toplevel.title("This is a Toplevel, whose parent is root"),
r_lbl = tk.Label(text="""This label is a children to the default master,
as it lacks the first positional argument for an explicit parent
assignment.""")
r_lbl2 = tk.Label(r_lbl.winfo_toplevel(), text="""This label checks who the
toplevel parent for r_lbl is, and then selects that parent as a parent
to itself.""")
r_lbl3 = tk.Label(root, text="""This label will appear on root, as it's
explicitly passed as the first positional argument, which is the parent,
as root.""")
t_lbl = tk.Label(toplevel, text="""This label will appear on toplevel, as it's
explicitly passed as the first positional argument, which is the parent,
as toplevel.""")
t_lbl2 = tk.Label(t_lbl.winfo_toplevel(), text="""This label checks who the
toplevel parent for t_lbl is, and then selects that parent as a parent
to itself.""")
r_lbl.pack()
r_lbl2.pack()
r_lbl3.pack()
t_lbl.pack()
t_lbl2.pack()
root.mainloop()
In conclusion, Tk, while being a Toplevel widget, is also the tcl interpreter for the entire GUI that runs in a thread. There can be more than one present, which is discouraged as usually multiple instances of it is unjustified, but there also has to be at least one instance present in order to have a GUI.
Toplevel can be considered to be the only visual part of a Tk instance, and it can be used when there is a need for multiple window-like widgets.
Finally, winfo_toplevel is merely a method returns the reference for the Toplevel-like parent that a widget is in, be the parent an instance of a Toplevel or a Tk.

Python: why must Tkinter class instantiation use a Frame?

If I want to create a Tkinter GUI simply with statements, I can do this:
from Tkinter import *
root = Tk()
root.title("Test Window")
tkFrame = Frame(root)
tkButton = Button(tkFrame)
[...]
The documentation, however, advises that Tkinter be used with a class definition, subclassing a Frame widget:
class App(Frame):
[...]
I would like to understand why that is so. Why can't we subclass the Frame's container, the window? It appears that is what is done with statements in the first example, so why not in a class definition?
EDIT (following Bryan Oakley's answer):
I would like to instantiate at the highest level of Tkinter, which I assume to be Tk() (though I have come across references stating Frame is the top level, but never mind). Indeed, the following will create a window:
from Tkinter import *
class Application(Tk):
pass
app = Application()
app.mainloop()
...but as soon as I try to add widgets I either get errors or two windows, with the widgets in a new window, depending on how I structure the code. Here's a basic example that will produce a second window with the button:
from Tkinter import *
class Application(Tk):
tkBtn = Button()
tkBtn.pack()
app = Application()
app.mainloop()
Anything more, using self, __init__, etc., produces errors. Could someone point me to working code that instantiates Tkinter at the highest level? Just like all the Frame subclasses I'm seeing, but at the highest level?
There is nothing that says a tkinter class must inherit from a frame. You can inherit from any of the tkinter widgets, or any other classs. If you have found documentation that states otherwise, that documentation is wrong. Using Frame is a logical choice since it is designed to be a container of other widgets, but it is not the only choice.
Personally I inherit from a frame because I find it convenient. Some of my GUIs need the ability to open more than one identical window. By having my main code in a Frame I am able to create multiple windows simply by creating multiple instances of the frame, and packing them in Toplevel widgets.
When you inherit from Tk, you can only have a single instance. In the real world that's usually enough, and there's absolutely nothing wrong with doing it that way. Since I personally write a fair number of tkinter programs, having them all start out exactly the same is convenient for me.
Another good choice is a Canvas since you can easily add a background image, which is not something you can do with a Frame.
Bottom line: you are absolutely not required to inherit from Frame. Inherit from whatever you want.
(the following was written in response to an edit of the original question)
In reference to this code:
from Tkinter import *
class Application(Tk):
tkBtn = Button()
tkBtn.pack()
app = Application()
app.mainloop()
The reason you see two windows is that you're not creating the class properly. You need to call the __init__ method of the superclass before creating widgets, because that's what actually creates the root window. Because you don't, you end up with two windows. You get one that is created implicitly when you add a button to a not-yet-constructed root window, and you get another when your subclass finishes initializing.
The solution is to not take shortcuts, and instead initialize the class properly:
from Tkinter import *
class Application(Tk):
def __init__(self):
Tk.__init__(self)
tkBtn = Button()
tkBtn.pack()
app = Application()
app.mainloop()
Note that this isn't a tkinter-specific problem. When subclassing, unless you have explicit reasons to do otherwise, you always should call the __init__ method of the superclass.
You asked for working examples, here are a couple:
https://stackoverflow.com/a/22424245/7432
https://stackoverflow.com/a/11405393/7432
You might also want to read the responses in the question Inheriting from Frame or not in a Tkinter application

Multiple tkinter windows look different when closing and reopening window

I got the following code from a tutorial. I then modified main() so that two windows are created as seperate threads. When I run it, only one window is created. Then when I press the Quit button in that window, a second window appears. In this new window the button has a different look than the first one (a look which I like better) and then if I press either of the two Quit buttons, both windows close and the program exits.
Why does the second window not appear until the first Quit button is pressed, and why does it look different when it does appear?
EDIT: This happens when no threads are used as well, where only one window is created at a time.
EDIT: This is a screenshot of the two windows that are created. The one on the left is created with the program is run, the one on the right is created after clicking the "Quit" button on the first.
from Tkinter import Tk, BOTH
from ttk import Frame, Button, Style
class Example(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.parent = parent
self.initUI()
def initUI(self):
self.parent.title("Quit button")
self.style = Style()
self.style.theme_use("default")
self.pack(fill=BOTH, expand=1)
quitButton = Button(self, text="Quit",
command=self.quit)
quitButton.place(x=50, y=50)
from threading import Thread
def main():
for i in range(2):
root = Tk()
root.geometry("250x150+300+300")
app = Example(root)
Thread(target=root.mainloop()).start()
if __name__ == '__main__':
main()
You cannot use tkinter this way. Tkinter isn't thread safe, you can only ever access tk widgets and commands except from the thread that created the root window.
As for one window only showing after the other is destroyed even without threading, it's hard to say since you don't show the code. If you're creating more than one instance of Tk, and calling mainloop more than once, that's the problem. Tkinter is designed to work when you create precisely one instance of Tk, and call mainloop precisely once.
If you want more than one window, create a single instance of Tk for the first window, and instances of Toplevel for additional windows.

Categories