WxPython - trigger checkbox event while setting its value in the code - python

Consider the following piece of code:
import wx
class MyFrame(wx.Frame):
def __init__(self, *args, **kwds):
wx.Frame.__init__(self, *args, **kwds)
self.cb1 = wx.CheckBox(self, -1, "CheckBox 1")
self.cb2 = wx.CheckBox(self, -1, "CheckBox 2")
self.cb3 = wx.CheckBox(self, -1, "CheckBox 3")
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.cb1, 0, wx.ADJUST_MINSIZE, 0)
sizer.Add(self.cb2, 0, wx.ADJUST_MINSIZE, 0)
sizer.Add(self.cb3, 0, wx.ADJUST_MINSIZE, 0)
self.SetSizer(sizer)
self.Layout()
self.Bind(wx.EVT_CHECKBOX, self.OnCb1, self.cb1)
self.Bind(wx.EVT_CHECKBOX, self.OnCb2, self.cb2)
def OnCb1(self, evt):
self.cb2.SetValue(evt.IsChecked())
def OnCb2(self, evt):
self.cb3.SetValue(evt.IsChecked())
if __name__ == "__main__":
app = wx.PySimpleApp(0)
frame = MyFrame(None, -1, "")
app.SetTopWindow(frame)
frame.Show()
app.MainLoop()
Here I have 3 checkboxes bound together, so cb2 gets checked when cb1 does and cb3 gets checked when cb2 does. However, when I set the value of cb2 in OnCb1 routine, the cb2 checkbox event is not triggered, and cb3 checkbox remains unchecked. So I'd like to find a way to trigger somehow cb2 event manually to check all 3 boxes at once when checking only cb1. I'd be very grateful if anyone gives me a hint.

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)

I couldn't adopt nmz787's code directly, and had to hack around a bit, but I finally got it working for
changing the state of a checkbox and
having the checkbox receive the event,
just as if the user clicked on it.
I thought I'd post my code in case others are stuck trying to get this to work too. Since it only depended on the checkbox and not on which control was calling it I factored it out as an independent "top-level" function, not as a method on any class.
def toggleCheckBox(cb):
evt = wx.CommandEvent(commandType=wx.EVT_CHECKBOX.typeId)
evt.SetEventObject(cb)
cb.SetValue( not cb.GetValue())
wx.PostEvent(cb, evt)
I have no idea why the CommandEvent constructor required a keyword, or if there's a more sensible, robust way to get the typeId required, but this worked for me.

I'm not experienced with wxPython so I can't give you a specific example, but I do know that setting the value programmatically will not trigger command events for the widgets. My assumption is that you will have to manually post the event for cb2 after you set its value. You can review a similar question here: wxPython: Calling an event manually
What I might suggest is to subclass the wx.CheckBox and create a SetValueWithEvent() or similar method that will both call SetValue, and post a wx. EVT_CHECKBOX event.
PyQt has similar situations where signals may or may not be emitted when programmatically setting values on a widget. They will sometimes give you more than one signal that you can listen for to accomodate either way. Unfortunately, based only on my limited exposure to wxPython examples, I think its a lot more primitive, and a bit less pythonic. So you seem to have to do things yourself a little more often.

Related

Global event handlers in wxPython (Phoenix)

I'm having a problem trying to handle focus events in wxPython 3, the phoenix fork.
I'm trying to clear the value in a TextCtrl when it's focused if it still has its default value, and then restore that default value on leaving focus if the user hasn't entered a different value.
This is the handler I have:
def on_enter(self, event):
control = wx.Window.FindFocus()
if control.Id in TEXT_MAPPING.keys():
if control.Value == TEXT_MAPPING[control.Id]:
control.SetValue('')
exit_control = event.GetWindow()
if exit_control.Id in (TEXT_MAPPING.keys()):
if not exit_control.GetValue():
exit_control.SetValue(TEXT_MAPPING[exit_control.Id])
event.Skip()
The handler itself works fine, though it's a little clunky. My issue is that I would like to bind this on a global level, such that whenever any wx.EVT_SET_FOCUS event is fired, I can have it be handled by this function.
I found this:
import wx
class MyApp(wx.App):
def FilterEvent(self, evt):
print evt
return -1
app = MyApp(redirect=False)
app.SetCallFilterEvent(True)
frm = wx.Frame(None, title='Hello')
frm.Show()
app.MainLoop()
but I'm confused on how I would pass the event down to the children of MyApp.
What you can do is bind focus kill and focus select to functions, like this:
self.user_text.Bind(wx.EVT_SET_FOCUS, self.on_focus_username)
self.user_text.Bind(wx.EVT_KILL_FOCUS, self.on_deselect_username)
where self is the panel where the TextCtrl "user_text" is located".
The functions might look like this:
def on_focus_username(self, event):
if self.user_text.GetForegroundColour() != wx.BLACK:
self.user_text.SetForegroundColour(wx.BLACK)
# Clear text when clicked/focused
self.user_text.Clear()
def on_deselect_username(self, event):
if self.user_text.GetLineLength(0) is 0:
self.user_text.SetForegroundColour(wx.Colour(192, 192, 192))
# Put a default message when unclicked/focused away
self.user_text.SetValue("Enter Username")
Hope this helped, if I didn't answer something clearly/correctly let me know.

