Tkinter method "deiconify" seems not working on ubuntu(12.04, unity), but the the following code works as expected on windows 7. I need to show the window even it is minimized when something happens in another project https://github.com/thinker3/youdao.
from time import sleep
from Tkinter import Tk
class GUI():
def __init__(self):
self.root = Tk()
self.root.title("Test")
self.root.protocol("WM_DELETE_WINDOW", self.close_handler)
self.root.mainloop()
def close_handler(self):
self.root.iconify()
sleep(1)
self.root.deiconify()
if __name__ == '__main__':
gui = GUI()
What you are seeing is apparently the difference between Windows and non-Windows. It looks to me like the Windows behavior is a bug. At the very least, it's not expected behavior. What you are seeing on ubuntu is what I would expect to see.
For a GUI to do anything, the event loop must be running. Everything that happens is the response to an event. The drawing of a window on the screen is a response to an event. A button click is a response to an event. The visual effect of a button being pressed is a response to an event. And so on.
When you call iconify, that causes an event to be sent to the app saying "remove the window from the screen". The event loop sees the event, and redraws (or "un"draws) the window. The reverse happens when you call deiconify -- the system gets a redraw event, and tkinter redraws the window on the screen.
In your code, you never give the event loop a chance to respond to these events. You ask it to iconfiy, then you sleep, then you deiconify, all without giving the event loop a chance to respond. While the sleep command is running no events are processed. So, when you wake from sleep, the system hides the window, and then microseconds later it redraws it.
What is probably happening on windows is that the window manager is getting the iconfiy command and removing the window without tkinter's involvement. In other words, tkinter doesn't actually do anything to make it go away. On X11-based systems, however, this is all managed by the event loop.
If you want the window to go away, and a second later reappear, use the event loop to your advantage. Allow the event loop to update the screen, and arrange for the deiconify to happen at sometime in the future. For example:
def close_handler(self):
self.root.iconify()
self.after(1000, self.root.deiconify)
This should work on all platforms. It allows the event loop to respond to the iconify event, and then a second later the deiconify command will run.
Related
I have been trying to use tkinter to make a gui to select some excel files and sheets from those files.
I have a lot of experience in python, but I'd probably say a novice at tkinter.
The code I have written to select the files is shown below (typos are likely because I cannot access the internet on the machine these files are on, so I am typing it up here).
My question is about mainloop(), the update functions, and after(). I had mainloop() at the end of my code, but my program wouldn't terminate (i.e. the terminal would remain) after it did what it does. So I removed the mainloop() and now it functions perfectly without any mainloop(), update(), or after() calls.
I don't really understand this and would really like to. I get that mainloop() stops the code from progressing until the root closes, but I thought nothing would show up without mainloop(), and this code does wait for the user to close the windows before continuing.
I was also wondering if I do not have mainloop (or the like), the code still closes fine whether or not I have root.destroy() at the end of the App class (commented in the code). I don't get that either.
This information would help me make better and correct code in the future and hopefully improve this one. FYI, I have searched for the answer to this everywhere and could not find one, at least one I could understand. Thanks a bunch.
This is as minimal as I can think of for this code.
Code (Edited from original post):
import tkinter as tk
from tkinter import ttk
class App:
def __init__(self, parent):
self.root = parent
self.root.withdraw()
tl = tk.Toplevel(parent)
b = ttk.Button(tl, text="Test widget")
b.grid()
tl.wait_window()
# This line does not change how the code functions at all
#self.root.destroy()
def run(self):
# Whether or not this is a function in the class or called globally doesn't matter.
self.root.mainloop()
if __name__ == "__main__":
root = tk.Tk()
app = App(root)
# This is the mainloop() call. If I include it, the program does not close after
# running the code in App.__init__. Without it, it runs perfectly.
# app.run()
mainloop enters an event listening loop for your tk.Tk() application.
You should create only one object with Tk() in every tkinter application.
Now this loop will "globally" listen for GUI events and will call your registered callbacks (event handlers). Code before the mainloop call will run but nothing will be shown or updated in the screen until you call it. No event (user input, mouse movement, keyboard and others) will be queued and responded to before that. But the widgets are created and configured, just not shown yet. If you call root.update() without ever entering the mainloop you will eventually see the window flash and disappear because everything is running, and you force window refresh, but then suddenly the program ends.
So configure your GUI and in the end always run root.mainloop()
Now when you call wait_window, you are creating a local event loop (and blocking the mainloop if it was running). For a perfect explanation of wait_window by Brian see here
What happened to you is that your app is happily running with your local event loop, but without mainloop. That would be a strange thing to do. Even if you are making as Brian explains, a modal window, you want to "return" to the mainloop after the window closes.
As for the after method it just registers a callback function to run after your choosen time, or after the mainloop becames idle, with after_idle (nothing more to refresh, no events in the queue..., also think as soon as possible). And if you want to re-run that function again in the same way it should re-register with after or after_idle before returning.
So always use your mainloop() ending call - or should I say event loop starting call :)
Edit:
Also, don't do things in your code that are slow, or that may block the loops, such as calling sleep or any other blocking function. That will freeze the GUI, until the block ends.
I am trying to create a basic Tkinter window.
According to on-line tutorials, to create a window one must use the following:
import Tkinter
window=Tkinter.Tk()
window.mainloop()
But when I try the same code python directly displays the window in window=Tkinter.Tk() and window.mainloop() has no effect.
Can anyone explain why ?
EDIT: The code works perfectly when I put it in a file and run it. It just doesn't work from interactive prompt.
The call to mainloop is there so that you can interact with the Window once it's created. If you had a Python script that only did this:
import Tkinter
window = Tkinter.Tk()
The script would exit immediately after window was created, so you'd be luckily to even see it get drawn before it disappeared as the script exited. (That is if window was even drawn at all; in my tests on both Linux and Windows, window was never drawn unless mainloop was called; even if I put a call to time.sleep after the Tkinter.Tk() call, window would only be drawn without a mainloop call in the interactive prompt).
The mainloop() also (and most importantly) allows Tkinter to listen for events to occur on the Tk object, such as pressing buttons, radios, etc. that might be embedded in it, and dispatch those events to methods you have bound to the event being triggered. Without that functionality you'd just have a window that you can look at and not much else.
I am trying to build an application with a main window, and a background thread that checks a certain condition in a loop, and when the condition is met, brings the main window to the front.
To bring the window to the front i use the lift() method, but it doesn't work.
So I did the following experiment from the python command line:
from Tkinter import *
root = Tk()
A tk window appears. Minimize the window and then:
root.lift()
Nothing happens. I also tried start the window's mainloop before lifting:
import thread
# start mainloop in separate thread so we can continue typing
thread.start_new_thread(root.mainloop, ())
root.lift()
Again, nothing happens. In my actual code it is even worse - once I call lift(), the window is stuck and stops responding.
What am I doing wrong?
(I'm using Python 2.7.2 on Windows 7.)
You cannot run the mainloop in a thread different from the one where you create the widgets. In fact, you can't interact with widgets at all from another thread.
To revert the effects of minimizing the window you need to use the deiconify method.
Using time.sleep in my wxPython code just after re-positioning a bitmapbutton caused my button to go totally blank. Just a white space was left in the region where the button should have been. Can any one please explain the reason and suggest any solution? Here's my code:
import wx
import time
class gui(wx.Frame):
def __init__(self,parent,id):
wx.Frame.__init__(self,parent,id,'New Window',pos=(0,0),size=wx.DisplaySize())
panel=wx.Panel(self)
self.SetBackGroundColour('green')
self.pic=wx.BitmapButton(self,-1,wx.Image("Candle.jpg",wx.BITMAP_TYPE_ANY).ConvertToBitmap(),pos=(700,300))
self.Bind(wx.EVT_BUTTON,self.position,self.pic)
def positon(self,event):
self.pic.Hide()
self.pic=wx.BitmapButton(self,-1,wx.Image("Candle.jpg",wx.BITMAP_TYPE_ANY).ConvertToBitmap(),pos=(700,300))
time.sleep(2)
self.pic.Hide()
if __name__=='__main__':
app=wx.PySimpleApp()
frame=gui(None,-1)
frame.Show()
app.MainLoop()
Well there's no wonder your button goes blank, you've pretty much programmed it to do so.
self.pic.Hide() => hides the button
self.pic=wx.BitmapButton(self,-1,wx.Image("Candle.jpg",wx.BITMAP_TYPE_ANY).ConvertToBitmap(),pos=(700,300)) => displays the button once again
time.sleep(2) => takes a brake for 2 seconds
self.pic.Hide() => hides the button again
The conclusion is, your button won't show up. So I don't see what's the problem, as it does exactly what you programmed it to.
time.sleep() blocks wx's mainloop and makes the GUI unresponsive for however long you've told it to sleep. There are several alternatives. You can use a wx.Timer or use threads (or similar). I think using a Timer makes more sense in your use case though.
well it depends, was time sleep used in the button's event ?, cause I believe if it was it's because of that. The button waits for the event it triggered to end so it would go back to its initial state.
sleep is blocking, so execution is stuck in your position method for two seconds and is unable to return to the MainLoop to process other events, like painting your changes to the screen. After the two seconds are up the image is hidden, but was never drawn.
To get the effect you want you'll have to start a timer, and bind the timer to a handler which can show the StaticBitmap again.
By the way you can also call Show again rather than creating a new control, and it's parent should also be the panel, not the frame.
Every tkinter tutorial I have seen claims that tkinter.mainloop must be called for windows to be drawn and events to be processed, and they always call this function, even in hello world programs. However, when I try these out in the interactive shell, windows are drawn correctly without having to call mainloop. This example of embedding matplotlib graphics in tkinter produces a relatively complex application, with buttons for panning, zooming and resizing a plot within a tkinter window, and again, this all works if you remove the call to mainloop and run the code in the interactive shell. Of course, if I run the script (with mainloop removed) outside the interactive shell, the program ends too quickly to see what happens, but if I add a call to input to hold the program open everything works correctly (I'm running python 3.2.2 on linux).
So what exactly does mainloop do, and when is it necessary to call it?
EDIT:
To clarify, if I open up the GNOME terminal and type
$python3
>>> import tkinter
>>> root = tkinter.Tk()
a window immediately appears without having to call mainloop, and more complex tkinter functionality seems to work as well (for example, adding buttons to the window). In IDLE, a call to mainloop is necessary. It was my understanding that nothing should be drawn, and no events should be processed, until mainloop is called.
The answer to your main question is, you must call mainloop once and only once, when you are ready for your application to run.
mainloop is not much more than an infinite loop that looks roughly like this (those aren't the actual names of the methods, the names merely serve to illustrate the point):
while True:
event=wait_for_event()
event.process()
if main_window_has_been_destroyed():
break
In this context, "event" means both the user interactions (mouse clicks, key presses, etc) and requests from the toolkit or the OS/window manager to draw or redraw a widget. If that loop isn't running, the events don't get processed. If the events don't get processed, nothing will appear on the screen and your program will likely exit unless you have your own infinite loop running.
So, why don't you need to call this interactively? That's just a convenience, because otherwise it would be impossible to enter any commands once you call mainloop since mainloop runs until the main window is destroyed.
Compare a program with an interactive GUI to a program that calculates the hundredth Fibonacci number. All the latter program has to go through a series of steps in order, top to bottom. The set of steps and their sequencing can be known in advance, and it'll remain constant no matter how many times you run the program.
But the GUI program is different: at any given moment, it has to be able to handle all sorts of different kinds of events and interactions. This requirement is often implemented using a programming construct called an event loop. An event loop is the central control structure of a program. It waits for an event to happen, and then dispatches the appropriate handler.
You didn't mention which interactive shell you're using, but I'm guessing it's IDLE. IDLE itself is a Tkinter program, and it already has an event loop going. So possibly the Tkinter code you are typing into the shell is getting bound to IDLE's event loop.
When you execute your code, the tkinter window will refuse to open without there being a mainloop function.
For example this will not work:
from tkinter import*
root=Tk()
This, however, will work:
from tkinter import*
root=Tk()
root.mainloop()
If you’re using python interective shell, you don’t need to call the root.mainloop() function. But if you are coding in a file, suppose one in IDLE, you need to call out the mainloop() function in order for your program to work as follows:
from tkinter import *
root = Tk()
# Elements in the GUI must go here eg:
lbl = Label(root, text=“Text”)
lbl.pack()
# At the end of the program, do this:
root.mainloop()
As follows:
from tkinter import *
tk = Tk()
canvas = Canvas(tk, width=500, height=500)
canvas.pack()
canvas.create_line(0, 0, 500, 500)
mainloop()
I've decided that, instead of sticking a call directly to mainloop anywhere in my script, I'll just add it as part of atexit - that is, when the Python interpreter decides it's time to start closing down, it's going to enter Tk's mainloop. This then prevents it from finishing the shut down sequence until the user actually tells Tk to quit (IE, with command-Q on a Mac, or by clicking on the red X in Windows.)
from Tkinter import Tk
root = Tk()
import atexit
atexit.register(root.mainloop)
There seems to be no need to call mainloop from a system command line. The Python interpreter will continue running without it, because it's waiting for further input from you (until you run exit()).