Python Forum
[Tkinter] Sections of the code causes the GUI to freeze. What should I do?
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
[Tkinter] Sections of the code causes the GUI to freeze. What should I do?
#1
Question 
Im kind of a beginner to coding. To be fair its my first coding besides college excercises but I've tried to write small program which purpose is to check if user clicked certain button in other application and if so, it would terminate process of that app.

Everything works pretty much as it should but I noticed that program can't get out of one of sections of the code which causes GUI to freeze. What should I do?

from pynput import *
import pyautogui
from tkinter import *
import psutil

window = Tk()
window.geometry("600x300")
window.title("Zamknij Lige-inator")
window.iconbitmap("logo.ico")
window.config(background="#525252")
window.resizable(False, False)



def check_coords(x1, y1, button, pressed):
    if pressed and pyautogui.locateCenterOnScreen("loot_icon.png", confidence = 0.9) != None: 
        x2, y2 = pyautogui.locateCenterOnScreen("loot_icon.png", confidence = 0.9)

        #print("Image at: {}".format((x2, y2)))
        #print("Cursor at: {}".format((x1, y1)))

        if x2-29<=x1 and x2+29>=x1 and y2-41<=y1 and y2+41>=y1:
            #print("Coordinates matched!")
            for proc in psutil.process_iter():
                if proc.name()=="LeagueClient.exe":
                    proc.kill()
            is_paused = True
    elif pressed:
        #print("Image not found!")
        is_paused = True

is_paused = False

def run():
    global is_paused

    if is_paused == False:
        #print("debug")
        with mouse.Listener(on_click = check_coords) as listen:
            listen.join()
    else:
        is_on = False

is_on = False

def switch():
    global is_on

    if is_on == True:
        b1.config(background="#3eca5a", activebackground="#65c979", text="Start")
        is_on = False
    else:
        b1.config(background="#CA3E47", activebackground="#c95b62", text="Stop")
        is_on = True
        is_paused = False
        run()



f1 = Frame(window, width=580, height=260)
f1.grid(row=0, column=0, padx=(10,10), pady=(20,20))
f1.grid_propagate(False)
f1.config(bg="#414141")

l1 = Label(f1,
text="Zamknij Lige-inator",
font = ("Helvetica", 24),
bg="#313131",
fg="white")
l1.grid(row=0, column=0, columnspan=3, 
padx=53, pady=(20,30), ipadx=98, ipady=20, sticky=EW)

b1 = Button(f1,
text="Start",
bg="#3eca5a",
fg="black",
activebackground="#65c979",
command=switch)
b1.grid(row=1, column=0, columnspan=2, 
padx=(53,0), pady=(0,0), ipadx=150, ipady=40)

b2 = Button(f1,
text="Exit",
bg="#313131",
fg="white",
activebackground="#525252",
command=window.quit)
b2.grid(row=1, column=2, 
padx=(0,53), pady=(0,0), ipadx=60, ipady=40)

window.mainloop()
[Image: 8476dac0cc93f.png]
Im not sure if this is allowed but I'll leave one more comment to "bump" the post
Reply
#2
The gui is frozen as soon as you press "Start" because "listen.join()" is blocking your mainloop() from running. The GUI cannot process mouse click events or window move events or anything until the mouse listener thread ends.

I think you need to run the run() function in its own thread. This way waiting for the mouse listener does not prevent mainloop() from running. Something like this:
# Do not use from module import *
from pynput import mouse
from pyautogui import locateCenterOnScreen
import tkinter as tk
import psutil
import threading


def check_coords(x1, y1, button, pressed):
    """Mouse button click callback function.
    Called when mouse listener is running and user clicks mouse button.
    If user clicked on image, kill process.
    """
    if pressed and locateCenterOnScreen("games/dice1.png", confidence=0.9):
        x2, y2 = locateCenterOnScreen("games/dice1.png", confidence=0.9)
        if abs(x2 - x1) <= 29 and abs(y2 - y1) <= 41:
            for proc in psutil.process_iter():
                if proc.name() == "LeagueClient.exe":
                    proc.kill()
    return running


def mouse_listener():
    """Mouse listener function.
    Run function in its own thread to prevent blocking GUI mainloop.
    """
    listen = mouse.Listener(on_click=check_coords)
    listen.start()
    while running:
        pass
    listen.stop()


def exit_program():
    """Exit the program.
    Shut down the mouse listener and quit the main window
    """
    global running

    if thread:
        running = False
        thread.join()
    window.quit()


def start_stop():
    """Start/stop the mouse listener"""
    global running, thread

    if running:
        start_btn.config(text="Start")
        running = False
        if thread:
            thread.join()
            thread = None
    else:
        start_btn.config(text="Stop")
        running = True
        thread = threading.Thread(target=mouse_listener)
        thread.start()


running = False
thread = None
font = ("Helvetica", 24)

window = tk.Tk()

