Updating wxpython progressbar from a non-main thread, but there is no error

996 views
Skip to first unread message

steve

unread,
Dec 17, 2015, 1:34:12 PM12/17/15
to wxPython-users

This wxpython code updates the progressbar window message from a non-main thread but it doesn't create a "only the main thread can process Windows messages" error.
Could you please explain how it updates the progressbar window message from a separate thread without any errors? Because sometimes I cannot update the progressbar from a separate thread, I get
"only the main thread can process Windows messages".
But in this example I don't get this error.
How???


import
wx import wx.lib.agw.pyprogress as PP import threading ID_ADD_BUTTON = 100 ID_ADD_BUTTON2 = 101 class MyForm(wx.Frame): def __init__(self): wx.Frame.__init__(self, None, wx.ID_ANY, "My Panel", size=(400,300)) self.panel = wx.Panel(self, wx.ID_ANY) self.mytext = wx.StaticText(self.panel, -1, "Start", size=(20,-1)) self.toolbar = wx.ToolBar(self, -1, style=wx.TB_FLAT|wx.BORDER_RAISED) self.toolbar.AddLabelTool(ID_ADD_BUTTON, 'Refresh', wx.ArtProvider.GetBitmap(wx.ART_PLUS)) self.toolbar.Bind(wx.EVT_TOOL, self.refreshButton, id=ID_ADD_BUTTON) self.toolbar.Realize() self.sizer = wx.BoxSizer(wx.VERTICAL) self.sizer.Add(self.toolbar, 0, wx.EXPAND) self.sizer.Add(self.panel, 1, wx.EXPAND) self.SetSizer(self.sizer) def refreshButton(self, event): self.myprogress = ProgressThread() self.toolbar2 = wx.ToolBar(self, -1, style=wx.TB_FLAT|wx.BORDER_RAISED) self.toolbar2.AddLabelTool(ID_ADD_BUTTON2, 'Print', wx.ArtProvider.GetBitmap(wx.ART_PLUS)) self.toolbar2.Bind(wx.EVT_TOOL, self.refreshButton, id=ID_ADD_BUTTON2) update = MyThread(self.mytext) self.toolbar2.Realize() self.myprogress.dlg.UpdatePulse("toolbar 2 is added") for i in xrange(5000): self.myprogress.dlg.UpdatePulse(str(i)) #print "toolbar 2 is added" self.sizer.Add(self.toolbar2, 0, wx.EXPAND) self.Layout() for i in xrange(8000, 12000): self.myprogress.dlg.UpdatePulse(str(i)) self.myprogress.keepGoing = False def refreshButton2(self, event): print "button 2 is pressed" class ProgressThread(threading.Thread): def __init__(self): threading.Thread.__init__(self) style = wx.PD_APP_MODAL self.dlg = PP.PyProgress(None, -1, "Updating Data ", "Please wait...", agwStyle=style) self.start() def run(self): self.dlg.SetGaugeProportion(20/100.0) self.dlg.SetGaugeSteps(50) self.dlg.SetGaugeBackground(wx.WHITE) self.dlg.SetFirstGradientColour(wx.WHITE) self.dlg.SetSecondGradientColour(wx.GREEN) self.keepGoing = True while self.keepGoing: wx.MilliSleep(30) try: self.dlg.UpdatePulse() except: pass self.dlg.Destroy() class MyThread(threading.Thread): def __init__(self, my_text): self.my_text= my_text threading.Thread.__init__(self) self.start() def run(self): for i in xrange(50000): self.my_text.SetLabel(str(i)) # Run the program if __name__ == "__main__": app = wx.PySimpleApp() frame = MyForm().Show() app.MainLoop()

Tim Roberts

unread,
Dec 17, 2015, 8:07:53 PM12/17/15
to wxpytho...@googlegroups.com
steve wrote:
This wxpython code updates the progressbar window message from a non-main thread but it doesn't create a "only the main thread can process Windows messages" error. 
Could you please explain how it updates the progressbar window message from a separate thread without any errors? Because sometimes I cannot update the progressbar from a separate thread, I get "only the main thread can process Windows messages".
But in this example I don't get this error. How???

