状态栏中的进度表,使用 Cody Precord 的 ProgressStatusBar

发布于 2024-10-21 16:20:37 字数 5112 浏览 5 评论 0原文

我正在尝试在我的应用程序的状态栏中创建一个进度表,并且我正在使用 Cody Precord 的 wxPython 2.8 应用程序开发手册中的示例。我在下面复制了它。

现在,我只是希望显示仪表并在应用程序繁忙时让它发出脉冲,因此我假设我需要使用 Start/StopBusy() 方法。问题是,这些似乎都不起作用,而且本书没有提供如何使用该类的示例。

在框架的 __init__ 中,我像这样创建状态栏:

self.statbar = status.ProgressStatusBar( self )
self.SetStatusBar( self.statbar )

然后,在完成所有工作的函数中,我尝试了以下操作:

self.GetStatusBar().SetRange( 100 )
self.GetStatusBar().SetProgress( 0 )
self.GetStatusBar().StartBusy()
self.GetStatusBar().Run()

# work done here

self.GetStatusBar().StopBusy()

以及这些命令的几种组合和排列,但没有任何反应,也没有显示仪表。这项工作需要几秒钟的时间,所以这并不是因为仪表再次消失得太快而让我注意到。

我可以通过从 Precord 的 __init__ 中删除 self.prog.Hide() 行来显示仪表,但它仍然不会发出脉冲,并且一旦工作第一次完成就消失,永远不会返回。

这是 Precord 的类:

class ProgressStatusBar( wx.StatusBar ):
    '''Custom StatusBar with a built-in progress bar'''
    def __init__( self, parent, id_=wx.ID_ANY,
                  style=wx.SB_FLAT, name='ProgressStatusBar' ):
        super( ProgressStatusBar, self ).__init__( parent, id_, style, name )

        self._changed = False
        self.busy = False
        self.timer = wx.Timer( self )
        self.prog = wx.Gauge( self, style=wx.GA_HORIZONTAL )
        self.prog.Hide()

        self.SetFieldsCount( 2 )
        self.SetStatusWidths( [-1, 155] )

        self.Bind( wx.EVT_IDLE, lambda evt: self.__Reposition() )
        self.Bind( wx.EVT_TIMER, self.OnTimer )
        self.Bind( wx.EVT_SIZE, self.OnSize )

    def __del__( self ):
        if self.timer.IsRunning():
            self.timer.Stop()

    def __Reposition( self ):
        '''Repositions the gauge as necessary'''
        if self._changed:
            lfield = self.GetFieldsCount() - 1
            rect = self.GetFieldRect( lfield )
            prog_pos = (rect.x + 2, rect.y + 2)
            self.prog.SetPosition( prog_pos )
            prog_size = (rect.width - 8, rect.height - 4)
            self.prog.SetSize( prog_size )
        self._changed = False

    def OnSize( self, evt ):
        self._changed = True
        self.__Reposition()
        evt.Skip()

    def OnTimer( self, evt ):
        if not self.prog.IsShown():
            self.timer.Stop()

        if self.busy:
            self.prog.Pulse()

    def Run( self, rate=100 ):
        if not self.timer.IsRunning():
            self.timer.Start( rate )

    def GetProgress( self ):
        return self.prog.GetValue()

    def SetProgress( self, val ):
        if not self.prog.IsShown():
            self.ShowProgress( True )

        if val == self.prog.GetRange():
            self.prog.SetValue( 0 )
            self.ShowProgress( False )
        else:
            self.prog.SetValue( val )

    def SetRange( self, val ):
        if val != self.prog.GetRange():
            self.prog.SetRange( val )

    def ShowProgress( self, show=True ):
        self.__Reposition()
        self.prog.Show( show )

    def StartBusy( self, rate=100 ):
        self.busy = True
        self.__Reposition()
        self.ShowProgress( True )
        if not self.timer.IsRunning():
            self.timer.Start( rate )

    def StopBusy( self ):
        self.timer.Stop()
        self.ShowProgress( False )
        self.prog.SetValue( 0 )
        self.busy = False

    def IsBusy( self ):
        return self.busy