start_btn = tk.Button(window, text="Start", font=font, width=10, command=start_stop)
start_btn.pack(side=tk.LEFT, ipadx=20, ipady=20)

exit_btn = tk.Button(window, text="Exit", font=font, command=exit_program)
exit_btn.pack(side=tk.LEFT, ipadx=20, ipady=20)

window.mainloop()
For purposes of clarity, I tried to remove any code not directly related to toggling the listener. It is easier to focus on the threading code if it is not hidden by a bunch of GUI decorations.

I removed the wildcard imports because they are a terrible programming practice. When I first looked at the code, I had no idea where mouse.Listener came from. Using "from pynput import mouse" makes it obvious. For tkinter I used "import tkinter as tk" because most program import so many tkinter objects that the "from import" notation is tedius. I used "from pyautogui import locateCenterOnScreen" instead of "import pyautogu" just to shrink down the really long pyautogui.locateCenterOnScreen() (uses up half the characters I have per line before lint complains).

Instead of "if is_paused == False" use "if is_paused is False" or "if not is_paused".

Instead of "if is_on == True" use "if is_on is True" or "if is_on".
Wilkk likes this post
Reply
#3
Thank you for help good sir. Sorry for late response, I didnt expect it that fast this time. I pretty much copied code you sent me and reapplied GUI settings I prepared and now its working as intended.

I noticed that you removed
!= None: 
from
("loot_icon.png", confidence = 0.9) != None:
line. Im not sure now why I put that here but I think it was something with program getting stuck/buggy when there weren't any image on screen that it could detect as matched in the first place. I see that now it works even without that? I dont quite get that yet.

Also I would have one more question. At the moment while running program, everything gets quite laggy. Even if Task Manager says that Python barely uses any CPU/Memory. Will that change after building it into standalone app and its just coding enviroment thing or there is still something to change?

Once again thanks for all the help you've provided
Reply
#4
if pyautogui.locateCenterOnScreen("loot_icon.png", confidence = 0.9):
    ...
pyautogui.locateCenterOnScreen could return None or a tuple with two int.

The if statement asks the object for its boolean. bool(None) evaluates to False.
An empty list, tuple, dict, set, str, bytes, etc. evaluates to False. If they contain elements, then the bool() call returns True.

The result that you want is a non-empty Tuple, so the approach not to check for None, instead check for truthiness, is better.
Almost dead, but too lazy to die: https://sourceserver.info
All humans together. We don't need politicians!
Reply
#5
Alright, thanks for help
Also I have one more and possibly last question. I want to reduce CPU usage of script and I've read that the simpliest solution is to put
time.sleep(amount of time)
into code. What would be the best place for it tho?
Reply
#6
It's best not to use time.sleep with GUI code or it will interrupt the mainloop from updating and freeze the GUI.
If you want to call something after a duration of time use tkinters after method to call something after a certain amount of milliseconds.
There is an example of a solution using after in a recent thread shown here TKinter Not Executing
Also the following thread has information on not freezing the GUI:
[Tkinter] How to deal with code that blocks the mainloop, freezing the gui
Reply
#7
If you are using code from my earlier post, replace pass with sleep(seconds) in mouse_listener(). This will not interfere with the gui because it runs in a different thread. It should be a very short wait or else the mouse listener response will suffer.
Reply
#8
Thank you both for the response. I'll try 2nd solution since I didnt modify the code pretty much at all.

I also noticed that with every mouse click, the mouse itself gets pretty laggy which is also problematic. Was seeking for some fix to that too but havent found anything yet. Would you have any idea for that?
Reply
#9
Try this. Is it more responsive than your program? .join() is probably more efficient that looping. Running the listener in a separate thread prevents blocking the GUI event handler. The trick is finding a way to stop the listener.
from pynput.mouse import Listener
import tkinter as tk
import threading


def on_click(x1, y1, button, pressed):
    """Mouse listener event handler.  Return False to stop listener."""
    if window.winfo_containing(x1, y1) in (start_btn, exit_btn):
        # Stop listener if user clicks start or exit buttons
        return False
    if pressed:
        print(x1, y1)  # Put mouse click event code here
    return True


def mouse_listener():
    """Worker function for mouse listener thread"""
    with Listener(on_click=on_click) as listener:
        listener.join()


def start_stop():
    """Start/Stop button callback"""
    if start_btn["text"] == "Start":
        start_btn["text"] = "Stop"
        threading.Thread(target=mouse_listener).start()
    else:
        start_btn["text"] = "Start"


def exit_program():
    """Exit button callback"""
    window.quit()


font = ("Helvetica", 24)
window = tk.Tk()

start_btn = tk.Button(window, text="Start", font=font, width=5, command=start_stop)
start_btn.pack(side=tk.LEFT, ipadx=20, ipady=20)

exit_btn = tk.Button(window, text="Exit", font=font, command=exit_program)
exit_btn.pack(side=tk.LEFT, ipadx=20, ipady=20)