Many of the methods you call do nothing except send a message to the window.  That CAN be done from another thread, because the message will be HANDLED in the main thread.
-- 
Tim Roberts, ti...@probo.com
Providenza & Boekelheide, Inc.

steve

unread,
Dec 19, 2015, 6:15:18 PM12/19/15
to wxPython-users
I don't understand. The separate thread is sending and processing in my example.

Tim Roberts

unread,
Dec 21, 2015, 2:46:51 AM12/21/15
to wxpytho...@googlegroups.com
On Dec 19, 2015, at 3:15 PM, steve <osloc...@gmail.com> wrote:
>
> I don't understand. The separate thread is sending and processing in my example.

Maybe. You’re calling UpdatePulse. i haven’t looked at the code for the PyProgress dialog, but remember that many of the wxPython controls are very thin wrappers around C controls, and those C controls often implement thing like UpdatePulse by just sending a window message to the control window. When a method is implemented that way, you automatically get switched to the main thread to handle the message that is sent. In such a wrapped control, the Python code doesn’t do anything to the underlying data, so it doesn’t matter what thread it’s on.

steve

unread,
Dec 21, 2015, 6:31:06 AM12/21/15
to wxPython-users
 
 
Please see this example below.
 
In this example, I have an initial panel on my frame, and a start button on it. When I press the button, a progress bar should be displayed on initial panel, then a new thread is started. This new thread creates a new panel and add a notebook with pages on it to new panel. At the same time the progress bar should be updated. After it is done, the initial panel with progress bar will be hidden, and new panel will be placed on the frame. But I can't get it work. Could you please help?
 
import sys
import wx
import wx.lib.agw.aui as agw_aui
import wx.lib.agw.flatnotebook as fnb
import threading
from wx.lib.buttons import ThemedGenBitmapTextButton
import wx.lib.agw.pygauge as PG

szflags = wx.EXPAND | wx.ALL
min_height = 50
height_ratio = 4
pborder = 10
lborder = 5

class NotebookPage(wx.Panel):
    def __init__(self, *args, **kwargs):
        wx.Panel.__init__(self, *args, **kwargs)

class Notebook(fnb.FlatNotebook):
    def __init__(self, *args, **kwargs):
        self.gauge = kwargs.pop('gauge', None)
        fnb.FlatNotebook.__init__(self, *args, **kwargs)
        self.SetBackgroundColour(wx.Colour(226,226,226))
        self.nbPages = {}
        pages = ["a","b","c"]
        for page in pages:
            self.nbPages[page] = NotebookPage(self, name='NotebookPage0')
            self.gauge.SetValue(self.gauge.GetValue()+10)

        for page in pages:
            self.AddPage(self.nbPages[page], page, True)

class MainPanel(wx.Panel):
    def __init__(self, *args, **kwargs):
        self.gauge = kwargs.pop('gauge', None)
        wx.Panel.__init__(self, *args, **kwargs)
        bookStyle = agw_aui.AUI_NB_DEFAULT_STYLE
        bookStyle &= ~(agw_aui.AUI_NB_CLOSE_ON_ACTIVE_TAB)       
        self.noteBook = Notebook(self, name='Notebook', agwStyle=fnb.FNB_NO_NAV_BUTTONS | fnb.FNB_NODRAG | fnb.FNB_NO_X_BUTTON |fnb.FNB_VC8|fnb.FNB_BOTTOM, gauge=self.gauge)
        self.gauge.SetValue(self.gauge.GetValue()+10)
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.noteBook, 1, szflags)
        self.SetSizer(sizer)