更新:这是我的 __init__ 和 Go 方法。当用户单击按钮时调用 Go()。它做了很多与这里无关的工作。 Setup* 函数是设置控件和绑定的其他方法,我认为它们在这里也无关紧要。

我可以省略 SetStatusBar,但状态栏会出现在顶部而不是底部,并覆盖其他控件,即使这样问题仍然相同,所以我将其保留。

我在这里使用 Start/StopBusy ,但与 SetProgress 完全相同。

def __init__( self, *args, **kwargs ):
    super( PwFrame, self ).__init__( *args, **kwargs )

    self.file = None
    self.words = None

    self.panel = wx.Panel( self )

    self.SetupMenu()      
    self.SetupControls()

    self.statbar = status.ProgressStatusBar( self )
    self.SetStatusBar( self.statbar )

    self.SetInitialSize()
    self.SetupBindings()

def Go( self, event ):
    self.statbar.StartBusy()

    # Work done here

    self.statbar.StopBusy( )

更新 2 我尝试了您建议的代码,下面是整个测试应用程序,与原样完全相同。它仍然不起作用,仪表仅在最后 10 秒过去后才显示。

import time
import wx

import status

class App( wx.App ):
    def OnInit( self ):
        self.frame = MyFrame( None, title='Test' )
        self.SetTopWindow( self.frame )
        self.frame.Show()
        return True

class MyFrame(wx.Frame):
    def __init__(self, *args, **kargs):
        wx.Frame.__init__(self, *args, **kargs)
        self.bt = wx.Button(self)
        self.status = status.ProgressStatusBar(self)
        self.sizer = wx.BoxSizer(wx.VERTICAL)

        self.Bind(wx.EVT_BUTTON, self.on_bt, self.bt)

        self.sizer.Add(self.bt, 1, wx.EXPAND)
        self.sizer.Add(self.status, 1, wx.EXPAND)

        self.SetSizer(self.sizer)
        self.Fit()
        self.SetSize((500,50))

    def on_bt(self, evt):
        "press the button and it will start" 
        for n in range(100):
            time.sleep(0.1)
            self.status.SetProgress(n)

if __name__ == "__main__":    
    root = App()
    root.MainLoop()

I am attempting to create a progress gauge in the status bar for my application, and I'm using the example in Cody Precord's wxPython 2.8 Application Development Cookbook. I've reproduced it below.

For now I simply wish to show the gauge and have it pulse when the application is busy, so I assume I need to use the Start/StopBusy() methods. Problem is, none of it seems to work, and the book doesn't provide an example of how to use the class.

In the __init__ of my frame I create my status bar like so:

self.statbar = status.ProgressStatusBar( self )
self.SetStatusBar( self.statbar )

Then, in the function which does all the work, I have tried things like:

self.GetStatusBar().SetRange( 100 )
self.GetStatusBar().SetProgress( 0 )
self.GetStatusBar().StartBusy()
self.GetStatusBar().Run()

# work done here

self.GetStatusBar().StopBusy()

And several combinations and permutations of those commands, but nothing happens, no gauge is ever shown. The work takes several seconds, so it's not because the gauge simply disappears again too quickly for me to notice.

I can get the gauge to show up by removing the self.prog.Hide() line from Precord's __init__ but it still doesn't pulse and simply disappears never to return once work has finished the first time.

Here's Precord's class:

