0

I have a program that runs a while loop (using pyautogui & pysimplegui). I want to allow the user to manually cancel the loop / stop the program.

Currently (on mac) if I try to interact with the main pyautogui window in any way I get a spinning beach ball.

I believe threading may be the answer but I can't seem to get it to work in my code after much searching and testing.

Below is the loop in question:

window=sg.Window('Meetup Auto Message', layout1, element_justification='c')
while True:
    event, values = window.read()
    if event in (sg.WIN_CLOSED, '  Cancel  '):
          break
    if event == '    Ok    ':
        messageOptions = [values['textBox1'], values['textBox2'], values['textBox3'], values['textBox4'], values['textBox5']]
         
        for i, (nameList, profileLink) in enumerate(zip(nameList, profileLink)):
            message = "Hi " + f'{nameList}' + ", " + "\n" + f'{choice(messageOptions)}'

            webbrowser.open(profileLink)
            time.sleep(15)
            pyautogui.press("tab")
            pyautogui.write(message, interval=random.uniform(0.03, 0.15))

            with pyautogui.hold("command"):
                pyautogui.press("w")
                window['-PROG-'].update(i+1)
            if i == 10:
                time.sleep(300)
            
        
window.close()

And for reference the full code:

import webbrowser
import pyautogui
import random
from random import choice
import time
import PySimpleGUI as sg
import threading

font = ("Arial", 13)

layout = [  [sg.Text('Select your .txt profiles file. Must be a list of URLs with each URL on a new line', font=("Arial", 16))],
            [sg.Combo(sorted(sg.user_settings_get_entry('-filenames-', [])), default_value=sg.user_settings_get_entry('-last filename-', ''), size=(50, 1), key='-FILENAME-', font="Arial, 16"), sg.FileBrowse(font="Arial, 16"), sg.B('Clear History', font="Arial, 16")],
            [sg.Button('  Ok  ', bind_return_key=True, font="Arial, 16", button_color='green'),  sg.Button('Cancel', font="Arial, 16")] ]

window = sg.Window('Meetup Auto Message - Profiles selection', layout)

while True:
    event, values = window.read()

    if event in (sg.WIN_CLOSED, 'Cancel'):
        break
    if event == '  Ok  ':
        # If OK, then need to add the filename to the list of files and also set as the last used filename
        sg.user_settings_set_entry('-filenames-', list(set(sg.user_settings_get_entry('-filenames-', []) + [values['-FILENAME-'], ])))
        sg.user_settings_set_entry('-last filename-', values['-FILENAME-'])
        lastFile=values['-FILENAME-']
        break
    elif event == 'Clear History':
        sg.user_settings_set_entry('-filenames-', [])
        sg.user_settings_set_entry('-last filename-', '')
        window['-FILENAME-'].update(values=[], value='')

window.close()

profileLink = open(lastFile).read().splitlines()

profileLink = [item.replace("+","name=") for item in profileLink]
newLinks = [item.replace("%","name=") for item in profileLink]
nameList = [i.split("name=")[1] for i in newLinks]
num_lines = sum(1 for line in open(lastFile))
BAR_MAX = num_lines

layout1 = [ [sg.Text('This program will take your messages, rotate them, add random delays and automatically message people from your profile list. You won\'t be able to use your computer while the program is running.', font="Arial, 18")],
            [sg.Text('Enter your messages below just the content, we handle the "Hi Name" part.', font="Arial, 18")],
            [sg.Multiline(size=(100,10), key='textBox1', font=font)],
            [sg.Multiline(size=(100,10), key='textBox2', font=font)],
            [sg.Multiline(size=(100,10), key='textBox3', font=font)],
            [sg.Multiline(size=(100,10), key='textBox4', font=font)],
            [sg.Multiline(size=(100,10), key='textBox5', font=font)],
            [sg.Button('    Ok    ', bind_return_key=True, font="Arial, 24", button_color='green'),  sg.Button('  Cancel  ', font="Arial, 24")],
            [sg.Text('Progress Bar', font="Arial, 15")],
            [sg.ProgressBar(BAR_MAX, orientation='h', size=(20,20), key='-PROG-')] ]





window=sg.Window('Meetup Auto Message', layout1, element_justification='c')
while True:
    event, values = window.read()
    if event in (sg.WIN_CLOSED, '  Cancel  '):
          break
    if event == '    Ok    ':
        messageOptions = [values['textBox1'], values['textBox2'], values['textBox3'], values['textBox4'], values['textBox5']]
         
        for i, (nameList, profileLink) in enumerate(zip(nameList, profileLink)):
            message = "Hi " + f'{nameList}' + ", " + "\n" + f'{choice(messageOptions)}'

            webbrowser.open(profileLink)
            time.sleep(15)
            pyautogui.press("tab")
            pyautogui.write(message, interval=random.uniform(0.03, 0.15))

            with pyautogui.hold("command"):
                pyautogui.press("w")
                window['-PROG-'].update(i+1)
            if i == 10:
                time.sleep(300)
            
        
window.close()

Bonus points if you can show me how to implement the GUI to break the loop! :)

EDIT: I've tried refactoring while loops to functions, allowing me to use try: except: on the functions... no luck. When doing so I get value not defined. So I put the event, value window.open outside of the function... tried declaring global variables and so on... this led to the program crashing with an error of "could not read settings file" when hitting the clear button on my program.

At a loss here.

dan.rad
  • 35
  • 8

1 Answers1

1

You are tripping over one of the hard problems of computer science. It will have terms such as "asynchronous", "parallel execution", "preemption", and more. When faced with this genearlized hard problem, I suggest you rely on the tried and true solution.

Cheat.

If your specific example, its enough that you wait a few milliseconds to see if the button was pressed. Normally, PySimpleGui's read waits forever until an event happens. You want a timeout parameter, specifically event, values = window.read(timeout=10). You may also want to read this article for more information.

Keep coding. Keep notes.

Charles Merriam
  • 19,908
  • 6
  • 73
  • 83
  • Thank you for your help. I read this and the article you shared with great hope. However, the timeout parameter only seems to serve to keep the window open for the duration of the timeout before closing the window. E.g. at 10 the window closes within a blink of an eye. If I set it longer it stays open for longer but still doesn't solve the beachball effect when the program is running. – dan.rad Sep 30 '22 at 10:46
  • The goal is to have a narrow time waiting for the event and use the event to either set a global variable or make a call, then wait a while between calls. Having the Window disappear is surprising; perhaps setting to always on top might help. How do you need to interrupt your computation? Once per loop? You might also see https://stackoverflow.com/questions/60729385/python-threading-and-pysimplegui – Charles Merriam Oct 04 '22 at 20:46