Есть ли что-то вроде метода wxpython TextCtrl flush?

Пожалуйста, рассмотрите следующий код:

    import wx
    import time

    class MyFrame(wx.Frame):
        def __init__(self,parent,id,title):
            wx.Frame.__init__(self,parent,id,title)
            self.txtu = wx.TextCtrl(self, -1)
            btnco = wx.Button(self, -1,"Rotate",pos=(0,30))
            self.Bind(wx.EVT_BUTTON, self.OnCo, id = btnco.GetId() )

        def OnCo(self,event):
            self.txtu.write("|")
            chars = ['|','/','-','\\']
            counter = 1
            for i in range(60):
                self.txtu.Replace(0, 1, chars[counter])
                counter += 1
                counter %= 4
                time.sleep(0.1)

    app = wx.App()
    frame = MyFrame(None,-1,"TextCtrl Problem")
    frame.Show()
    app.MainLoop()

Моя цель - визуализировать вращающуюся панель в TextCtrl в течение нескольких секунд при нажатии кнопки. Однако при запуске этого кода приложение блокируется некоторое время и, наконец, после завершения цикла печатается только последний символ в серии. Как я мог адаптировать этот код, чтобы увидеть вращение? Есть ли какой-то метод сброса (или другой прием), который бы позволил это?

Спасибо

3 ответа

Решение

Вот пример кода, который я наконец-то использовал. Это сочетание ответов Майка Дрисколла и Йориса. Оба их ответа хороши и верны для вопроса, который был задан. Однако они перестают работать всякий раз, когда вращающийся символ должен указывать на выполняемый расчет. Чтобы предотвратить это, я в основном запускаю два потока, один из которых связан с фактическим вычислением, а другой - с вращающимся персонажем. Когда вычисление закончено, его поток публикует событие в главном фрейме. Последний может использовать это событие, чтобы сообщить о результате расчета и прервать поток выполнения. Поток прогресса в свою очередь регулярно публикует события (опосредованные wx.Timer) для обновления TextCtrl.

Код следующий:

import wx
from threading import Thread
from itertools import cycle

# Define notification event for thread completion
EVT_RESULT_ID = wx.NewId()
EVT_PROGRESSUPDATE_ID = wx.NewId()

def EVT_RESULT(win, func):
    """Define Result Event."""
    win.Connect(-1, -1, EVT_RESULT_ID, func)

class ResultEvent(wx.PyEvent):
    """Simple event to carry arbitrary result data."""
    def __init__(self, data):
        wx.PyEvent.__init__(self)
        self.SetEventType(EVT_RESULT_ID)
        self.data = data

def EVT_PROGRESSUPDATE(win, func):
    win.Connect(-1,-1,EVT_PROGRESSUPDATE_ID,func)

class ProgressUpdateEvent(wx.PyEvent):
    def __init__(self,data):
        wx.PyEvent.__init__(self)
        self.SetEventType(EVT_PROGRESSUPDATE_ID)
        self.data = data

# Thread class that shows progress
class ProgressThread(Thread):
    def __init__(self,notify_window):
        Thread.__init__(self)
        self._notify_window = notify_window
        self.chars = cycle(('|','/','-','\\'))

        self.tinyTimer = wx.Timer(notify_window)
        notify_window.Bind(wx.EVT_TIMER, self.updateTextCtrl, self.tinyTimer)
        self.tinyTimer.Start(100)

    def updateTextCtrl(self,event):
        wx.PostEvent(self._notify_window, ProgressUpdateEvent(next(self.chars)))

    def abort(self):
        self.tinyTimer.Stop()
        return

# Thread class that executes processing
class WorkerThread(Thread):
    """Worker Thread Class."""
    def __init__(self, notify_window):
        """Init Worker Thread Class."""
        Thread.__init__(self)
        self._notify_window = notify_window
        self.start()

    def run(self):
        """Run Worker Thread."""
        x = 0
        for i in range(100000000):
            x += i
        wx.PostEvent(self._notify_window, ResultEvent(x))