class ProgressStatusBar( wx.StatusBar ):
    '''Custom StatusBar with a built-in progress bar'''
    def __init__( self, parent, id_=wx.ID_ANY,
                  style=wx.SB_FLAT, name='ProgressStatusBar' ):
        super( ProgressStatusBar, self ).__init__( parent, id_, style, name )

        self._changed = False
        self.busy = False
        self.timer = wx.Timer( self )
        self.prog = wx.Gauge( self, style=wx.GA_HORIZONTAL )
        self.prog.Hide()

        self.SetFieldsCount( 2 )
        self.SetStatusWidths( [-1, 155] )

        self.Bind( wx.EVT_IDLE, lambda evt: self.__Reposition() )
        self.Bind( wx.EVT_TIMER, self.OnTimer )
        self.Bind( wx.EVT_SIZE, self.OnSize )

    def __del__( self ):
        if self.timer.IsRunning():
            self.timer.Stop()

    def __Reposition( self ):
        '''Repositions the gauge as necessary'''
        if self._changed:
            lfield = self.GetFieldsCount() - 1
            rect = self.GetFieldRect( lfield )
            prog_pos = (rect.x + 2, rect.y + 2)
            self.prog.SetPosition( prog_pos )
            prog_size = (rect.width - 8, rect.height - 4)
            self.prog.SetSize( prog_size )
        self._changed = False

    def OnSize( self, evt ):
        self._changed = True
        self.__Reposition()
        evt.Skip()

    def OnTimer( self, evt ):
        if not self.prog.IsShown():
            self.timer.Stop()

        if self.busy:
            self.prog.Pulse()

    def Run( self, rate=100 ):
        if not self.timer.IsRunning():
            self.timer.Start( rate )

    def GetProgress( self ):
        return self.prog.GetValue()

    def SetProgress( self, val ):
        if not self.prog.IsShown():
            self.ShowProgress( True )

        if val == self.prog.GetRange():
            self.prog.SetValue( 0 )
            self.ShowProgress( False )
        else:
            self.prog.SetValue( val )

    def SetRange( self, val ):
        if val != self.prog.GetRange():
            self.prog.SetRange( val )

    def ShowProgress( self, show=True ):
        self.__Reposition()
        self.prog.Show( show )

    def StartBusy( self, rate=100 ):
        self.busy = True
        self.__Reposition()
        self.ShowProgress( True )
        if not self.timer.IsRunning():
            self.timer.Start( rate )

    def StopBusy( self ):
        self.timer.Stop()
        self.ShowProgress( False )
        self.prog.SetValue( 0 )
        self.busy = False

    def IsBusy( self ):
        return self.busy

Update: Here are my __init__ and Go methods. Go() is called when a button is clicked by the user. It does a lot of work which should be irrelevant here. The Setup* functions are other methods which sets up the controls and bindings, I think they're also irrelevant here.

I can leave out the SetStatusBar, but then the status bar appears at the top rather than the bottom and covers other controls, and the problem remains the same even then, so I've left it in.

I'm using Start/StopBusy here, but it's exactly the same with SetProgress.

def __init__( self, *args, **kwargs ):
    super( PwFrame, self ).__init__( *args, **kwargs )

    self.file = None
    self.words = None

    self.panel = wx.Panel( self )

    self.SetupMenu()      
    self.SetupControls()

    self.statbar = status.ProgressStatusBar( self )
    self.SetStatusBar( self.statbar )

    self.SetInitialSize()
    self.SetupBindings()

def Go( self, event ):
    self.statbar.StartBusy()

    # Work done here

    self.statbar.StopBusy( )

Update 2 I tried your suggested code, below is the entire test application, exactly as is. It still doesn't work, the gauge only shows up at the very end, after the 10 seconds have passed.

import time
import wx

import status

class App( wx.App ):
    def OnInit( self ):
        self.frame = MyFrame( None, title='Test' )
        self.SetTopWindow( self.frame )
        self.frame.Show()
        return True

class MyFrame(wx.Frame):
    def __init__(self, *args, **kargs):
        wx.Frame.__init__(self, *args, **kargs)
        self.bt = wx.Button(self)
        self.status = status.ProgressStatusBar(self)
        self.sizer = wx.BoxSizer(wx.VERTICAL)

        self.Bind(wx.EVT_BUTTON, self.on_bt, self.bt)

        self.sizer.Add(self.bt, 1, wx.EXPAND)
        self.sizer.Add(self.status, 1, wx.EXPAND)

        self.SetSizer(self.sizer)
        self.Fit()
        self.SetSize((500,50))

    def on_bt(self, evt):
        "press the button and it will start" 
        for n in range(100):
            time.sleep(0.1)
            self.status.SetProgress(n)

if __name__ == "__main__":    
    root = App()
    root.MainLoop()

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

