0

I would like to make a python tkinter window with custom-moving widgets on a canvas to simulate motion. For now, I have one canvas, and one not-moving oval widget. I am having problems at the base level; mainloop(). I understand that it runs in wait for the user to do something, but I am having a hard time seeing:

  1. How to control/see exactly what code mainloop() is reiterating (where, and only tkinter?);

  2. How to properly interrupt it and return to it from another function, if it doesn't do it itself;

  3. What code should be reiterated? All tkinter objects, or only updating changing ones? Use some kind of update operation instead? Finally;

  4. What is the functionality difference between tkinter.mainloop() and window.mainloop()? Perhaps the previous questions will answer.

I have minor experience with Swift, and started learning the very similar Python yesterday evening. I've tried probably hundred of mutations to my code, which currently is in the test stage. I have moved everything in and out of the apparent range of the mainloop, and even got several hundred tiny Python windows all over the screen. Everything does one of two things: it does nothing, or gives me an error. Since I don't know what is even running, or if it is running, I can't diagnose anything. My goal is simply to move a circle one hundred pixels repeatedly. I've scanned around for sources, but—it may be me—a clear one is scarce. I have my code here all marked up. This page is closest to what I am looking for: Move a ball inside Tkinter Canvas Widget (simple Arkanoid game). Everything appears to be under mainloop. So, everything is redrawn every pass? Here, unfortunately, is my whole script; I can't only show pieces. It, for some reason, only brings up a small window, not a full-screen one. (Edit: I seem to have lost the screen size code)

import tkinter
import time

# Initial values for circle's corners and start idicator ('b'):
x1 = 10
y1 = 10
x2 = 210
y2 = 210

b = 0

# Window ('window')
window = tkinter.Tk()

# Canvas ('area')
area = tkinter.Canvas(window, width=1368, height=650)
area.place(x=0, y=0)


# Ovals to be placed on 'area'
oval1 = area.create_oval(x1,y1,x2,y2,fill='#42befe')
oval2 = area.create_oval(100,10,300,210,fill='#d00000')

# Turns b to 1 to start shifting when 'butt' is pressed:
def startFunc():
    b = 1
    print('b = 1')

# My button to activate 'startFunc'  
butt = tkinter.Button(window, text='Start movement', command=startFunc)
butt.pack()

# Adjusts the x and y coordinates when they are fed in:
def Shift(A, B, C, D):
    print('Shift activated.')
    window.after(1000)
    print('Edit and return:')
    A += 100
    B += 100
    C += 100
    D += 100
    return(A, B, C, D)


# Problems start about here: my Mainloop section;
# I have little idea how this is supposed to be.
while True:

    if b == 1:
        # Takes adjusted tuple
        n = Shift(x1, y1, x2, y2)
        print('Returned edited tuple.')

        # Changes coordinates
        x1 = n[0]
        y1 = n[1]
        x2 = n[2]
        y2 = n[3]
        print(f'{x1}, {y1}, {x2}, and {y2}')

        # Reiterate moving oval
    oval1 = area.create_oval(x1,y1,x2,y2,fill='#42befe')

    #Does this re-run 'window' relations outside here, or only within the 'while'?
    window.mainloop()

It ought to show a 1368 by 650 window, not a tiny one. The button does nothing but print, which means the final 'while' is not running, despite the mainloop. It want it to loop inside the 'while' line, which should adjust coordinates and move my blue circle. The iteration may NOT touch the initial values, or else it would reset them.

Scott V.
  • 11
  • 2
  • Well, `startFunc()` does nothing - the `b` it sets is a local variable, not related to your global `b`. It doesn't matter what widget you call `.mainloop()` on, but it's pointless to do so in a loop, as it's not going to return until all windows are closed. The way to do an ongoing operation, such as an animation, is to do a single step of the work, and call `.after()` to schedule the next step some time in the future; a loop would just block the GUI from doing anything. – jasonharper Jan 27 '19 at 05:16

1 Answers1

1

