Tkinter: mainloop() looping over an init function? Why and how? - python

What code exactly does mainloop() loop over? I've seen a couple examples where the loop appears to be run over the init function of a window object, but also some where it seems to run over the whole program/no particular object. For what reasons would you set it up one way or the other? And how can you tell what mainloop() is going to execute?
I've read a bunch of Tkinter documentation and StackOverflow questions and (admittedly only skimmed) a couple tutorials, and they all tell me that mainloop() creates and executes an eternal event loop... but there's no detail on what code constitutes the event loop.

The event loop is internal to Tk. It's the code that waits for the user to do something and dispatches those events to the handlers you registered. When it has dispatched one event, it loops back to wait for the next user action. You don't need to do anything with it, but it's what makes the whole system work.
The event loop is found in most (probably virtually all) GUI programming models; it's not just a Tk thing. They don't explain it because it is assumed that you're already familiar with it.
https://en.wikipedia.org/wiki/Event_loop

Related

Functions acting strange when run as thread in Kivy/Python

I am building an app that, when the user hits a 'run' button, generates a table of buttons.
Because this process takes a while, I want to add a popup or progress bar to alert the user that the function is running and not frozen. To do this I decided to create a popup and call my function using threading so that the screen will be updated when the function starts (as opposed to once it is done).
mythread = threading.Thread(target=run_function)
mythread.start()
The trouble is that when I call my function from the above code it works very strangely: the columns of my table are the wrong width, some of my buttons are arbitrarily empty, and others have the wrong fill color. To fix this, all I need to do is to remove the threading operation and simply call run_function()
Any idea why this is happening?
I am new to Python, so it is likely some dumb mistake, but I have no idea. What is different between a process running as a thread and its default operation?
Disclaimer: I haven't worked with Kivy.
Not every framework works well with multithreading.
But most of the GUI frameworks have an event loop which is responsible for managing user events (mouse clicks, keyboard) and queued drawing operations (widgets).
In your case if don't want the UI to be freezed, you should regularly give control to your framework's event loop.
I guess kivy.base.EventLoopBase.dispatch_input is what you need to call to show an added widget or to handle user events.

Reading user's input during simulation via GUI [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 8 years ago.
Improve this question
I'm in the process of converting a simulation based application using simpy into a GUI.
The program currently runs within the console and the simpy which is doing the simulation runs by default in a loop like syntax. And that's where my issue seems to be.
Currently, within the console version of the code, I grab user input through the raw_input() function and that is able to interrupt the code and allow the user to input the values that the simulation desires. However, despite researching it, there does not seem to be a similar and clean way of doing this through pyqt inisde the GUI I'm building.
Would the only way be to run the processes in different threads? And if I were to do that approach, how exactly would that look and truly function?
PyQt is event-based. It's running a loop continuously waiting for events, and it calls your callbacks (or signals your slots) when it gets an event you care about. So, there's no way to directly say "block until I get input".
But, before you even get to that point, if your simulation is running a loop continuously in the main thread, PyQt can't also be running a loop continuously in the main thread. So it can't respond to events from the OS like "update your window" or "quit". As far as your user is concerned, the app is just frozen; she'll see nothing but the ever-popular beachball (or other platform equivalent).
And however you choose to solve that first problem will solve most of the second one almost for free.
Why your GUI app freezes attempts to explain the whole issue, and all of the possible solutions, in general terms, using Tkinter as an example of a GUI library. If you want something more Qt-specific, I'm pretty sure there's a whole section about it in the Qt tutorial, although I'm not sure where, and you may have to translate a bit of C++ to Python in your head.
But there are two major options: Callbacks, or threads.
First, you can break your loop up into small pieces, each of which only takes a few milliseconds. Instead of running the whole loop, you run the first piece, and as its last line, it asks PyQt to schedule the next piece as soon as possible (e.g., using a QTimer with a timeout of 0). Now, Qt will get to check for events every few milliseconds, and if it's got nothing to do, it will immediately kick off the next step of your work.
If your flow control is already built around an iterator (or push-coroutine) that yields appropriately-sized chunks, this is very easy. If not, it can mean turning the flow control in your outer loop inside-out, which can be hard to understand.
So, having done this, how do you get user input? Simple:
Where you would have called raw_input, instead of scheduling the next piece of your code, instead do some appropriate GUI stuff—create a popup messagebox, unhide a text entry control and button, whatever.
Connect the next piece of your code as a handler for the button-clicking or messagebox-accepting or whatever signal.
Alternatively, you can run your work in a background thread. This doesn't require you to reorganize anything, but it does require you to be careful not to share anything between threads. Unfortunately, this includes calling methods on GUI widgets from the background thread, which you'd think would make it impossible to do anything useful. Fortunately, PyQt has mechanisms to deal with that pretty easily: signals are automatically routed between threads as necessary.
So, how do you ask for user input in this scenario?
Split off everything after the raw_input into a separate function, which you connect as a handler for a got_input signal.
In the original function, where you used to call raw_input, you instead emit a gimme_input signal.
Write a handler for that gimme_input signal to run in the main thread, which will put up the GUI widgets (as with the single-threaded example above).
Write a handler for the OK button that emits the got_input signal back to the worker thread.

Force update GUI in kivy

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.

Kivy equivalent to tkinter's messagebox

I'm attempting to transition a program from tkinter to Kivy. When I got to a section where I used messagebox.askyesno, I figured that I could just create a popup with a couple of buttons, and I'd be done. The issue I've encountered is that, while Kivy's popup and tkinter's messagebox are both modal, when I call messagebox.askyesno in a function, messagebox will halt all execution of the current function until the messagebox is destroyed, while the popup will allow the function to finish. My original program had
flag = messagebox.askyesno(message='...',parent=self)
if flag:
#Stuff if flag is true
else:
#Stuff if flag is false
However, this will not work with a Kivy popup since the popup will open, and the program will continue to execute. Is there a way to halt execution until the popup has been destroyed, or another way to solve the problem?
The basic idea is a quasi-dialog for a two player game. Here, the program asks one player if he wants to perform an action, such as move a piece. If the player says "yes," then the second player is given a messagebox.askyesno for a counter-move. A simple analogy is advancing a runner from first base to third base on a single in baseball. You would have to ask the offensive team if he wants to advance the runner, or have the runner remain at second. If the answer is yes, then the program would have to ask the defensive team if he wants to throw to third. It would definitely be possible to create a function to handle each instance of askyesno, with appropriate bindings, but it seems excessive.
I'm not very familiar with how tkinter does things, but kivy requires a slightly different mental model here. You don't want to stop and start the eventloop in between bits of python code, but instead probably want to start the popup, pass any state you need into it or store it somewhere else, then bind the result of the popup (e.g. when the user presses a 'done' button) to some new function that does the rest of your calculation.
I can provide an example if you like, especially if you give more information about what you're trying to do.

Running Python graphics and code

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.

Categories

Resources