class MainFrame(wx.Frame):
    def __init__(self, parent, id):
        wx.Frame.__init__(self, parent, id, 'Thread Test')

        self.start_button = wx.Button(self, -1, 'Start', pos=(0,0))
        self.progress = wx.TextCtrl(self,-1,'',pos=(0,50))
        self.status = wx.StaticText(self, -1, '', pos=(0,100))
        self.Bind(wx.EVT_BUTTON, self.OnStart, self.start_button)

        # Set up event handlers
        EVT_RESULT(self,self.OnResult)
        EVT_PROGRESSUPDATE(self,self.OnProgressUpdate)

        # And indicate we don't have a worker thread yet
        self.worker = None

    def OnStart(self, event):
        """Start Computation."""
        # Trigger the worker thread unless it's already busy
        if not self.worker:
            self.status.SetLabel('Starting computation')
            self.worker = WorkerThread(self)
            self.p = ProgressThread(self)

    def OnResult(self, event):
        """Show Result status."""
        self.p.abort()
        self.status.SetLabel('Computation Result: %s' % event.data)
        self.worker = None

    def OnProgressUpdate(self,event):
        self.progress.ChangeValue(event.data)

class MainApp(wx.App):
    def OnInit(self):
        self.frame = MainFrame(None, -1)
        self.frame.Show(True)
        self.SetTopWindow(self.frame)
        return True

if __name__ == '__main__':
    app = MainApp(0)
    app.MainLoop()

Вот несколько удобных декораторов, позволяющих использовать ваши методы.

import wx
import time
from functools import wraps
from threading import Thread
from itertools import cycle


def runAsync(func):
    '''Decorates a method to run in a separate thread'''
    @wraps(func)
    def wrapper(*args, **kwargs):
        func_hl = Thread(target=func, args=args, kwargs=kwargs)
        func_hl.start()
        return func_hl
    return wrapper


def wxCallafter(target):
    '''Decorates a method to be called as a wxCallafter'''
    @wraps(target)
    def wrapper(*args, **kwargs):
        wx.CallAfter(target, *args, **kwargs)
    return wrapper


class MyFrame(wx.Frame):
    def __init__(self, parent, id_, title):
        wx.Frame.__init__(self, parent, id_, title)
        panel = wx.Panel(self)
        self.txtu = wx.TextCtrl(panel, -1)
        btnco = wx.Button(panel, -1, "Rotate", pos=(0, 30))
        btnco.Bind(wx.EVT_BUTTON, self.onBtn)

    @wxCallafter
    def setTextu(self, value):
        self.txtu.ChangeValue(value)

    @runAsync
    def onBtn(self, event):
        chars = cycle(('|', '/', '-', '\\'))
        for _ in range(60):
            if not self:  # Stops if the frame has been destroyed
                return
            self.setTextu(next(chars))
            time.sleep(0.1)

app = wx.App()
frame = MyFrame(None, -1, "TextCtrl Problem")
frame.Show()
app.MainLoop()

Вы не можете использовать time.sleep(), так как он блокирует основной цикл wxPython. Вместо этого вы должны использовать wx.Timer. Я изменил ваш код, чтобы использовать их следующим образом:

import wx

class MyFrame(wx.Frame):

    #----------------------------------------------------------------------
    def __init__(self,parent,id,title):
        wx.Frame.__init__(self,parent,id,title)
        panel = wx.Panel(self)
        self.counter = 1

        self.txtu = wx.TextCtrl(panel)
        btnco = wx.Button(panel, -1,"Rotate",pos=(0,30))
        self.Bind(wx.EVT_BUTTON, self.OnCo, id = btnco.GetId() )

        self.tinyTimer = wx.Timer(self)
        self.Bind(wx.EVT_TIMER, self.updateTextCtrl, self.tinyTimer)

        self.sixtyTimer = wx.Timer(self)
        self.Bind(wx.EVT_TIMER, self.onSixty, self.sixtyTimer)

    #----------------------------------------------------------------------
    def OnCo(self,event):
        self.tinyTimer.Start(100)
        self.sixtyTimer.Start(6000)

    #----------------------------------------------------------------------
    def onSixty(self, event):
        """
        Stop the timers and the animation
        """
        self.tinyTimer.Stop()
        self.sixtyTimer.Stop()

    #----------------------------------------------------------------------
    def updateTextCtrl(self, event):
        """
        Update the control so it appears to be animated
        """
        self.txtu.write("|")
        chars = ['|','/','-','\\']

        self.txtu.Clear()
        self.txtu.SetValue(chars[self.counter])
        self.counter += 1
        self.counter %= 4

#----------------------------------------------------------------------
app = wx.App()
frame = MyFrame(None,-1,"TextCtrl Problem")
frame.Show()
app.MainLoop()

Обратите внимание, что нам нужны два таймера. Один обновляет отображение каждый раз, а другой останавливает анимацию через X секунд. В этом случае я сказал, чтобы остановить анимацию через 6 секунд. Я также немного изменил обновление, так как при использовании вашего исходного кода в текстовый элемент управления вместо набора крутится группа символов.

Другие вопросы по тегам