class InitialPanel(wx.Panel):
    def __init__(self, *args, **kwargs):
        wx.Panel.__init__(self, *args, **kwargs)
        self.font_box_title = wx.Font(12, wx.FONTFAMILY_DEFAULT, wx.NORMAL, wx.FONTWEIGHT_BOLD, underline=False, faceName="Microsoft Sans Serif")
        self.new_button = ThemedGenBitmapTextButton(self, -1, wx.ArtProvider.GetBitmap(wx.ART_PLUS), label="Start Gauge and add widgets", pos=(-1,-1), size=(320,128))
        self.new_button.SetFont(self.font_box_title)
        self.sizer = wx.BoxSizer(wx.VERTICAL)
        self.sizer.Add((20,150))
        self.sizer.Add(self.new_button, 0, wx.LEFT, 100)
        self.SetSizer(self.sizer)
    def bindStartButton(self, mypanel):
        self.Bind(wx.EVT_BUTTON, lambda event: self.StartButton(event, mypanel), self.new_button)
    def StartButton(self, event, panel):
        panel.do_it()
    def onOpen(self, event):
        self.create_pyprogress()
    def create_pyprogress(self):
        self.gauge = PG.PyGauge(self, -1, size=(400,40),style=wx.GA_HORIZONTAL)
        self.gauge.SetRange(100)
        self.gauge.SetBackgroundColour(wx.WHITE)
        self.gauge.SetBorderColor(wx.BLACK)
        self.keepGoing = True
        self.gauge.SetValue(self.gauge.GetValue()+10)
        self.sizer.Add((20,40))
        self.sizer.Add(self.gauge, 0, wx.LEFT, 100)
        self.Layout()
class MainFrame(wx.Frame):
    def __init__(self, *args, **kwargs):
        wx.Frame.__init__(self, *args, **kwargs)
        self.sizer = wx.BoxSizer(wx.VERTICAL)
        self.initial_panel = InitialPanel(self)
        self.initial_panel.bindStartButton(self)
        self.temp_panel = wx.Panel(self)
        self.temp_panel.Hide()
        self.sizer.Add(self.initial_panel, 1, szflags)
        self.SetSizer(self.sizer)
    def do_it(self):
        self.create_pyprogress()
        print "ok"
        self.updater = ProgressThread(self)
        self.Layout()
    def create_pyprogress(self):
        self.gauge = PG.PyGauge(self.initial_panel, -1, size=(300,30),style=wx.GA_HORIZONTAL)
        self.gauge.SetRange(100)
        self.gauge.SetBackgroundColour(wx.WHITE)
        self.gauge.SetBorderColor(wx.BLACK)
        self.initial_panel.sizer.Add((10,10))
        self.initial_panel.sizer.Add(self.gauge, 0, wx.LEFT, 10)
        self.initial_panel.Layout()
        self.initial_panel.Refresh()
        self.initial_panel.Layout()
        self.Layout()
    def layout_main(self):
        self.mainPanel = MainPanel(self.temp_panel, name='MainPanel',  gauge= self.gauge)
        self.Bind(wx.EVT_CLOSE, self.OnClose)
        self.sizer.Hide(self.initial_panel)
        self.sizer.Remove(self.initial_panel)
        self.sizer.Add(self.mainPanel, 1, szflags)
        self.mainPanel.Reparent(self)
        self.Layout()
    def onExit(self, event):
        """"""
        self.Close()
        sys.exit(1)
       
    def OnClose(self, event):
        self.Destroy()
        sys.exit(1)
class ProgressThread(threading.Thread):
    def __init__(self, panel):
        self.main_panel = panel
        threading.Thread.__init__(self)
        self.start()
    def run(self):
        self.main_panel.layout_main()
class App(wx.App):
    def OnInit(self):
        self.mainFrame = MainFrame(None,
            size=wx.Size(1000,700),
            title='MyFrame', name='MyFrame')
        wx.GetApp().SetTopWindow( self.mainFrame )
        self.mainFrame.Show()
        self.mainFrame.SetSize((500,400))
        return True
       
def main(*args, **kwargs):
    global app
    app = App()
    app.MainLoop()
if __name__ == "__main__":
    app = None
    main()
       

Tim Roberts

unread,
Dec 21, 2015, 12:57:37 PM12/21/15
to wxpytho...@googlegroups.com
steve wrote:
>
>
> Please see this example below.
>
> In this example, I have an initial panel on my frame, and a start
> button on it. When I press the button, a progress bar should be
> displayed on initial panel, then a new thread is started. This new
> thread creates a new panel and add a notebook with pages on it to new
> panel. At the same time the progress bar should be updated. After it
> is done, the initial panel with progress bar will be hidden, and new
> panel will be placed on the frame. But I can't get it work. Could you
> please help?

