Tkinter: why do both windows open? - python

I started looking at some Tkinter tutorials. I always like to mess around with code before going on with the next chapter: for instance I tried to open more than one independent root.
from Tkinter import *
def main():
root1, root2 = Tk(), Tk()
root1.title("First window")
root2.title("Second window")
root1.mainloop()
root2.mainloop()
main()
What I don't understand is why this code works (i.e. it shows two different windows at the same time).
If the Tk "mainloop" method is a loop, why do I see the second window? Shouldn't I see it only after closing the first window (i.e. after breaking the first while loop)?
Please, explain me how it works

EDIT: Removed incorrect answer, added answer below
The short of why are confused is that the behavior of your code is undefined. Really, it isn't running as differently as you think. Try running this code:
from Tkinter import *
def main():
root1, root2 = Tk(), Tk()
root1.title("First window")
root2.title("Second window")
root1.mainloop()
print 'root1.mainloop() has finished running.'
root2.mainloop()
print 'root2.mainloop() has finished running.'
main()
So the first mainloop doesn't actually continue on immediately. It hangs on root1.mainloop() (as you expected) until you close both of the windows, then it continues on. I am not sure exactly why running root1.mainloop() opens both windows as I don't have access to all of the source code but I think it would be a little bit like asking about the results of 0/0. It just isn't supposed to happen.

Related

Correct way to run two Tk() mainloops independently, with second being started from first script?

script_a.py
from tkinter import *
from script_b import test
root = Tk()
var1 = stuff
var2 = stuff
def start_scriptb():
test([var1, var2])
Button(root, text="Start",
command=start_scriptb)
root.mainloop()
script_b.py
from tkinter import *
def test(x):
main = Tk()
Label(main, text=x)
Button(main, text="Exit", command=main.destroy())
main.mainloop()
This is a very basic version of what I'm trying to achieve. I actually am spawning a progress window that uses subprocess.Popen in script 2 with the passed through variables from script one and viewing the progess through a scrolled text widget on the 2nd program. I'm trying to spawn a new process, independent from the root GUI each time that button is hit from script_a.
It works fine in my tests so far, but I wanted to see if this could cause any issues or if this is actually spawning two processes?
I know there should only be one Tk() per process.
Using a TopLevel() window to show the progress works fine with the threading module, but the TopLevel() window will freeze as well as root (and any other open TopLevels()) if root is doing any sort of processing that takes any length of time.
With many hours of testing, I did have success in running two Tk() loops, but it had potential to be problematic, as "Bryan Oakley" had posted in many threads about.
Ultimately, I decided when I was in need of running something alone, I'd start my GUI with arguments and process it in an entirely new process instead of passing any arguments directly. Seems like a safer option.

How to control when does the console shows in python

I am making a python script that shows content in the console for the first 10-20 seconds after executed before the tkinter UI is shown. So is there any lines I can add to control the visibility of the console? .pyw file extension completely disables the console but I need the console to occasionally show up. Any suggestions?
First of all, you need to write your entire Tkinter code under a function.
def main_program():
# root = Tk() ...
Below this you need to write an empty print statement for the console. Below this, you can write anything. But the problem is sometimes, console quickly opens, prints the statement and closes it, so you barely have time to see
So, use time.sleep() -
import time
from tkinter import *
def main():
root = Tk()
root.mainloop()
print('Wait for a second')
time.sleep(1)
main()
This will show the print statement, delay the program for 1 second, and then open Tkinter window.
There are 2 ways to do it.
You can withdraw the window, print whatever you want and at the last, make it visible. .withdraw basically hides the window without any changes to the widgets.
from tkinter import *
root=Tk()
root.withdraw()
print("Hey there! Please wait for 10 seconds for the app to start...")
root.after(10000,lambda: root.deiconify())
root.mainloop()
Or, the second one is
from tkinter import *
import time
def create_window():
root = Tk()
root.mainloop()
print('Hey there! Please wait for 10 seconds for the app to start...')
time.sleep(10)
create_window()

With two tkinter windows, script does not execute after one's mainloop

I have a script which has two tkinter.Tk() objects, two windows. One is hidden from the start (using .withdraw()), and each has a button which hides itself and shows the other (using .deiconify()). I use .mainloop() on the one shown in the beginning. Everything works, but when I close either window, the code after the mainloop() doesn't run, and the script doesn't end.
I suppose this is because one window is still open. If that is the case, how do I close it? Is it possible to have a check somewhere which closes a window if the other is closed?
If not, how do I fix this?
The essence of my code:
from tkinter import *
window1 = Tk()
window2 = Tk()
window2.withdraw()
def function1():
window1.withdraw()
window2.deiconify()
def function2():
window2.withdraw()
window1.deiconify()
button1 = Button(master=window1, text='1', command=function1)
button2 = Button(master=window2, text='2', command=function2)
button1.pack()
button2.pack()
window1.mainloop()
Compiling answers from comments:
Use Toplevel instead of multiple Tk()s. That's the recommended practice, because it decreases such problems and is a much better choice in a lot of situations.
Using a protocol handler, associate the closing of one window with the closing of both. One way to do this is the following code:
from _tkinter import TclError
def close_both():
for x in (window1,window2):
try:
x.destroy()
except TclError:
pass
for x in (window1,window2):
x.protocol("WM_DELETE_WINDOW", close_both)

How to use idlelib.PyShell to embed an interpreter in a tkinter program?

