I'm trying to used nested events.
When I browse a file, the filename alone being stripped from the full path triggers an event that makes the filename to be transferred to a textbox which has the enable_events set to true, which will trigger another event to call a function and get the pdf details.
If I enable the two commented lines, you can see that the function works and transfers the return value, but I'm trying to separate these two events as the function to get the details of the PDF takes a while.
So the order is:
__pdfpath__ gets the full path of a certain browsed file which triggers an event that transfers the filename to __bookfilename__ which should trigger another event which will call a function that will send its response to __pdfdetails__
import PySimpleGUI as sg
import os
def get_pdf_details(pdfname):
return pdfname + ' was processed'
layout = [
[sg.InputText('',key='_pdfpath_',enable_events=True),sg.FileBrowse(key='_filepath_')],
[sg.Text('',key='_bookfilename_',enable_events=True,size=(40, 1))],
[sg.Text('',key='_pdfdetails_', size=(40, 1) )],
]
window = sg.Window('', layout)
while True:
event, value = window.Read()
if event == '_pdfpath_':
filename = os.path.basename(value['_pdfpath_'])
window.Element('_bookfilename_').Update(filename)
#response = get_pdf_details(filename)
#window.Element('_pdfdetails_').Update(response)
if event == '_bookfilename_':
response = get_pdfdetails(value['_bookfilename_'])
window.Element('_pdfdetails_').Update(response)
So the question is, how can I trigger the second event?
I tried creating a second window.Read() to create a second loop like this:
event2, value2 = window.Read()
but didn't work.
Any ideas?
Thanks
The way through this isn't events traveling around PySimpleGUI. What you need to do is break out your long-running function into a Thread.
EDIT - Since the original answer in early 2019 a lot of continued to be added to PySimpleGUI.
Because having a function take too long be one of the most common problems first encountered writing a GUI, a method was added so that beginners that are not quite ready to write their own threaded code aren't held up.
The Window.perform_long_operation takes a function or a lambda expression as a parameter and a key that will be returned when your function returns.
window.perform_long_operation(my_long_operation, '-OPERATION DONE-')
You'll get the benefits of multi-threading without needing to do all the work yourself. It's a "stepping stone" approach. Some users have only been using Python for 2 or 3 weeks when they write their first GUI and simply aren't ready for the threading module.
The Cookbook has a section on this topic and the eCookbook has a number of examples that can be immediately run. http://Cookbook.PySimpleGUI.org and http://Cookbook.PySimpleGUI.org
The Demo Programs are always a great place to look too - http://Demos.PySimpleGUI.org. There are at least 13 examples shown there as of 2021.
Try this:
while True:
event, value = window.Read()
process_event(event, value)
def process_event(event, value):
if event == '_pdfpath_':
filename = os.path.basename(value['_pdfpath_'])
window.Element('_bookfilename_').Update(filename)
value.update(_bookfilename_=filename)
process_event('_bookfilename_', value)
if event == '_bookfilename_':
response = get_pdfdetails(value['_bookfilename_'])
window.Element('_pdfdetails_').Update(response)
PSG has one magic element that can Fire event whenever you want, It's basically is Button, it can be hidden by setting visible=False. Just call window['ButtonKey'].click() where you want to fire 'ButtonKey' event.
Related
I wanted to write a program that moves the mouse cursor and presses the shift key so that the computer won't be locked. The Problem is I want that if the user doesn't set two variables they should be automatically 0 and 1 but it seems I am missing something. The other thing that I can't understand is when I start the program if I don't click on it everything is ok, but if I click on the program's window it says "Not Responding" but the program is running. I can't understand what I am doing wrong here. Below is my code:
#!/usr/bin/python
import pyautogui
import time
import sys
import os
import PySimpleGUI as sg
from datetime import datetime
def main():
sg.theme('DarkAmber')
layout = [ [sg.Text('Please enter the time intervall between the movements:', size = (45,1)), sg.Input(key='-IT-', enable_events=True)],
[sg.Text('Please enter how long should the script run:', size = (45,1)), sg.Input(key='-DURATION-', enable_events=True)],
[sg.Button('Start'), sg.Button('Stop')],
[sg.Output(size=(60,15))] ]
window = sg.Window('Press to start to move!', layout, size=(450,250), element_justification='right')
while True:
event, values = window.read()
if event in (sg.WIN_CLOSED, 'Stop'):
break
if event == '-IT-' and values['-IT-'] and values['-IT-'][-1] not in ('0123456789'):
window['-IT-'].update(values['-IT-'][:-1])
if event == '-DURATION-' and values['-DURATION-'] and values['-DURATION-'][-1] not in ('0123456789'):
window['-DURATION-'].update(values['-DURATION-'][:-1])
elif event == 'Start':
if values['-IT-'] == "" and values['-DURATION-'] == "":
window['-IT-'].update(1)
window['-DURATION-'].update(0)
elif values['-IT-'] != "" and values['-DURATION-'] == "":
window['-DURATION-'].update(0)
elif values['-IT-'] == "" and values['-DURATION-'] != "":
window['-IT-'].update(1)
move(numMin=int('0'+values['-IT-']), numDuration=int('0'+values['-DURATION-']))
window.close()
main()
Edit:
I've just cut out the unnecessary part of the code.
So if I'm not misunderstanding, I need to use a thread, the second question is for me unclear can I typecast an empty string without adding zero (0) to it? Because when I try just typecast it doesn't work, and if I add a zero the behavior of the code is changing.
Operations That Take a "Long Time"
If you're a Windows user you've seen windows show in their title bar "Not Responding" which is soon followed by a Windows popup stating that "Your program has stopped responding". Well, you too can make that message and popup appear if you so wish! All you need to do is execute an operation that takes "too long" (i.e. a few seconds) inside your event loop.
You have a couple of options for dealing this with. If your operation can be broken up into smaller parts, then you can call Window.Refresh() occasionally to avoid this message. If you're running a loop for example, drop that call in with your other work. This will keep the GUI happy and Window's won't complain.
If, on the other hand, your operation is not under your control or you are unable to add Refresh calls, then the next option available to you is to move your long operations into a thread.
The "Old Way"
There are a couple of demo programs available for you to see how to do this. You basically put your work into a thread. When the thread is completed, it tells the GUI by sending a message through a queue. The event loop will run with a timer set to a value that represents how "responsive" you want your GUI to be to the work completing.
The "New Way" - Window.write_event_value
This new function that is available currently only in the tkinter port as of July 2020 is exciting and represents the future way multi-threading will be handled in PySimpleGUI (or so is hoped).
Previously, a queue was used where your event loop would poll for incoming messages from a thread.
Now, threads can directly inject events into a Window so that it will show up in the window.read() calls. This allows a your event loop to "pend", waiting for normal window events as well as events being generated by threads.
You can see this new capability in action in this demo: Demo_Multithreaded_Write_Event_Value.py
Here is that program for your inspection and education. It's SO nice to no longer poll for threaded events.
import threading
import time
import PySimpleGUI as sg
"""
Threaded Demo - Uses Window.write_event_value communications
Requires PySimpleGUI.py version 4.25.0 and later
This is a really important demo to understand if you're going to be using multithreading in PySimpleGUI.
Older mechanisms for multi-threading in PySimpleGUI relied on polling of a queue. The management of a communications
queue is now performed internally to PySimpleGUI.
The importance of using the new window.write_event_value call cannot be emphasized enough. It will hav a HUGE impact, in
a positive way, on your code to move to this mechanism as your code will simply "pend" waiting for an event rather than polling.
Copyright 2020 PySimpleGUI.org
"""
THREAD_EVENT = '-THREAD-'
cp = sg.cprint
def the_thread(window):
"""
The thread that communicates with the application through the window's events.
Once a second wakes and sends a new event and associated value to the 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
cp('This is cheating from the thread', c='white on green')
i += 1
def main():
"""
The demo will display in the multiline info about the event and values dictionary as it is being
returned from window.read()
Every time "Start" is clicked a new thread is started
Try clicking "Dummy" to see that the window is active while the thread stuff is happening in the background
"""
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, finalize=True)
while True: # Event Loop
event, values = window.read()
cp(event, values)
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')
window.close()
if __name__ == '__main__':
main()
Multithreaded Programs
While on the topic of multiple threads, another demo was prepared that shows how you can run multiple threads in your program that all communicate with the event loop in order to display something in the GUI window. Recall that for PySimpleGUI (at least the tkinter port) you cannot make PySimpleGUI calls in threads other than the main program thread.
The key to these threaded programs is communication from the threads to your event loop. The mechanism chosen for these demonstrations uses the Python built-in queue module. The event loop polls these queues to see if something has been sent over from one of the threads to be displayed.
You'll find the demo that shows multiple threads communicating with a single GUI is called:
Demo_Multithreaded_Queued.py
Once again a warning is in order for plain PySimpleGUI (tkinter based) - your GUI must never run as anything but the main program thread and no threads can directly call PySimpleGUI calls.
I have decided to finally work on a project, as I've tried to code in python before, with at least some success. In my project, I am trying to build a menu that lets me "Auto-farm" in a game. It uses 3 modules, namely pynput, pause, and PySimpleGUI.
Whenever I run the code, it runs fine, until I click the button that starts the automation part. It runs completely fine, but I have to force close the GUI prompt that shows up as it just completely stops responding to input until you close it.
How I can make a stop button, and stop my program from freezing up?
I am using 2 files to keep this project slightly more organized, although I don't know if this is the best way to go around doing this. These 2 files are main.py and chand.py.
main.py
import PySimpleGUI as sg
import chand
loop = 0
layout = [[sg.Text("Welcome to RedGrowie's autofarm menu!")], [sg.Button("Chandeliers")]]
window = sg.Window("Autofarming Menu", layout)
while True:
event, values = window.read()
if event == sg.WIN_CLOSED:
break
if event == "Chandeliers":
loop = 1
if loop == 1:
chand.Chandeliers.start(self=chand.Chandeliers)
window.close
chand.py
from pynput.keyboard import Key, Controller
import pause
keyboard = Controller()
start = "0"
class Chandeliers:
def d_press(self):
keyboard.press("d")
pause.milliseconds(70)
keyboard.release("d")
pause.milliseconds(300)
keyboard.release(Key.space)
def space_press(self):
keyboard.press(Key.space)
pause.milliseconds(1)
#keyboard.release(Key.space)
def start(self):
start = "1"
while start == "1":
self.d_press(self)
self.space_press(self)
Your Chandeliers.start function loops indefinitely, so the call from the main loop in main.py never gets returned to. If you want both loops to be running at the same time, you probably need to use threading or some other means of concurrency. Or you might be able to interleave the two loops somehow, depending on the timing requirements for each one.
As a side note, you are using your Chandeliers class in a very odd way. You're never creating an instance of the class, but rather calling the methods it defines as if they were class methods (but with manual passing of the class, in the misleadingly named self argument.
You should probably not do that. Either treat the class as a normal one, and create an instance:
cha = chand.Chandeliers()
chat.start() # and change start to not manually pass self any more
Or you should do away with the unneeded class all together and just make the methods into top-level functions.
I have followed this guide, but the code below
event1 = events[0]
event2 = events[1]
event = service.events().import_(calendarId='blah#group.calendar.google.com', body=event1).execute()
event = service.events().import_(calendarId='blah#group.calendar.google.com', body=event2).execute()
In which events is an array of my events, seems to overwrite the first event, once I try to import the second event. If I just run the first event declaration, the event is added, but once I add in the second one, it overwrites the first one, and just shows the second one.
Execute returns an event resource
event = service.events().import_(calendarId='blah#group.calendar.google.com', body=event1).execute()
the event variable will now equal the event resource for event1.
event = service.events().import_(calendarId='blah#group.calendar.google.com', body=event2).execute()
the event variable will now equal the event resource for event2.
If you want the result back then i suggest you change the response variable names to something like event1Response and event2Response.
Yea, so you need a unique icaluid, otherwise Google Calendar will erase any other event that has that uid. I simply imported time, and used time.time() for the icaluid.
I wrote a modified program of the 'mines' game, and I hope it shows every step/click graphically. I use time.sleep(0.5) to make a pause. So, in general the main program is like:
check_block():
if mine == 0:
buttons[current].config(image = tile_clicked)
elif mine == 1:
buttons[current].config(image = tile[1])
...
while(1):
time.sleep(0.5)
check_block()
get_next()
if check_fail():
break
However, the buttons don't update every 0.5 second: they are all updated together when the game(loop) finishes.
I guess it's just like 'cout' in C++: if you don't flush they will get stacked. So, is there a method to get them updated step by step, or say, instantly?
Thanks!
In all GUI systems you have to allow the message loop to run so that Windowing events occur promptly. So do not use a while loop like this. Instead, create a method that calls check_block() and get_next() and use after to call that function after a delay. At the end of that function, you use after again to call the same function again so that this function is called every 0.5 second forever. The after function queues a timer event and then lets the message queue be processed. Once your timer event fires, the callback function is run which allows you to do things and keep the UI responsive.
You should never call sleep in a GUI program. This is because the GUI must be "awake" at all times so that it can service events (including internal events that cause the screen to update). Instead, leverage the already-running eventloop by using the after method to put events on the queue at regular intervals.
In your case, you would replace the while loop with something like:
def do_check():
check_block()
if not check_fail():
root.after(500, do_check)
# in your initialization code, start the loop by calling it directly:
do_check()
I don't know what your get_next function does, so I don't know if you need to call it periodically too. Probably not. I'm guessing it waits for the next button press, which you don't need to do with tkinter or most other GUI toolkits. Instead, you configure the button to call a function when clicked.
Regardless, the way to do the type of looping you want is to place events on the event queue at a regular interval.
I initialize a wx.ListBox like this :
mylistbox = wx.ListBox(self, style=wx.LB_SINGLE)
mylistbox.Bind(wx.EVT_LISTBOX, self.OnEventListBox)
# some other things (append some items to the list)
mylistbox.SetSelection(5)
I also have :
def OnEventListBox(self, event):
print 'hello'
# plus lots of other things
How to make that the command mylistbox.SetSelection(5) in the initialization is immediately followed by the call of OnEventListBox?
Remark : It seems that SetSelection() doesn't generate a wx.EVT_LISTBOX automatically.
From the documentation:
Note that [SetSelection] does not cause any command events to be emitted...
This is on purpose, so that the events don't all trigger while you're trying to set up the UI. You could just manually call OnEventListBox for the desired functionality.
Better yet, if you don't need the event for what you're doing on init, you could extract the initialisation into a separate function, then call that on init and in OnEventListBox.