I have a wxPython gui and I want to programmatically generate an event.
I've tried a syntax like this:
e = wx.Event.__init__(grid, eventType=wx.EVT_LEFT_DOWN)
which results in:
TypeError: unbound method __init__() must be called with Event instance as first argument (got Grid instance instead)
or:
e = wx.CommandEvent(commandType=wx.EVT_BUTTON)
TypeError: in method 'new_CommandEvent', expected argument 1 of type 'wxEventType'
So question is simply, what is the exact, literal syntax that I need to use to create an event object? Or, can someone point me to a good resource for making sense of events? I don't know if I'm just missing something simple in my understanding. I haven't been able to find a direct answer to this question yet online. I checked out this question: Generate a custom CommandEvent in wxPython , but I don't want to make a custom event.
Thanks in advance!
You would want to use wx.PostEvent
To programatically generate an event:
wx.PostEvent(self.GetEventHandler(), wx.PyCommandEvent(wx.EVT_BUTTON.typeId, self.GetId()))
if you want to post a wx.EVT_BUTTON event. Making it a PyCommandEvent means it will propagate upwards; other event types don't propagate by default.
The general form of wx.PostEvent(): http://www.wxpython.org/docs/api/wx-module.html#PostEvent
Here's a small example code:
import wx
class MyFrame ( wx.Frame ):
def __init__( self, parent ):
wx.Frame.__init__ ( self, parent, id = wx.ID_ANY, title = u"Test", pos = wx.DefaultPosition, size = wx.Size( 200,200 ), style = wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL )
sizer_inside = wx.BoxSizer( wx.VERTICAL )
# Adding a button and a textCtrl widget
self.button = wx.Button( self, wx.ID_ANY, u"Click Me", wx.DefaultPosition, wx.DefaultSize, 0 )
sizer_inside.Add( self.button, 0, wx.ALIGN_CENTER|wx.ALL, 5 )
self.textCtrl = wx.TextCtrl( self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.TE_NO_VSCROLL )
sizer_inside.Add( self.textCtrl, 0, wx.ALIGN_CENTER|wx.ALL, 5 )
self.SetSizer( sizer_inside )
self.Layout()
self.Centre( wx.BOTH )
self.Show()
self.counter = 0
# Binding Events
self.Bind( wx.EVT_BUTTON, self.on_click )
self.Bind( wx.EVT_CHOICE, self.test_dummy)
#Event handlers
def on_click( self, event ):
self.counter += 1
wx.PostEvent(self.GetEventHandler(), wx.PyCommandEvent(wx.EVT_CHOICE.typeId, self.GetId()))
def test_dummy(self, event):
self.counter += 1
self.textCtrl.SetValue(str(self.counter))
if __name__ == "__main__":
app = wx.App(False)
MyFrame(None)
app.MainLoop()
If you run this, notice that the textCtrl will display 2 after clicking the button. The first event handler manually fires the second event which is handled by test_dummy.
I think you'd better using win32gui.PostMessage().
This would help you.
http://markmail.org/message/epiclzlaph44f3kk
Use wx.PostEvent... like so:
class launcherWindow(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, parent=None, title='New Window')
#now add the main body, start with a panel
panel = wx.Panel(self)
#instantiate a new dropdown
self.productDropDown = wx.ComboBox(panel, size=wx.DefaultSize, style = wx.CB_READONLY)
#get the products and product subtypes
self.productDict = self.getProductsAndSubtypes()
#setup subtypes first, just in case, since onProductSelection will reference this
self.productSubtypeDropDown = wx.ComboBox(panel, size=wx.DefaultSize, style = wx.CB_READONLY)
#add products
for product in self.productDict.keys():
self.productDropDown.Append(product)
#bind selection event
self.productDropDown.Bind(wx.EVT_COMBOBOX, self.onProductSelection)
#set default selection
self.productDropDown.SetSelection(0)
#pretend that we clicked the product selection, so it's event gets called
wx.PostEvent(self.productDropDown, wx.CommandEvent(wx.wxEVT_COMMAND_COMBOBOX_SELECTED))
#now add the dropdown to a sizer, set the sizer for the panel, fit the panel, etc...
def onProductSelection(self, event):
productSelected = self.productDropDown.GetStringSelection()
productSubtypes = self.productDict[productSelected]
#clear any existing product subtypes, since each product may have different ones
self.productSubtypeDropDown.Clear()
for productSubtype in productSubtypes:
self.productSubtypeDropDown.Append(productSubtype)
#select the first item by default
self.productSubtypeDropDown.SetSelection(0)
thanks. #i5on9i comment saved me.
I was trying to call the Next button on WxPython under Windows.
When I did try to use a pythonic event my wizard would just end without going through the pages as if I pressed the "Finish" button even though it was not visible on the page.
If I understand correctly, since this is a Windows class (and not a pythonic class) I could not use a pythonic event. I therefore had to call the windows event.
Perhaps a different strategy would have been to switch from a windows Wizard class to a pythonic wizard class but I didn't try it.
BTW the message I ended up sending was:
win32gui.PostMessage(wizard.GetHandle(),0x0111,wx.ID_FORWARD,self.wizard.FindWindowById(wx.ID_FORWARD).GetHandle())
Related
EDITED ~ Now with code.
I’m new to coding, and struggling getting variables input into ‘frameA’ output into ‘frameB’. My question is, how should the layout of the code be written so that a variable entered into FrameA is usable, (and refreshed on user input - either on a button click or deselecting textctrl etc-) in frameB?
I’m using wxpython, and using Wx builder as a guide, but writing the actual UI code used myself to help understand what’s going on underneath. I’ve tried class -> def -> frame UI and the frames are organised as I want them, show and hide as I want and the variables I want are printable on entry/button clicks etc within that frame, but are not accessible from outside of that frame, even though I am returning the variables needed.
In this example I want to take what is written in frameA's textctrl, and save it as a variable accessible (and updated) to other classes, in this case FrameB.
import wx
var = "This Should change to whatever is typed in FrameA"
class FrameA ( wx.Frame ):
def __init__( self, parent ):
wx.Frame.__init__ ( self, parent, id = wx.ID_ANY, title = wx.EmptyString, pos = (0,0), size = wx.Size( 500,300 ), style = wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL )
bSizer1 = wx.BoxSizer( wx.HORIZONTAL )
self.INPUT = wx.TextCtrl( self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 )
bSizer1.Add( self.INPUT, 1, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 )
self.SetSizer( bSizer1 )
self.INPUT.Bind( wx.EVT_TEXT, self.InputUpdate)
def InputUpdate(self, Evt):
var = self.INPUT.GetValue()
#print (var)
return var
class FrameB ( wx.Frame ):
def __init__( self, parent ):
wx.Frame.__init__ ( self, parent, id = wx.ID_ANY, title = wx.EmptyString, pos = (100,100), size = wx.Size( 500,300 ), style = wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL )
bSizer2 = wx.BoxSizer( wx.HORIZONTAL )
self.OUTPUT = wx.StaticText( self, wx.ID_ANY, var, wx.DefaultPosition, wx.DefaultSize, 0 )
bSizer2.Add( self.OUTPUT, 1, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 )
self.SetSizer( bSizer2 )
if __name__ == '__main__':
app = wx.App()
frmA = FrameA(None)
frmB = FrameB(None)
frmA.Centre()
frmB.Centre()
frmA.Show()
frmB.Show()
app.MainLoop()
As I’m still new I am at the point where I can understand each single line of code, but the larger organisation is still getting messy for me.
My understanding is that the code in its state above the edited 'var' can be accessed from within frameA only, and 'return var' might(?) update the 'global' var, but is not being refreshed into frameB. What am I missing? I've been trying to avoid using global variables as I've heard they're a bad habit to get into this early on...
Thanks, Sundown
If you want the value of var to be accessible to all classes then you need to add the line global var to the method InputUpdate() in Class FrameA before assigning the new value to var.
However, this will make var accessible to all classes in this module, only. If you split the code into two files, then you will have two modules and var will be accessible only in the module that defines it, unless you also import the module defining var in the other module.
Code with some relevant comments:
import wx
var = "This Should change to whatever is typed in FrameA"
class FrameA ( wx.Frame ):
def __init__(self, parent):
wx.Frame.__init__ (self, parent, id=wx.ID_ANY, title=wx.EmptyString, pos=(0,0), size=(500,300), style=wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL)
bSizer1 = wx.BoxSizer(wx.HORIZONTAL)
self.INPUT = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0)
bSizer1.Add(self.INPUT, 1, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5)
self.SetSizer(bSizer1)
self.INPUT.Bind(wx.EVT_TEXT, self.InputUpdate)
def InputUpdate(self, Evt):
#### You need to add the following line before assigning the new value to
#### var in order to modify the global var variable and not the local
#### var variable
global var
var = self.INPUT.GetValue()
#print (var)
#### The try/except block is placed to avoid having an error if the text
#### in frameA changes but frameB does not exist anymore. The code inside
#### the try statement changes the text in frameB in real time.
try:
frmB.OUTPUT.SetLabel(var)
except Exception:
pass
class FrameB ( wx.Frame ):
def __init__(self, parent):
wx.Frame.__init__ (self, parent, id=wx.ID_ANY, title=wx.EmptyString, pos=(100,100), size=(500,300), style=wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL)
bSizer2 = wx.BoxSizer(wx.VERTICAL)
self.OUTPUT = wx.StaticText(self, wx.ID_ANY, var, wx.DefaultPosition, wx.DefaultSize, 0)
bSizer2.Add(self.OUTPUT, 1, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5)
#### Button to check the value of the global var variable and make sure
#### that the code in InputUpdate actually changes the value of the global
#### variable var
self.button = wx.Button(self, wx.ID_ANY, label='Var Value')
bSizer2.Add(self.button, 1, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5)
self.SetSizer(bSizer2)
self.button.Bind(wx.EVT_BUTTON, self.varValue)
def varValue(self, event):
#### Print the value of the global variable var
print(var)
if __name__ == '__main__':
app = wx.App()
frmA = FrameA(None)
frmB = FrameB(None)
frmA.Centre()
frmB.Centre()
frmA.Show()
frmB.Show()
app.MainLoop()
I have created a web browser in python using WxPython. I've been able to get everything to function, go forward/back, reload, ect. My only problem is I'd like to be able to set the title at the top of the program to the title of the web page. I understand how to set the title with self.SetTitle(title), I also know from some of the research I've done that you can get the tile of the page using self.browser.GetCurrentTitle(). Only problem with using that is that it's a one time thing that doesn't refresh it's self, when a user clicks a new link. I'm assuming there is some sort of function or something I can catch when a new page is loaded and tell python to do something like this:
def OnLoad(self, event):
self.webtitle = self.browser.GetCurrentTitle()
self.browser.SetTitle(self.webtitle)
Or something along those lines I'm just not sure where or how I can connect or "catch" that function. I have read through the documentation of wx.html2.WebView, but I'm not able to make sense of how to do this, I have also looked through this site as well as a few mailing lists but I can't seem to find anything that would explain how to do this.
Here is the main code that will run my browser (obviously I've shortened it).
class PyBrowser(wx.Frame):
def __init__(self, *args, **kwds):
wx.Frame.__init__(self, *args, **kwds)
self.SetTitle('PyBrowser')
sizer = wx.BoxSizer(wx.VERTICAL)
self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
#Element Variables
self.browser = wx.html2.WebView.New(self)
self.address = wx.TextCtrl(self, value="http://",size=(500, 26))
self.go = wx.Button(self, label="Go", id=wx.ID_OK)
self.back = wx.BitmapButton(self, -1, wx.Bitmap('img/Back Button.png', wx.BITMAP_TYPE_PNG), size=(26,26) ,style=wx.NO_BORDER)#wx.Button(self, label="Back")
self.forward = wx.BitmapButton(self, -1, wx.Bitmap('img/Forward Button.png', wx.BITMAP_TYPE_PNG), size=(26,26) ,style=wx.NO_BORDER)#wx.Button(self, label="Forward")
self.reload = wx.BitmapButton(self, -1, wx.Bitmap('img/Reload Button.png', wx.BITMAP_TYPE_PNG), size=(26,26) ,style=wx.NO_BORDER)#wx.Button(self, label = "Refresh")
self.result = None
#Nav area
addressarea = wx.BoxSizer()
addressarea.Add(self.address, proportion = 1, border = 0)
...
#Adding elements
sizer.Add(addressarea, proportion = 0, flag = wx.EXPAND, border = 0)
sizer.Add(self.browser, 1, wx.EXPAND, 10)
#Button binding
self.Bind(wx.EVT_BUTTON, self.OnGo, id=wx.ID_OK)
...
#Menu bar stuff
#File Menu
self.fileMenu = wx.Menu()
self.New = self.fileMenu.Append(wx.ID_ANY, 'New Window')
...
#Help Menu
self.helpMenu = wx.Menu()
self.Help = self.helpMenu.Append(wx.ID_ANY, 'Help')
...
self.Bind(wx.EVT_MENU,self.OnHelp,self.Help)
...
#Adding the actual Menu Bar
self.menuBar = wx.MenuBar()
self.menuBar.Append(self.fileMenu, 'File')
self.menuBar.Append(self.helpMenu, 'Help')
self.SetMenuBar(self.menuBar)
self.SetSizer(sizer)
self.SetSize((1000, 700))
def OnGo(self, event):
self.result = self.address.GetValue()
self.browser.LoadURL(self.result)
if __name__ == '__main__':
app = wx.App()
dialog = PyBrowser(None, -1)
dialog.browser.LoadURL("http://www.google.com")
dialog.Show()
app.MainLoop()
To summarize what I'm asking is how to I set the title of a wx.Frame to the current title of a web page inside of wx.html2.WebView and change the name every time a new link is clicked?
The webview events that you require are listed here
I would catch the EVT_WEBVIEW_LOADED event and update the title in the event handler. I believe you could do something like this:
def onLoaded(self, event):
title = self.browser.GetCurrentTitle()
self.SetTitle(title)
What may be even better is to catch EVT_WEBVIEW_TITLE_CHANGED as that actually detects when the page title changes.
I figured out that from what Mike said, using an event to catch it would work. So instead of EVT_WEBVIEW_TITLE_CHANGED I used EVT_WEBVIEW_LOADED, I then binded the event to my function to handle changing the title. It looked like this:
#Binding the function the event to the function.
self.Bind(wx.html2.EVT_WEBVIEW_LOADED, self.OnPageLoad, self.browser)
#Function binding to the page loading
def OnPageLoad(self, event):
self.WebTitle = self.browser.GetCurrentTitle()
self.SetTitle(self.WebTitle)
I just started learning Python so I'm trying to avoid working too much in Python 2. Currently learning GUI elements with wxPython. The Python 3 documentation doesn't have an introductory section yet so I'm using the Python 2 'getting started' documentation and converting to Python 3 where needed.
I'm currently at this section. There is a section for wx.EVT_CHAR for event handling when a keypress is detected on the focused object. I don't see a reference to it in the comparison chart, or in the CommandEvent docs, or in the "events emmitted by this class" section of the wx.TextCtrl docs. I've been able to convert most other non-Python 3 code such as SizerFlags, but I can't find an equivalent for this.
This is what I'm working with.
import wx
class ExampleFrame(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent)
baseSizer = wx.BoxSizer(wx.VERTICAL)
# Create an editable text field
self.textfield = wx.TextCtrl(self)
# Attach event handlers to text field
# Event for when the text changes
self.Bind(wx.EVT_TEXT, self.OnChange, self.textfield)
# Event for when a key is pressed, for example an arrow key should fire this event but not the EVT_TEXT event
self.Bind(wx.EVT_CHAR, self.OnKeyPress, self.textfield)
# Create a button that will clear the textfield
clearButton = wx.Button(self, wx.ID_CLEAR, "Clear")
# Attach event handler on the clearButton to call OnClear()
self.Bind(wx.EVT_BUTTON, self.OnClear, clearButton)
# Multiline text field for seeing the events fire
self.logger = wx.TextCtrl(self, -1, style= wx.TE_MULTILINE | wx.TE_READONLY )
# Add items to frame sizer
baseSizer.Add(self.textfield, wx.SizerFlags(0).Expand())
baseSizer.Add(clearButton, wx.SizerFlags(0).Expand())
baseSizer.Add(self.logger, wx.SizerFlags(1).Expand())
# Set sizer for frame
self.SetSizer(baseSizer)
# Show
self.Show()
def OnClear(self, e):
# Clear all text entered into the textfield and return focus
self.textfield.SetValue("")
self.textfield.SetFocus()
def OnChange(self, e):
# Log every time this event is fired
self.logger.AppendText("OnChange: " + e.GetString() + '\n')
def OnKeyPress(self, e):
# Log every key press in the textfield
self.logger.AppendText("OnKeyPress: " + e.GetKeyCode() + '\n')
app = wx.App(False)
ExampleFrame(None)
app.MainLoop()
The OnChange() will fire every single time the text in textfield changes. The OnKeyPress never fires. If I did get it to fire though, I don't see a GetKeyCode() equivalent in the CommandEvent methods summary.
EDIT:
Problem solved thanks to Mike Driscoll. I implemented his change which was to change this:
self.Bind(wx.EVT_CHAR, self.OnKeyPress, self.textfield)
to this:
self.textfield.Bind(wx.EVT_CHAR, self.OnKeyPress, self.textfield)
Also I had to add e.Skip() to the OnKeyPress function. Otherwise it was logging the key, but not adding text to the textfield. The other events were fine without Skip()ing to pass the event up the control tree to other listeners.
You just bound the event incorrectly in this case. You want
self.textfield.Bind(wx.EVT_CHAR, self.OnKeyPress, self.textfield)
instead of
self.Bind(wx.EVT_CHAR, self.OnKeyPress, self.textfield)
And here it is in context:
import wx
class ExampleFrame(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent)
baseSizer = wx.BoxSizer(wx.VERTICAL)
# Create an editable text field
self.textfield = wx.TextCtrl(self)
# Attach event handlers to text field
# Event for when the text changes
self.Bind(wx.EVT_TEXT, self.OnChange, self.textfield)
# Event for when a key is pressed, for example an arrow key should fire this event but not the EVT_TEXT event
self.textfield.Bind(wx.EVT_CHAR, self.OnKeyPress, self.textfield)
# Create a button that will clear the textfield
clearButton = wx.Button(self, wx.ID_CLEAR, "Clear")
# Attach event handler on the clearButton to call OnClear()
self.Bind(wx.EVT_BUTTON, self.OnClear, clearButton)
# Multiline text field for seeing the events fire
self.logger = wx.TextCtrl(self, -1, style= wx.TE_MULTILINE | wx.TE_READONLY )
# Add items to frame sizer
baseSizer.Add(self.textfield, 1, flag=wx.EXPAND)
baseSizer.Add(clearButton,0)
baseSizer.Add(self.logger, 1, flag=wx.EXPAND)
# Set sizer for frame
self.SetSizer(baseSizer)
# Show
self.Show()
def OnClear(self, e):
# Clear all text entered into the textfield and return focus
self.textfield.SetValue("")
self.textfield.SetFocus()
e.Skip()
def OnChange(self, e):
# Log every time this event is fired
self.logger.AppendText("OnChange: " + e.GetString() + '\n')
e.Skip()
def OnKeyPress(self, e):
# Log every key press in the textfield
self.logger.AppendText("OnKeyPress: " + str(e.GetKeyCode()) + '\n')
e.Skip()
app = wx.App(False)
ExampleFrame(None)
app.MainLoop()
You will probably want to read the following wiki entry on the difference binding methods:
http://wiki.wxpython.org/self.Bind%20vs.%20self.button.Bind
As for your other question, while I can't be certain, I would be very surprised if OnKeyPress didn't exist in Phoenix. I would give that a try even if it isn't in the documentation as I would guess it's still in there.
I'm teaching myself (Wx) Python and got stuck. I want to create a child window that has a recurring set of information inside of my TestFrame, which contains most of my code. The problem is, it only shows one widget in my code. I am trying to figure out the following, in order of importance to me.
**Note that this code is an extension to this page.*
How can I allow multiple widgets in the "AddBox" class to appear correctly?
When my window is resized, it corrupts the button images as in the screen attached. How could I fix this?
How do you call/bind each of these dynamically created widgets?
Bonus: is the "OnSize" module needed here?
Thank you for your help. If allowed/appropriate, I'm willing to contribute $5 via Paypal to the winner if you PM me.
import wx
class AddBox(wx.Window):
def __init__(self, parent):
wx.Window.__init__(self, parent)
pbox = wx.BoxSizer(wx.VERTICAL)
controlback = wx.Button(self, label="Back")
controlforward = wx.Button(self, label="Forward")
pbox.AddMany([(controlback, 1, wx.ALL), (controlforward, 1, wx.ALL)])
class TestFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1, size=(1000, 550))
self.Bind(wx.EVT_SIZE, self.OnSize)
pbox0 = wx.BoxSizer(wx.VERTICAL)
controlback0 = wx.Button(self, label="Back0")
controlforward0 = wx.Button(self, label="Forward0")
pbox0.AddMany([(controlback0, 1, wx.ALL), (controlforward0, 1, wx.ALL)])
pbox2 = wx.BoxSizer(wx.VERTICAL)
self.scrolling_window = wx.ScrolledWindow( self )
self.scrolling_window.SetScrollRate(1,6)
self.scrolling_window.EnableScrolling(True,True)
self.sizer_container = wx.BoxSizer( wx.VERTICAL )
self.sizer = wx.BoxSizer( wx.VERTICAL )
self.sizer_container.Add(self.sizer,1,wx.CENTER,wx.EXPAND)
self.child_windows = []
for i in range(0,8):
wind = AddBox(self.scrolling_window)
self.sizer.Add(wind, 0, wx.CENTER|wx.ALL, 5)
self.child_windows.append(wind)
self.scrolling_window.SetSizer(self.sizer_container)
#self.Layout() #not needed?
pbox2.AddMany([(self.sizer_container, 1, wx.ALL)])
def OnSize(self, event):
self.scrolling_window.SetSize(self.GetClientSize())
if __name__=='__main__':
app = wx.PySimpleApp()
f = TestFrame()
f.Show()
app.MainLoop()
The code here is pretty convoluted and hard to follow. You almost never need to use wx.Window. In fact, in almost 6 years of using wxPython, I have NEVER used it directly. PySimpleApp is deprecated as well. Anyway, I cleaned up the code a bunch and re-did it below. I'm not sure if this is what you're looking for or not though. Also note that I swapped out ScrolledWindow for ScrolledPanel as I think the latter is easier to use:
import wx
import wx.lib.scrolledpanel as scrolled
class TestFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, size=(1000, 550))
panel = wx.Panel(self)
mainSizer = wx.BoxSizer(wx.VERTICAL)
pbox0 = wx.BoxSizer(wx.VERTICAL)
controlback0 = wx.Button(panel, label="Back0")
controlforward0 = wx.Button(panel, label="Forward0")
pbox0.Add(controlback0, 0, wx.ALL)
pbox0.Add(controlforward0, 0, wx.ALL)
mainSizer.Add(pbox0)
self.scrolling_window = scrolled.ScrolledPanel( panel )
self.scrolling_window.SetAutoLayout(1)
self.scrolling_window.SetupScrolling()
self.sizer = wx.BoxSizer( wx.VERTICAL )
self.child_windows = []
for i in range(0,8):
wind = self.addBox()
self.sizer.Add(wind, 0, wx.CENTER|wx.ALL, 5)
self.scrolling_window.SetSizer(self.sizer)
mainSizer.Add(self.scrolling_window, 1, wx.EXPAND)
panel.SetSizer(mainSizer)
def addBox(self):
pbox = wx.BoxSizer(wx.VERTICAL)
controlback = wx.Button(self.scrolling_window, label="Back")
controlforward = wx.Button(self.scrolling_window, label="Forward")
pbox.AddMany([(controlback, 0, wx.ALL), (controlforward, 0, wx.ALL)])
return pbox
def OnSize(self, event):
self.scrolling_window.SetSize(self.GetClientSize())
if __name__=='__main__':
app = wx.App(False)
f = TestFrame()
f.Show()
app.MainLoop()
You should also take a look at the Widget Inspection Tool. It will help you figure out layout problems like this: http://wiki.wxpython.org/Widget%20Inspection%20Tool
EDIT: I think the code above takes care of items #1 and 2. For #3, see the following article I wrote last year: http://www.blog.pythonlibrary.org/2011/09/20/wxpython-binding-multiple-widgets-to-the-same-handler/
Basically you bind in the loop itself and then in the event handler, you can use event.GetEventObject() to return the calling widget and set its value or whatever. As for your 4th question, I would say no, you don't need the OnSize method. You almost never need to override that in wxPython and I certainly didn't in my example code.
I just have an answer to 3 for now: Assign to each dynamically created widget an ID, store this ID in a dictionary ({id : widget }) and pass as the callback function a lambda-function or use functools.partial to pass the ID to the "real" callback. (of course you can pass the widget directly over the lambda-function, but I like to have instances to the created widgets somewhere, if I need to access these sometime)
def __init__(...):
self.event_dispatch = dict()
...
for id in xrange(500, 508):
wind = AddBox(self.scrolling_window, id)
...
self.event_dispatch[id] = wind
self.scrolling_window.Bind(wx.MY_EVENT, lambda evt: self.on_event(evt, id), id=id)
def on_event(event, id):
widget = self.event_dispatch[id]
# do something with widget
Could someone show me how I could return a value from a wxPython Frame? When the use clicks close, I popup a message dialog asking him a question. I would like to return the return code of this message dialog to my calling function.
Thanks
Because the wxFrame has events that process via the app.MainLoop() functionality, the only way to get at the return value of a wx.Frame() is via catching an event.
The standard practice of handling events is typically from within the class which derives from wx.Window itself (e.g., Frame, Panel, etc.). Since you want code exterior to the wx.Frame to receive information that was gathered upon processing the OnClose() event, then the best way to do that is to register an event handler for your frame.
The documentation for wx.Window::PushEventHandler is probably the best resource and even the wxpython wiki has a great article on how to do this. Within the article, they register a custom handler which is an instance of "MouseDownTracker." Rather than instantiating within the PushEventHandler call, you'd want to instantiate it prior to the call so that you can retain a handle to the EventHandler derived class. That way, you can check on your derived EventHandler class-variables after the Frame has been destroyed, or even allow that derived class to do special things for you.
Here is an adaptation of that code from the wx python wiki (admittedly a little convoluted due to the requirement of handling the results of a custom event with a "calling" function):
import sys
import wx
import wx.lib.newevent
(MyCustomEvent, EVT_CUSTOM) = wx.lib.newevent.NewEvent()
class CustomEventTracker(wx.EvtHandler):
def __init__(self, log, processingCodeFunctionHandle):
wx.EvtHandler.__init__(self)
self.processingCodeFunctionHandle = processingCodeFunctionHandle
self.log = log
EVT_CUSTOM(self, self.MyCustomEventHandler)
def MyCustomEventHandler(self, evt):
self.log.write(evt.resultOfDialog + '\n')
self.processingCodeFunctionHandle(evt.resultOfDialog)
evt.Skip()
class MyPanel2(wx.Panel):
def __init__(self, parent, log):
wx.Panel.__init__(self, parent)
self.log = log
def OnResults(self, resultData):
self.log.write("Result data gathered: %s" % resultData)
class MyFrame(wx.Frame):
def __init__(self, parent, ID=-1, title="", pos=wx.DefaultPosition, size=wx.DefaultSize, style=wx.DEFAULT_FRAME_STYLE):
wx.Frame.__init__(self, parent, ID, title, pos, size, style)
self.panel = panel = wx.Panel(self, -1, style=wx.TAB_TRAVERSAL | wx.CLIP_CHILDREN | wx.FULL_REPAINT_ON_RESIZE)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add((25, 25))
row = wx.BoxSizer(wx.HORIZONTAL)
row.Add((25,1))
m_close = wx.Button(self.panel, wx.ID_CLOSE, "Close")
m_close.Bind(wx.EVT_BUTTON, self.OnClose)
row.Add(m_close, 0, wx.ALL, 10)
sizer.Add(row)
self.panel.SetSizer(sizer)
def OnClose(self, evt):
dlg = wx.MessageDialog(self, "Do you really want to close this frame?", "Confirm Exit", wx.OK | wx.CANCEL | wx.ICON_QUESTION)
result = dlg.ShowModal()
dlg.Destroy()
if result == wx.ID_CANCEL:
event = MyCustomEvent(resultOfDialog="User Clicked CANCEL")
self.GetEventHandler().ProcessEvent(event)
else: # result == wx.ID_OK
event = MyCustomEvent(resultOfDialog="User Clicked OK")
self.GetEventHandler().ProcessEvent(event)
self.Destroy()
app = wx.App(False)
f2 = wx.Frame(None, title="Frame 1 (for feedback)", size=(400, 350))
p2 = MyPanel2(f2, sys.stdout)
f2.Show()
eventTrackerHandle = CustomEventTracker(sys.stdout, p2.OnResults)
f1 = MyFrame(None, title="PushEventHandler Tester (deals with on close event)", size=(400, 350))
f1.PushEventHandler(eventTrackerHandle)
f1.Show()
app.MainLoop()
You can get the result of clicking the OK, CANCEL buttons from the Dialog ShowModal method.
Given dialog is an instance of one of the wxPython Dialog classes:
result = dialog.ShowModal()
if result == wx.ID_OK:
print "OK"
else:
print "Cancel"
dialog.Destroy()
A few years late for the initial question, but when looking for the answer to this question myself, I stumbled upon a built-in method of getting a return value from a modal without messing with any custom event funniness. Figured I'd post here in case anyone else needs it.
It's simply this guy right here:
wxDialog::EndModal void EndModal(int retCode)
Ends a modal dialog, passing a value to be returned from the
*wxDialog::ShowModal invocation.*
Using the above, you can return whatever you want from the Dialog.
An example usage would be subclassing a wx.Dialog, and then placing the EndModal function in the button handlers.
class ProjectSettingsDialog(wx.Dialog):
def __init__(self):
wx.Dialog.__init__(self, None, -1, "Project Settings", size=(600,400))
sizer = wx.BoxSizer(wx.VERTICAL) #main sized
sizer.AddStretchSpacer(1)
msg = wx.StaticText(self, -1, label="This is a sample message")
sizer.Add(msg, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALL, 15)
horizontal_sizer = wx.BoxSizer(wx.HORIZONTAL)
okButton = wx.Button(self, -1, 'OK')
self.Bind(wx.EVT_BUTTON, self.OnOK, okButton)
cancelBtn = wx.Button(self, -1, "Cancel")
self.Bind(wx.EVT_BUTTON, self.OnCancel, cancelBtn)
horizontal_sizer.Add(okButton, 0, wx.ALIGN_LEFT)
horizontal_sizer.AddStretchSpacer(1)
horizontal_sizer.Add(cancelBtn, 0, wx.ALIGN_RIGHT)
sizer.Add(horizontal_sizer, 0)
sizer.AddStretchSpacer(1)
self.SetSizer(sizer)
def OnOK(self, event):
self.EndModal(wx.ID_OK) #returns numeric code to caller
self.Destroy()
def OnCancel(self, event):
self.EndModal(wx.ID_CANCEL) #returns numeric code to caller
self.Destroy()
(Note: I just banged this code out quickly; didn't test the sizers)
As you can see, all you need to do is call the EndModal from a button event to return a value to whatever spawned the dialog.
I wanted to do the same thing, to have a graphical "picker" that I could run from within a console app. Here's how I did it.
# Fruit.py
import wx
class Picker (wx.App):
def __init__ (self, title, parent=None, size=(400,300)):
wx.App.__init__(self, False)
self.frame = wx.Frame(parent, title=title, size=size)
self.apple_button = wx.Button(self.frame, -1, "Apple", (0,0))
self.apple_button.Bind(wx.EVT_BUTTON, self.apple_button_click)
self.orange_button = wx.Button(self.frame, -1, "Orange", (0,100))
self.orange_button.Bind(wx.EVT_BUTTON, self.orange_button_click)
self.fruit = None
self.frame.Show(True)
def apple_button_click (self, event):
self.fruit = 'apple'
self.frame.Destroy()
def orange_button_click (self, event):
self.fruit = 'orange'
self.frame.Destroy()
def pick (self):
self.MainLoop()
return self.fruit
Then from a console app, I would run this code.
# Usage.py
import Fruit
picker = Fruit.Picker('Pick a Fruit')
fruit = picker.pick()
print 'User picked %s' % fruit
user1594322's answer works but it requires you to put all of your controls in your wx.App, instead of wx.Frame. This will make recycling the code harder.
My solution involves define a "PassBack" variable when defining your init function. (similar to "parent" variable, but it is normally used already when initiating a wx.Frame)
From my code:
class MyApp(wx.App):
def __init__ (self, parent=None, size=(500,700)):
wx.App.__init__(self, False)
self.frame = MyFrame(parent, -1, passBack=self) #Pass this app in
self.outputFromFrame = "" #The output from my frame
def getOutput(self):
self.frame.Show()
self.MainLoop()
return self.outputFromFrame
and for the frame class:
class MyFrame(wx.Frame):
def __init__(self, parent, ID, passBack, title="My Frame"):
wx.Frame.__init__(self, parent, ID, title, size=(500, 700))
self.passBack = passBack #this will be used to pass back variables/objects
and somewhere during the execution of MyFrame
self.passBack.outputFromFrame = "Hello"
so all in all, to get a string from an application
app = MyApp()
val = app.getOutput()
#Proceed to do something with val
Check this answer on comp.lang.python: Linkie
I don't think a wxFrame can return a value since it is not modal. If you don't need to use a wxFrame, then a modal dialog could work for you. If you really need a frame, I'd consider using a custom event.
It would go something like this:
(1) User clicks to close the wxFrame
(2) You override OnClose (or something like that) to pop up a dialog to ask the user a question
(3) Create and post the custom event
(4) Close the wxFrame
(5) Some other code processes your custom event
I think I just had the same problem as you. Instead of making that popup a frame, I made it a dialog instead. I made a custom dialog by inheriting a wx.dialog instead of a wx.frame. Then you can utilize the code that joaquin posted above. You check the return value of the dialog to see what was entered. This can be done by storing the value of the textctrl when the user clicks ok into a local variable. Then before it's destroyed, you get that value somehow.
The custom dialog section of this site helped me out greatly.
http://zetcode.com/wxpython/dialogs/