I need to embed an interative python interpreter into my tkinter program. Could anyone help me out as to how to integrate it?
I have already looked at the main() function, but it's way to complex for my needs, but I can't seem to reduce it without breaking it.
Some details of what you must do may depend on what you want to do with IDLE's Shell once you have it running. I would like to know more about that. But let us start simple and make the minimum changes to pyshell.main needed to make it run with other code.
Note that in 3.6, which I use below, PyShell.py is renamed pyshell.py. Also note that everything here amounts to using IDLE's private internals and is 'use at your own risk'.
I presume you want to run Shell in the same process (and thread) as your tkinter code. Change the signature to
def main(tkroot=None):
Change root creation (find # setup root) to
if not tkroot:
root = Tk(className="Idle")
root.withdraw()
else:
root = tkroot
In current 3.6, there are a couple more lines to be indented under if not tkroot:
if use_subprocess and not testing:
NoDefaultRoot()
Guard mainloop and destroy (at the end) with
if not tkroot:
while flist.inversedict: # keep IDLE running while files are open.
root.mainloop()
root.destroy()
# else leave mainloop and destroy to caller of main
The above adds 'dependency injection' of a root window to the function. I might add it in 3.6 to make testing (an example of 'other code') easier.
The follow tkinter program now runs, displaying the both the root window and an IDLE shell.
from tkinter import *
from idlelib import pyshell
root = Tk()
Label(root, text='Root id is '+str(id(root))).pack()
root.update()
def later():
pyshell.main(tkroot=root)
Label(root, text='Use_subprocess = '+str(pyshell.use_subprocess)).pack()
root.after(0, later)
root.mainloop()
You should be able to call pyshell.main whenever you want.

Python Tkinter Flashing Standard Window

I am building an Interface with TKinter and I have the problem, that whenever create a new window the standardized tkinter window of 200x200 pixel with nothing in it flashes up for a fraction of a second and AFTER that all my modifications (widgets ect.) are made. This happens before AND after calling the mainloop.
The Main-Interface is created.
Mainloop stats
Flashing window
Main-Interface appears
Also after Mainloop was called this happens with newly created windows.
Main-Interface appears--> Push a button, that creates a new window
Flashing window
new window appears
Sadly I cannot give you a sample code... If I try to do a minimal example, this doesn't happen. Maybe the standard window is created, but it is changed so fast, that it doesn't appear on screen. I don't even know what to look up in this case... searching "tkinter flashing window" yields nothing.
EDIT: I found the source of the problem. It seems to be caused by wm_iconbitmap, FigureCanvasTkAgg and tkinter.Toplevel. If you remove the the icon out of the code, it works fine, no flashing. But if I use it together with one of the other, the window flashes when created. Try it out with the code below. You have to put the icon in the working directory of course.
Here is a code sample and the link to the icon I am using, but I suppose any icon will do.
# coding=utf-8
import numpy as np
import matplotlib as mpl
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import tkinter as tk
import os
class INTERFACE(object):
def __init__(self):
self.root = tk.Tk()
self.root.protocol("WM_DELETE_WINDOW", self.EXIT)
self.root.wm_iconbitmap( os.path.abspath("icon.ico")) #<---- !!!!!!
self.root.geometry("1024x768")
canvas = FigureCanvasTkAgg(self.testfigure(), master=self.root) #<---- !!!!!!
canvas.get_tk_widget().grid(sticky=tk.N+tk.W+tk.E+tk.S)
self.root.rowconfigure(0, weight=1)
self.root.columnconfigure(0, weight=1)
def testfigure(self):
x=np.linspace(0, 2*np.pi,100)
y=np.sin(x)
fig = mpl.figure.Figure()
sub = fig.add_subplot(111)
sub.plot(x,y)
return fig
def EXIT(self):
Top = tk.Toplevel(master=self.root)
Top.wm_iconbitmap( os.path.abspath("icon.ico")) #<---- !!!!!!
Top.transient(self.root)
Top.resizable(width=False, height=False)
Top.title("Exit")
tk.Message(Top,text="Do you really want to quit?", justify=tk.CENTER, width=300).grid(row=0,columnspan=3)
tk.Button(Top,text="YES",command=self.root.destroy).grid(row=1,column=0)
tk.Button(Top,text="No",command=self.root.destroy).grid(row=1,column=1)
tk.Button(Top,text="Maybe",command=self.root.destroy).grid(row=1,column=2)
def start(self):
self.root.mainloop()
if __name__ == '__main__':
INTERFACE().start()
I know this is an old question, but I've experienced a similar situation and have found a solution.
In my case, I've isolated the issue to the use of iconbitmap. I've managed to solve it by calling iconbitmap with the after method just before calling root.mainloop().
Example:
from tkinter import *
root = Tk()
w = Label(root, text="Hello, world!")
w.pack()
root.geometry('300x300+500+500')
root.after(50, root.iconbitmap('icon.ico'))
root.mainloop()
This method has worked on Toplevel() windows with icons as well.
Tested on Win 8.1 with Python 3.5.0.
Edit: Upon further inspection I've noticed that the behavior changes relative to root.geometry's presence as well. My initial example didn't have it and I only noticed after a few tries that it still had the same issue. The time delay in the after method doesn't seem to change anything.
Moving root.geometry below the after method yields the same issue for some reason.
Most likely, somewhere in your initialization code you're calling update or update_idletasks, which causes the current state of the GUI to be drawn on the screen.
Another possible source of the problem is if you're creating multiple instances of Tk rather than Toplevel.
Without seeing your code, though, all we can do is guess.
Your best route to solving this problem is to create a small example that has the same behavior. Not because we need it to help you, but because the effort you put into recreating the bug will likely teach you what is causing the bug.
This should work but it requires win32gui
import win32gui
def FlashMyWindow(title):
ID = win32gui.FindWindow(None, title)
win32gui.FlashWindow(ID,True)

Categories

Resources