我正在尝试在我的应用程序的状态栏中创建一个进度表,我正在使用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()
答案 0 :(得分:1)
我被建议回答我自己的问题,也许它会帮助别人。这个问题似乎是一个平台(Linux?)的具体问题。请参阅joaquin的回答和随附的评论。
可以通过在每次调用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(),其中仪表是连续更新而不是离散的块;或者如果有更好的方法。
答案 1 :(得分:0)
如果这是任何用途,我将您的代码与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()