My Tkinter GUI won't appear because of loops, how can i make the GUI appear without disturbing the flow of the loops? Here's the code.
for record in data:
# The rest of the code...
# Datetime and auto validation for web automation
while True:
convert_time_record = datetime.datetime.strptime(record[2], '%H:%M')
date_now = datetime.datetime.now()
time_val = record[0] == date_now.strftime('%A') and convert_time_record.strftime('%H:%M:%S') == date_now.strftime('%H:%M:%S')
if time_val and record[3] == "Yes" and record[4] == "Link":
print("time true")
chrome.open(record[5])
time.sleep(5)
os.system("taskkill /im chrome.exe /f")
break
# Check if the method was by meeting ID
elif time_val and record[3] == "Yes" and record[4] == "Meeting ID":
# Open Zoom
subprocess.call("C:\\Users\\bryan\\AppData\\Roaming\\Zoom\\bin\\Zoom.exe")
time.sleep(3)
# Locate the center of the join button then move the cursor
Click('join_button.png')
time.sleep(3)
# Write the meeting id to the text field
pyautogui.write(record[5])
# Press the enter key
pyautogui.press('enter')
time.sleep(3)
# Write the passcode to the text field
pyautogui.write(record[6])
# Press the enter key
pyautogui.press('enter')
time.sleep(8)
join_computer_audio_btn = pyautogui.locateCenterOnScreen('join_audio.png')
pyautogui.moveTo(join_computer_audio_btn)
pyautogui.click()
root.mainloop()
I've tried putting root.mainloop() inside the for-loop and while-loop but that will make the loops unable to execute the function.
You should run root.mainloop() only once.
Using loops prevents the GUI from updating, therefore consider using a different thread to run the loop and send updates to the GUI from the other thread, or use an object for maintaining the state of the loop and use a non blocking function to get the state of the object and have it update and re-set the call with the after function of tkinter.
Related
I tried to find codes online about rising events in my PySimpleGUI program by simple keyboard clicks like ENTER or Ctrl+A for instance but I couldn't just find any about it, I went to the documentation of PySimpleGUI and didn't shut my tap without learning a thing.
Here is a simple code i wrote:
import PySimpleGUI as sg
layout = [[sg.I(key='In'), sg.B('Ok')],[sg.T(enable_events=True,key='T')]]
win=sg.Window("Keyboard Events", layout)
while True:
event, value= win.read()
#close event
if event == sg.WIN_CLOSED:
break
#greeting evnt
if event in ('Ok'): #( 'OK', 'KEYBOARD ENTER EVENT'):
msg = "Hello "+value['In'] # message to show user
win['T'].update(msg) # show user message
win['In'].update("") # clear input field after submitting
win.close()
What should I say to PySimpleGUI for let it run #greeting event when I press the ENTER key? Can someone help me please? Thanks guys!
Good question! And yes, its the documentation, not you. While this may not answer your question directly, here is some useful information.
You can choose to set return_keyboard_events=True as option when creating the window (sg.Window()). This will return every key hitting an input, as well as some scrolling events, meta keys, and so on.
window.read() returns a tuple (event, values). event can be:
None (sg.WIN_CLOSED)
A string of length one with the key from an input. For example 'a'.
The key of a control. For example, 'Ok'.
A tuple with more information. For example, clicking in a table gives a tuple of (keyname, "+CLICKED+", (row, column)
A special key, such as Return:6033979789 or Meta_L:922810622.
I use this as an alternative to bind_return_key when I might have multiple text entries on the screen:
if isinstance(event, str) and event.startswith('Return'):
event = 'return ' + self.window.find_element_with_focus().key
Also, I find printing to the console is necessary to debug events:
event, values = self.window.read()
print(f" event (type:{type(ui_event)}, {ui_event}, len:({len(ui_event)})"
f" with values {ui_values}")
Option required for element
sg.Input(do_not_clear=False, key='In') clear input field after event
sg.Button('Ok', bind_return_key=True) the return key will cause this button to be pressed
import PySimpleGUI as sg
layout = [[sg.I(do_not_clear=False, key='In'), sg.B('Ok', bind_return_key=True)],[sg.T(enable_events=True,key='T')]]
win=sg.Window("Keyboard Events", layout)
while True:
event, value= win.read()
#close event
if event == sg.WIN_CLOSED:
break
#greeting evnt
if event == 'Ok':
msg = "Hello "+value['In'] # message to show user
win['T'].update(msg) # show user message
win.close()
Why is the input() after the if statement not working?
I need some help, the input() after the if statement is supposed to stop the script, however it just continues. If i give it another command, for example print("...") or time.sleep(10) it will execute, it is only the input() that does not work. Any ideas?
Edit: Because it might not be clear what the intention is. When asking for an input after the if statement the script should wait before continuing. This is because I want it to pause after I move the mouse so that it does not keep spamming the keys, but I am able to resume it if needed.
import time
from pynput.keyboard import Key, Controller as KeyController
from pynput.mouse import Controller as MouseController
key = KeyController()
mouse = MouseController()
def f1_toggle():
key.press(Key.f1)
key.release(Key.f1)
def enter_toggle():
key.press(Key.enter)
key.release(Key.enter)
input("Press any key to start:")
time.sleep(5)
while True:
start_position = mouse.position
key.press(Key.ctrl_l)
print("Ctrl pressed")
f1_toggle()
print("F1 toggled")
key.release(Key.ctrl_l)
print("Ctrl released")
time.sleep(1)
enter_toggle()
print("Enter toggled")
time.sleep(.5)
end_position = mouse.position
if start_position != end_position:
input("Press any key to continue ")
You're calling enter_toggle() before you ask for input. This is simulating pressing the Enter key. This is being used as the response to the input() call, so it doesn't wait for the user to enter something manually.
Are you sure the two variables are different?
I don't have pyinput, and I am using python 3.8, but in my computer, the input isn't skipped (I commented the pyinput).
Try this:
try:
input("Press any key to continue ")
except Exception as e:
print(e);
Try to see if this helps and what it prints.
I am learning python on my own and my level is probably a poor excuse for a "script kiddie" as I kinda understand and mostly end up borrowing and mashing together different scripts till it does what I want. However this is the first time I'm trying to create a GUI for one of the scripts I have. I'm using PySimpleGUI and I've been able to understand it surprisingly well. All but one thing is working the way I want it to.
The issue is I want to stop a running daemon thread without exiting the GUI. If I get the stop button to work the GUI closes and if the GUI does not close it doesn't stop the thread. The issue is between lines '64-68'. I have tried a few things and just put a place holder on line '65' to remember that I was trying to keep the GUI ("Main Thread" in my head-speak) running. The script will run in this state but the 'Stop' button does not work.
Note: I put a lot of comments in my scripts so I remember what each part is, what it does and what I need to clean up. I don't know if this is a good practice if I plan on sharing a script. Also, if it matters, I use Visual Studio Code.
#!/usr/local/bin/python3
import PySimpleGUI as sg
import pyautogui
import queue
import threading
import time
import sys
from datetime import datetime
from idlelib import window
pyautogui.FAILSAFE = False
numMin = None
# ------------------ Thread ---------------------
def move_cursor(gui_queue):
if ((len(sys.argv)<2) or sys.argv[1].isalpha() or int(sys.argv[1])<1):
numMin = 3
else:
numMin = int(sys.argv[1])
while(True):
x=0
while(x<numMin):
time.sleep(5) # Set short for debugging (will set to '60' later)
x+=1
for i in range(0,50):
pyautogui.moveTo(0,i*4)
pyautogui.moveTo(1,1)
for i in range(0,3):
pyautogui.press("shift")
print("Movement made at {}".format(datetime.now().time()))
# --------------------- GUI ---------------------
def the_gui():
sg.theme('LightGrey1') # Add a touch of color
gui_queue = queue.Queue() # Used to communicate between GUI and thread
layout = [ [sg.Text('Execution Log')],
[sg.Output(size=(30, 6))],
[sg.Button('Start'), sg.Button('Stop'), sg.Button('Click Me'), sg.Button('Close')] ]
window = sg.Window('Stay Available', layout)
# -------------- EVENT LOOP ---------------------
# Event Loop to process "events"
while True:
event, values = window.read(timeout=100)
if event in (None,'Close'):
break
elif event.startswith('Start'): # Start button event
try:
print('Starting "Stay Available" app')
threading.Thread(target=move_cursor,
args=(gui_queue,), daemon=True).start()
except queue.Empty:
print('App did not run')
elif event.startswith('Stop'): # Stop button event
try:
print('Stopping "Stay Available" app')
threading.main_thread # To remind me I want to go back to the original state
except queue.Empty:
print('App did not stop')
elif event == 'Click Me': # To see if GUI is responding (will be removed later)
print('Your GUI is alive and well')
window.close(); del window
if __name__ == '__main__':
gui_queue = queue.Queue() # Not sure if it goes here or where it is above
the_gui()
print('Exiting Program')
From this answer: create the class stoppable_thread.
Then: store the threads on a global variable:
# [...]
# store the threads on a global variable or somewhere
all_threads = []
# Create the function that will send events to the ui loop
def start_reading(window, sudo_password = ""):
While True:
window.write_event_value('-THREAD-', 'event')
time.sleep(.5)
# Create start and stop threads function
def start_thread(window):
t1 = Stoppable_Thread(target=start_reading, args=(window,), daemon=True)
t1.start()
all_threads.append(t1)
def stop_all_threads():
for thread in all_threads:
thread.terminate()
Finally, on the main window loop, handle the events that start, stop or get information from the thread.
Edit: The below answer to use keyboard.on_press(callback, suppress=False) works fine in ubuntu without any issues.
But in Redhat/Amazon linux, it fails to work.
I have used the code snippet from this thread
import keyboard # using module keyboard
while True: # making a loop
try: # used try so that if user pressed other than the given key error will not be shown
if keyboard.is_pressed('q'): # if key 'q' is pressed
print('You Pressed A Key!')
break # finishing the loop
except:
break # if user pressed a key other than the given key the loop will break
But the above code requires the each iteration to be executed in nano-seconds. It fails in the below case:
import keyboard # using module keyboard
import time
while True: # making a loop
try: # used try so that if user pressed other than the given key error will not be shown
print("sleeping")
time.sleep(5)
print("slept")
if keyboard.is_pressed('q'): # if key 'q' is pressed
print('You Pressed A Key!')
break # finishing the loop
except:
print("#######")
break # if user pressed a key other than the given key the loop will break
You can make use of event handlers in keyboard module to achieve the desired result.
One such handler is keyboard.on_press(callback, suppress=False):
Which invokes a callback for every key_down event.
You can refer more at keyboard docs
Here is the code you can try:
import keyboard # using module keyboard
import time
stop = False
def onkeypress(event):
global stop
if event.name == 'q':
stop = True
# ---------> hook event handler
keyboard.on_press(onkeypress)
# --------->
while True: # making a loop
try: # used try so that if user pressed other than the given key error will not be shown
print("sleeping")
time.sleep(5)
print("slept")
if stop: # if key 'q' is pressed
print('You Pressed A Key!')
break # finishing the loop
except:
print("#######")
break # if user pressed a key other than the given key the loop will break
for people that might need this in the future, you can use keyboard.wait() which will basically wait untill the key gets pressed
keyboard.wait("o")
print("you pressed the letter o")
Do keep in mind that it blocks code execution after it. if you want to run code if the key is not being pressed i'd suggest doing
if keyboard.is_pressed("0"):
#do stuff
else:
#do other stuff
Edit: never mind, the other answer uses pretty much the same approach
This is what i could come up with, using the same "keyboard" module, see in-code comments below
import keyboard, time
from queue import Queue
# keyboard keypress callback
def on_keypress(e):
keys_queue.put(e.name)
# tell keyboard module to tell us about keypresses via callback
# this callback happens on a separate thread
keys_queue = Queue()
keyboard.on_press(on_keypress)
try:
# run the main loop until some key is in the queue
while keys_queue.empty():
print("sleeping")
time.sleep(5)
print("slept")
# check if its the right key
if keys_queue.get()!='q':
raise Exception("terminated by bad key")
# still here? good, this means we've been stoped by the right key
print("terminated by correct key")
except:
# well, you can
print("#######")
finally:
# stop receiving the callback at this point
keyboard.unhook_all()
You could use a thread
import threading
class KeyboardEvent(threading.Thread):
def run(self):
if keyboard.is_pressed('q'): # if key 'q' is pressed
print('You Pressed A Key!')
break # finishing the loop
keyread = KeyboardEvent()
keyread.start()
This would run in parallel to anything in the main thread and be dedicated to listening for that key press essentially.
So bassicaly the user will press the picture_pin buton first, then this calls the picture_taking() function which then it should stop and wait for either (Accept_Pin or Decline_Pin) buttons to be pressed, it should not let the user to continue unless a selection is made. so when the user makes his/her selection then it go back and wait for the picture_pin button. at this stage the Accept_Pin and Decline_Pin should have no affect at all.
I have a python program that waits for a button press from the user then it runs a function to do its thing. what I would like to accomplish is, in that function I would like to also wait for another button press.
(Main.py)
----etc
## PICTURE FUNCTION ##
def picture_taking():
.....etc
returnvalue = subprocess.check_output("sudo ./" + config.layout + ".sh", shell=True)
GPIO.wait_for_edge(Accept_pin, GPIO.FALLING)
GPIO.wait_for_edge(Decline_Pin, GPIO.FALLING)
pad1alreadyPressed = False
pad4alreadyPressed = False
while True:
pad1pressed = not GPIO.input(Accept_pin)
pad4pressed = not GPIO.input(Decline_Pin)
if pad1pressed and not pad1alreadyPressed:
print "Accepted photo:" + str(returnvalue)
pad1alreadyPressed = pad1pressed
if pad4pressed and not pad4alreadyPressed:
print "Declined photo:" + str(returnvalue)
pad4alreadyPressed = pad4pressed
(This here is my Main Program)
#Main section
while True:
time.sleep(0.2)
#camera.hflip = false
camera.shutter_speed = 2000000000
camera.exposure_mode = 'off'
#camera.iso = 1000
camera.preview_fullscreen = True
camera.preview_alpha = 150
camera.start_preview()
GPIO.wait_for_edge(picture_pin, GPIO.FALLING)
picture_taking()
So in the picture_taking() function I would like to ask the user if they accept this picture or not
if they press button (GPIO 19) then they accept or (GPIO 6) as Decline
after they do there selection the program should go back and wait for the main button to select below. and these two buttons should only be selectable inside the function.
I tried this in the Picture_taking() function
when I make the selection it only accepts the "Decline_pin" button, but after I press the "Decline button" then it reads "Accept button".
The other issue is that it does not go back and wait for the Main button "picture_pin" to be pressed. it seems to be stuck here and not exiting.
I am not sure if this is something to do with Indent stuff in Python.
Thank you.
(This should be a comment, not an answer but I don't have reputation 50 yet).
Is this your actual code? I can't understand how it ever returns from the while True: clause in picture_taking(). I'd think you'd need to have something like
while (!(pad1alreadyPressed or pad4alreadyPressed)):