Your current plan is hopeless. Here is the important point you're
missing. Message queues are associated with threads, NOT with
processes. A window is owned by the thread that created it, and
messages for that window get sent to the owning thread. The reason you
are asked to do all of your I/O in the main thread is because that way,
all of the messages get sent to the main thread's queue, so you only
need one message loop to handle them.

In your case, when you create a new window in a secondary thread, the
messages for that window are sent to the message queue for your
secondary thread, but you don't have a message loop in your secondary
thread, so those messages are going to pile up forever, unhandled.

You need to get in the habit of using something like wx.CallAfter so
that your UI manipulation all happens in the main thread.

--

steve

unread,
Dec 22, 2015, 11:23:19 AM12/22/15
to wxPython-users
I still don't get it.
 
Main thread is creating the new panel, and adding notebook on it. While, the separate thread is updating the progress bar on main panel at the same time.
You are saying that all GUI updates should be done my main thread, but it is impossible. That way the GUI will become irresponsive and progress bar won't be updated.
 
Please see the image below.
 
1- At first there is a main frame, and initial panel on it. There is a button on the initial panel.
2- When user pushes the button, a progress bar is displayed below the button. Then a thread is created, this new thread creates a new panel. But the new panel is not placed on main frame yet. The new thread also creates a notebook and adds 3 pages on it.
3- As each page is added, progres bar is updated. The user only sees the initial panel and the progress bar. The updates to progress bar is presented to the user. He doesn't see the new panel and notebook yet.
4- When all 3 pages are added to notebook, the inital panel is hidden, and new panel is added on the main frame.
 
 

Michael Salin

unread,
Dec 22, 2015, 11:42:51 AM12/22/15
to wxPython-users
Sorry, if someone has told in before.

First, the general rule is that you can touch any GUI items only from the main thread. (To be more precise: there is a thread, where the wxApp endless loop is running, this loop triggers callback aka events in the same thread, and only these calback can do something with GUI.)  Otherwise the magic behind it will be broken. No mater how it will fall, but it will fall.

Second, there is a nice tutorial here: http://wiki.wxpython.org/LongRunningTasks

Third, I use wxCallAfter

With best, Mike

steve

unread,
Dec 22, 2015, 12:07:31 PM12/22/15
to wxPython-users
I don't understand, you are calling wxCallAfter in the separate thread but it uses the main thread to update GUI?
 
In my example, how will progress bar know that a page is added to new panel? Threads can't talk to each other.

Torsten

unread,
Dec 22, 2015, 1:36:57 PM12/22/15
to wxPython-users
Hello Steve,

i've reworked your sample app to to what i think you would it to do. I'm no expert and i'm sure there are better ways to do it.  :)


Torsten
GaugeThread.py

Chris Barker

unread,
Dec 22, 2015, 2:43:42 PM12/22/15
to wxpython-users
On Tue, Dec 22, 2015 at 9:07 AM, steve <osloc...@gmail.com> wrote:
I don't understand, you are calling wxCallAfter in the separate thread but it uses the main thread to update GUI?

yes, exactly.
  
In my example, how will progress bar know that a page is added to new panel? Threads can't talk to each other.

you need to make all the GUI calls in wx from the same thread -- but there are a few calls you can make from any thread -- notably osting events. wx.CallAfter is the easiest way to do this.

so you use wxCallAfter in your secondary thread when you want the progress bar to update.

but I note: " how will progress bar know that a page is added to new panel" -- this implies that the new page is added ina a secondsary thred -- you can't do that (reliabley). all teh page addingl etc need to take place in the main thread. but you can call a function from a separete thread to tell the main thread to add a panel -- using, you guessed it, wx.CallAfter().

-CHB


 
 

On Tuesday, December 22, 2015 at 6:42:51 PM UTC+2, Michael Salin wrote:
Sorry, if someone has told in before.

