Python - Clearing and resetting Text widgit - Tkinter - python

I'm having a heck of time clearing this text wigit. I'll admit that I'm new to python and its GUI API, but I've read the documention and tried the suggestions on Stack Overflow to no avail.
I've seen many people suggest: self.text.delete(0.0, 'end')
this however is not working in the listener. Oddly it does work if I put it in the constructor. I get not stack trace from the listener either. Below is the code:
import tkinter
from tkinter import Text
def main():
CalculatorGUI(CalculatorController())
class CalculatorController:
def __init__(self):
self.ans = "0"
def calculate(self, textBox):
value = str("")
try:
inputValue = textBox.replace(",", "").replace(" ", "")
if inputValue[:1] in "-*/+%":
value = str(eval(self.ans + inputValue))[::-1]
else:
value = str(eval(inputValue))[::-1]
return self.makeHumanReadable(value)
except:
return "I cannot math that!"
def makeHumanReadable(self, stringValue):
if "." in stringValue:
decimal = stringValue[:stringValue.index(".")]
integer = stringValue[stringValue.index(".") + 1:]
self.ans = (decimal + "." + (','.join(integer[i:i+3] for i in range(0, len(integer), 3))))[::-1]\
.replace(",", "").replace(" ", "")
print("Current answer is: " + self.ans)
return (decimal + "." + (','.join(integer[i:i+3] for i in range(0, len(integer), 3))))[::-1]
else:
self.ans = ','.join(stringValue[i:i+3] for i in range(0, len(stringValue), 3))[::-1] \
.replace(",", "").replace(" ", "")
return ','.join(stringValue[i:i+3] for i in range(0, len(stringValue), 3))[::-1]
class CalculatorGUI:
def __init__(self, controller):
self.controller = controller
self.root = tkinter.Tk()
self.frame1 = tkinter.Frame(self.root)
self.frame2 = tkinter.Frame(self.frame1)
self.text = BetterText(self.frame1, height=1, borderwidth=0)
self.text.insert(1.0, "Enter a math statement:")
# self.text.delete(0.0, 'end') # If this is not commented out, it deletes
# the text but not when put in the listener
# self.text.clearAll() # Same here
self.text.configure(state="disabled")
self.entry = tkinter.Entry(self.frame2, width = 30)
self.calcButton = tkinter.Button(self.frame2, text="Calculate", \
command=self.calculate)
self.text.pack()
self.entry.pack()
self.calcButton.pack()
self.frame1.pack()
self.frame2.pack()
self.root.mainloop()
def calculate(self):
self.entry.delete(0, "end")
self.text.clearAll() # Does not work
if self.entry.get() != "":
self.text.insert("END", self.controller.calculate(self.entry.get()))
main()
Any ideas??? Using Python 3.4
EDIT: I even tried extending the Text widget and making a clearAll() method. Again it works in the constructor but not in the listener and it throws no errors. Its likely that there is a problem somewhere else in the code and I just don't see it.
class BetterText(Text):
def __init__(self, master=None, cnf={}, **kw):
Text.__init__(self, master, kw)
def clearAll(self):
self.delete(0.0, 'end')

The problem seems to be that you are using the text.configure(state="disabled") and then trying to write to the widget. In python, disabling the text box means that the user can't edit it, but it also means you can't. The reason your commented out code worked was that it came before the line you disabled the text widget. Try adding text.configure(state="normal") in your clear method before you attempt to change the text widget, and then set the state back when you are done. That seemed to fix the problem when I tried it out.

Related

Removing a particular line in Text

