状态栏中的进度表,使用 Cody Precord 的 ProgressStatusBar
我正在尝试在我的应用程序的状态栏中创建一个进度表,并且我正在使用 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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
如果这有任何用处,我将您的代码与 wx.lib.delayedresult 演示结合起来。当仪表更新时,GUI 保持响应。在 XP 和 Linux 上测试。
要记住的关键一点是,您无法直接从后台线程更新任何 GUI 元素。不过,发布事件是可行的,所以这就是我在这里所做的(ProgressBarEvent 和 EVT_PROGRESSBAR)。
请发布对代码的任何改进。谢谢!
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!
建议我回答我自己的问题,也许对其他人有帮助。这个问题似乎是一个平台(Linux?)特定的问题。请参阅华金的回答和随附的评论。
每次调用 SetProgress() 后,可以通过在框架本身上调用 Update() 来解决此问题,如本例所示。
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
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.