wx.Gauge在Windows中无法超过25%更新,在Linux中正常工作
我在使用wxPython时遇到了很多麻烦,尤其是在不同平台之间的兼容性问题 :(
下面是一个函数。当用户点击一个按钮时,这个函数会被调用,它会执行一些可能需要时间的工作。在这个过程中,状态栏会显示一个进度条。
def Go(self, event):
progress = 0
self.statbar.setprogress(progress)
self.Update()
# ...
for i in range(1, numwords + 1):
progress = int(((float(i) / float(numwords)) * 100) - 1)
self.wrdlst.Append(words.next())
self.statbar.setprogress(progress)
self.Update()
self.wrdlst.Refresh()
# ...
progress = 100
self.PushStatusText(app.l10n['msc_genwords'] % numwords)
self.statbar.setprogress(progress)
在Linux系统下,调用self.Update()
是必要的,否则进度条不会更新,直到函数执行完毕,这样就没什么意义了。而在Windows(至少是Win 7)下,这些调用似乎没有效果。
在Linux上,这一切都运行得很好(有调用Update()),但在Windows 7上,进度条在大约20-25%的时候就停止了,距离函数结束还有一段时间。也就是说,进度条应该移动到25%左右,然后就停下来了,没什么明显的原因,但函数依然正常执行并输出正确的结果。
为了找出问题,我尝试在更新进度条之前插入一行print progress
,想看看progress
的值是不是我想的那样。令我惊讶的是,这样进度条就正常工作了,但一旦我去掉print
,它又停止工作了。我也可以用time.sleep(0.001)
替代print,但即使是这么短的暂停,程序也几乎停滞不前,如果我把时间再缩短一点,问题又会出现,所以这并没有什么帮助。
我搞不清楚发生了什么,也不知道该怎么解决,但我猜在Windows下,事情的进展太快了,以至于progress
在一段时间后没有正确更新,始终保持在一个固定的值(大约25)。不过,我也不知道为什么会这样,这对我来说没有意义。当然,print
和sleep
都不是好的解决方案。即使我打印“nothing”,Windows仍然会为这个不存在的输出打开另一个窗口,这很烦人。
如果你需要更多信息或代码,请告诉我。
编辑:好的,这里有一个工作应用程序(至少对我来说)有这个问题。虽然它还是挺长的,但我尽量删掉了与当前问题无关的部分。
在Linux上它运行正常,就像完整的应用程序一样。在Windows上,它是否正常工作取决于Go函数中numwords
的值。如果我把它的值增加到1000000(100万),问题就消失了。我怀疑这可能与系统有关,所以如果它在你那儿正常工作,可以尝试调整numwords
的值。也可能是因为我把它改成了Append()
一个静态文本,而不是像原始代码那样调用生成器。
不过,当前numwords
的值(100000)在我这里在Windows上确实失败了。
import wx
class Wordlist(wx.TextCtrl):
def __init__(self, parent):
super(Wordlist, self).__init__(parent,
style=wx.TE_MULTILINE|wx.TE_READONLY)
self.words = []
self.SetValue("")
def Get(self):
return '\r\n'.join(self.words)
def Refresh(self):
self.SetValue(self.Get())
def Append(self, value):
if isinstance(value, list):
value = '\r\n'.join(value)
self.words.append(unicode(value))
class ProgressStatusBar(wx.StatusBar):
def __init__(self, *args, **kwargs):
super(ProgressStatusBar, self).__init__(*args, **kwargs)
self._changed = False
self.prog = wx.Gauge(self, style=wx.GA_HORIZONTAL)
self.prog.Hide()
self.SetFieldsCount(2)
self.SetStatusWidths([-1, 150])
self.Bind(wx.EVT_IDLE, lambda evt: self.__reposition())
self.Bind(wx.EVT_SIZE, self.onsize)
def __reposition(self):
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 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 showprogress(self, show=True):
self.__reposition()
self.prog.Show(show)
class MainFrame(wx.Frame):
def __init__(self, *args, **kwargs):
super(MainFrame, self).__init__(*args, **kwargs)
self.SetupControls()
self.statbar = ProgressStatusBar(self)
self.SetStatusBar(self.statbar)
self.panel.Fit()
self.SetInitialSize()
self.SetupBindings()
def SetupControls(self):
self.panel = wx.Panel(self)
self.gobtn = wx.Button(self.panel, label="Go")
self.wrdlst = Wordlist(self.panel)
wrap = wx.BoxSizer()
wrap.Add(self.gobtn, 0, wx.EXPAND|wx.ALL, 10)
wrap.Add(self.wrdlst, 0, wx.EXPAND|wx.ALL, 10)
self.panel.SetSizer(wrap)
def SetupBindings(self):
self.Bind(wx.EVT_BUTTON, self.Go, self.gobtn)
def Go(self, event):
progress = 0
self.statbar.setprogress(progress)
self.Update()
numwords = 100000
for i in range(1, numwords + 1):
progress = int(((float(i) / float(numwords)) * 100) - 1)
self.wrdlst.Append("test " + str(i))
self.statbar.setprogress(progress)
self.Update()
self.wrdlst.Refresh()
progress = 100
self.statbar.setprogress(progress)
class App(wx.App):
def __init__(self, *args, **kwargs):
super(App, self).__init__(*args, **kwargs)
framestyle = wx.MINIMIZE_BOX|wx.CLOSE_BOX|wx.CAPTION|wx.SYSTEM_MENU|\
wx.CLIP_CHILDREN
self.frame = MainFrame(None, title="test", style=framestyle)
self.SetTopWindow(self.frame)
self.frame.Center()
self.frame.Show()
if __name__ == "__main__":
app = App()
app.MainLoop()
编辑2:下面是一个更简单的代码版本。我觉得我不能再缩小了。对我来说,它仍然有问题。我可以在IDLE中运行它,或者直接通过双击.py文件在Windows中运行,两种方式效果一样。
我尝试了不同的numwords
值。似乎问题并没有像我最初说的那样消失,而是当我增加numwords
时,进度条在调用print
之前能达到更高的进度。在当前值为1,000,000时,这个简化版本能达到大约50%。在上面的长版本中,值为1,000,000时能达到大约90%,值为100,000时能达到大约25%,而值为10,000时只能达到大约10%。
在下面的版本中,一旦调用print
,进度就会继续,甚至在循环结束时能达到99%。在原始版本中,调用self.wrdlst.Refresh()
时,当numwords
值较高时需要几秒钟,这可能导致进度条暂停。所以我认为事情是这样的:在循环中,进度条只能达到某个点,当循环结束时,函数继续执行,而进度条保持不动,等到函数结束时,进度条才继续,直到达到99%。因为print
语句不会花太多时间,下面的版本让人感觉进度条从0%平滑移动到99%,但print
却暗示了其他情况。
import wx
class MainFrame(wx.Frame):
def __init__(self, *args, **kwargs):
super(MainFrame, self).__init__(*args, **kwargs)
self.panel = wx.Panel(self)
self.gobtn = wx.Button(self.panel, label="Go")
self.prog = wx.Gauge(self, style=wx.GA_HORIZONTAL)
wrap = wx.BoxSizer()
wrap.Add(self.gobtn, 0, wx.EXPAND|wx.ALL, 10)
wrap.Add(self.prog, 0, wx.EXPAND|wx.ALL, 10)
self.panel.SetSizer(wrap)
self.panel.Fit()
self.SetInitialSize()
self.Bind(wx.EVT_BUTTON, self.Go, self.gobtn)
def Go(self, event):
numwords = 1000000
self.prog.SetValue(0)
for i in range(1, numwords + 1):
progress = int(((float(i) / float(numwords)) * 100) - 1)
self.prog.SetValue(progress)
print "Done"
if __name__ == "__main__":
app = wx.App()
frame = MainFrame(None)
frame.Show()
app.MainLoop()
1 个回答
其实,你的长时间运行的任务正在阻塞图形界面线程。这可能在某些平台和/或电脑上运行得不错,也可能不行。
import wx
from wx.lib.delayedresult import startWorker
class MainFrame(wx.Frame):
def __init__(self, *args, **kwargs):
super(MainFrame, self).__init__(*args, **kwargs)
self.panel = wx.Panel(self)
self.gobtn = wx.Button(self.panel, label="Go")
self.prog = wx.Gauge(self, style=wx.GA_HORIZONTAL)
self.timer = wx.Timer(self)
wrap = wx.BoxSizer()
wrap.Add(self.gobtn, 0, wx.EXPAND|wx.ALL, 10)
wrap.Add(self.prog, 0, wx.EXPAND|wx.ALL, 10)
self.panel.SetSizer(wrap)
self.panel.Fit()
self.SetInitialSize()
self.Bind(wx.EVT_BUTTON, self.Go, self.gobtn)
self.Bind(wx.EVT_TIMER, self.OnTimer, self.timer)
def Go(self, event):
# Start actual work in another thread and start timer which
# will periodically check the progress and draw it
startWorker(self.GoDone, self.GoCompute)
self.progress = 0
self.timer.Start(100)
def OnTimer(self, event):
# Timer draws the progress
self.prog.SetValue(self.progress)
def GoCompute(self):
# This method will run in another thread not blocking the GUI
numwords = 10000000
self.prog.SetValue(0)
for i in range(1, numwords + 1):
self.progress = int(((float(i) / float(numwords)) * 100) - 1)
def GoDone(self, result):
# This is called when GoCompute finishes
self.prog.SetValue(100)
self.timer.Stop()
print "Done"
if __name__ == "__main__":
app = wx.App()
frame = MainFrame(None)
frame.Show()
app.MainLoop()
另外,注意和你的例子相反:
- 按钮在点击后会恢复到未点击状态
- 你可以移动窗口,它不会卡住
一般来说,像这样的方法 def Something(self, event)
应该只运行几毫秒。
编辑:我还观察到在Windows 7上,进度条开始增长是在你调用 self.prog.SetValue()
的时候,它会在一段时间内慢慢增长到指定的值。它不会“跳”到那个值,而是慢慢增长到设定的值。这似乎是Windows 7的一个特性。我不得不在性能选项中关闭“在窗口内部动画控件和元素”来消除这种行为。