window.mainloop()
The listener stops running when the event function returns False. This code stops the listener when you click on the Start/Stop or Exit buttons. It does this by having the Listener event function return False when the mouse button is clicked while the cursor is over the button. The rest of the code looks like a normal tkinter program.

And a quick note about the buffalo operator ":=".

Instead of this:
if pressed and locateCenterOnScreen(icon, confidence=0.9) != None:
    x2, y2 = locateCenterOnScreen(icon, confidence=0.9)
    if x2-29<=x1 and x2+29>=x1 and y2-41<=y1 and y2+41>=y1:
You can use the buffalo operator to do the assignment and check the return value at the same time.
if pressed and (icon_pos := locateCenterOnScreen(icon, confidence=0.9)):
    x2, y2 = icon_pos
    if -30 < x2 - x1 < 30 and -42 < y2 - y1 < 42:
Reply
#10
Hey, once again thanks for the response! I really appreciate your help.
The code is without doubts way better.

Full code atm:

from pynput.mouse import Listener
from pyautogui import locateCenterOnScreen
import tkinter as tk
import psutil
import threading

def on_click(x1, y1, button, pressed):
    """Mouse listener event handler.  Return False to stop listener."""
    if window.winfo_containing(x1, y1) in (start_btn, exit_btn):
        # Stop listener if user clicks start or exit buttons
        return False
    if pressed:
        if pressed and (icon_pos := locateCenterOnScreen("loot_icon.png", confidence=0.9)):
            x2, y2 = icon_pos
            if -30 < x2 - x1 < 30 and -42 < y2 - y1 < 42:
                print("Image at: {}".format((x2, y2)))
                print("Cursor at: {}".format((x1, y1)))
                for proc in psutil.process_iter():
                    if proc.name() == "LeagueClient.exe":
                        proc.kill()
    return True

def mouse_listener():
    """Worker function for mouse listener thread"""
    with Listener(on_click=on_click) as listener:
        listener.join()

def start_stop():
    """Start/Stop button callback"""
    if start_btn["text"] == "Start":
        start_btn["text"] = "Stop"
        start_btn.config(background="#CA3E47", activebackground="#c95b62")
        threading.Thread(target=mouse_listener).start()
    else:
        start_btn["text"] = "Start"
        start_btn.config(background="#3eca5a", activebackground="#65c979")
        

def exit_program():
    """Exit button callback"""
    window.quit()

window = tk.Tk()
window.geometry("600x300")
window.title("Ligo-terminator")
window.iconbitmap("logo.ico")
window.config(background="#525252")
window.resizable(False, False)

f1 = tk.Frame(window, width=580, height=260)
f1.grid(row=0, column=0, padx=(10,10), pady=(20,20))
f1.grid_propagate(False)
f1.config(bg="#414141")

l1 = tk.Label(f1,
text="Ligo-terminator",
font = ("Helvetica", 24),
bg="#313131",
fg="white")
l1.grid(row=0, column=0, columnspan=3, 
padx=53, pady=(20,30), ipadx=98, ipady=20)

start_btn = tk.Button(f1,
text="Start",
bg="#3eca5a",
fg="black",
activebackground="#65c979",
command=start_stop)
start_btn.grid(row=1, column=0, columnspan=2, 
padx=(53,0), pady=(0,0), ipadx=150, ipady=40)

exit_btn = tk.Button(f1,
text="Exit",
bg="#313131",
fg="white",
activebackground="#525252",
command=window.quit)
exit_btn.grid(row=1, column=2, 
padx=(0,53), pady=(0,0), ipadx=60, ipady=40)

window.mainloop()
Its basically code modified by you with added proper code that was intended to run upon clicking mouse + more fancy GUI from original post. Once again, it is already way better than it was tho after adding:

if pressed and (icon_pos := locateCenterOnScreen("loot_icon.png", confidence=0.9)):
            x2, y2 = icon_pos
            if -30 < x2 - x1 < 30 and -42 < y2 - y1 < 42:
                print("Image at: {}".format((x2, y2)))
                print("Cursor at: {}".format((x1, y1)))
                for proc in psutil.process_iter():
                    if proc.name() == "LeagueClient.exe":
                        proc.kill()
to code, mouse again noticeably lags while clicking on screen and moving it at the same time. Now I clearly see whats causing this problem. Could the code be improved even more? It seems for me like theres not much to do anymore Undecided Think

Btw I checked it with yours, simplified GUI and its same.
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  [Tkinter] mpv window freeze before video end rfeyer 7 3,440 Feb-20-2021, 09:34 PM
Last Post: rfeyer
  [Tkinter] tkinter freeze DeanAseraf1 0 2,114 Jul-20-2019, 07:46 AM
Last Post: DeanAseraf1
  Tkinter GUI freeze petterg 4 10,922 Jul-01-2019, 03:54 PM
Last Post: petterg
  [Tkinter] GUI Freeze/tkinter SamGer 2 4,106 Jun-24-2019, 07:25 PM
Last Post: noisefloor

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020