wx.Gauge在Windows中无法更新超过25%,在Linux中运行

时间:2011-05-14 16:54:18

标签: python windows wxpython

我似乎只有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)来替换打印,但即使睡眠时间过短,这个过程仍然会停止,如果我进一步降低它,问题就会恢复,所以它几乎没有用处。 / p>

我无法弄清楚发生了什么或者如何修复它,但是我觉得在Windows下移动速度太快以至于progress在一段时间后没有正确更新并且只是停留在固定值(~25)。我不知道为什么会这样,但对我来说没有意义。当然,printsleep都不是好的解决方案。即使我打印出“没有”,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中运行它,或者直接双击Windows中的.py文件,无论哪种方式都是一样的。

我尝试了numwords的各种值。看起来这个问题实际上并没有像我刚才所说的那样消失,相反,当我增加numwords时,测量仪会在调用print之前越来越远。当前值为1.000.000,这个较短版本达到约50%。在上面的较长版本中,1.000.000的值达到约90%,100.000的值达到约25%,而10.000的值仅达到约10%。

在下面的版本中,一旦调用了print,即使循环必须在那时结束,进度仍会继续并达到99%。在原始版本中,对self.wrdlst.Refresh()的调用(当numwords为高时需要几秒钟)必须导致仪表暂停。所以我认为发生的事情是这样的:在环路中,仪表仅到达某一点,当环路退出时,功能继续,而仪表保持静止,当功能退出时,仪表继续,直到达到99%。因为打印声明不需要花费很多时间,所以下面的版本使得仪表看起来像是从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 个答案:

答案 0 :(得分:2)

因此,实际上,您正在阻止GUI线程由您长时间运行的任务。它可能会在某些平台和/或计算机上正常运行。

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的功能。我不得不在性能选项中关闭“Animate控件和窗口内的元素”来摆脱这种行为。