First, the general rule is that you can touch any GUI items only from the main thread. (To be more precise: there is a thread, where the wxApp endless loop is running, this loop triggers callback aka events in the same thread, and only these calback can do something with GUI.)  Otherwise the magic behind it will be broken. No mater how it will fall, but it will fall.

Second, there is a nice tutorial here: http://wiki.wxpython.org/LongRunningTasks

Third, I use wxCallAfter

With best, Mike

--
You received this message because you are subscribed to the Google Groups "wxPython-users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to wxpython-user...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.



--

Christopher Barker, Ph.D.
Oceanographer

Emergency Response Division
NOAA/NOS/OR&R            (206) 526-6959   voice
7600 Sand Point Way NE   (206) 526-6329   fax
Seattle, WA  98115       (206) 526-6317   main reception

Chris....@noaa.gov

Tim Roberts

unread,
Dec 22, 2015, 2:53:50 PM12/22/15
to wxpytho...@googlegroups.com
steve wrote:
> I don't understand, you are calling wxCallAfter in the separate thread
> but it uses the main thread to update GUI?

Yes, that's exactly what CallAfter is for. wx.CallAfter simply sends a
window message to the main window for the application and then returns.
The message loop (app.MainLoop), which is running in your main thread,
pulls the window message from the message queue, and calls the function
that you specified in the wx.CallAfter.

Michael Haimes

unread,
Dec 22, 2015, 3:21:53 PM12/22/15
to wxpython-users
wx.CallAfter is a "non-blocking" call that tells the main thread to call the function specified next time it wakes up. the call to CallAfter itself returns immediately, often before the function specified is called (but do not rely on this fact). So, code following the call to callafter should NOT expect the results of the function that is the argument to callafter to be completed.

There is another important point that I don't think has been made yet. Windows, OS X and Linux all work slightly differently. It is possible on some of these platforms to interact with the GUI message queues from other threads than the main thread, even sometimes from other threads than the one creating the GUI objects. Even if you get this to work, DO NOT do it as it will surely fail on some platform even if it is not failing on your own. The reason that people religiously only interact with the GUI from the main thread is that it creates the most portable and failproof code. If you get in the habit of it now, your life will be better.

Igor Korot

unread,
Dec 22, 2015, 4:10:55 PM12/22/15
to wxpytho...@googlegroups.com
Hi, Steve,

On Tue, Dec 22, 2015 at 3:21 PM, Michael Haimes
<michael...@gmail.com> wrote:
> wx.CallAfter is a "non-blocking" call that tells the main thread to call the
> function specified next time it wakes up. the call to CallAfter itself
> returns immediately, often before the function specified is called (but do
> not rely on this fact). So, code following the call to callafter should NOT
> expect the results of the function that is the argument to callafter to be
> completed.
>
> There is another important point that I don't think has been made yet.
> Windows, OS X and Linux all work slightly differently. It is possible on
> some of these platforms to interact with the GUI message queues from other
> threads than the main thread, even sometimes from other threads than the one
> creating the GUI objects. Even if you get this to work, DO NOT do it as it
> will surely fail on some platform even if it is not failing on your own. The
> reason that people religiously only interact with the GUI from the main
> thread is that it creates the most portable and failproof code. If you get
> in the habit of it now, your life will be better.

Exactly.
Workiing with the GUI from anything other than the main thread is not
supported and
should be avoided.

You can post the message to the main thread to notify the GUI to
update itself and do
everything GUI-related on the main thread (create GUI controls, update
GUI controls).
There are some controversy about wxTimer and whether it should work
from the secondary
thread, but thats should not be the issue as if every single wx class
is made from the main
thread than everything will work just fine.

Thank you.

steve

unread,
Dec 22, 2015, 4:33:31 PM12/22/15
to wxPython-users
Dear sirs,
 
There is still one point not explained yet:
 
The script is trying to update the GUI simultaneously:
 
1- A new panel with a notebook is created, then pages are added to this notebook
2- For each page added, the progress bar is updated
3- Finally, when all pages are added, notebook panel is placed on main frame, whereas the initial panel with progress bar is hidden.
 
