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.
Related
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.
Right now I have a child panel that post some event. I've tried
myEvent = events.ChangedAvailModelsEvent()
#self.GetEventHandler().ProcessEvent(myEvent)
wx.PostEvent(self, myEvent)
I create my event with
ChangedAvailModelsEvent, EVT_CHANGEDAVAILMODELS = NewEvent()
I bind with
self.Bind(events.EVT_CHANGEDAVAILMODELS, self.OnUpdate)
which takes place in some nth grandparent. I have print statements telling me the event was processed, but I my function is never called afterwards. I'm not sure what the problem is. I feel like the event is not propagating upwards. Any help?
Change it to use NewCommandEvent instead of NewEvent. Command events will automatically propagate up the parent chain in search of a handler. Non-command events will only be processed by the object they are posted to. See http://wiki.wxpython.org/self.Bind_vs._self.button.Bind.
I am trying to figure out how to unittest a bind command in a dialog window. I'm attempting this with tkinter's event_generate. It is not working the way I expect. For this StackOverflow question I've set up some code with a single call to event_generate. Sometimes that line works and sometimes it is as if the line doesn't even exist.
The bind in the dialog's __init__ method looks like this:
self.bind('<BackSpace>', #Print "BackSpace event generated."
lambda event: print(event.keysym, 'event generated.'))
Any action in the dialog will call back to its terminate method (The dialog is based on Frederik Lundh's example Dialog in 'An Introduction to Tkinter'.)
def terminate(self, event=None):
print('terminate called') # Make sure we got here and the next line will be called
self.event_generate('<BackSpace>')
self.parent.focus_set()
self.destroy()
When the dialog is called using the code below any user action will end up calling terminate. In each case "terminate called" and "BackSpace event generated." are displayed. This proves that the call to event_generate is set up correctly.
parent = tk.Tk()
dialog = Dialog(parent)
dialog.wait_window()
In case it's relevant I ought to mention that I have moved Lundh's call to self.wait_window from his dialog's __init__ method to the caller. Whilst this breaks the neat encapsulation of his dialog it appears to be necessary for automated unittests. Otherwise the unittest will display the dialog and halt waiting for user input. I don't like this solution but I'm not aware of any alternative.
The problem I'm having is when wait_window is replaced with a direct call to the terminate method. This is the sort of thing that I'd expect to be able to do in unittesting which is to test my GUI code without running tkinter's mainloop or wait_window.
parent = tk.Tk()
dialog = Dialog(parent)
dialog.terminate()
This only prints "terminate called" and does not print "BackSpace event generated.". The call to event_generate appears to have no effect. If I follow the call in the debugger I can see that tkinter's event_generate() is being called with the correct arguments. self = {Dialog} .99999999, sequence = {str}'<BackSpace>', kw = {dict}{}
In view of the warning in the TkCmd man pages about window focus I have verified the dialog with the binding is given focus in its __init__ method.
Tkinter is not executing the callback. Why?
EDIT: This bare bones code shows update working. However, it only works if it is called in __init__ before event_generate is called by the main program. (This puzzle has been raised as a separate question)
class UpdWin(tk.Tk):
def __init__(self):
super().__init__()
self.bind('<BackSpace>',
lambda event: print(event.keysym, 'event generated.'))
self.update() # Update works if placed here
app = UpdWin()
app.event_generate('<BackSpace>')
# app.update() # Update doesn't work if placed here
Six Years On
4/12/2021. See Mark Roseman's excellent web site for a detailed explanation of why any use of update is a bad idea.
The problem posed by this six year old question is entirely avoided by better program design in which tkinter widget objects are never subclassed. Instead they should be created by composition where they can be easily monkey patched. (This advice is contrary to patterns shown in Frederik Lundh's example Dialog in 'An Introduction to Tkinter'.)
For unittest design, not only is there no need to start Tk/Tcl via tkinter but it is also unwise.
event_generate will by default process all event callbacks immediately. However, if you don't call update before calling event_generate, the window won't be visible and tkinter will likely ignore any events. You can control when the generated event is processed with the when attribute. By default the value is "now", but another choice is "tail" which means to append it to the event queue after any events (such as redraws) have been processed.
Full documentation on the when attribute is on the tcl/tk man page for event_generate: http://tcl.tk/man/tcl8.5/TkCmd/event.htm#M34
Don't know if this is relevant to your problem, but I got widget.event_generate() to work by calling widget.focus_set() first.
#lemi57ssss I know this is an old question, but I just want to highlight the point brought up by Bryan Oakley and to correct your last code to make it work. He said you have to update first before it can respond to the generated event. So if you switch the positions of update() and event_generate(), you will get the "BackSpace event generated." text printed out.
It worked when you put the update() in the __init__() was because of the same reason, i.e., it got called first before the event_generated().
See the amended code below:
class UpdWin(tk.Tk):
def __init__(self):
super().__init__()
self.bind('<BackSpace>',
lambda event: print(event.keysym, 'event generated.'))
#self.update() # Update works if placed here
app = UpdWin()
app.update() # Update also works if you placed it here
app.event_generate('<BackSpace>')
I'm using Python 2.7, if that matters.
Here is a code I wrote for fun:
def p():
root = Tk()
def cmd(event):
print int(slider.get())
slider = Scale(root, orient = "horizontal", from_ = 0, to = 100, command = cmd, state = "disabled")
def enable():
slider.config(state = "active")
b = Button(root, text = "Enable slider", command = enable)
b.grid()
slider.grid(row = 1)
root.mainloop()
For this code, I'm wondering why the command for Scale requires an event, but that for Button does not. It seems that for some widgets in Tkinter their commands need to have "event" as an argument, and other don't. Why? How to distinguish them?
Thanks.
Scale doesn't take an event. It takes the current value. Try this:
def cmd(value):
print int(value)
If you read the Tk tutorial, it explains this:
There is a "command" configuration option, which lets you specify a script to call whenever the scale is changed. Tk will automatically append the current value of the scale as a parameter each time it invokes this script (we saw a similar thing with extra parameters being added to scrollbar callbacks and those on the widgets they scroll).
Or, if you read the actual manpage:
Specifies the prefix of a Tcl command to invoke whenever the scale's value is changed via a widget command. The actual command consists of this option followed by a space and a real number indicating the new value of the scale.
In other words, the way to distinguish them is to read the docs. Unfortunately, the Tkinter docs aren't all that complete—they assume you already know how Tcl/Tk works, or how to look it up yourself. That's why the docs start off with a list of links to sources of Tk documentation.
If you prefer to figure it out by trial and error, it's not that hard to see what gets passed:
def cmd(*args):
print('Scale command says {}'.format(args))
def enable(*args):
print('Button command says {}'.format(args))
But this won't always tell you everything you need to know; there are other callbacks whose arguments aren't obvious enough to figure out without a lot more work, or which are configurable (e.g., the validate callback).
When you set up a binding (with the bind) command, the callback always is given an event object.
When you are working with the command attribute of a widget, different widgets send different information to the command. In this case they never send an event, but they will send other types of data. This is simply due to the fact different commands do different things.
The scale widget is no different -- you claim the callback takes an event, but that is false. It is passed the current value of the scale widget rather than an event object.
What I'm trying to do here is select multiple files from within a wxPython frame app and then utilize those file paths within another function within the same wxPython app. I realize I can use the following code to select multiple files:
def OnOpen(self,e)
dialog = wx.FileDialog(self, "Choose a file", "", "", "*.", wx.MULTIPLE)
if dialog.ShowModal() == wx.ID_OK:
filelist = dialog.GetPaths()
So the 'filelist' variable is now a list of the files the user selects. However, I can't seem to find a way to utilize this list in another function. I was hoping I could just use a return statement within this function in another function and assign it to a variable, but it doesn't seem to allow me to do this. I guess this is because I have a button event already using this function. Any help on this would be greatly appreciated.
I don't think I fully understand the issue here. The OnOpen function is an event handler for a button click? So it is executed when button is clicked. A windowed application (like yours using wxPython) is usually event driven, it responds to events like a button click. The main GUI thread loops and analyses events which can be handled by you application. So when you write an application these event handlers are entry points for actions you would like to perform.
I think the best way to do is simply the other function and pass filelist in parameter e.g:
def OnOpen(self,e)
dialog = wx.FileDialog(self, "Choose a file", "", "", "*.", wx.MULTIPLE)
if dialog.ShowModal() == wx.ID_OK:
filelist = dialog.GetPaths()
self.otherFunc(filelist)
def otherFunc(self, filelist):
"do something here"
saving filelist to an attribute may be problematic sometimes. As I said you may have multiple entry points which would like to use filelist attribute, you have to predict possible problems. But as I said at the beginning - I might have not fully understood your problem.
Another possiblity: maybe your problem is that you would like to actually reuse OnOpen? First possibility is to pass None as an argument because e is not used. Another, better possibility is to define OnOpen without e argument and change the event binding to self.Bind(wx.EVT_BUTTON, self.OnButtonwx.EVT_BUTTON(<some object>, lambda evt: self.OnOpen()). You explicitly say here that you don't need the evt argument.
Put the list as an attribute in self, or have self contain as an attribute a listener that gets the paths, or call the other function giving it the paths as an argument?