1

I'm working on a tkinter GUI to ease the operation of some scripts that are meant to both send signals to an Allen-Bradley PLC to write tag values and also read other tags repeatedly in a logging file. I want to have something in a tkinter window that would have the same behavior as using the terminal in this way:

print("Log script starting")
logger = subprocess.Popen(*start logging args*)
time.sleep(3)
plc.write((onTag, 1))
print("Cycle started\nCycle running...")
time.sleep(cycle_runtime)

print("Cycle finished")
logger.stop()

Can tkinter.after() be called multiple times, one after the other, and have those events happen similar to above? Or does each tkinter.after() call need to have the time based on a shared starting time, i.e.:

logStartWidget.grid()
widget.after(3000, lambda: plc.write((onTag,1)))
cycleStartWidget.grid() # will this happen immediately after logStartWidget.grid(), or
# wait for the "after" call to happen?

cycleRunningWidget.grid()

widget.after(3000+cycle_runtime*1000, lambda: plc.write((onTag, 0))) # does the time
# here start at the same time as the previous after() call or after it returns?
2
  • 2
    you can create function which uses after to run itself - and this way you will have loop which updates data periodically. There are examples which uses it to display current time. Commented Jul 16 at 18:22
  • 3
    might be a good opportunity to learn asyncio and use async-tkinter-loop instead of whatever monstrosity you are trying to do, the first snippet translates beautifully into async code. Commented Jul 16 at 18:26

1 Answer 1

4

Yes, you can use tkinter.after() to create sequential operations.

How tkinter.after() works:

  1. Non-blocking: this function schedules callbacks to run later without blocking the mainloop.

  2. sequential timing: each after() call's delay is relative to when it was scheduled, not when the previous one completed.

  3. chaining: You can chain operations by scheduling the next step in the current step's callback.

what you can do:

chain after() calls:

def start_cycle():
    plc.write((onTag, 1))
    cycleStartWidget.grid()
    print("Cycle started\nCycle running...")
    # Schedule cycle finish after runtime
    widget.after(cycle_runtime * 1000, finish_cycle)

def start_logger():
    logger.start()
    # Schedule PLC write after 3 seconds
    widget.after(3000, start_cycle)

def start_sequence():
    print("Log script starting")
    logStartWidget.grid()
    
    # Start logger immediately
    widget.after(0, start_logger)

def finish_cycle():
    print("Cycle finished")
    plc.write((onTag, 0))
    logger.stop()
    cycleRunningWidget.grid_remove()  # or whatever cleanup needed

or, use a single timeline:

def handle_cycle_start():
    plc.write((onTag, 1))
    cycleStartWidget.grid()
    print("Cycle started\nCycle running...")

def handle_cycle_end():
    print("Cycle finished")
    plc.write((onTag, 0))
    logger.stop()

def start_sequence():
    print("Log script starting")
    logStartWidget.grid()
    logger.start()
    
    # Schedule all events relative to start time
    widget.after(3000, handle_cycle_start)
    widget.after(3000 + cycle_runtime*1000, handle_cycle_end)

differences:

  • In your question's example, cycleStartWidget.grid() would execute immediately after logStartWidget.grid(), not waiting for the 3-second delay

  • Each after() call's time is relative to when it was scheduled from the main thread

  • For truly sequential operations, you should chain them (Option 1) rather than scheduling all at once

the chained approach is generally clearer and the recommended best practice for sequential operations because:

  1. It makes the sequence of events more obvious

  2. Each step clearly follows the previous one

  3. It's easier to add error handling between steps

also, remember that all GUI updates (like grid()) must happen in the main thread, which after() handles properly.

Edit: As @TheLizzard pointed out:

Using multiple statements inside a lambda is discouraged in python since it's much harder to read. For clarity, I suggest defining a separate helper function.

so I edited my code to fix that. 😀

Sign up to request clarification or add additional context in comments.

2 Comments

Using multiple statements inside a lambda is discouraged in python since it's much harder to read. For clarity, I suggest defining a separate helper function.
You're right. thanks for pointing it out. I just edited my code to fix this. :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.