请考虑以下代码:
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中显示旋转条几秒钟。但是,运行此代码时,应用程序会暂停一段时间,最后只有完成循环后才会打印系列中的最后一个字符。我怎样才能调整这段代码看旋转?是否有某种冲洗方法(或其他技巧)可以实现这一点?
由于
答案 0 :(得分:1)
你不能使用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秒后停止动画。我更改了更新,就像使用原始代码时一样,它会在文本控件中放入一堆字符,而不是只旋转一个。
答案 1 :(得分:1)
这里有一些方便的装饰器,允许你的方法被线程化。
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()
答案 2 :(得分:0)
以下是我最终使用的代码示例。这是Mike Driscoll和Yoriz的答案的组合。他们的答案都很好,对于提出的问题也是有效的。但是,只要旋转角色需要指示正在进行的计算,它们就会停止工作。为了防止这种情况,我基本上开始了两个线程,一个处理实际计算,另一个处理旋转字符。计算完成后,其线程将事件发布到主框架。后者可以使用此事件来报告计算结果并中止进度线程。进度线程依次定期发布事件(由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()