Bonjour°[大白 2024-10-28 16:20:38

如果这有任何用处,我将您的代码与 wx.lib.delayedresult 演示结合起来。当仪表更新时,GUI 保持响应。在 XP 和 Linux 上测试。

要记住的关键一点是,您无法直接从后台线程更新任何 GUI 元素。不过,发布事件是可行的,所以这就是我在这里所做的(ProgressBarEvent 和 EVT_PROGRESSBAR)。

请发布对代码的任何改进。谢谢!

import time
import wx
import wx.lib.delayedresult as delayedresult
import wx.lib.newevent

ProgressBarEvent, EVT_PROGRESSBAR = wx.lib.newevent.NewEvent()

class ProgressStatusBar( wx.StatusBar ):
    '''Custom StatusBar with a built-in progress bar'''
    def __init__( self, parent, id_=wx.ID_ANY,
                  style=wx.SB_FLAT, name='ProgressStatusBar' ):
        super( ProgressStatusBar, self ).__init__( parent, id_, style, name )

        self._changed = False
        self.busy = False
        self.timer = wx.Timer( self )
        self.prog = wx.Gauge( self, style=wx.GA_HORIZONTAL )
        self.prog.Hide()

        self.SetFieldsCount( 2 )
        self.SetStatusWidths( [-1, 155] )

        self.Bind( wx.EVT_IDLE, lambda evt: self.__Reposition() )
        self.Bind( wx.EVT_TIMER, self.OnTimer )
        self.Bind( wx.EVT_SIZE, self.OnSize )
        self.Bind( EVT_PROGRESSBAR, self.OnProgress )

    def __del__( self ):
        if self.timer.IsRunning():
            self.timer.Stop()

    def __Reposition( self ):
        '''Repositions the gauge as necessary'''
        if self._changed:
            lfield = self.GetFieldsCount() - 1
            rect = self.GetFieldRect( lfield )
            prog_pos = (rect.x + 2, rect.y + 2)
            self.prog.SetPosition( prog_pos )
            prog_size = (rect.width - 8, rect.height - 4)
            self.prog.SetSize( prog_size )
        self._changed = False

    def OnSize( self, evt ):
        self._changed = True
        self.__Reposition()
        evt.Skip()

    def OnTimer( self, evt ):
        if not self.prog.IsShown():
            self.timer.Stop()

        if self.busy:
            self.prog.Pulse()

    def Run( self, rate=100 ):
        if not self.timer.IsRunning():
            self.timer.Start( rate )

    def GetProgress( self ):
        return self.prog.GetValue()

    def SetProgress( self, val ):
        if not self.prog.IsShown():
            self.ShowProgress( True )

        self.prog.SetValue( val )
        #if val == self.prog.GetRange():
        #    self.prog.SetValue( 0 )
        #    self.ShowProgress( False )

    def OnProgress(self, event):
        self.SetProgress(event.count)

    def SetRange( self, val ):
        if val != self.prog.GetRange():
            self.prog.SetRange( val )

    def ShowProgress( self, show=True ):
        self.__Reposition()
        self.prog.Show( show )

    def StartBusy( self, rate=100 ):
        self.busy = True
        self.__Reposition()
        self.ShowProgress( True )
        if not self.timer.IsRunning():
            self.timer.Start( rate )

    def StopBusy( self ):
        self.timer.Stop()
        self.ShowProgress( False )
        self.prog.SetValue( 0 )
        self.busy = False

    def IsBusy( self ):
        return self.busy

class MyFrame(wx.Frame):
    def __init__(self, *args, **kargs):
        wx.Frame.__init__(self, *args, **kargs)
        self.bt = wx.Button(self)
        self.bt.SetLabel("Start!")
        self.status = ProgressStatusBar(self)

        self.sizer = wx.BoxSizer(wx.VERTICAL)
        self.Bind(wx.EVT_BUTTON, self.handleButton, self.bt)
        self.sizer.Add(self.bt, 1, wx.EXPAND)

        self.SetStatusBar(self.status)

        self.SetSizer(self.sizer)
        self.Fit()
        self.SetSize((600,200))

        #using a flag to determine the state of the background thread
        self.isRunning = False

        #number of iterations in the delayed calculation
        self.niter = 200

        #from the delayedresult demo
        self.jobID = 0
        self.abortEvent = delayedresult.AbortEvent()
        self.Bind(wx.EVT_CLOSE, self.handleClose)

    def handleButton(self, evt):
        "Press the button and it will start. Press again and it will stop." 
        if not self.isRunning:
            self.bt.SetLabel("Abort!")
            self.abortEvent.clear()
            self.jobID += 1

            self.log( "Starting job %s in producer thread: GUI remains responsive"
                      % self.jobID )

            #initialize the status bar (need to know the number of iterations)
            self.status.SetRange(self.niter)
            self.status.SetProgress(0)

            delayedresult.startWorker(self._resultConsumer, self._resultProducer, 
                                      wargs=(self.jobID,self.abortEvent), jobID=self.jobID)
        else:
            self.abortEvent.set()
            self.bt.SetLabel("Start!")

            #get the number of iterations from the progress bar (approximatively at least one more)
            result = self.status.GetProgress()+1
            self.log( "Aborting result for job %s: Completed %d iterations" % (self.jobID,result) )

        self.isRunning = not self.isRunning

    def handleClose(self, event):
        """Only needed because in demo, closing the window does not kill the 
        app, so worker thread continues and sends result to dead frame; normally
        your app would exit so this would not happen."""
        if self.isRunning:
            self.log( "Exiting: Aborting job %s" % self.jobID )
            self.abortEvent.set()
        self.Destroy()

    def _resultProducer(self, jobID, abortEvent):
        """Pretend to be a complex worker function or something that takes 
        long time to run due to network access etc. GUI will freeze if this 
        method is not called in separate thread."""

        count = 0
        while not abortEvent() and count < self.niter:

            #5 seconds top to get to the end...
            time.sleep(5./self.niter)
            count += 1

            #update after a calculation
            event = ProgressBarEvent(count=count)
            wx.PostEvent(self.status, event)

        #introduce an error if jobID is odd
        if jobID % 2 == 1:
            raise ValueError("Detected odd job!")

        return count

    def _resultConsumer(self, delayedResult):
        jobID = delayedResult.getJobID()
        assert jobID == self.jobID
        try:
            result = delayedResult.get()
        except Exception, exc:
            result_string = "Result for job %s raised exception: %s" % (jobID, exc) 
        else:
            result_string = "Got result for job %s: %s" % (jobID, result)

        # output result
        self.log(result_string)

        # get ready for next job:
        self.isRunning = not self.isRunning
        self.bt.SetLabel("Start!")

        #Use this to hide the progress bar when done.
        self.status.ShowProgress(False)

    def log(self,text):
        self.SetStatusText(text)

if __name__ == '__main__':
    app = wx.PySimpleApp()
    frame = MyFrame(None)
    frame.Show()
    app.MainLoop()

In case this is any use, I combined your code with the wx.lib.delayedresult demo. GUI stays responsive while the gauge is updated. Tested on XP and Linux.

Key thing to remember is that you cannot update any GUI elements directly from the background thread. Posting events works though, so that's what I'm doing here (ProgressBarEvent and EVT_PROGRESSBAR).

Please post any improvements to the code. Thanks!

import time
import wx
import wx.lib.delayedresult as delayedresult
import wx.lib.newevent

ProgressBarEvent, EVT_PROGRESSBAR = wx.lib.newevent.NewEvent()

class ProgressStatusBar( wx.StatusBar ):
    '''Custom StatusBar with a built-in progress bar'''
    def __init__( self, parent, id_=wx.ID_ANY,
                  style=wx.SB_FLAT, name='ProgressStatusBar' ):
        super( ProgressStatusBar, self ).__init__( parent, id_, style, name )

        self._changed = False
        self.busy = False
        self.timer = wx.Timer( self )
        self.prog = wx.Gauge( self, style=wx.GA_HORIZONTAL )
        self.prog.Hide()

        self.SetFieldsCount( 2 )
        self.SetStatusWidths( [-1, 155] )

        self.Bind( wx.EVT_IDLE, lambda evt: self.__Reposition() )
        self.Bind( wx.EVT_TIMER, self.OnTimer )
        self.Bind( wx.EVT_SIZE, self.OnSize )
        self.Bind( EVT_PROGRESSBAR, self.OnProgress )

    def __del__( self ):
        if self.timer.IsRunning():
            self.timer.Stop()

    def __Reposition( self ):
        '''Repositions the gauge as necessary'''
        if self._changed:
            lfield = self.GetFieldsCount() - 1
            rect = self.GetFieldRect( lfield )
            prog_pos = (rect.x + 2, rect.y + 2)
            self.prog.SetPosition( prog_pos )
            prog_size = (rect.width - 8, rect.height - 4)
            self.prog.SetSize( prog_size )
        self._changed = False

    def OnSize( self, evt ):
        self._changed = True
        self.__Reposition()
        evt.Skip()

    def OnTimer( self, evt ):
        if not self.prog.IsShown():
            self.timer.Stop()

        if self.busy:
            self.prog.Pulse()

    def Run( self, rate=100 ):
        if not self.timer.IsRunning():
            self.timer.Start( rate )

    def GetProgress( self ):
        return self.prog.GetValue()

    def SetProgress( self, val ):
        if not self.prog.IsShown():
            self.ShowProgress( True )

        self.prog.SetValue( val )
        #if val == self.prog.GetRange():
        #    self.prog.SetValue( 0 )
        #    self.ShowProgress( False )

    def OnProgress(self, event):
        self.SetProgress(event.count)

    def SetRange( self, val ):
        if val != self.prog.GetRange():
            self.prog.SetRange( val )

    def ShowProgress( self, show=True ):
        self.__Reposition()
        self.prog.Show( show )

    def StartBusy( self, rate=100 ):
        self.busy = True
        self.__Reposition()
        self.ShowProgress( True )
        if not self.timer.IsRunning():
            self.timer.Start( rate )

    def StopBusy( self ):
        self.timer.Stop()
        self.ShowProgress( False )
        self.prog.SetValue( 0 )
        self.busy = False

    def IsBusy( self ):
        return self.busy

class MyFrame(wx.Frame):
    def __init__(self, *args, **kargs):
        wx.Frame.__init__(self, *args, **kargs)
        self.bt = wx.Button(self)
        self.bt.SetLabel("Start!")
        self.status = ProgressStatusBar(self)

        self.sizer = wx.BoxSizer(wx.VERTICAL)
        self.Bind(wx.EVT_BUTTON, self.handleButton, self.bt)
        self.sizer.Add(self.bt, 1, wx.EXPAND)

        self.SetStatusBar(self.status)

        self.SetSizer(self.sizer)
        self.Fit()
        self.SetSize((600,200))

        #using a flag to determine the state of the background thread
        self.isRunning = False

        #number of iterations in the delayed calculation
        self.niter = 200

        #from the delayedresult demo
        self.jobID = 0
        self.abortEvent = delayedresult.AbortEvent()
        self.Bind(wx.EVT_CLOSE, self.handleClose)

    def handleButton(self, evt):
        "Press the button and it will start. Press again and it will stop." 
        if not self.isRunning:
            self.bt.SetLabel("Abort!")
            self.abortEvent.clear()
            self.jobID += 1

            self.log( "Starting job %s in producer thread: GUI remains responsive"
                      % self.jobID )

            #initialize the status bar (need to know the number of iterations)
            self.status.SetRange(self.niter)
            self.status.SetProgress(0)

            delayedresult.startWorker(self._resultConsumer, self._resultProducer, 
                                      wargs=(self.jobID,self.abortEvent), jobID=self.jobID)
        else:
            self.abortEvent.set()
            self.bt.SetLabel("Start!")

            #get the number of iterations from the progress bar (approximatively at least one more)
            result = self.status.GetProgress()+1
            self.log( "Aborting result for job %s: Completed %d iterations" % (self.jobID,result) )

        self.isRunning = not self.isRunning

    def handleClose(self, event):
        """Only needed because in demo, closing the window does not kill the 
        app, so worker thread continues and sends result to dead frame; normally
        your app would exit so this would not happen."""
        if self.isRunning:
            self.log( "Exiting: Aborting job %s" % self.jobID )
            self.abortEvent.set()
        self.Destroy()

    def _resultProducer(self, jobID, abortEvent):
        """Pretend to be a complex worker function or something that takes 
        long time to run due to network access etc. GUI will freeze if this 
        method is not called in separate thread."""

        count = 0
        while not abortEvent() and count < self.niter:

            #5 seconds top to get to the end...
            time.sleep(5./self.niter)
            count += 1

            #update after a calculation
            event = ProgressBarEvent(count=count)
            wx.PostEvent(self.status, event)

        #introduce an error if jobID is odd
        if jobID % 2 == 1:
            raise ValueError("Detected odd job!")

        return count

    def _resultConsumer(self, delayedResult):
        jobID = delayedResult.getJobID()
        assert jobID == self.jobID
        try:
            result = delayedResult.get()
        except Exception, exc:
            result_string = "Result for job %s raised exception: %s" % (jobID, exc) 
        else:
            result_string = "Got result for job %s: %s" % (jobID, result)

        # output result
        self.log(result_string)

        # get ready for next job:
        self.isRunning = not self.isRunning
        self.bt.SetLabel("Start!")

        #Use this to hide the progress bar when done.
        self.status.ShowProgress(False)

    def log(self,text):
        self.SetStatusText(text)

if __name__ == '__main__':
    app = wx.PySimpleApp()
    frame = MyFrame(None)
    frame.Show()
    app.MainLoop()
疯狂的代价 2024-10-28 16:20:37

建议我回答我自己的问题,也许对其他人有帮助。这个问题似乎是一个平台(Linux?)特定的问题。请参阅华金的回答和随附的评论。

每次调用 SetProgress() 后,可以通过在框架本身上调用 Update() 来解决此问题,如本例所示。

import time
import wx

class MyFrame(wx.Frame):
    def __init__(self, *args, **kargs):
        wx.Frame.__init__(self, *args, **kargs)
        self.bt = wx.Button(self)
        self.status = ProgressStatusBar(self)
        self.sizer = wx.BoxSizer(wx.VERTICAL)

        self.Bind(wx.EVT_BUTTON, self.on_bt, self.bt)

        self.sizer.Add(self.bt, 1, wx.EXPAND)
        self.sizer.Add(self.status, 1, wx.EXPAND)

        self.SetSizer(self.sizer)
        self.Fit()
        self.SetSize((500,200))

    def on_bt(self, evt):
        "press the button and it will start" 
        for n in range(100):
            time.sleep(0.1)
            self.status.SetProgress(n)
            self.Update()

if __name__ == '__main__':
    app = wx.PySimpleApp()
    frame = MyFrame(None)
    frame.Show()
    app.MainLoop()

Update() 方法立即重新绘制窗口/框架,而不是等待 EVT_PAINT。显然,Windows 和 Linux 之间在调用和/或处理此事件时存在差异。

我不知道这个技巧是否适用于 Start/StopBusy(),其中仪表连续更新而不是离散块;或者是否有更好的方法。

I was advised to answer my own question, maybe it'll help others. This problem seems to be a platform (Linux?) specific problem. See joaquin's answer and accompanying comments.

It can be solved by calling Update() on the frame itself after each call to SetProgress(), as in this example

import time
import wx

class MyFrame(wx.Frame):
    def __init__(self, *args, **kargs):
        wx.Frame.__init__(self, *args, **kargs)
        self.bt = wx.Button(self)
        self.status = ProgressStatusBar(self)
        self.sizer = wx.BoxSizer(wx.VERTICAL)

        self.Bind(wx.EVT_BUTTON, self.on_bt, self.bt)

        self.sizer.Add(self.bt, 1, wx.EXPAND)
        self.sizer.Add(self.status, 1, wx.EXPAND)

        self.SetSizer(self.sizer)
        self.Fit()
        self.SetSize((500,200))

    def on_bt(self, evt):
        "press the button and it will start" 
        for n in range(100):
            time.sleep(0.1)
            self.status.SetProgress(n)
            self.Update()

if __name__ == '__main__':
    app = wx.PySimpleApp()
    frame = MyFrame(None)
    frame.Show()
    app.MainLoop()

The Update() method immediately repaints the window/frame, rather than waiting for EVT_PAINT. Apparently there is a difference between Windows and Linux in when this event is called and/or processed.

I do not know if this trick will work well with Start/StopBusy() where the gauge is updated continuously rather than in discrete chunks; or if there is a better way entirely.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文