0

I'm a python noob and apologize if the solution to my question is obvious. None of the other solutions I've tried seemed to work and I've been stumped on this issue for almost the entire day now. Anyway, I'm trying to create a simple auto clicker that clicks in random intervals with a given range. Functionally, I have everything working but I cannot get my program to stop clicking once it starts.

Here is my clicking function:

def start_clicking(event=None):
min = min_var.get()
max = max_var.get()

def report_click():
    text1.insert(tk.INSERT, "\nTime since last click: " + range_str_s + " (" + range_str_ms + ")\n")

text1.config(state="normal")
try:    # Show exception to user
    min = float(min)
    max = float(max)
except ValueError:
    text1.insert(tk.INSERT, "One or both of your inputs are invalid.")
if running:     # For some reason, this runs even though running is False. I only want this to run while running is true
    random_range = random.uniform(min, max)
    random_range_s_to_ms = int(random_range * 1000)     # Convert seconds to ms
    range_str_s = "{:,.2f}s".format(random_range)
    range_str_ms = "{:,.2f}ms".format(random_range_s_to_ms)
    print(running)
    report_click()
    pyautogui.click()
    root.after(random_range_s_to_ms, start_clicking)    # Recursively call start_clicking
text1.see("end")
text1.config(state="disabled")

This is my stop function:

def stop_clicking(event=None):
    text1.config(state="normal")
    text1.insert(tk.INSERT, "\nStopping...\n")
    global running
    running = False # This is not doing what I want it to do.
    text1.see("end")
    text1.config(state="disabled")

Some things I noted while trying to get this to work is that all of the code under my if running condition in my start_clicking() function still runs despite the fact that it's false (or so I think). I confirmed this by printing it into the console. Whether or not running is true or false seems to have no effect on my code. I commented this into in my program to clarify where the issue is.

Here is a minimal reproducible example:

import pyautogui, time, random, keyboard
import tkinter as tk

root = tk.Tk()
root.geometry("530x320")

min_var = tk.StringVar()
max_var = tk.StringVar()

min_var_entry = tk.Entry(root, textvariable=min_var, font=('calibre', 10, 'normal'), width=8)
max_var_entry = tk.Entry(root, textvariable=max_var, font=('calibre', 10, 'normal'), width=8)

min_var_entry.insert(tk.INSERT, "0")
max_var_entry.insert(tk.INSERT, "1")

def start_clicking_task(event=None):
    global running
    running = True
    start_clicking()


def stop_clicking(event=None):
    text1.insert(tk.INSERT, "\nStopping...\n")
    global running
    running = False
    print("Running is currently ", running)
    text1.see("end")
    text1.config(state="disabled")


def force_close(even=None):
    start_btn.master.destroy()  # This destroys the entire window

def start_clicking_event(event=None):
    global running
    running = True

def start_clicking(event=None):
    global running
    running = True
    min = min_var.get()
    max = max_var.get()
    min = float(min)
    max = float(max)

    if running is True:
        random_range = random.uniform(min, max)
        random_range_s_to_ms = int(random_range * 1000)  # Convert seconds to ms
        range_str_s = "{:,.2f}s".format(random_range)
        range_str_ms = "{:,.2f}ms".format(random_range_s_to_ms)
        pyautogui.click()
        root.after(random_range_s_to_ms, start_clicking)    # This continues to run
        text1.insert(tk.INSERT, "\nTime since last click: " + range_str_s + " (" + range_str_ms + ")\n") # But this stops printing
        print("Running is currently ", running)


text1 = tk.Text(root, height=20, width=41, bg="#A4B0BD")

keyboard.add_hotkey('f2', start_clicking)
keyboard.add_hotkey('f3', stop_clicking)
keyboard.add_hotkey('esc', force_close)

text1.config(state="normal")
text1.grid(row=1, column=0, rowspan=10,padx=10)
range_label = tk.Label(root, text="Click Interval\n(seconds)", font=('calibre', 10, 'bold'), bg="#0A3D62", width=11, fg="#7ddeff")
min_label = tk.Label(root, text="Min", font=('calibre', 10, 'bold'), bg="#0A3D62", width=3, fg="#7ddeff")
max_label = tk.Label(root, text="Max", font=('calibre', 10, 'bold'), bg="#0A3D62", width=3, fg="#7ddeff")
start_btn = tk.Button(root, text='Start(F2)', bg="#2C3335", width=7, fg="#7ddeff", command=start_clicking_event)
stop_btn = tk.Button(root, text='Stop(F3)', bg="#2C3335", width=7, fg="#7ddeff", command=stop_clicking)
force_close_btn = tk.Button(root, text='Force close\n(Esc)', bg="#2C3335", width=8, fg="#7ddeff", command=force_close)
range_label.grid(row=1, column=2)
min_label.grid(row=2, column=1)
min_var_entry.grid(row=2, column=2)
max_label.grid(row=3, column=1)
max_var_entry.grid(row=3, column=2)
start_btn.grid(row=2, column=3, padx=3)
stop_btn.grid(row=3, column=3, padx=3)
force_close_btn.grid(row=8, column=2)

