Python Tkinter管理我的事件循环和我的主循环

Python Tkinter管理我的事件循环和我的主循环,python,loops,user-interface,widget,tkinter,Python,Loops,User Interface,Widget,Tkinter,我一直在慢慢地学习Tkinter和面向对象编程,但我已经把自己编程到了这个角落。请原谅我在这一点上缺乏批判性思维,但我问过我认识的每一个比我更了解python的人,我们在这里无法找到有效的解决方案 我有一个我正在开发的gui应用程序,它允许用户输入股票符号,为每个符号创建新标签,然后定期更新每个标签。(有点像一个非常基本的etrade应用程序或其他东西)。我发现在没有gui的情况下很容易做到这一点,因为我可以说: while True: sPrice = get_stock_price(s

我一直在慢慢地学习Tkinter和面向对象编程,但我已经把自己编程到了这个角落。请原谅我在这一点上缺乏批判性思维,但我问过我认识的每一个比我更了解python的人,我们在这里无法找到有效的解决方案

我有一个我正在开发的gui应用程序,它允许用户输入股票符号,为每个符号创建新标签,然后定期更新每个标签。(有点像一个非常基本的etrade应用程序或其他东西)。我发现在没有gui的情况下很容易做到这一点,因为我可以说:

while True:
   sPrice = get_stock_price(s)
   print sPrice
但我已经将get_stock_price(s)函数绑定到一个按钮上,该按钮生成一个子帧和包含在其中的标签。我面临的问题是标签不会更新。一位朋友建议只添加另一种方法来更新标签,但是我知道如何持续更新标签的唯一方法是执行

while True:
    # get new price
    # update the label
    # time.sleep(~1 minute?)
这会导致我的gui窗口永久冻结和旋转。 我一直在阅读与这一特殊情况相关的所有其他线索,我看到了许多不同的建议;不要在主线程中调用sleep,不要使用root.update,使用events,调用root.something.after(500,函数),我已经尝试过实现。我留下的是一个弗兰肯斯坦式的代码,它仍然会检索我的股票价值,但不会更新它们,还有一些方法我不知道如何更新,或者在代码中调用到哪里

我所希望的是(我知道,可能是很长的时间。对不起!)对我做错了什么的解释,以及如何修复它的建议。我真的希望自己能理解并解决这个问题,但只要能解释代码解决方案,它们就会很棒

提前谢谢你

PS:这是我到目前为止的代码:

from Tkinter import *
import urllib
import re
import time

class MyApp(object):
    def __init__(self, parent):
        self.myParent = parent
        self.myContainer1 = Frame(parent)
        self.myContainer1.pack()
        self.createWidgets()
        button1 = Button(self.myContainer1, command = self.addStockToTrack)
        self.myContainer1.bind("<Return>", self.addStockToTrack)
        button1.configure(text = "Add Symbol")
        button1.pack()  

    def createWidgets(self):
        # title name
        root.title("Stock App")
        # creates a frame inside myContainer1
        self.widgetFrame = Frame(self.myContainer1)
        self.widgetFrame.pack()
        # User enters stock symbol here:
        self.symbol = Entry(self.widgetFrame) 
        self.symbol.pack()
        self.symbol.focus_set()

    def addStockToTrack(self):
        s = self.symbol.get()
        labelName = str(s) + "Label"
        self.symbol.delete(0, END)
        stockPrice = get_quote(s)
        self.labelName = Label(self.myContainer1, text = s.upper() + ": " + str(stockPrice))
        self.labelName.pack()
        self.myContainer1.after(500, self.get_quote)

    def updateStock(self):
        while True:
            labelName = str(s) + "Label"
            stockPrice = get_quote(s)
            self.labelName = Label(self.myContainer1, text = s.upper() + ": " + str(stockPrice))
            self.labelName.pack()
            time.sleep(10)

def get_quote(symbol):
    base_url = 'http://finance.google.com/finance?q='
    content = urllib.urlopen(base_url + symbol).read()
    m = re.search('id="ref_\d*_l".*?>(.*?)<', content)
    if m:
        quote = m.group(1)
    else:
        quote = 'Not found: ' + symbol
    return quote

root = Tk()
myapp = MyApp(root)
root.mainloop()
从Tkinter导入*
导入URL库
进口稀土
导入时间
类MyApp(对象):
定义初始化(自身,父级):
self.myParent=parent
self.myContainer1=帧(父级)
self.myContainer1.pack()
self.createWidgets()
button1=按钮(self.myContainer1,command=self.addStockToTrack)
self.myContainer1.bind(“,self.addStockToTrack)
按钮1.配置(text=“添加符号”)
按钮1.pack()
def createWidgets(自):
#书名
root.title(“股票应用程序”)
#在myContainer1内创建一个框架
self.widgetFrame=Frame(self.myContainer1)
self.widgetFrame.pack()
#用户在此处输入股票符号:
self.symbol=条目(self.widgetFrame)
self.symbol.pack()
self.symbol.focus_set()
def addStockToTrack(自身):
s=self.symbol.get()
labelName=str(s)+“标签”
self.symbol.delete(0,结束)
股票价格=获取报价
self.labelName=Label(self.myContainer1,text=s.upper()+“:”+str(股票价格))
self.labelName.pack()
self.myContainer1.after(500,self.get_quote)
def updateStock(自我):
尽管如此:
labelName=str(s)+“标签”
股票价格=获取报价
self.labelName=Label(self.myContainer1,text=s.upper()+“:”+str(股票价格))
self.labelName.pack()
时间。睡眠(10)
def get_报价(符号):
基本url=http://finance.google.com/finance?q='
content=urllib.urlopen(base_url+symbol).read()

m=re.search('id=“ref\ud*\u l.”*?>(.*)您正在寻找线程。 将要运行的事件放在另一个线程中。请参见此示例:

import thread, time
def myfunc(a1,a2):
    while True:
      print a1,a2
      time.sleep(1)
thread.start_new_thread(myfunc,("test","arg2")
tkroot.mainloop()
现在您有了一个与Tkinter窗口一起运行的函数,该窗口每秒打印一次参数

编辑:我不知道为什么会有这么多的反对票。Tkinter在线程方面做得很好,我已经多次使用这个技巧,没有出现任何问题。请参见此示例:

import thread, time
def myfunc(a1,a2):
    while True:
      print a1,a2
      time.sleep(1)
thread.start_new_thread(myfunc,("test","arg2")
tkroot.mainloop()
下载一个10 MB的文件并将进度记录到Tkinter窗口

无螺纹:

import urllib2,thread
import Tkinter as tk

class Example(tk.Frame):
    def __init__(self, parent):
        tk.Frame.__init__(self, parent)   

        self.parent = parent        
        self.initUI()

    def initUI(self):

        self.pack(fill=tk.BOTH, expand=1)

        canvas = tk.Canvas(self)

        self.text = canvas.create_text(18,18,anchor=tk.W,font="Purisa",text="Status: Press start to download...")
        but=tk.Button(text="Start",command=self.start)
        canvas.create_window((270,18),window=but)

        canvas.pack(fill=tk.BOTH, expand=1)
        self.canvas=canvas

    def start(self):
        #thread.start_new_thread(
        self.download("http://ipv4.download.thinkbroadband.com/10MB.zip","10mb.zip")
        #)

    def onEnd(self):
            self.canvas.itemconfig(self.text, text="Status: done!")

    def download(self,url,file_name):
        u = urllib2.urlopen(url) 
        f = open(file_name, 'wb')
        meta = u.info()
        file_size = int(meta.getheaders("Content-Length")[0])
        print "Downloading: %s Bytes: %s" % (file_name, file_size)

        file_size_dl = 0
        block_sz = 1024*50 #50 kb
        while True:
            buffer = u.read(block_sz)
            if not buffer:
                break

            file_size_dl += len(buffer)
            f.write(buffer)
            status = r"[%3.2f%%]" % (file_size_dl * 100. / file_size)
            self.canvas.itemconfig(self.text,text="Status: downloading..."+status)

        f.close()

        self.onEnd()

def main():
    root = tk.Tk()
    root.resizable(0,0)
    ex = Example(root)
    root.geometry("300x70")
    root.mainloop()  

main()
窗口冻结,直到下载完成

带螺纹:

import urllib2,thread
import Tkinter as tk

class Example(tk.Frame):
    def __init__(self, parent):
        tk.Frame.__init__(self, parent)   

        self.parent = parent        
        self.initUI()

    def initUI(self):

        self.pack(fill=tk.BOTH, expand=1)

        canvas = tk.Canvas(self)

        self.text = canvas.create_text(18,18,anchor=tk.W,font="Purisa",text="Status: Press start to download...")
        but=tk.Button(text="Start",command=self.start)
        canvas.create_window((270,18),window=but)

        canvas.pack(fill=tk.BOTH, expand=1)
        self.canvas=canvas

    def start(self):
        thread.start_new_thread(
        self.download("http://ipv4.download.thinkbroadband.com/10MB.zip","10mb.zip")
        )

    def onEnd(self):
            self.canvas.itemconfig(self.text, text="Status: done!")

    def download(self,url,file_name):
        u = urllib2.urlopen(url) 
        f = open(file_name, 'wb')
        meta = u.info()
        file_size = int(meta.getheaders("Content-Length")[0])
        print "Downloading: %s Bytes: %s" % (file_name, file_size)

        file_size_dl = 0
        block_sz = 1024*50 #50 kb
        while True:
            buffer = u.read(block_sz)
            if not buffer:
                break

            file_size_dl += len(buffer)
            f.write(buffer)
            status = r"[%3.2f%%]" % (file_size_dl * 100. / file_size)
            self.canvas.itemconfig(self.text,text="Status: downloading..."+status)

        f.close()

        self.onEnd()

def main():
    root = tk.Tk()
    root.resizable(0,0)
    ex = Example(root)
    root.geometry("300x70")
    root.mainloop()  

main()

不会冻结,文本会正常更新。

您已经有一个无限循环在运行,因此不应该尝试添加另一个循环。相反,您可以使用
after
方法使函数每隔一段时间重复调用一次。在您的情况下,您可以替换此方法:

def updateStock(self):
   while True:
        labelName = str(s) + "Label"
        stockPrice = get_quote(s)
        self.labelName = Label(self.myContainer1, text = s.upper() + ": " + str(stockPrice))
        self.labelName.pack()
        time.sleep(10)
……关于这一点:

def updateStock(self):
    labelName = str(s) + "Label"
    stockPrice = get_quote()
    self.labelName = Label(self.myContainer1, text = s.upper() + ": " + str(stockPrice))
    self.labelName.pack()
    self.after(10000, self.updateStock)
这将获得一个报价,添加一个标签,然后安排自己在10秒内(10000毫秒)再次被调用

但是,我怀疑您是否希望每10秒创建一个新标签,是吗?最终窗口将充满标签。相反,您可以创建一次标签,然后在每次迭代中更新标签。例如,在init中创建一次
self.label
,然后在循环中执行以下操作:

self.labelName.configure(text=s.upper() + ": " + str(stockPrice))

Tkinter不能很好地处理线程。打印(如您的示例中所示)工作得很好。但是,OP希望更新不那么直接的GUI。我一单击“开始”按钮,您的代码就会挂起,这并非意外。此代码将崩溃或不确定地挂起。它可能会运行数小时,也可能会在运行时崩溃几秒钟。Tkinter根本不是线程安全的。唯一可靠的解决方案是将更新GUI的工作放在一个线程安全队列上,主线程可以轮询并执行该队列。我并不是说线程对任何事情都不合适。然而,在这种情况下,OP显然是在下载少量的信息,而不是10MB的zip文件。无可否认,线程下载10mb zip文件时,这是一个合理的解决方案,但在许多情况下也很可能不使用线程来完成相同的工作。Nacib,感谢您建议使用线程。我原本打算在这个程序中使用线程,但对于我想用tkinter解决的问题,这似乎是一个复杂的解决方案。我的主要目标是理解和理解框架。不过,最终,我可能要做的是在这个程序中实现线程,因为每个股票都有股票对象从线程池中拉出来