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()
Related
i am trying to make a windows 10 toast notification that will run code if its clicked but my code only shows the notification and gives me an error when i click it
import os
import wx
import wx.adv
class MyApp(wx.App):
def OnInit(self):
sTitle = 'test'
sMsg = 'test'
nmsg = wx.adv.NotificationMessage(title=sTitle, message=sMsg)
nmsg.SetFlags(wx.ICON_INFORMATION)
nmsg.Show(timeout=wx.adv.NotificationMessage.Timeout_Auto)
self.Bind(wx.EVT_NOTIFICATION_MESSAGE_CLICK, self.notifclicked)
return True
def _notifclicked(self, evt):
print("notification has been clicked")
app = MyApp()
app.MainLoop()
error code : AttributeError: module 'wx' has no attribute 'EVT_NOTIFICATION_MESSAGE_CLICK'
Without wishing to say definitely that NotificationMessage is unfinished business, I'm going to suggest it.
I suspect that it is based on notify and notify2 which purport to support action callbacks but don't.
They should be relying on Dbus sending stuff back but it doesn't look like it does or you have to jump through hoops to make it happen. (Dbus MainLoop setup for example)
I have decided to adapt your code slightly, just to show how to add action buttons, although they are nothing more than eye candy and to show how I think the event callbacks should work, if it ever comes to reality.
Of course, if it currently does work and I haven't worked out how to do it, I'll happily eat these words. I code exclusively on Linux, so there's that as a caveat.
import wx
import wx.adv
Act_Id_1 = wx.NewIdRef()
Act_Id_2 = wx.NewIdRef()
class MyFrame(wx.Frame):
def __init__(self, parent=None, id=wx.ID_ANY, title="", size=(360,100)):
super(MyFrame, self).__init__(parent, id, title, size)
self.m_no = 0
self.panel = wx.Panel(self)
self.Mbutton = wx.Button(self.panel, wx.ID_ANY, label="Fire off a message", pos=(10,10))
self.Bind(wx.EVT_BUTTON, self.OnFire, self.Mbutton)
#self.Bind(wx.adv.EVT_NOTIFICATION_MESSAGE_CLICK, self._notifclicked)
#self.Bind(wx.adv.EVT_NOTIFICATION_MESSAGE_DISMISSED, self._notifdismissed)
#self.Bind(wx.adv.EVT_NOTIFICATION_MESSAGE_ACTION, self._notifaction, id=Act_Id_1)
self.Show()
def OnFire(self, event):
self.m_no +=1
sTitle = 'Test heading'
sMsg = 'Test message No '
nmsg = wx.adv.NotificationMessage(title=sTitle, message=sMsg+str(self.m_no))
nmsg.SetFlags(wx.ICON_INFORMATION)
nmsg.AddAction(Act_Id_1, "Cancel")
nmsg.AddAction(Act_Id_2, "Hold")
nmsg.Show(timeout=10)
def _notifclicked(self, event):
print("notification has been clicked")
def _notifdismissed(self, event):
print("notification dismissed")
def _notifaction(self, event):
print("Action")
app = wx.App()
frame = MyFrame()
app.MainLoop()
I have a wxPython GUI, and I am attempting to use unittest to test some of my modal dialogs. I tried to follow the example given here (you have to scroll down to the bottom of the page): http://wiki.wxpython.org/Unit%20Testing%20with%20wxPython, but it does not work for me. It simply freezes in the middle.
I've adapted the code from the wiki to this:
btn_id = wx.NewId()
class MyDialog(wx.Dialog):
def __init__(self, parent):
wx.Dialog.__init__(self, parent, -1, 'Test')
self.btn = wx.Button(self, btn_id, label="OK!!")
self.btn.Bind(wx.EVT_BUTTON, self.close_dialog)
def close_dialog(self, event):
print 'close me'
class TestMyDialog(unittest.TestCase):
def setUp(self):
self.app = wx.App()
self.frame = wx.Frame(None)
self.frame.Show()
def tearDown(self):
wx.CallAfter(self.app.Exit)
self.app.MainLoop()
def testDialog(self):
def clickOK():
clickEvent = wx.CommandEvent(wx.wxEVT_COMMAND_BUTTON_CLICKED, btn_id)
self.dlg.ProcessEvent(clickEvent)
print 'finished clickOK'
wx.CallAfter(clickOK)
self.ShowDialog()
def ShowDialog(self):
self.dlg = MyDialog(self.frame)
self.dlg.ShowModal()
self.dlg.Destroy()
if __name__ == '__main__':
unittest.main()
To my understanding, what should happen is that ShowDialog is called, then gets 'stuck' on ShowModal, at which time clickOk should run (called by wx.CallAfter). This seems to happen, but for some reason the click event isn't actually processed, and the tests hangs. When I run MyDialog not in testing the event binding works fine and the dialog closes when the Ok button is clicked.
I shouldn't need app.mainloop() to be able to ProcessEvent, right? What is going on here?
Have a look at the unittests in Phoenix https://github.com/wxWidgets/Phoenix , look at test_dialog.py and the base staff in wtc.py
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'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()
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.