In effect, calling mainloop is the same as if you added this to your code instead of calling mainloop():

while the_program_is_running():
    event = wait_for_event()
    process_the_event(event)

As a rule of thumb, mainloop() should be called exactly once after the UI has initialized and you are ready for the user to start interacting with your program. When it exits, you typically won't have any code after it, and your program will exit.


How to control/see exactly what code mainloop() is reiterating (where, and only tkinter?);

I don't know what you mean by "reiterating". It doesn't run any code except it's own internal code. It simply waits for events, and then dispatches them to handlers.

How to properly interrupt it and return to it from another function, if it doesn't do it itself;

It's exceedingly rare to do this in a running program. Typically, calling mainloop is the last thing your program does before the user starts interacting with it, and as soon as it exits your program quits.

However, to answer the specific answer of how to interrupt it, you can call the quit method of the root window. That will cause the most recent call to mainloop() to return.

What code should be reiterated? All tkinter objects, or only updating changing ones? Use some kind of update operation instead?

That question is hard to answer because it doens't make much sense. When you call mainloop(), it will watch for all events on all tkinter objects.

What is the functionality difference between tkinter.mainloop() and window.mainloop()

They have exactly the same effect and behavior. Tkinter oddly chose to make mainloop available from any widget. The most common way to call it is from either the tkinter module itself, or from the root window.

My goal is simply to move a circle one hundred pixels repeatedly.

The normal way to do that is to create a function that moves it one hundred pixels. Then, that function (-- or a function that calls it -- can put itself on an event queue to be run in the future.

For example, the following code will move a canvas object 100 pixels every second until the program exits:

def move_object():
    the_canvas.move(item_id, 100, 0)
    the_canvas.after(1000, move_object)

When it is called, it will move the item 100 pixels to the right. Then, it will place a new call to itself on the event queue to be picked up and handled in approximately 1000 milliseconds.

There are many working examples of using after on this site, including the question you linked to in your question.

Everything appears to be under mainloop. So, everything is redrawn every pass?

No, not exactly. The only objects that are redrawn are things that need to be redrawn. Moving objects on a canvas, resizing a window, dragging another window over your window, etc, all place an event on the event queue that tells tkinter "this object needs to be redrawn". The processing of that event happens automatically by mainloop. If nothing is happening in your application, nothing gets redrawn by mainloop.

It ought to show a 1368 by 650 window, not a tiny one

That is because you haven't given the main window a size. You've given the canvas a size, but you're using place which won't cause the containing window to grow or shrink to fit. As a beginner, you should completely avoid place and instead use pack or grid, because pack and grid will both automatically size your window to fit everything inside.

While it's tempting to use place for its perceived simplicity, in reality it usually requires you to do a lot more work than if you used one of the other geometry managers, and it results in a GUI that isn't particularly responsive to change.

while True:

You should almost never do this in tkinter. Tkinter -- and almost all event based programs -- rely on a steady flow of events. When you have an infinite loop, it cannot process those events. You can put an explicit call to update the screen inside your loop, but that is inefficient and should be avoided. If you need to do something periodically, create a function that encapsulates the body of your loop, then use after to get mainloop to run it while it is processing events.

window.after(1000)

You should almost never use after this way without a second argument. This usage is functionally no different than calling time.sleep(1) in that it prevents mainloop from processing events. You should structure your code to allow for a steady stream of events to be processed by mainloop.

while True: ... window.mainloop()

You definitely need to avoid calling mainloop inside a loop. A well behaved tkinter program should call mainloop() exactly once.

Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685
  • If your move_object function is run, then the canvas moves, and the function is re-run. If a conditional is added on the inside to decide whether it will continue or not decides 'false', then this loop terminates. Will mainloop() continue running, or quit? (I'd rather it still worked to keep the screen up!) Or, while this function is waiting for 'after', is mainloop still going? – Scott V. Jan 27 '19 at 14:24
  • @ScottV.: mainloop will continue to run until the main window is destroyed. – Bryan Oakley Jan 27 '19 at 15:45