wx.Gauge 在 Windows 中更新超过 25% 时失败,在 Linux 中工作

发布于 2024-11-07 06:04:04 字数 6888 浏览 4 评论 0原文

我似乎只有 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 下似乎没有任何作用。 (至少赢 7 场)

。整个过程在 Linux 下完美运行(通过调用 Update()),但在 Windows 7 上,仪表似乎在函数退出前一段时间停止在 20-25% 标记附近,因此它会按预期移动,直到达到〜。 25%,然后仪表无明显原因停止移动,但功能继续正常运行并以正确的输出退出。

在尝试找出问题时,我尝试插入一个 print Progress 行。在更新循环内的仪表之前,思考也许progress 的值不是我想象的那样。令我大吃一惊的是,仪表现在可以正常工作,但当我删除打印时,它就停止工作了。我还可以用对 time.sleep(0.001) 的调用来替换打印,但即使睡眠时间如此短,该过程仍然几乎停止,如果我进一步降低它,问题就会返回,所以这几乎没有什么帮助。

我不知道发生了什么或如何修复它,但我猜想在 Windows 下事情进展得太快,以至于 progress 一段时间后无法正确更新,只是停留在固定值(~25)。我不知道为什么会这样,但是,这对我来说毫无意义。当然,printsleep 都不是好的解决方案。即使我打印出“无”,Windows 仍然会打开另一个窗口来显示不存在的输出,这很烦人。

如果您需要更多信息或代码,请告诉我。

编辑:好的,这是一个工作应用程序(至少对我来说)有问题。它仍然很长,但我尝试删除与当前问题无关的所有内容。

它可以在 Linux 上运行,就像完整的应用程序一样。在 Windows 下,它要么失败,要么有效,具体取决于 Go 函数中 numwords 的值。如果我将其值增加到 1000000(100 万),问题就会消失。我怀疑这可能取决于系统,因此如果它适用,请尝试调整 numwords 的值。也可能是因为我更改了它,所以它是静态文本,而不是像原始代码中那样调用生成器。

尽管如此,对于我来说,使用 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%。由于 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()

I seem to have nothing but trouble with wxPython and cross-platform compatibility :(

I have the function below. It's called when the user clicks a button, it does some work which may take a while, during which a progress gauge is shown in the status bar.

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)

The calls to self.Update() are apparently needed under Linux, otherwise the gauge doesn't update until the function exits which makes it kinda pointless. These calls seem to have no effect under Windows (Win 7 at least).

The whole thing works perfectly under Linux (with the calls to Update()), but on Windows 7 the gauge seems to stop around the 20-25% mark, a while before the function exits. So it moves as it should until it reaches ~25%, then the gauge stops moving for no apparent reason but the function continues on just fine and exits with the proper output.

In my attempt to find out the problem, I tried inserting a print progress line just before updating the gauge inside the loop, thinking maybe the value of progress wasn't what I thought it should be. To my big surprise, the gauge now worked as it should, but the moment I remove that print it stops working. I can also replace the print with a call to time.sleep(0.001), but even with such a short sleep the process still grinds to almost a halt, and if I lower it even further the problem returns, so it's hardly very helpful.

I can't figure out what is going on or how to fix it, but I guess somehow things move too fast under Windows so that progress doesn't get updated properly after a while and just stays at a fixed value (~25). I have no idea why that would be, however, it makes no sense to me. And of course, neither print nor sleep are good solutions. Even if I print out "nothing", Windows still opens another window for the non-existent output, which is annoying.

Let me know if you need further info or code.

Edit: Ok, here's a working application which (for me at least) has the problem. It's still pretty long, but I tried to cut out everything not related to the problem at hand.

It works on Linux, just like the complete app. Under Windows it either fails or works depending on the value of numwords in the Go function. If I increase its value to 1000000 (1 million) the problem goes away. I suspect this may depend on the system, so if it works for you try to tweak the value of numwords. It may also be because I changed it so it Append()s a static text rather than calling a generator as it does in the original code.

Still, with the current value of numwords (100000) it does fail on Windows for me.

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()

Edit 2: Below is an even simpler version of the code. I don't think I can make it much smaller. It still has the problem for me. I can run it from within IDLE, or directly by double clicking the .py file in Windows, either way works the same.

I tried with various values of numwords. It seems the problem doesn't actually go away as I first said, instead when I increase numwords the gauge just reaches further and further before the print is called. At the current value of 1.000.000 this shorter version reaches around 50%. In the longer version above, a value of 1.000.000 reaches around 90%, a value of 100.000 reaches around 25%, and a value of 10.000 only reaches around 10%.

In the version below, once the print is called, the progress continues on and reaches 99% even though the loop must have ended by then. In the original version the call to self.wrdlst.Refresh(), which takes a few seconds when numwords is high, must have caused the gauge to pause. So I think that what happens is this: In the loop the gauge only reaches a certain point, when the loop exits the function continues on while the gauge stays still, and when the function exits the gauge continues on until it reaches 99%. Because a print statement doesn't take a lot of time, the version below makes it seem like the gauge moves smoothly from 0% to 99%, but the print suggests otherwise.

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()

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

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

发布评论

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

评论(1

轻许诺言 2024-11-14 06:04:04

因此,实际上,长时间运行的任务阻塞了 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 的功能。我必须在性能选项中关闭“动画控件和窗口内的元素”才能摆脱这种行为。

So, actually, you are blocking the GUI thread by your long running task. It may and may not run fine on some platforms and/or computers.

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()

Also notice that contrary your example:

  • Button goes back to unclicked state after clicked
  • You can move the window and it will not freeze

As a rule of thumb, every method which looks like this def Something(self, event) should run just a few milliseconds.

EDIT: Another thing what I have observed on Windows 7. The gauge starts to grow at the time you call self.prog.SetValue() and grows in some time to specified value. It does not "jump" to that value, rather it grows slowly to hit set value. It seems to be Windows 7 feature. I had to switch off "Animate controls and element inside windows" in performance options to get rid of this behavior.

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