wx.Gauge не обновляется больше 25% в Windows, работает в Linux - PullRequest
3 голосов
/ 14 мая 2011

У меня, кажется, нет ничего, кроме проблем с 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)

Вызовы self.Update(), по-видимому, необходимы в Linux, в противном случае датчик не обновляется до тех пор, пока функция не выйдет, что делает его несколько бессмысленным.Похоже, что эти вызовы не действуют в Windows (по крайней мере, Win 7).

Все это прекрасно работает в Linux (с вызовами Update ()), но в Windows 7 индикатор, кажется, останавливается вокруг20-25% отметки, за некоторое время до выхода из функции. Таким образом, она движется, как и должно, пока не достигнет ~ 25%, затем датчик прекращает движение без видимой причины, но функция продолжает работать нормально и выходит с надлежащим выходом.

В попытке выяснить проблему я попытался вставить строку print progress непосредственно перед обновлением датчика внутри цикла, thInking, возможно, значение progress не то, что я думал, что должно быть.К моему большому удивлению, датчик теперь работал как надо, но как только я уберу этот print, он перестанет работать.Я также могу заменить печать вызовом time.sleep(0.001), но даже при таком коротком сне процесс по-прежнему почти останавливается, и если я опускаю его еще больше, проблема возвращается, так что это вряд ли очень полезно.

Я не могу понять, что происходит или как это исправить, но я думаю, что как-то в Windows все происходит слишком быстро, так что progress не обновляется должным образом через некоторое время и просто остается на фиксированном значении(~ 25).Я понятия не имею, почему это было бы, однако, это не имеет смысла для меня.И, конечно же, ни print, ни sleep не являются хорошими решениями.Даже если я распечатываю «ничего», Windows все равно открывает другое окно для несуществующего вывода, что раздражает.

Дайте мне знать, если вам нужна дополнительная информация или код.

Edit: Хорошо, вот рабочее приложение, которое (по крайней мере для меня) имеет проблему.Это все еще довольно долго, но я попытался вырезать все, что не связано с проблемой, под рукой.

Он работает в Linux, как и все приложение.Под Windows он либо не работает, либо работает в зависимости от значения numwords в функции Go.Если я увеличу его значение до 1000000 (1 миллион), проблема исчезнет.Я подозреваю, что это может зависеть от системы, поэтому, если она работает, попробуйте настроить значение 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%. Поскольку утверждение печати не занимает много времени, в приведенной ниже версии создается впечатление, что датчик плавно перемещается от 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 Ответ

2 голосов
/ 18 мая 2011

Итак, на самом деле, вы блокируете поток 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.Мне пришлось отключить «Анимация элементов управления и элементов внутри окон» в параметрах производительности, чтобы избавиться от этого поведения.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...