I saw a sample demo code from PySimpleGUI and I'm still not sure if how the window variable
The window was passed as an argument to the_thread() function and somehow it managed to do some operations for window variable like window.write_event_value which is inside the main function.
I have a cp(values) inside the main that proves that it was added.
Can anyone explain to me how is that possible?
The
import threading
import time
import PySimpleGUI as sg
THREAD_EVENT = '-THREAD-'
cp = sg.cprint
def the_thread(window):
i = 0
while True:
time.sleep(1)
cp('This keeps on running')
window.write_event_value('-THREAD-', (threading.current_thread().name, i)) # Data sent is a tuple of thread name and counter
cp('This is cheating from the thread', c='white on green')
i += 1
def main():
layout = [ [sg.Text('Output Area - cprint\'s route to here', font='Any 15')],
[sg.Multiline(size=(65,20), key='-ML-', autoscroll=True, reroute_stdout=True, write_only=True, reroute_cprint=True)],
[sg.T('Input so you can see data in your dictionary')],
[sg.Input(key='-IN-', size=(30,1))],
[sg.B('Start A Thread'), sg.B('Dummy'), sg.Button('Exit')] ]
window = sg.Window('Window Title', layout)
while True: # Event Loop
event, values = window.read()
if event == sg.WIN_CLOSED or event == 'Exit':
break
if event.startswith('Start'):
threading.Thread(target=the_thread, args=(window,), daemon=True).start()
if event == THREAD_EVENT:
cp(f'Data from the thread ', colors='white on purple', end='')
cp(f'{values[THREAD_EVENT]}', colors='white on red')
cp(values)
window.close()
if __name__ == '__main__':
main()
Sorry for being noob, looks like the reason it gets modified on a separate function is because it's a mutable object.
Still not sure what the question is.
Try to explain this script.
Define the layout of window
layout = [ [sg.Text('Output Area - cprint\'s route to here', font='Any 15')],
[sg.Multiline(size=(65,20), key='-ML-', autoscroll=True, reroute_stdout=True, write_only=True, reroute_cprint=True)],
[sg.T('Input so you can see data in your dictionary')],
[sg.Input(key='-IN-', size=(30,1))],
[sg.B('Start A Thread'), sg.B('Dummy'), sg.Button('Exit')] ]
Define a window by
window = sg.Window('Window Title', layout)
While loop to handle events generated by action of mouse or keyboard inputs on window. Here, window.read() will create the GUI of the window before update if GUI not yet finalized, then update the GUI of the window
while True: # Event Loop
event, values = window.read()
...
Break from while loop if event of close button of window clicked, or event of 'Exit' button clicked.
if event == sg.WIN_CLOSED or event == 'Exit':
break
Start a new thread by call function the_thread(window) if button 'Start A Thread' clicked.
if event.startswith('Start'):
threading.Thread(target=the_thread, args=(window,), daemon=True).start()
Thread function send one event '-THREAD-' every sleep one second with value (threading.current_thread().name, i). In general, you should not update GUI of window not in main thread for you may get troubles when your code running, send a event to main thread to handle GUI update, so I removed all cp calls here.
def the_thread(window):
i = 0
while True:
time.sleep(1)
window.write_event_value('-THREAD-', (threading.current_thread().name, i)) # Data sent is a tuple of thread name and counter
i += 1
Update elements or window, and print messages to sg.Multiline when event -THREAD- generated from thread.
if event == THREAD_EVENT:
cp(f'Data from the thread ', colors='white on purple', end='')
cp(f'{values[THREAD_EVENT]}', colors='white on red')
After break from while loop, close window and exit.
window.close()
Related
I am learning about making a GUI in Python, with the goal of eventually using a GUI to operate some devices in my laboratory. I am having a problem with telling the GUI to wait for a condition to be met.
I tried first to use a while loop like this
while i < some_limit:
time.sleep(1)
print(i)
i+=1
#in the actual code this will be sending a query to a measurement device at regular intervals, e.g. to see whether it has finished conducting a measurement
After doing some reading I found that apparently it is time.sleep() that is causing the GUI to freeze, and one way to avoid the issue is to use threads.
Here is an example code of what I have now:
import threading
import time
import PySimpleGUI as sg
def side_thread(limit):
i=0
while i<=limit:
time.sleep(1)
print(i)
i+=1
return
layout = [
[sg.Text('current state:'), sg.Text(key='-STATE_OUT-')],
[sg.Text('limit'), sg.Input(key='-LIMIT-', s=5)],
[sg.Button('start counting')]
]
window = sg.Window('thread and GUI testing',layout)
while True:
event, values = window.read()
if event == sg.WIN_CLOSED:
break
if event == 'start counting':
timelimit = float(values['-LIMIT-'])
window['-STATE_OUT-'].update('count starting')
c_thread = threading.Thread(target=side_thread, kwargs={'limit': timelimit})
c_thread.start()
# if side thread has returned then continue
c_thread.join()
window['-STATE_OUT-'].update('count finished')
What I want to happen is this following sequence:
I press the 'start counting' button
The text 'current state: count starting' appears
The program starts counting until the limit is reached, preferably without freezing the GUI
the text 'current state: count finished' appears
but what currently happens is that when I press the button, the GUI window immediately freezes (the text 'count starting' does not appear) until the while loop has finished, then the text 'count finished' appears.
I have also tried using window.TKroot.after()
window = sg.Window('thread and GUI testing',layout)
while True:
event, values = window.read()
if event == sg.WIN_CLOSED:
break
if event == 'start counting':
timelimit = float(values['-LIMIT-'])
window['-STATE_OUT-'].update('count starting')
i=0
while i<timelimit:
window.TKroot.after(1000)
print(i)
i+=1
window['-STATE_OUT-'].update('count finished')
but I still get the same problem.
EDIT: Follow up to Jason Yang's answer
Is it possible to implement write_event_value inside a for loop?
For example I have:
import threading
import time
import PySimpleGUI as sg
layout = [
[sg.Text('Counting Sequence')],
[sg.Text('current progress'), sg.Text(key='-PR_OUT-')],
[sg.Text('repeat count'), sg.Text(key='-RC_OUT-')],
[sg.Text('set repeat count'), sg.Input(key='-Set_RC-', s=10)],
[sg.Button('start sequence')]
]
def sequence_thread(limit):
i=0
while i<=limit:
time.sleep(1)
print(i)
i+=1
return
window = sg.Window('thread and GUI testing',layout)
while True:
event, values = window.read()
if event == sg.WIN_CLOSED:
break
if event == 'start sequence':
endpoint = int(values['-Set_RC-'])
window['-PR_OUT-'].update('sequence starting')
for i in range(endpoint):
c_thread = threading.Thread(target=sequence_thread, args=(3,))
c_thread.start()
c_thread.join()
rep_count = i+1
window['-RC_OUT-'].update(str(rep_count))
window['-PR_OUT-'].update('sequence finished')
window.close()
Main thread blocked at c_thread.join(), so GUI show no response.
Using method window.write_event_value to generate an event to main thread to update GUI.
import time
import threading
import PySimpleGUI as sg
def side_thread(window, limit):
i=0
while i<=limit:
time.sleep(1)
print(i)
i+=1
window.write_event_value('counting done', None)
layout = [
[sg.Text('current state:'), sg.Text(key='-STATE_OUT-')],
[sg.Text('limit'), sg.Input(key='-LIMIT-', s=5)],
[sg.Button('start counting')]
]
window = sg.Window('thread and GUI testing',layout)
while True:
event, values = window.read()
if event == sg.WIN_CLOSED:
break
elif event == 'start counting':
timelimit = float(values['-LIMIT-'])
window['-STATE_OUT-'].update('count starting')
c_thread = threading.Thread(target=side_thread, args=(window, timelimit), daemon=True)
c_thread.start()
elif event == 'counting done':
window['-STATE_OUT-'].update('count finished')
window.close()
Of course, for loop under your event loop is OK, but don't take much time before you back to statement window.read(). Update GUI in your for loop is required, call window.refresh() to update the GUI, or it will show last result only.
Again, don't call c_thread.join(), or it will be blocked here. To update GUI, you can call window.write_event_value to generate an event anywhere. you can keep all threads in a list and monitor the end event to confirm if all threads finished before you close window and quit.
In following code, the GUI update in the for loop is too quick, so you cannot find the change. Add one statement, sleep(1) to slow down the GUI.
import threading
import time
import PySimpleGUI as sg
layout = [
[sg.Text('Counting Sequence')],
[sg.Text('current progress'), sg.Text(key='-PR_OUT-')],
[sg.Text('repeat count'), sg.Text(key='-RC_OUT-')],
[sg.Text('set repeat count'), sg.Input(key='-Set_RC-', s=10)],
[sg.Button('start sequence')]
]
def sequence_thread(window, index, limit):
i=0
while i <= limit:
time.sleep(1)
print(i)
i+=1
window.write_event_value('Done', index)
window = sg.Window('thread and GUI testing',layout)
threads = []
while True:
event, values = window.read()
if event == sg.WIN_CLOSED:
break
elif event == 'start sequence':
endpoint = int(values['-Set_RC-'])
window['-PR_OUT-'].update('sequence starting')
for i in range(endpoint):
c_thread = threading.Thread(target=sequence_thread, args=(window, i, 3), daemon=True)
c_thread.start()
threads.append(i)
window['-RC_OUT-'].update(i)
window.refresh()
# time.sleep(1)
elif event == 'Done':
i = values[event]
threads.remove(i)
if not threads:
window['-PR_OUT-'].update('sequence finished')
window.close()
For sequence threads, IMO, one thread for all of them should be OK.
import threading
import time
import PySimpleGUI as sg
def func(limit):
i=0
while i <= limit:
time.sleep(1)
print(i)
i += 1
def sequence_thread(window, endpoint, limit):
for i in range(endpoint):
window.write_event_value('Step', i+1)
func(limit)
window.write_event_value('Done', None)
layout = [
[sg.Text('Counting Sequence')],
[sg.Text('current progress'), sg.Text(key='-PR_OUT-')],
[sg.Text('repeat count'), sg.Text(key='-RC_OUT-')],
[sg.Text('set repeat count'), sg.Input(key='-Set_RC-', s=10)],
[sg.Button('start sequence')]
]
window = sg.Window('thread and GUI testing', layout)
while True:
event, values = window.read()
if event == sg.WIN_CLOSED:
break
elif event == 'start sequence':
endpoint = int(values['-Set_RC-'])
window['-PR_OUT-'].update('sequence starting')
threading.Thread(target=sequence_thread, args=(window, endpoint, 3)).start()
elif event == 'Step':
i = values[event]
window['-RC_OUT-'].update(i)
elif event == 'Done':
window['-PR_OUT-'].update('sequence finished')
window.close()
Hello i have a small program that gets input from user, I suddenly noticed that after few uses in the program I cant use CONTROL + A / CONTROL + C /CONTROL + D the inputText doesnt respond.
if I restart the program this buttons events work.
layout = [[sg.Text('WELCOME', justification='center', size=(50))],
[sg.InputText(), sg.Text(': ENTER SOME TEXT ', justification='right')],
[sg.Button('Search', bind_return_key=True)]
window = sg.Window('Client Connection V2.0Beta2', layout, element_justification='c')
while True:
event, values = window.read()
user_text= values[0]
if event == sg.WIN_CLOSED : # if user closes window or clicks cancel
window.close()
break
elif event == 'Search: # First step user search for
#some Code....
It should be fine for following code
import PySimpleGUI as sg
layout = [
[sg.Text('WELCOME', justification='center', expand_x=True)],
[sg.Text('ENTER SOME TEXT: '), sg.InputText()],
[sg.Button('Search', bind_return_key=True)],
]
window = sg.Window('Client Connection V2.0Beta2', layout, element_justification='c')
while True:
event, values = window.read()
if event == sg.WIN_CLOSED: # if user closes window or clicks cancel
break
elif event == 'Search':
user_text = values[0]
print(user_text)
window.close()
In your description, it didn't show how about the other keyboard input.
It maybe caused by the focus not on the input element, maybe you press the TAB key by accidentally, then the focus shift to next element, 'Search` button here, then all the keyboard input will not be responded in the input element.
In following figure, button Search focused and with a dash box on it, and then the keyboard input to input element will be not responded.
Try to press TAB key to move focus to input element.
I'm working with plots and I want to call a PySimpleGUI window on a press of a TK button which will show my plot tuples and allow modifying them to later re-draw the plot. So far I took this cookbook example and added a save button:
layout += [[sg.Button('Save')]]
This is how I call it:
def readWindow(event):
values = window.read()
print(values)
editButton.on_clicked(readWindow)
and it successfully passes values when I click "save". But if I close the window and try to open it again, I get (None, None) in the console.
You don't need tkinter Button.
import PySimpleGUI as sg
# layout = ...
layout += [[sg.Button("Save", ...]]
window = sg.Window("Title", layout)
while True:
event, values = window.read()
if event == sg.WINDOW_CLOSED:
break
elif event == "Save":
print(event, values)
window.close()
I am very new to PySimpleGUI. I am building a GUI for a desktop application and wanted to use a calendar. But there came a problem with retrieving the value of Calendar Button events in the while loop without timeouts in window.read() when I chose from Calendar Picker Popup. I tried to get its value using event == 'CalendarButton's text', but couldn't, though its button text changes every time if you choose to set a different date. Can you please help with that, or how can I get its (or any element's) value using its key inside the while loop. Or at least can I use Tkinter calendar package. If so, how can I connect Tkinter Calendar with PySimpleGUI window, will I have to use an element's key bindings with Tkinter?
Here's my code for Calendar Button definition which I put into my window's layout:
sg.CalendarButton('Calendar', target='_CALENDAR_', pad=None, font=('MS Sans Serif', 10, 'bold'),
button_color=('red', 'white'), key='_CALENDAR_', format=('%d %B, %Y'))
and here's the while loop events handling part
# LOOP EVENTS #
while True:
# READ EVENT VALUES #
event, values = window.read()
# EXIT OR CLOSE EVENT #
if event is None or event == 'Exit':
break
# BUTTON EVENTS
# CALENDAR BUTTON CLICKED EVENT #
if event == 'Calendar':
window['_CALENDAR_'](disabled=True)
# CLOSE WINDOW
window.close()
# POPUP
sg.Popup('Title',
'The results of the window.',
'The button clicked was "{}"'.format(event),
'The values are', values)
Also, I cannot see this event's value in the sg.Popup() output after I exit the window
'EDITED' Sorry, there were errors in sg.Popup(). Now fixed it.
The way to both save the value and get the event is to create a hidden input field. Enable events for the input field and you'll get an event when the calendar fills in the input field that you set as the target.
import PySimpleGUI as sg
layout = [ [sg.Text('Calendar example')],
[sg.In(key='-CAL-', enable_events=True, visible=False), sg.CalendarButton('Calendar', target='-CAL-', pad=None, font=('MS Sans Serif', 10, 'bold'),
button_color=('red', 'white'), key='_CALENDAR_', format=('%d %B, %Y'))],
[sg.Exit()]]
window = sg.Window('Calendar', layout)
while True: # Event Loop
event, values = window.read()
print(event, values)
if event in (None, 'Exit'):
break
window.close()
import PySimpleGUI as sg
import os
layout = [[sg.Text('Velg mappe som skal tas backup av og hvor du vil plassere backupen')],
[sg.Text('Source folder', size=(15, 1)), sg.InputText(), sg.FolderBrowse()],
[sg.Text('Backup destination ', size=(15, 1)), sg.InputText(), sg.FolderBrowse()],
[sg.Text('Made by Henrik og Thomas™')],
[sg.Submit(), sg.Cancel()]]
window = sg.Window('Backup Runner v2.1')
event, values = window.Layout(layout).Read()
How can I call a function when I press the submit button? or any other button?
The PySimpleGUI documentation discusses how to do this in the section on events / callbacks
https://pysimplegui.readthedocs.io/#the-event-loop-callback-functions
It is not lot the other Python GUI frameworks that use callbacks to signal button presses. Instead all button presses are returned as "events" coming back from a Read call.
To achieve a similar result, you check the event and make the function call yourself.
import PySimpleGUI as sg
def func(message):
print(message)
layout = [[sg.Button('1'), sg.Button('2'), sg.Exit()] ]
window = sg.Window('ORIGINAL').Layout(layout)
while True: # Event Loop
event, values = window.Read()
if event in (None, 'Exit'):
break
if event == '1':
func('Pressed button 1')
elif event == '2':
func('Pressed button 2')
window.Close()
To see this code run online, you can run it here using the web version:
https://repl.it/#PySimpleGUI/Call-Func-When-Button-Pressed
Added 4/5/2019
I should have also stated in my answer that you could add the event checks to right after your call to Read. You don't have to use an Event Loop as I showed. It could look like this:
event, values = window.Layout(layout).Read() # from OP's existing code
if event == '1':
func('Pressed button 1')
elif event == '2':
func('Pressed button 2')
[ Edit Nov 2020 ] - Callable keys
This isn't a new capability, just didn't mention it in the answer previously.
You can set keys to be functions and then call them when the event is generated. Here is an example that uses a few ways of doing this.
import PySimpleGUI as sg
def func(message='Default message'):
print(message)
layout = [[sg.Button('1', key=lambda: func('Button 1 pressed')),
sg.Button('2', key=func),
sg.Button('3'),
sg.Exit()]]
window = sg.Window('Window Title', layout)
while True: # Event Loop
event, values = window.read()
if event in (None, 'Exit'):
break
if callable(event):
event()
elif event == '3':
func('Button 3 pressed')
window.close()