我想以特定的时间间隔更改GUI中显示的文本。经过大量的方法,我发现,特别是根据我的要求,我必须使用time.sleep()
而不是wx.Timer
,但是time.sleep()
会冻结完整的GUI。这是我的代码示例:
import wx
import time
DWELL_TIMES = [1, 2, 1, 3]
SCREEN_STRINGS = ['nudge nudge', 'wink wink', 'I bet she does', 'say no more!']
class DM1(wx.Frame):
def __init__(self, *args, **kwargs):
wx.Frame.__init__(self, *args, **kwargs)
panel = wx.Panel(self)
text_display = wx.StaticText(panel, pos = (400, 150))
for dwell_time in DWELL_TIMES:
text_display.SetLabel(SCREEN_STRINGS[dwell_time])
time.sleep(float(DWELL_TIMES[dwell_time]))
app = wx.App()
DM1Frame = DM1(None, size = (800, 600))
DM1Frame.Center()
DM1Frame.Show()
app.MainLoop()
有人知道为什么会这样,以及如何使GUI不阻止?
我想Threading
可以帮助我,不是吗?如果是,那么将线程置于此代码中的正确方法是什么?是否有替代Threading
?
非常感谢!
答案 0 :(得分:3)
正如其他人所说,wx.CallAfter
和wx.CallLater
是您的朋友。研究它们并学习它们。以下是使用wx.CallLater
的完整实用示例。我认为合适的其他重构包括在内。
import wx
DATA = [
(1, 'nudge nudge'),
(2, 'wink wink'),
(1, 'I bet she does'),
(3, 'say no more!'),
]
class Frame(wx.Frame):
def __init__(self):
super(Frame, self).__init__(None)
panel = wx.Panel(self)
self.text = wx.StaticText(panel)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.AddStretchSpacer(1)
sizer.Add(self.text, 0, wx.ALIGN_CENTER)
sizer.AddStretchSpacer(1)
panel.SetSizer(sizer)
self.index = 0
self.update()
def update(self):
duration, label = DATA[self.index]
self.text.SetLabel(label)
self.index = (self.index + 1) % len(DATA)
wx.CallLater(int(duration * 1000), self.update)
if __name__ == '__main__':
app = wx.App(None)
frame = Frame()
frame.SetTitle('Example')
frame.SetSize((400, 300))
frame.Center()
frame.Show()
app.MainLoop()
答案 1 :(得分:2)
如果查看time.sleep()的文档,您会发现它基本上阻止了指定时间间隔内该线程的执行。问题是目前你的GUI只有一个线程,所以如果你阻塞线程,那么你阻止该线程中的所有执行。这意味着,正如您所经历的那样,GUI在睡眠期间无法使用。
即使使用线程,time.sleep()
调用也不能与GUI在同一个线程中,因此在睡眠结束后尝试让GUI刷新将非常复杂。除此之外,它基本上重新实现了wx.Timer
!不能重做已经为你做过的事情。
在我看来,你的问题应该更少“我如何让睡眠起作用?”以及“为什么wx.Timer
没有正常工作?”请详细解释您使用wx.Timer
时遇到的问题。为什么它不起作用?也许发布一些代码。我的猜测是你可能没有正确绑定wx.EVT_TIMER
。看看this tutorial。
答案 2 :(得分:2)
将线程置于此代码中的正确方法是什么?
虽然使用wx.Timer
是这个简化示例的正确解决方案,但如果您的真正目标是知道如何使用工作线程执行长任务并在不冻结整个应用程序的情况下更新主GUI,这里是如何:
import wx
import threading
import time
class WorkerThread(threading.Thread):
DWELL_TIMES = [1, 2, 1, 3]
SCREEN_STRINGS = ['nudge nudge', 'wink wink', 'I bet she does', 'say no more!']
def __init__(self, window):
threading.Thread.__init__(self)
self.window = window
def run(self):
for i in range(len(WorkerThread.DWELL_TIMES)):
wx.CallAfter(self.window.set_text, WorkerThread.SCREEN_STRINGS[i])
time.sleep(float(WorkerThread.DWELL_TIMES[i]))
wx.CallAfter(self.window.close)
class DM1(wx.Frame):
def __init__(self, *args, **kwargs):
wx.Frame.__init__(self, *args, **kwargs)
panel = wx.Panel(self)
self.text_display = wx.StaticText(panel, pos = (400, 150))
self.kickoff_work()
def kickoff_work(self):
t = WorkerThread(self)
t.start()
def set_text(self, text):
self.text_display.SetLabel(text)
def close(self):
self.Close()
app = wx.App()
DM1Frame = DM1(None, size = (800, 600))
DM1Frame.Center()
DM1Frame.Show()
app.MainLoop()
答案 3 :(得分:0)
您可能会尝试创建一个全局变量来获取第一次启动时的时间,然后让第二个变量获取当前时间,看看两次是否相距足够远。像这样:
当文字改为新内容时,
global timestart
timestart = gettime()
然后,在那里检查您是否要更改代码,
timestop = gettime()
if timestop - timestart >= timebetweenchanges:
change code
答案 4 :(得分:0)
我不明白为什么你不能使用计时器。它们似乎是出于您需要它们的确切目的而制造的。正如已经提到的那样,我在这个主题上写了tutorial。
他完全正确。使用time.sleep()将冻结GUI,因为它会阻止wx的主事件循环。如果你绝对必须使用time.sleep()(我怀疑),那么你可以使用一个线程。我也就这个问题写了tutorial。实际上,我实际上在该示例中使用了time.sleep()。
答案 5 :(得分:0)
我可能会建议您使用wx.CallLater
。请参阅官方文档:http://wxpython.org/docs/api/wx.CallLater-class.html
wx.Timer的便捷类,它调用给定的可调用对象 一旦达到给定的毫秒数,通过任何位置或 关键字args。可调用的返回值在它之后是可用的 已经使用GetResult方法运行。
如果您不需要获取返回值或重新启动计时器 没有必要持有对这个对象的引用。它将持有一个 定时器运行时引用自身(定时器有一个 参考self.Notify)但定时器时循环将被打破 完成后,自动清理wx.CallLater对象。
可以在此问题中找到可能的进一步参考:Using wx.CallLater in wxPython