0

Alright, so all I want is to wait for a function to be run and then continue the rest of the code. Basically, all I want to do is wait until the user presses a Tkinter button, and then after that start the rest of the code.

from tkinter import *

def function():
    [insert something here]



root=Tk()
btn1=Button(root, command=function)

Now wait until the user presses the button and then continue

print("Yay you pressed a button!")
  • the `mainloop` function does exactly that: It waits for events to happen (like clicking your button), and dispatches the appropriate commands (callback functions): https://stackoverflow.com/questions/29158220/tkinter-understanding-mainloop – Aaron Aug 03 '22 at 15:10
  • GUIs don't work like `input()` - if you want to continue some code after pressing buttont then put this code inside `function()` – furas Aug 03 '22 at 15:20
  • research the tkinter widget methods `wait_variable` and `wait_window`. – Bryan Oakley Aug 03 '22 at 15:29

2 Answers2

1

If you want to continue some code after pressing button then put this code inside function()

import tkinter as tk  # PEP8: `import *` is not preferred

# --- functions ---

def function():
    print("Yay you pressed a button!")
    # ... continued code ...
    
# --- main ---

root = tk.Tk()

btn1 = tk.Button(root, command=function)
btn1.pack()  # put button in window

root.mainloop()   # run event loop which will execute function when you press button

PEP 8 -- Style Guide for Python Code


EDIT:

GUIs don't work like input() which waits for user data. Button doesn't wait for user's click but it only inform mainloop what it has to display in window. And mainloop runs loop which gets key/mouse events from system and it checks which Button was clicked and it runs assigned function. GUI can't stops code and wait for button because it would stop also other widgets in window and it would look like it frozen.

The same is in other GUIs - ie. PyQt, wxPython, Kivy - and other languages - ie. Java, JavaScript.

furas
  • 134,197
  • 12
  • 106
  • 148
  • That's a good answer and should work with my code. But is there another way? – Keyboard's_Slave Aug 03 '22 at 16:09
  • why do you need another way? It is standard way in most GUIs - Tkinter, PyQt, wxPython, Kivy - and in most languages - i.e. Java, JavaScript, etc.. `Button()` can't wait for your click - it only informs `mainloop` what it has to display in window. And `mainloop` runs loop which gets key/mouse events from system and it checks which Button was clicked and it runs assigned function. `GUI` can't stops code and wait for button because it would stop also other widgets in window and it would look like it freezes. – furas Aug 03 '22 at 16:32
0

The last time I needed that 'non-blocking' wait in tkinter was in a simple MusicApp. To avoid blocking the tkinter mainloop() I have to run the code in chunks. As I use pexpect I can't run the code in an other thread and using .after/.after_cancel to schedule the 'chunks' execution. As Bryan Oakley suggested I'm using .wait_variable(). Here's the code:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
'''"Basically, all I want to do is wait until the user presses a
Tkinter button (labeled 'Press me'), and then after that start the
rest of the code (everyting represented by print('run the code...')".

Custom code is represented by RUN_CNT function. UPDATE_MSG is to
visualize that functionality. Non-blocking wait is done by tkinter
wait_variable() function

'''
import tkinter as tk
RUN, DONE, TERMINATED = 0, 1, 2


class App(tk.Tk):
    def __init__(self):
        super().__init__()
        self.cnt = 100
        self.msg = tk.Label(self, text='{:5d}'.format(self.cnt))
        self.msg.pack()
        self.var = tk.IntVar()
        self.var.set(RUN)
        self.id = None
        tk.Button(self, text='Start', command=self.run_cnt).pack()
        self.btn = tk.Button(self, text='Press me', state='disabled',
                             command=lambda: self.var.set(DONE))
        self.btn.pack()
        self.protocol('WM_DELETE_WINDOW', self.quit)

    def quit(self):
        # w/o the following WM_DELETE_WINDOW never ends the app
        self.var.set(TERMINATED)
        self.unsked()
        super().quit()

    def update_msg(self):
        '''Run the user code in chunks

        to give the mainloop() the chance to handle events'''

        if 0 < self.cnt:
            self.msg.configure(text='{:5d}'.format(self.cnt))
            self.cnt -= 2
        else:
            self.var.set(DONE)
        self.sked()

    def unsked(self):
        if self.id:
            self.after_cancel(self.id)
            self.id = None

    def sked(self, func=None):
        self.unsked()
        self.id = self.after(200, func if func else self.update_msg)

    def run_cnt(self):
        self.sked(self.update_msg)
        self.btn.configure(state='normal')
        self.wait_variable(self.var)
        if self.var.get() == DONE:
            print("Yay you pressed a button!")
        else:
            print('Terminated')
        self.quit()


if __name__ == '__main__':
    App().mainloop()

run_cnt(), and update_msg() represent the 'custom' code. update_msg() updates the tk.Label to visually confirm the functionality. 'Press me' button indicates the event we're waiting for.

tkwait.py

Tested for Python 3.9.2, Debian GNU/Linux 11 (bullseye)

It prints:

$ python tkwait.py
Yay you pressed a button!
$ python tkwait.py
Terminated