I wrote a small program with Tkinter and user asked me to add a function in which user could able to delete a particular line.
My question is how to delete a particular line of text in Tkinter?
My code:
import tkinter as tk
class MainApplication(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.parent = parent
self.parent.state('zoomed')
self.Scrol = tk.Scrollbar(self.parent)
self.Text = tk.Text(self.parent, height=50, width=100)
self.Scrol.pack(side="right")
self.Text.pack()
self.Scrol.config(command=self.Text.yview)
self.Text.config(yscrollcommand=self.Scrol.set)
self.Text.tag_configure('red', foreground='red', underline=1)
self.count = 0
self.Text.insert('end', "{} test test test".format(str(self.count)) + "\n", 'red')
self.B = tk.Button(self.parent, text="add text", command=self.addText)
self.B.pack()
self.Text.bind("<Double-Button-1>", self.deleteLine)
def deleteLine(self, event):
self.Text.delete(1.0, 2.0)
def addText(self):
self.count += 1
self.Text.insert('end', "{} test test test".format(str(self.count)) + "\n", 'red')
root = tk.Tk()
app = MainApplication(root)
root.mainloop()
Now my code only deletes the first line every time when I click on any line. I would like to delete that line which I click on.
For deleting the current line the simplest way would probably be, as suggested in Bryan Oakley's comment, using 'current' argument in delete directly:
def deleteLine(self, event):
self.Text.delete('current linestart', 'current lineend+1c')
#return "break" # uncomment if you want to disable selection caused by double-clicking
Use the event.x and event.y attributes to get the line number the user clicked on and then use the Text widget to query what row that corresponds to. Finally delete the row. One side effect is that the text widget will select some text, because that is the default binding for the text widget when the double-button is clicked (not changeable unfortunately). You can remove the effects of that by returning "break" at the end of the deleteLine method.
def deleteLine(self, event):
point = '#' + str(event.x) + ',' + str(event.y)
line_number = self.Text.index(point).split('.')[0]
self.Text.delete(line_number + '.0', line_number + '.end + 1 char')
# Interrupt the default sel tag binding.
return "break"

Use variable outside its function

I'm using a function that allows the user to select an item from a list and store the index into a variable. I would like to use that variable outside the function but it's not working.
Here's what I have so far. (FYI: mylist section code is not shown)
class FirstWindow(Frame):
selection_index = '0'
def __init__(self, parent, controller):
Frame.__init__(self, parent)
num_lines_lights = 4
scrollbar = Scrollbar(self)
#scrollbar.pack(side=RIGHT, fill=Y)
scrollbar.place(x=55,y=200)
#scrollbar.place(x=25, y=50)
mylist = Listbox(self, yscrollcommand = scrollbar.set)
for line in range(num_lines_lights):
mylist.insert(END, "Light " + str(line))
mylist.pack(side = LEFT, fill = BOTH)
mylist.place(x=70,y=200)
scrollbar.config(command = mylist.yview)
def select_item(event):
global selection_index
widget = event.widget
selection_index = (widget.curselection()[0])
value = widget.get(index)
print("Inside function %s " % selection_index) #works fine
mylist.bind('<<ListboxSelect>>', select_item)
print("This is return %s " % selection_index) #NOT WORKING
And here's the error I receive:
print("This is return %s " % selection_index)
NameError: name 'selection_index' is not defined
because of global keyword, it is restricting the scope of the variable. Instead of global you can use self and define variable in def __init__()
def __init__(self, parent, controller):
self.selection_index = '0'
now, instead of using selection_index use self.selection_index
You shouldn't be trying to use global for this. One of the main reasons for using a class is so it can hold related objects in a way that makes them easy to access.
You can make selection_index an instance attribute of FirstWindow. Like this:
class FirstWindow(Frame):
def __init__(self, parent, controller):
Frame.__init__(self, parent)
self.selection_index = '0'
def select_item(event):
widget = event.widget
selection_index = (widget.curselection()[0])
value = widget.get(index)
print("Inside function %s " % self.selection_index)
mylist.bind('<<ListboxSelect>>', select_item)
print("This is return %s " % self.selection_index)
Of course, that will just print This is return 0 because select_item hasn't had a chance to run yet.
Here's a runnable example based on the new code you posted, which still wasn't a MCVE: I had to add extra things to make it runnable. BTW, you should not mix different layout methods (pack, place, grid) in the one container widget because they don't co-operate with one another.
I've added an extra button to demonstrate that self.selection_index is available throughout FirstWindow.
from tkinter import *
class FirstWindow(Frame):
def __init__(self, parent, controller):
Frame.__init__(self, parent)
self.selection_index = '-1'
num_lines_lights = 4
scrollbar = Scrollbar(self)
scrollbar.pack(side=RIGHT, fill=Y)
mylist = Listbox(self, yscrollcommand=scrollbar.set)
for line in range(num_lines_lights):
mylist.insert(END, "Light " + str(line))
mylist.pack(side=LEFT, fill=BOTH)
scrollbar.config(command = mylist.yview)
def select_item(event):
widget = event.widget
self.selection_index = (widget.curselection()[0])
value = widget.get(self.selection_index)
print("Inside function %s: %s" % (self.selection_index, value))
mylist.bind('<<ListboxSelect>>', select_item)
print("This is return %s " % self.selection_index)
# A button to print the current selection_index
Button(parent, text='Show index', command=self.show_index).pack()
def show_index(self):
print(self.selection_index)
root = Tk()
win = FirstWindow(root, root)
win.pack()
root.mainloop()
BTw, it's better to not use from tkinter import *. Instead, use
import tkinter as tk
and then you do stuff like root = tk.Tk(), scrollbar = tk.Scrollbar(self), etc. This makes the code easier to read, since it makes it clear where the imported names are coming from, and it stops Tkinter from dumping over 130 names into your global namespace, which can lead to name collisions.

How do I get rid of the lag that occurs In my while loop [duplicate]

I've been slowly learning Tkinter and object-oriented programming but I've programmed myself into a corner with this one. please forgive my lack of critical thinking on this one, but i've asked everyone I know who knows python better than me and we can't get to a working solution here.
I've got a gui app im working on that is meant to allow the user to input stock symbols, create new labels for each symbol, and then update each label periodically. (kinda like a really basic etrade app or something). I've found it's really easy to do this without a gui because I can just say:
while True:
sPrice = get_stock_price(s)
print sPrice
but i've bound my get_stock_price(s) function to a button, which spawns a sub-frame and a label contained inside it. The problem i've faced is the label will not update. A friend recommended to add another method solely to update the label, however the only way I know how to continuously update it is do a
while True:
# get new price
# update the label
# time.sleep(~1 minute?)
this causes my gui window to freeze and spin forever.
I've been reading up on all the other threads related to this particular situation, and I've seen many different advices; don't call sleep in your main thread, don't use root.update, use events, call root.something.after(500, function) and i've tried to implement. What i've been left with is a frankenstein of code that will still retrieve my stock values, but wont update them, and a few methods that I don't know how to update, or where to call in my code.
What im hoping for is a (potentially long, I know. Sorry!) explanation of what i'm doing wrong, and suggestions on how to fix it. I'm really looking to understand and fix the issue myself, but code-solutions would be awesome so long as they are explained.
Thanks so much in advance!!!
PS: Here is my code so far:
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()
You already have an infinite loop running, so you shouldn't be trying to add another one. Instead, you can use the after method to cause a function to be repeatedly called every so often. In your case, you can replace this:
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)
... with this:
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)
This will get a quote, add a label, then arrange for itself to be called again in 10 seconds (10,000 ms).
However, I doubt that you want to create a new label every 10 seconds, do you? Eventually the window will fill up with labels. Instead, you can create a label once, then update the label in each iteration. For example, create self.label once in the init, then in the loop you can do:
self.labelName.configure(text=s.upper() + ": " + str(stockPrice))
You are looking for threading.
Put the event you want to run in another thread. See this example:
import thread, time
def myfunc(a1,a2):
while True:
print a1,a2
time.sleep(1)
thread.start_new_thread(myfunc,("test","arg2")
tkroot.mainloop()
Now you have a function running along with the Tkinter window that prints the args every second.
EDIT: I don't know why so many down votes. Tkinter DOES work well with threads, I've already used this trick several times without problems. See this example:
Download a 10 MB file and log the progress to a Tkinter window.
Without threading:
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()
The window freezes till the download is done.
With thread:
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()
Doesn't freeze and the text is updated normally.

Python Tkinter frame inside frame limitation or user error?

I've been building an app to track stock prices. The user should see a window with an entry widget and a button that creates a new frame with a label and a button. The label is the stock price and symbol, the button is a delete button, and should hide that frame if clicked.
I've re-written this program 4 times now, and it's been a great learning experience, but what I've learned is that I can't have the "mini-frames" being called from methods part of the main GUI class - this funks up the delete buttons, and updates the value behind frame.pack_forget() so it only deletes the last item ever.
I've moved my mini-frame widgets down into the class for the actual stock values. I've packed them (what I assume to be correct) but they don't show up. They also don't error out, which isn't very helpful. Here's my code, although I've omitted a lot of the functional parts to show what is happening with my frames. Keep in mind I need to keep it so that I can call my updater (self.update_stock_value) with a .after method against myapp.myContainer.
Is there a better way to do this?? Thanks in advance, my head hurts.
import re
import time
import urllib
from Tkinter import *
import threading
from thread import *
runningThreads = 0
# each object will be added to the gui parent frame
class MyApp(object):
def __init__(self, parent):
self.myParent = parent
self.myContainer = Canvas(parent)
self.myContainer.pack()
self.create_widgets()
# METHOD initiates basic GUI widgets
def create_widgets(self):
root.title("Stocker")
self.widgetFrame = Frame(self.myContainer)
self.widgetFrame.pack()
self.input = Entry(self.widgetFrame)
self.input.focus_set()
self.input.pack()
self.submitButton = Button(self.widgetFrame, command = self.onButtonClick)
self.submitButton.configure(text = "Add new stock")
self.submitButton.pack(fill = "x")
# METHOD called by each stock object
# returns the "symbol" in the entry widget
# clears the entry widget
def get_input_value(self):
var = self.input.get()
self.input.delete(0, END)
return var
# METHOD called when button is clicked
# starts new thread with instance of "Stock" class
def onButtonClick(self):
global runningThreads # shhhhhh im sorry just let it happen
runningThreads += 1 # count the threads open
threading.Thread(target = self.init_stock,).start() # force a tuple
if runningThreads == 1:
print runningThreads, "thread alive"
else:
print runningThreads, "threads alive"
def init_stock(self):
new = Stock()
class Stock(object):
def __init__(self):
# variable for the stock symbol
symb = self.stock_symbol()
# lets make a GUI
self.frame = Frame(myapp.myContainer)
self.frame.pack
# give the frame a label to update
self.testLabel = Label(self.frame)
self.testLabel.configure(text = self.update_stock_label(symb))
self.testLabel.pack(side = LEFT)
# create delete button to kill entire thread
self.killButton = Button(self.frame, command = self.kill_thread)
self.killButton.configure(text = "Delete")
self.killButton.pack(side = RIGHT)
# create stock label
# call updater
def kill_thread(self):
global runningThreads
runningThreads -= 1
self.stockFrame.pack_forget() # hide the frame
self.thread.exit() # kill the thread
def update_stock_label(self, symb):
self.testLabel.configure(text = str(symb) + str(get_quote(symb)))
myapp.myContainer.after(10000, self.update_stock_label(symb))
def stock_symbol(self):
symb = myapp.get_input_value()
print symb
# The most important part!
def get_quote(symbol):
try:
# go to google
base_url = "http://finance.google.com/finance?q="
# read the source code
content = urllib.urlopen(base_url + str(symbol)).read()
# set regex target
target = re.search('id="ref_\d*_l".*?>(.*?)<', content)
# if found, return.
if target:
print "found target"
quote = target.group(1)
print quote
else:
quote = "Not Found: "
return quote
# handling if no network connection
except IOError:
print "no network detected"
root = Tk()
root.geometry("280x200")
myapp = MyApp(root)
root.mainloop()
Your code won't run because of numerous errors, but this line is definitely not doing what you think it is doing:
self.frame.pack
For you to call the pack function you must include (), eg:
self.frame.pack()
You ask if your code is the best way to do this. I think you're on the right track, but I would change a few things. Here's how I would structure the code. This just creates the "miniframes", it doesn't do anything else:
import Tkinter as tk
class Example(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.entry = tk.Entry(self)
self.submit = tk.Button(self, text="Submit", command=self.on_submit)
self.entry.pack(side="top", fill="x")
self.submit.pack(side="top")
def on_submit(self):
symbol = self.entry.get()
stock = Stock(self, symbol)
stock.pack(side="top", fill="x")
class Stock(tk.Frame):
def __init__(self, parent, symbol):
tk.Frame.__init__(self, parent)
self.symbol = tk.Label(self, text=symbol + ":")
self.value = tk.Label(self, text="123.45")
self.symbol.pack(side="left", fill="both")
self.value.pack(side="left", fill="both")
root = tk.Tk()
Example(root).pack(side="top", fill="both", expand=True)
root.mainloop()

Tkinter managing my event loops alongside my mainloop

I've been slowly learning Tkinter and object-oriented programming but I've programmed myself into a corner with this one. please forgive my lack of critical thinking on this one, but i've asked everyone I know who knows python better than me and we can't get to a working solution here.
I've got a gui app im working on that is meant to allow the user to input stock symbols, create new labels for each symbol, and then update each label periodically. (kinda like a really basic etrade app or something). I've found it's really easy to do this without a gui because I can just say:
while True:
sPrice = get_stock_price(s)
print sPrice
but i've bound my get_stock_price(s) function to a button, which spawns a sub-frame and a label contained inside it. The problem i've faced is the label will not update. A friend recommended to add another method solely to update the label, however the only way I know how to continuously update it is do a
while True:
# get new price
# update the label
# time.sleep(~1 minute?)
this causes my gui window to freeze and spin forever.
I've been reading up on all the other threads related to this particular situation, and I've seen many different advices; don't call sleep in your main thread, don't use root.update, use events, call root.something.after(500, function) and i've tried to implement. What i've been left with is a frankenstein of code that will still retrieve my stock values, but wont update them, and a few methods that I don't know how to update, or where to call in my code.
What im hoping for is a (potentially long, I know. Sorry!) explanation of what i'm doing wrong, and suggestions on how to fix it. I'm really looking to understand and fix the issue myself, but code-solutions would be awesome so long as they are explained.
Thanks so much in advance!!!
PS: Here is my code so far:
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()
You already have an infinite loop running, so you shouldn't be trying to add another one. Instead, you can use the after method to cause a function to be repeatedly called every so often. In your case, you can replace this:
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)
... with this:
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)
This will get a quote, add a label, then arrange for itself to be called again in 10 seconds (10,000 ms).
However, I doubt that you want to create a new label every 10 seconds, do you? Eventually the window will fill up with labels. Instead, you can create a label once, then update the label in each iteration. For example, create self.label once in the init, then in the loop you can do:
self.labelName.configure(text=s.upper() + ": " + str(stockPrice))
You are looking for threading.
Put the event you want to run in another thread. See this example:
import thread, time
def myfunc(a1,a2):
while True:
print a1,a2
time.sleep(1)
thread.start_new_thread(myfunc,("test","arg2")
tkroot.mainloop()
Now you have a function running along with the Tkinter window that prints the args every second.
EDIT: I don't know why so many down votes. Tkinter DOES work well with threads, I've already used this trick several times without problems. See this example:
Download a 10 MB file and log the progress to a Tkinter window.
Without threading:
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()
The window freezes till the download is done.
With thread:
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()
Doesn't freeze and the text is updated normally.

Categories

Resources