I'm trying to update some static text using a timer and the output of a function.
The code is here: code.
I know very little about wxPython, it's one of the many things that I just don't get and this is maddening, if I print the output of apper to console it works perfectly, all I want to do is have what prints out to the console applied to the text.
What am I doing wrong?
Timers can be a pain to use, an easier way is to use the functions wx.CallAfter and/or wx.CallLater - also these functions are thread-safe and can be used to invoke functions on the GUI thread from other worker threads. Here is a sample...
import random
import wx
class Frame(wx.Frame):
def __init__(self):
super(Frame, self).__init__(None)
self.SetTitle('Title')
panel = wx.Panel(self)
style = wx.ALIGN_CENTRE | wx.ST_NO_AUTORESIZE
self.text = wx.StaticText(panel, style=style)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.AddStretchSpacer(1)
sizer.Add(self.text, 0, wx.EXPAND)
sizer.AddStretchSpacer(1)
panel.SetSizer(sizer)
self.on_timer()
def on_timer(self):
self.text.SetLabel(str(random.randint(0, 100)))
wx.CallLater(1000, self.on_timer)
if __name__ == '__main__':
app = wx.App()
frame = Frame()
frame.Show()
app.MainLoop()
Related
The following Python code runs fine on windows, but cause a segmentation fault on
osx. Any suggestions why? It does not make a difference to use CallAfter...
import wx
class myFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None)
self.sizer = wx.BoxSizer(wx.VERTICAL)
self.sizer.Add(wx.StaticText(self, -1, 'Static text'))
self.sizer.Add(wx.Button(self, -1, 'Button'))
self.SetSizer(self.sizer)
self.Bind(wx.EVT_BUTTON, self.on_button)
def on_button(self, event):
self.sizer.Clear()
self.DestroyChildren()
app = wx.App()
frame = myFrame()
frame.Show()
app.MainLoop()
It is because there are still system messages pending for the destroyed widgets, (mouse motion in this case) as well as the possibility that the code that is run after the return from the event handler will try to use the destroyed widgets (calling methods or accessing attributes.)
Using CallAfter to delay the destruction does solve the problem for me, which version of wxPython are you using? If this doesn't work for you then you may want to try using wx.CallLater instead, with a small timeout value.
import wx
class myFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None)
self.sizer = wx.BoxSizer(wx.VERTICAL)
self.sizer.Add(wx.StaticText(self, -1, 'Static text'))
self.sizer.Add(wx.Button(self, -1, 'Button'))
self.SetSizer(self.sizer)
self.Bind(wx.EVT_BUTTON, self.on_button)
def on_button(self, event):
wx.CallAfter(self.doit)
def doit(self):
self.sizer.Clear()
self.DestroyChildren()
app = wx.App()
frame = myFrame()
frame.Show()
app.MainLoop()
i am trying to develop python GUI to access webpages. below example is working fine. but i need to pass user credentials(username/password) with in this code.
i dont want to click on that button. just i need to fill text boxes in login page
import wx
import wx.html2
class MyBrowser(wx.Dialog):
def __init__(self, *args, **kwds):
wx.Dialog.__init__(self, *args, **kwds)
sizer = wx.BoxSizer(wx.VERTICAL)
self.browser = wx.html2.WebView.New(self)
self.browser.LoadURL("http://wiki.python.org/moin/GuiProgramming?action=login")
sizer.Add(self.browser, 1, wx.EXPAND, 10)
self.SetSizer(sizer)
self.SetSize((700, 700))
if __name__ == '__main__':
app = wx.App()
dialog = MyBrowser(None, -1)
dialog.Show()
app.MainLoop()
The "Use javascript" answer is certainly helpful, but with recent versions of wxPython anyway, it won't run unless wx.html2.EVT_WEB_VIEW_LOADED is changed to wx.html2.EVT_WEBVIEW_LOADED ("WEB_VIEW changed to "WEBVIEW").
Use javascript. Simple sample code below.
import wx
import wx.html2
class MyBrowser(wx.Dialog):
def __init__(self, *args, **kwds):
wx.Dialog.__init__(self, *args, **kwds)
sizer = wx.BoxSizer(wx.VERTICAL)
self.browser = wx.html2.WebView.New(self)
self.browser.LoadURL("http://wiki.python.org/moin/GuiProgramming?action=login")
sizer.Add(self.browser, 1, wx.EXPAND, 10)
self.SetSizer(sizer)
self.SetSize((700, 700))
# We have to bind an event so the javascript is only run once the page
# is loaded.
self.Bind(wx.html2.EVT_WEB_VIEW_LOADED, self.OnPageLoaded,
self.browser)
def OnPageLoaded(self, evt):
self.browser.RunScript("""
// There are probably better ways to get the elements you
// want, but this works.
document.getElementsByName('name')[0].value="hist";
document.getElementsByName('password')[0].value="bar";
document.getElementById('openididentifier').value="ident";
// If you want to submit the form you can use something like
//document.getElementsByName('login')[1].click()
""")
# And you probably want to unbind the event here
self.Bind(wx.html2.EVT_WEB_VIEW_LOADED, None,
self.browser)
if __name__ == '__main__':
app = wx.App()
dialog = MyBrowser(None, -1)
dialog.Show()
app.MainLoop()
I would check out Selenium. It's an open source web navigating automation tool which has developed an awesome module for python. I've used it to automate logging into multiple different websites and you could easily slap a wx GUI on top of it.
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()
I've boiled my problem down to the example code shown in this post. Note that I'm not calling app.MainLoop() because this isn't an interactive window; I want it to pop up at the beginning, show some progress bars while work happens, and disappear when complete.
My (limited) understanding of wxPython and wx.Yield() led me to believe that calling wx.Yield() after some UI work would flush those changes to the display. That is not occurring -- when I run this script, there is a gray box where "Hello World" should be.
What am I doing wrong?
import wx
class MyFrame(wx.Frame):
def __init__(self, parent, id):
wx.Frame.__init__(self, parent, id, size=(400,400))
self.panel = wx.Panel(self, -1)
wx.StaticText(self.panel, -1, "Hello World", (20,20))
wx.Yield()
class MyApp(wx.App):
def OnInit(self):
self.frame = MyFrame(None, -1)
self.frame.Show(True)
self.SetTopWindow(self.frame)
return True
def run():
app = MyApp(redirect=False)
import time; time.sleep(5)
run()
You need to be yielding or updating on a regular basis, so that when your OS/window manager sends repaint messages to your app, it can handle them. I am not 100% sure about wxPython as I haven't used it recently but I don't think you can do what you want without the main loop to handle the messages appropriately.
You might find something useful here about threading the main loop, however (as well as explanation of why the main loop is important): http://wiki.wxpython.org/MainLoopAsThread
instead of wx.Yield()
just call self.Update()
Without the MainLoop no events will be fired and also .Refresh will not work.
I guess wxSplashscreen may be what you are looking for. Example: http://wiki.wxpython.org/SplashScreen
Not that it will do the original poster any good after all this time but wx.Yield() would have done the job. It just needs to be in the right place as does the self.Show()
The following outputs a progress bar which gets updated.
import wx
import time
class MyFrame(wx.Frame):
def __init__(self, parent, id):
wx.Frame.__init__(self, parent, id, size=(290,200))
self.panel = wx.Panel(self, -1)
wx.StaticText(self.panel, -1, "Hello World", (20,20))
self.gauge = wx.Gauge(self.panel, -1, 50, pos=(20,50), size=(250, 20))
self.Show()
n = 0
while n < 50:
n = n+1
self.gauge.SetValue(n)
wx.Yield()
time.sleep(1)
class MyApp(wx.App):
def OnInit(self):
self.frame = MyFrame(None, -1)
self.SetTopWindow(self.frame)
return True
def run():
app = MyApp()
run()
Is it possible to run a wxApp from another wxApp?
I am trying to simply call a program I wrote (called DataDeck) from a method of another wxApp, like it was a plugin.
something like:
def on_datadeck_btn_click(self, event):
import datadeck.main
datadeck.main.run()
event.Skip()
where datadeck.main.run() is a classic start of a wxApp:
def run():
app = DataDeck(0)
app.SetAppName("DataDeck")
app.MainLoop()
Right now, it correctly opens DataDeck the first time and it works, but it won't reopen DataDeck a second time after I close it. This would freeze everything.
Update: based on #Mike Driscoll answer, I documented myself more and came to the following solution:
I added an "entry point" in datadeck
def run_as_plugin():
#[do some stuff related to XRC layouts and sysout redirection]
MainGUI = datadeck.gui.maingui.MainGUI()
Where the constructor of MainGUI() automatically shows the wxFrame. Now my application behaves like it was a component of the caller wxApp.
Therefore, I modify the application method as follows:
def on_datadeck_btn_click(self, event):
import datadeck.main
datadeck.main.run_as_plugin()
event.Skip()
It was very simple, indeed! I just had to modify my objects that deal with stdout redirection (not part of this question, I omit the details), and everything worked fine.
There should only be on wx.App. From what I've read online, you can't have two wx.App objects running in one script. You could probably do it using the subprocess module to open a new process though. Take a look at Editra to see some examples for how to do plugins. It is included with wxPython or you can download it separately.
It is perfectly feasible. Not sure why it doesnt work for you.
This example works perfectly:
--main.py--
import wx
class MainFrame(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent, title='Main', size=(353,270))
button= wx.Button(self, -1, 'call app', pos=(10,10), size=(-1,30))
self.Bind(wx.EVT_BUTTON, self.capp, button)
def capp(self, event):
import datadeck
datadeck.run()
if __name__ == '__main__':
app = wx.App(0)
frame = MainFrame(None)
frame.Show()
app.MainLoop()
--datadeck.py--
import wx
class DDFrame(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent, title='DDFrame', size=(353,270))
button = wx.Button(self, -1, 'print something', pos=(100,100), size=(-1,30))
self.Bind(wx.EVT_BUTTON, self.say_hello, button)
def say_hello(self, event):
print 'something'
class DataDeck(wx.App):
def OnInit(self):
frame = DDFrame(None)
frame.Show()
return True
def run():
app = DataDeck(1)
app.SetAppName("DataDeck")
app.MainLoop()
if you press the 'call app' button you get the new frame open. And you can open as many as you want.
Created aplications/frames are independent of each other. You can close any of them without affecting the others. And the system doesnt freeze.