JackSac67
JackSac67

Reputation: 23

wxPython - How to make a loading bar display while another process is occurring?

I'm creating an RSS feed aggregator client in wxPython, and one function I'm trying to implement is a refresh function that refreshes the feed and displays any new articles. However every time the refresh function is called, all the web articles must be scraped again and displayed on the screen, which usually takes around 6-7 seconds. Therefore I created a wx.Dialog class with a loading gauge that lasts for 7 seconds. My intention is to show this dialog with the gauge while all the articles are being scraped. I attempted to do this with the threading module, but to no avail. The dialog pops up but the gauge doesn't begin until everything has already happened. Here is the current code:

def OnRefresh(self, e): #Reloads the scraper module and mimics previous processes, replaces the old articles in the list with the new articles
    gauge = LoadingGauge(None, title='', size=(300,200))
    threading.Thread(target=gauge.InitUI()).start() #Show the loading screen while the main program is refreshing (But it doesn't...)
    reload(scraper)
    self.webpage = scraper.Scraper('http://feeds.huffingtonpost.com/huffingtonpost/LatestNews')
    self.statusBar.SetStatusText('Refreshing feed...')
    self.webpage.scrape()
    self.statusBar.SetStatusText('')
    self.listCtrl.DeleteAllItems()
    for i in range(0, 15):
        self.listCtrl.InsertStringItem(i, self.webpage.titles[i])
    self.browser.LoadURL(self.webpage.links[0])
    self.Layout()

If anybody can catch my mistake or redirect me to a new solution, that would be great. Full code can be found here: https://github.com/JackSac67/Huffeeder and the icon used for the refresh tool can be found here: http://files.softicons.com/download/internet-cons/feedicons-2-icons-by-zeusbox-studio/png/32/reload.png

Upvotes: 1

Views: 2852

Answers (1)

Mike Driscoll
Mike Driscoll

Reputation: 33091

You will need to send messages from the thread that is doing the scraping to your main application to tell it to update the progress bar. That means you'll have to use one of wxPython threadsafe methods:

  • wx.CallAfter
  • wx.CallLater
  • wx.PostEvent

I think one of the easiest ways to do this would be to use pubsub in combination with CallAfter. You can use pubsub to publish a message to the dialog which will need to have a listener in it. You can read about how to do that here: http://www.blog.pythonlibrary.org/2010/05/22/wxpython-and-threads/

UPDATE:

Here is an example app that shows how to update the gauge widget periodically from a thread (works with wxPython 2.8):

import time
import wx

from threading import Thread

from wx.lib.pubsub import Publisher

########################################################################
class TestThread(Thread):
    """Test Worker Thread Class."""

    #----------------------------------------------------------------------
    def __init__(self):
        """Init Worker Thread Class."""
        Thread.__init__(self)
        self.start()    # start the thread

    #----------------------------------------------------------------------
    def run(self):
        """Run Worker Thread."""
        # This is the code executing in the new thread.
        for i in range(20):
            time.sleep(1)
            wx.CallAfter(Publisher().sendMessage, "update", "")

########################################################################
class MyProgressDialog(wx.Dialog):
    """"""

    #----------------------------------------------------------------------
    def __init__(self):
        """Constructor"""
        wx.Dialog.__init__(self, None, title="Progress")
        self.count = 0

        self.progress = wx.Gauge(self, range=20)

        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.progress, 0, wx.EXPAND)
        self.SetSizer(sizer)

        # create a pubsub receiver
        Publisher().subscribe(self.updateProgress, "update")

    #----------------------------------------------------------------------
    def updateProgress(self, msg):
        """"""
        self.count += 1

        if self.count >= 20:
            self.Destroy()

        self.progress.SetValue(self.count)


########################################################################
class MyForm(wx.Frame):

    #----------------------------------------------------------------------
    def __init__(self):
        wx.Frame.__init__(self, None, wx.ID_ANY, "Tutorial")

        # Add a panel so it looks the correct on all platforms
        panel = wx.Panel(self, wx.ID_ANY)
        self.btn = btn = wx.Button(panel, label="Start Thread")
        btn.Bind(wx.EVT_BUTTON, self.onButton)

        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(btn, 0, wx.ALL|wx.CENTER, 5)
        panel.SetSizer(sizer)

    #----------------------------------------------------------------------
    def onButton(self, event):
        """
        Runs the thread
        """
        btn = event.GetEventObject()
        btn.Disable()

        TestThread()
        dlg = MyProgressDialog()
        dlg.ShowModal()

        btn.Enable()


#----------------------------------------------------------------------
# Run the program
if __name__ == "__main__":
    app = wx.PySimpleApp()
    frame = MyForm().Show()
    app.MainLoop()

If you are using wxPython 2.9, then pubsub has been updated to use the new pubsub API. Here's an example that should work for wxPython 2.9:

import time
import wx

from threading import Thread

from wx.lib.pubsub import pub

########################################################################
class TestThread(Thread):
    """Test Worker Thread Class."""

    #----------------------------------------------------------------------
    def __init__(self):
        """Init Worker Thread Class."""
        Thread.__init__(self)
        self.start()    # start the thread

    #----------------------------------------------------------------------
    def run(self):
        """Run Worker Thread."""
        # This is the code executing in the new thread.
        for i in range(20):
            time.sleep(1)
            wx.CallAfter(pub.sendMessage, "update", msg="")

########################################################################
class MyProgressDialog(wx.Dialog):
    """"""

    #----------------------------------------------------------------------
    def __init__(self):
        """Constructor"""
        wx.Dialog.__init__(self, None, title="Progress")
        self.count = 0

        self.progress = wx.Gauge(self, range=20)

        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.progress, 0, wx.EXPAND)
        self.SetSizer(sizer)

        # create a pubsub receiver
        pub.subscribe(self.updateProgress, "update")

    #----------------------------------------------------------------------
    def updateProgress(self, msg):
        """"""
        self.count += 1

        if self.count >= 20:
            self.Destroy()

        self.progress.SetValue(self.count)


########################################################################
class MyForm(wx.Frame):

    #----------------------------------------------------------------------
    def __init__(self):
        wx.Frame.__init__(self, None, wx.ID_ANY, "Tutorial")

        # Add a panel so it looks the correct on all platforms
        panel = wx.Panel(self, wx.ID_ANY)
        self.btn = btn = wx.Button(panel, label="Start Thread")
        btn.Bind(wx.EVT_BUTTON, self.onButton)

        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(btn, 0, wx.ALL|wx.CENTER, 5)
        panel.SetSizer(sizer)

    #----------------------------------------------------------------------
    def onButton(self, event):
        """
        Runs the thread
        """
        btn = event.GetEventObject()
        btn.Disable()

        TestThread()
        dlg = MyProgressDialog()
        dlg.ShowModal()

        btn.Enable()


#----------------------------------------------------------------------
# Run the program
if __name__ == "__main__":
    app = wx.App(False)
    frame = MyForm().Show()
    app.MainLoop()

Upvotes: 3

Related Questions