root.mainloop()


Hoswoo
  • 63
  • 1
  • 7
  • Does this answer your question? [Create a main loop with tkinter?](https://stackoverflow.com/questions/63118430/create-a-main-loop-with-tkinter) – Thingamabobs Dec 13 '20 at 08:49
  • No it doesnt, I'm already using .after in my program. I'm trying to have `root.after(random_range_s_to_ms, start_clicking)` happen when `if running:` is true. However it seems to just run regardless of whether or not that's the case. I've tried after_cancel before too but I couldn't get it to work. – Hoswoo Dec 13 '20 at 09:04
  • Did you read the answer on that topic where `after_cancel(id)` is used? Are you aware that your alarm is still registerd when you do it like this and will check if you need it or not? If so. Why dont you use `if running:` in the `start_clicking` function? – Thingamabobs Dec 13 '20 at 09:07
  • I tried to use after_cancel again, under `def start_clicking(event=None):` I added `global call_click` and `call_click = root.after(random_range_s_to_ms, start_clicking)` and under `def stop_clicking(event=None):` I added `stop_clicking.after_cancel(call_click)`. Is this wrong? This is what I did earlier and it doesn't stop anything. Also, whether or not I use `if running` in the `start_clicking` function, it just continues to click. Putting `root.after(...)` under `if running:` seems to have no effect on the outcome for me. – Hoswoo Dec 13 '20 at 09:20
  • I dont understand how your code is even work: Because you did never defiened `running` before so it should throw an `NameError`. Please provide a [MRE] – Thingamabobs Dec 13 '20 at 09:32
  • My best guess is that one of your modules that you are using *globals* running and you overwrite it like with the internal `min` and `max` which is bad behavior by the way. And while you are using it, it redefines it to the moduls need. – Thingamabobs Dec 13 '20 at 09:37
  • It's alright man, it's really late and I need to sleep. I appreciate the help though. I was trying to create a minimal reproducible example and it seemed to raise even more questions for me so I'll have to take a look at this again when I wake up. Maybe I'll figure it out while I work through making the minimal example. – Hoswoo Dec 13 '20 at 09:57

1 Answers1

1

Your code will raise exception when start_clicking() is executed:

Exception in Tkinter callback
Traceback (most recent call last):
  File "C:\Python38\lib\tkinter\__init__.py", line 1883, in __call__
    return self.func(*args)
  File "D:\Temp\python\tmp2.py", line 68, in start_clicking
    if running:     # For some reason, this runs even though running is False. I want this to stop when running is false
NameError: name 'running' is not defined

So you need to initialise running before executing start_clicking(). I suggest to create another function which initialize running and execute start_clicking() when F2 is pressed or Start button is clicked:

def start_clicking_task(event=None):
    global running
    running = True
    start_clicking()

...

keyboard.add_hotkey('f2', start_clicking_task)

...

start_btn = tk.Button(root, text='Start(F2)', bg="#2C3335", width=7, fg="#7ddeff", command=start_clicking_task)
acw1668
  • 40,144
  • 5
  • 22
  • 34
  • Hello, thank you for your response. I've tried doing this your way but I believe I've tried this already in a slightly different way to no avail. While I was testing this, I print `running` into the console. Once for each time `start_clicking` iterates and once each time I press stop. It seems like every time it prints, the boolean condition reports True and False respectively. However, even though `running` is false, 'root.after(random_range_s_to_ms, start_clicking)` continues to run despite the condition of `running`. – Hoswoo Dec 13 '20 at 17:55
  • I changed my full code into a minimal reproducible example, maybe that will make my question clearer. There must be something I'm misunderstanding. – Hoswoo Dec 13 '20 at 18:10
  • Actually, after going through the minimal example I realized what I did wrong. You actually resolved it and I must have typed it up wrong last night. Since I was declaring global to be true in `start_clicking()`, it would recursively set it to true every time which was why it continue to ran. Thank you so much man. – Hoswoo Dec 13 '20 at 19:10