I have a program that I'm just adding graphics to, but I'm having trouble running my main code along with the graphics. Basically I have something like this:
def mainFunction():
while True:
run code in here
root = Tk()
board = Canvas(root, height=710, width=1000)
board_image = PhotoImage(file="/path/example.jpg")
photo = board.create_image(0,0, anchor=NW, image=board_image)
board.pack()
mainFunction
root.mainloop()
I can only run either the mainFunction or the graphics because whichever one I make run first in the code is the only that runs. It doesn't stop to allow the next code to run. There has to be a simple way to get graphics and code to run together side by side. Thanks!
Use Tk.after_idle() to register a function that will do a piece of the work required. Keep doing piece after piece until all the work is done.
Generally speaking, you cannot put an infinite loop inside a Tkinter application. Why? It's already running an infinite loop: the event loop. Your code is the equivalent of this:
while <there are more events to service>:
while True:
<run code in here>
<get the next event>
<service the event>
See the problem? You're preventing the code from ever servicing events, and events are the life blood of a GUI.
Instead, you need to take advantage of the already-running infinite loop by adding code to be run inside the loop. You do this with after (and after_idle). This will put one even on the queue. If, during the processing of that event you again call after_idle, you've effectively set up an infinite loop that works within the event loop.
For example:
def do_one_iteration(self):
<run code in here>
self.after(100, self.do_one_iteration)
Then, somewhere in your main logic, or in response to a button, you call do_one_iteration. It will do one iteration of your previously-infinite-loop. When it is done it instructs Tkinter to call itself again 100 milliseconds later. When that time period elapses your code is run, it schedules another iteration in 100 milliseconds, etc. etc. You can change the interval to whatever you want; the smaller the interval the faster your code runs, but the greater the chance that you starve the GUI for events.
Note that this only works if <run code in here> runs relatively fast. While it is running your GUI will freeze. If it can complete one iteration in a couple hundred milliseconds then the user will never know. If it takes a second or more it will be noticeable.
Note: this example assumes your main application is an object that inherits from a Tkinter widget. If that's not the case it will still work, you just have to remove the self parameter. An even better solution is to refactor your GUI to use objects -- it's a much more flexible way of implementing GUIs.
Related
I am a beginner python learner, tkinter in particular.
I want to make a 'loading screen' of a simple python script and closes after the script ends.
But making a window requires a mainloop function which means that it will loops infinitely or wait for a user interaction(or so i think) and it will eliminate the idea of a 'loading' screen.
I tried something but ends up with (Put Loading Screen) -> (Loading screen still have mainloop) -> (Can't Run Script because of waiting)
What i wanted in detail was (Put Loading Script) -> (Run Script) -> (Script ends) -> (Loading Screen destroy)
I got a lot of experience in other languages especially Java but java can just declare a frame -> run other things afterwards -> call a frame.dispose() and that's just it. Any tips or suggestions for a learner?
EDIT: The script actually is a image processing algorithm that connects to a database and I can't just put a timed wait or sleep since the database can be expanded and it might be take longer than the allocated time.
Something along these lines might work for you. This creates the window root, and defines a function task which destroys root as the last thing it does. In this example, task just sleeps for two seconds, but you'd replace that sleep call with whatever code you want to run.
You put the task function into the main loop event queue with root.after(200, task). This means the code will first create the root window, wait 200 milliseconds, then call task(), which sleeps for two seconds and destroys the window. At least for this example you need the 200 millisecond delay so that the main loop has enough time to draw the window before the sleep call halts everything (the number might be different for you; increase it if the window doesn't draw properly).
import tkinter as tk
from time import sleep
def task():
# The window will stay open until this function call ends.
sleep(2) # Replace this with the code you want to run
root.destroy()
root = tk.Tk()
root.title("Example")
label = tk.Label(root, text="Waiting for task to finish.")
label.pack()
root.after(200, task)
root.mainloop()
print("Main loop is now over and we can do other stuff.")
Edit: Added a comment to the code.
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'm trying to display a message saying "Hi"
after a couple seconds, i want that message to go away and open a different file
https://hastebin.com/uhafehizok.vbs
so if its true I want it to place the label, wait two seconds, and then destroy it, but the Tkinter window is waiting two seconds to start, and then starting without any label
any ideas
In your code, the label is placed, and after 2 seconds it is destroyed. It is never actually shown in your window however as it is not updated.
This is as when entering Tk's mainloop, it updates the window in a loop, checking if changes have been made. In your case, you are preventing this check by using time.sleep.
With Tkinter, when wanting to have timings you should always use the after method, to arrange everything in terms of Tkinter's mainloop (This uses milliseconds).
To fix your code, you could add root.update() after placing your label. The time.sleep would still freeze up the mainloop whilst it is waiting however, so a better solution would be to remove the call to sleep, and instead call destroy on your label after 2 seconds.
This would look like root.after(2000, letsgolabel.destroy).
*Note that I have been referring to root as your access to tk.Tk(), as this is normally used.
I confused about loops. For example in python3:
import pygame
pygame.init()
....
....
while True:
....
....
pygame.display.update()
When i use this, the program using, about %110 CPU. But when i use a GUI toolkit like tkinter:
import tkinter
root = tkinter.Tk()
....
....
....
root.mainloop()
It using about %0.3 CPU. I think both are infinite loops. How can I optimize the first code?
The main difference is, that your typical GUI toolkit will blockingly wait for the arrival of new events and the loop body, including drawing operations, only gets executed in reaction to such events.
A typical game loop however does not wait for events to arrive, since there's a simulation going on, that needs to update continuously followed by updating the display.
I.e. the pygame loop is designed to use up as much CPU cycles as possible, to give the most smooth simulation. The tkinter loop however is designed to spend as much CPU cycles as possible to conserve system resources.
I am writing an app in kivy which does cpu-heavy calculations at launch. I want the app to display what it's doing at the moment along with the progress, however, since the main loop is not reached yet, it just displays empty white screen until it finishes working. Can I force kivy to update the interface?
Basically I'm looking for kivy's equivalent of Tkinter's root.update()
I could create a workaround by defining a series of functions with each calling the next one through Clock.schedule_once(nextFunction, 1), but that would be very sloppy.
Thanks in advance.
Leaving aside the question of whether you should be using threading or something instead (which possibly you should), the answer is just that you should move your cpu calculations to somewhere else. Display something simple initially (i.e. returning a simple widget from your build method), then do the calculations after that, such as by clock scheduling them.
Your calculations will still block the gui in this case. You can work around this by doing them in a thread or by manually breaking them up into small pieces that can be sequentially scheduled.
It might be possible to update the gui by manually calling something like Clock.tick(), but I'm not sure if this will work right, and even if so it won't be able to display graphics before they have been initialised.