Daniel Li
Daniel Li

Reputation: 21

Tkinter GUI freezes while running looping

I'm new to python coding and I have been working on a project which could click on an image based on a chosen color. I have been using a program which loops the search 50 times when I click the start button. However, I have been trying to implement a stop button, but the problem is that my code freezes when the loop is running. Any ideas?

I have heard to try threading but it seems very complicated and I have been unable to follow any tutorials properly in relation to my code. By the way, the image searched has been testing images I've been using stored inside the program files.

from imagesearch import *
import pyautogui
import tkinter as tk
from tkinter import *
from tkinter.ttk import *
import time
import threading


# ---Defined Programs---
def run():
    global enterColor
    enterColor = str(enterColorField.get())
    program(enterColor)


def program(color):
    whitePos = imagesearch_numLoop(str(color) + ".PNG", 0, 50)
    pyautogui.moveTo(whitePos[0] + 20, whitePos[1] + 10)
    pyautogui.click()


def stop():
    print("Placeholder")


# ---Main Runner---
window = tk.Tk()
window.geometry("250x250")
window.configure(background="#181b54")

app = tk.Frame(window)
app.grid()

enterColorLabel = tk.Label(window, text="Enter Color:", bg="#181b54", fg="white")
enterColorLabel.place(x=10, y=50)

enterColorField = Combobox(window)
enterColorField['values'] = ("Black", "White")
enterColorField.current("0")  # set the selected item
enterColorField.place(x=10, y=70)

submitButton = tk.Button(window, text="Start", bg="#66ff00", command=run)
submitButton.place(x=10, y=130)

stopButton = tk.Button(window, text="Stop", bg="red", command=stop)
stopButton.place(x=50, y=130)

window.mainloop()


#---New Python Script---
import cv2
import numpy as np
import pyautogui
import random
import time

def imagesearch_numLoop(image, timesample, maxSamples, precision=0.8):
    pos = imagesearch(image, precision)
    count = 0
    while pos[0] == -1:
        print(image+" not found, waiting")
        count = count + 1
        if count>maxSamples:
            break
        pos = imagesearch(image, precision)
    return pos

Whenever clicking start, the whole code freezes. I can't even (x) out.

Upvotes: 0

Views: 3672

Answers (2)

SyntaxVoid
SyntaxVoid

Reputation: 2623

Here's a hopefully simple multiprocessing recipe that will work for you. We'll have three main functions. The first will be an example loop that you would put your processing inside. I included arguments in the function to show you that it's possible to pass args and kwargs while using multiprocessing.

def loop(a, b, c, d):
     # Will just sleep for 3 seconds.. simulates whatever processing you do.
    time.sleep(3)
    return

Next is a function we will use to queue the multiprocessing process.

def queue_loop():
    p = multiprocessing.Process(target = loop, 
                                args = (1, 2),
                                kwargs = {"c": 3, "d": 4})
    # You can pass args and kwargs to the target function like that
    # Note that the process isn't started yet. You call p.start() to activate it.
    p.start()
    check_status(p) # This is the next function we'll define.
    return

Then, you may be interested in knowing the status of your process throughout its execution. For example it is sometimes desirable to disable certain buttons while a command is being run.

def check_status(p):
    """ p is the multiprocessing.Process object """
    if p.is_alive(): # Then the process is still running
        label.config(text = "MP Running")
        mp_button.config(state = "disabled")
        not_mp_button.config(state = "disabled")
        root.after(200, lambda p=p: check_status(p)) # After 200 ms, it will check the status again.
    else:
        label.config(text = "MP Not Running")
        mp_button.config(state = "normal")
        not_mp_button.config(state = "normal")
    return

Throwing this all together into one snippet:

import tkinter as tk
import multiprocessing
import time

def loop(a, b, c, d):
     # Will just sleep for 3 seconds.. simulates whatever processing you do.
    time.sleep(3)
    return

def queue_loop():
    p = multiprocessing.Process(target = loop, 
                                args = (1, 2),
                                kwargs = {"c": 3, "d": 4})
    # You can pass args and kwargs to the target function like that
    # Note that the process isn't started yet. You call p.start() to activate it.
    p.start()
    check_status(p) # This is the next function we'll define.
    return

def check_status(p):
    """ p is the multiprocessing.Process object """
    if p.is_alive(): # Then the process is still running
        label.config(text = "MP Running")
        mp_button.config(state = "disabled")
        not_mp_button.config(state = "disabled")
        root.after(200, lambda p=p: check_status(p)) # After 200 ms, it will check the status again.
    else:
        label.config(text = "MP Not Running")
        mp_button.config(state = "normal")
        not_mp_button.config(state = "normal")
    return


if __name__ == "__main__":
    root = tk.Tk()
    mp_button = tk.Button(master = root, text = "Using MP", command = queue_loop)
    mp_button.pack()
    label = tk.Label(master = root, text = "MP Not Running")
    label.pack()
    not_mp_button = tk.Button(master = root, text = "Not MP", command = lambda: loop(1,2,3,4))
    not_mp_button.pack()
    root.mainloop()

The result is that when you click the "Using MP" button, the command buttons will be disabled and the process will be started without freezing your UI. Clicking the "Not MP" button will start the function like 'normal' and will freeze your UI as you noticed in your own code.

Upvotes: 2

PC_Wind
PC_Wind

Reputation: 55

A simple answer is you cannot use while loop in GUI design.

But you can use the method .after(delay, callback=None) instead.

Here is an example:

from tkinter import *

root = Tk()

def loop():
    print("Hi!")
    root.after(1000, loop) # 1000 is equal to 1 second.

root.after(1000, loop) # This line is to call loop() in 1 second.
root.mainloop()

Upvotes: 0

Related Questions