So, the notebook panel is created in main thread. And separate thread is sending messages to main thread to update to progress bar each time a page is added to notebook.
 
But it is impossible, because main thread is busy with creating the notebook panel and its pages. It will not get the "progress bar update messages" sent from the separate thread. Thus, the user will not see the progression of progress bar. He will only see that he pressed the button, then the notebook panel is placed on mainframe.

Igor Korot

unread,
Dec 22, 2015, 4:46:45 PM12/22/15
to wxpytho...@googlegroups.com
Hi, Steve,

On Tue, Dec 22, 2015 at 4:33 PM, steve <osloc...@gmail.com> wrote:
> Dear sirs,
>
> There is still one point not explained yet:
>
> The script is trying to update the GUI simultaneously:
>
> 1- A new panel with a notebook is created, then pages are added to this
> notebook

IIUC, this step is performed on the secondary thread.
Why did you do a design like this?

Once again - no GUI should operate on the secondary thread.

Also, the creation of the controls should be very fast - couple of
miliseconds. You will not
be able to see anything on the progress bar.

Thank you.

Tim Roberts

unread,
Dec 22, 2015, 5:47:59 PM12/22/15
to wxpytho...@googlegroups.com
steve wrote:
>
> There is still one point not explained yet:
>
> The script is trying to update the GUI simultaneously:
>
> 1- A new panel with a notebook is created, then pages are added to
> this notebook
> 2- For each page added, the progress bar is updated
> 3- Finally, when all pages are added, notebook panel is placed on main
> frame, whereas the initial panel with progress bar is hidden.
>
> So, the notebook panel is created in main thread. And separate thread
> is sending messages to main thread to update to progress bar each time
> a page is added to notebook.
>
> But it is impossible, because main thread is busy with creating the
> notebook panel and its pages. It will not get the "progress bar update
> messages" sent from the separate thread. Thus, the user will not see
> the progression of progress bar. He will only see that he pressed the
> button, then the notebook panel is placed on mainframe.

Your original code tried to create the additional notebook pages from a
different thread. That is NEVER going to work. All windows have to be
created in the main thread.

Given that, you're going to have to use a different approach. If all
you're trying to do is show a progress bar while you open the notebook
pages, then you'll have to do the progres bar update inline in the main
thread and call wx.Yield to allow messages to get processed.

Chris Barker

unread,
Dec 23, 2015, 1:46:53 PM12/23/15
to wxpython-users
> But it is impossible, because main thread is busy with creating the
> notebook panel and its pages.

here is a good thing to keep in mind. IF you want your GUI to be responsive, then everything that happens as the result of an event should return quickly.

At Tim points out, creating a showing a few GUI elements should be plenty quick. Usually, the only things that take a long time are complex calculations, or pulling data from a remote service to database, or... 

in which case, you want o do THOSE operations in another thread to keep the GUI alive. That thread can then post events to update the GUI if you want (like a progress bar, etc.), using wx.CallAfter, or lower level event posting.

The OP seems to want a progress bar fro when notebook pages are being created, but that should be too fast an operation to require a progress bar. So I suspect that there is some other process that is going on in order to crate the pages -- THAT should be done in a separate thread.

THis sounds like classic mixing of GUI and business logic to me -- clean that up and you won't struggle with keeping the GUI in one thread.

-CHB






 
It will not get the "progress bar update
> messages" sent from the separate thread. Thus, the user will not see
> the progression of progress bar. He will only see that he pressed the
> button, then the notebook panel is placed on mainframe.

Your original code tried to create the additional notebook pages from a
different thread.  That is NEVER going to work.  All windows have to be
created in the main thread.

Given that, you're going to have to use a different approach.  If all
you're trying to do is show a progress bar while you open the notebook
pages, then you'll have to do the progres bar update inline in the main
thread and call wx.Yield to allow messages to get processed.

--
Tim Roberts, ti...@probo.com
Providenza & Boekelheide, Inc.

--
You received this message because you are subscribed to the Google Groups "wxPython-users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to wxpython-user...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.



--
Reply all
Reply to author
Forward
0 new messages