How to add hyperlinks in the help of a wxpython app?

I'm implementing the help menu of an App done in wxPython. By now, I'm using a txt file opened in a frame. I would like to have hyperlinks in the help text in order to open other txt files in the same frame. However, I don't know how to do this. I don't even know if this is the most elegant way to implement a help menu. Any suggestion will be very useful.
Below you can find part of the code I'm using (you will need a txt file called "Help_Main_App.txt"):
import wx
class Help_Frame(wx.Frame):
title = "Help, I need somebody, help..."
def __init__(self):
wx.Frame.__init__(self, wx.GetApp().TopWindow, title=self.title, size=(450,500))
self.CreateStatusBar()
panel = wx.Panel(self, wx.ID_ANY)
panel.SetBackgroundColour('#ededed')
self.Centre()
vBox = wx.BoxSizer(wx.VERTICAL)
hBox = wx.BoxSizer(wx.HORIZONTAL)
self.textbox = wx.TextCtrl(panel, style=wx.TE_MULTILINE, size=(-1, 295))
hBox.Add(self.textbox, 1, flag=wx.EXPAND)
vBox.Add(hBox, flag=wx.EXPAND | wx.LEFT | wx.RIGHT, border=10)
panel.SetSizer(hBox)
defaultdir, filename = './', 'Help_Main_App.txt'
self.filePath = '/'.join((defaultdir, filename))
self.textbox.LoadFile(self.filePath)
self.textbox.Disable()
class Main_Window(wx.Frame):
def __init__(self, parent, title):
#wx.Frame.__init__(self, parent, title = title, pos = (0, 0), size = wx.DisplaySize())
wx.Frame.__init__(self, parent, title=title, size=(1000,780))
self.Center()
# Setting up the menu.
filemenu = wx.Menu()
helpmenu = wx.Menu()
menuExit = filemenu.Append(wx.ID_EXIT,"&Exit"," Close window and exit program")
menuHelp = helpmenu.Append(wx.ID_HELP, "&Help"," Help of this program")
# Creating the menubar.
menuBar = wx.MenuBar()
menuBar.Append(filemenu,"&File") # Adding the "filemenu" to the MenuBar
menuBar.Append(helpmenu,"&Help") # Adding the "helpmenu" to the MenuBar
self.SetMenuBar(menuBar) # Adding the MenuBar to the Frame content.
# Set event handlers
self.Bind(wx.EVT_MENU, self.OnHelp, menuHelp)
self.Bind(wx.EVT_MENU, self.OnExit, menuExit)
def OnHelp(self,e):
Help_Frame().Show()
def OnExit(self,e):
self.Close(True) # Close the frame.
def main():
app = wx.App(False)
frame = Main_Window(None, "Main App")
frame.Show()
app.MainLoop()
if __name__ == "__main__" :
main()
I recommend using an HTMLWindow for something simple like that. It can handle only simple HTML, so don't try to make a website with it as HTMLWindow doesn't support CSS or javascript.
I wrote a simple About box using it. You can read about it here:
http://www.blog.pythonlibrary.org/2008/06/11/wxpython-creating-an-about-box/
The basic idea is to subclass HTMLWindow and override its OnLinkClicked method. Then you can use Python's webbrowser to open the user's default browser. Or you can try using subprocess, although that will be a lot less likely to work unless you always know what is installed on your target machines.
Further to Mikes answer if you are able to use wxPython 2.9.4 or above you can consider using the more advanced html2 webview which does support CSS and javascript. Using this you could make the help as a simple website that can be viewed in program.
http://wxpython.org/Phoenix/docs/html/html2.WebView.html
Its also worth mentioning that if (for some strange reason) you don't want to work with you could achieve a similar outcome with a StyledTxtCtrl.
Late to the party but just for the sake of completeness (seeing that the OP's code was using wx.TextCtrl to show the help text), here is an example on how to add and launch hyperlinks using wx.TextCtrl (I have attached any explanations on the code comments):
class HelpDialog(wx.Dialog):
"""Help Dialog."""
def __init__(self, parent, title, style):
"""Init."""
wx.Dialog.__init__(self, parent=parent, id=wx.ID_ANY,
title=title, pos=wx.DefaultPosition, size=wx.DefaultSize, style=style)
# We need the 'wx.TE_AUTO_URL' style set.
self.help = wx.TextCtrl(self, wx.ID_ANY, '', DPOS, DSIZE,
wx.TE_AUTO_URL|wx.TE_MULTILINE|wx.TE_READONLY|wx.TE_RICH2|wx.TE_WORDWRAP)
# Events - this is the interesting part,
# we catch the mouse on hovering the hyperlink:
self.help.Bind(wx.EVT_TEXT_URL, self.openHlpUrl)
# Show dialog
self.ShowModal()
def openHlpUrl(self, event):
"""Open help URL."""
# We get the starting and ending points on
# the text stored in our ctrl from this event
# and we slice it:
url = self.help.GetValue()[event.GetURLStart():event.GetURLEnd()]
# We want to capture the left up mouse event
# when hovering on the hyperlink:
if event.MouseEvent.LeftDown():
# Let's be wxpythion native and launch the browser this way:
wx.LaunchDefaultBrowser(url)

Invoking a wxPython EVT_BUTTON event programmatically

I've seen another question regarding this topic, but I couldn't quite get the information to work for me, so I thought I'd give my specifics - I suspect I'm just being short-sighted.
I'm trying to exercise my GUI from a test framework, which involves manually invoking an event (in this case a button press) within a test script. So far, in addition to other irrelevant guff, I have:
# In GUI class:
self.button_1 = wx.Button(self, id=wx.ID_ANY, label="Button 1")
self.button_1.Bind(wx.EVT_BUTTON, self.button_1)
# In GUI Test class:
event = wx.PyCommandEvent(X, Y)
wx.PostEvent(get_gui_instance(), event)
My problem is that I don't know what X and Y should be (assuming that the rest is ok). Any help is greatly appreciated.
btnInfo = wx.Button(self,-1,"Some Button")
evt = wx.PyCommandEvent(wx.EVT_BUTTON.typeId,btnInfo.GetId())
wx.PostEvent(self, evt) #attach event to self ... alternatively maybe attach to btnInfo
should work
So it turns out that because I've re-jigged my GUI to run in a worker thread from GUI Test, I can communicate with it directly. I should have realised this earlier, but nonetheless the result is that I don't need to bother with posting events from GUI Test to GUI since they're running in the same process.
Instead, I can now call the effects of events directly. For example, I can call on_button_press(), bypassing the actual clicking of the button, which would normally fire off the event in wxPython. This allows me to simulate user interaction and test workflows and input ranges, which is exactly what I wanted to do.
Of course, this only works because I'm running my GUI in the same process as the test suite. Posting events seems to be the way forward otherwise, and in answer to my own question, it seems custom events are the way to invoke button presses cross-process. This implies the use of some sort of "test agent" within the GUI to handle those events that are specific for testing.
import wx
class MessageDialog(wx.Dialog):
def __init__(self, message, title, tiempo = 2000):
style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER
super(MessageDialog, self).__init__(None, -1, title, style=style)
text = wx.StaticText(self, -1, message)
fuente = wx.Font(pointSize = 20,
family = wx.DEFAULT,
style = wx.NORMAL,
weight = wx.LIGHT,
underline=False,
faceName="",
encoding=wx.FONTENCODING_DEFAULT)
text.SetFont(fuente)
self.ok = wx.Button(self, wx.ID_OK, "OK")
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(text,0,wx.ALIGN_CENTER_HORIZONTAL|wx.ALIGN_CENTER_VERTICAL,5)
sizer.Add(self.ok, 0, wx.EXPAND|wx.ALL, 5)
self.SetSizerAndFit(sizer)
color = "WHEAT"
self.SetBackgroundColour(color)
self.Center()
self.Refresh()
wx.FutureCall(tiempo,self.salir_ok)
def salir_ok(self):
par_btn = getattr(self, "ok")
evt = wx.PyCommandEvent(wx.EVT_BUTTON.typeId, par_btn.GetId())
wx.PostEvent(self, evt)
return
if __name__ == '__main__':
app = wx.App()
dialog = MessageDialog( 'Teclee el nombre del proveedor', 'Proveedor')
dialog.ShowModal()
dialog.Destroy()
app.MainLoop()

How to set the value of a wx.combobox by posting an event

NOTE: This appears to be an OSX specific problem.
The code below demonstrates the problem I am running into. I am creating a wx.ComboBox and trying to mimic it's functionality for testing purposes by posting a wxEVT_COMMAND_COMBOBOX_SELECTED event... this event strangely works fine for wx.Choice, but it doesn't do anything to the ComboBox.
There doesn't appear to be a different event that I can post to the combobox, but maybe I'm missing something.
I'm running this code on Python 2.5 on a Mac OSX 10.5.8
import wx
app = wx.PySimpleApp()
def on_btn(evt):
event = wx.CommandEvent(wx.wxEVT_COMMAND_COMBOBOX_SELECTED,combobox.Id)
event.SetEventObject(combobox)
event.SetInt(1)
event.SetString('bar')
combobox.Command(event)
app.ProcessPendingEvents()
frame = wx.Frame(None)
panel = wx.Panel(frame, -1)
# This doesn't work
combobox = wx.ComboBox(panel, -1, choices=['foo','bar'])
# This works
#combobox = wx.Choice(panel, -1, choices=['foo','bar'])
combobox.SetSelection(0)
btn = wx.Button(panel, -1, 'asdf')
btn.Bind(wx.EVT_BUTTON, on_btn)
sz = wx.BoxSizer()
sz.Add(combobox)
sz.Add(btn)
panel.SetSizer(sz)
frame.Show()
app.MainLoop()
UPDATE: I hooked up the combobox to a handler of wx.EVT_COMBOBOX to see what event type is being caught there and I got the id 10016 which matches wxEVT_COMMAND_COMBOBOX_SELECTED... so generating this command event certainly SHOULD cause the ComboBox to update.
This seems to be a specific bug from OSX.
Both alternatives work perfect in windowsXP.
I think this fixes it, or at least points the way towards a more complete fix.
Edit: you can use PyCommandEvent if you prefer, as well as using SetInt and SetString to put more information in the Event if needed. But it is necessary, as far as I can tell, to set the selection of the combobox as well.
import wx
app = wx.PySimpleApp()
def on_btn(evt):
combobox.Selection=1
wx.PostEvent(combobox, wx.CommandEvent(wx.wxEVT_COMMAND_COMBOBOX_SELECTED))
print "foo"
def on_select(evt):
print "selected", combobox.Selection
frame = wx.Frame(None)
panel = wx.Panel(frame, -1)
# This doesn't work
combobox = wx.ComboBox(panel, -1, choices=['foo','bar'])
# This works
# combobox = wx.Choice(panel, -1, choices=['foo','bar'])
combobox.SetSelection(0)
btn = wx.Button(panel, -1, 'asdf')
btn.Bind(wx.EVT_BUTTON, on_btn)
combobox.Bind(wx.EVT_COMBOBOX, on_select)
sz = wx.BoxSizer()
sz.Add(combobox)
sz.Add(btn)
panel.SetSizer(sz)
frame.Show()
app.MainLoop()

wxPython: Handling events in a widget that is inside a notebook

I have a wxPython notebook, in this case a wx.aui.AuiNotebook. (but this problem has happened with other kinds of notebooks as well.) In my notebook I have a widget, in this case a subclass of ScrolledPanel, for which I am trying to do some custom event handling (for wx.EVT_KEY_DOWN). However, the events are not being handled. I checked my code outside of the notebook, and the event handling works, but when I put my widget in the notebook, the event handler doesn't seem to get invoked when the event happens.
Does the notebook somehow block the event? How do I solve this?
I tried reproducing your problem but it worked fine for me. The only thing I can think of is that there is one of your classes that also binds to wx.EVT_KEY_DOWN and doesn't call wx.Event.Skip() in its callback. That would prevent further handling of the event. If your scrolled panel happens to be downstream of such an object in the sequence of event handlers it will never see the event.
For reference, here's an example that worked for me (on Windows). Is what you're doing much different than this?
import wx
import wx.aui, wx.lib.scrolledpanel
class AppFrame(wx.Frame):
def __init__(self, *args, **kwds):
wx.Frame.__init__(self, *args, **kwds)
# The notebook
self.nb = wx.aui.AuiNotebook(self)
# Create a scrolled panel
panel = wx.lib.scrolledpanel.ScrolledPanel(self, -1)
panel.SetupScrolling()
self.add_panel(panel, 'Scrolled Panel')
# Create a normal panel
panel = wx.Panel(self, -1)
self.add_panel(panel, 'Simple Panel')
# Set the notebook on the frame
self.sizer = wx.BoxSizer()
self.sizer.Add(self.nb, 1, wx.EXPAND)
self.SetSizer(self.sizer)
# Status bar to display the key code of what was typed
self.sb = self.CreateStatusBar()
def add_panel(self, panel, name):
panel.Bind(wx.EVT_KEY_DOWN, self.on_key)
self.nb.AddPage(panel, name)
def on_key(self, event):
self.sb.SetStatusText("key: %d [%d]" % (event.GetKeyCode(), event.GetTimestamp()))
event.Skip()
class TestApp(wx.App):
def OnInit(self):
frame = AppFrame(None, -1, 'Click on a panel and hit a key')
frame.Show()
self.SetTopWindow(frame)
return 1
if __name__ == "__main__":
app = TestApp(0)
app.MainLoop()

Categories