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.
Related
I'm trying to do something with classes in Python (I come from a procedural languages background). Trying to create a version of tkinter's Label widget supporting a couple of new methods to manipulate the text of the label.
My problem is that I can't get the label to actually be visible on the screen.
Here's the code:
from tkinter import *
DEFAULT_BG = '#f0f0f0'
class cngMsg(Label):
"""Message Display label"""
def __init__(self, parent, w, h):
"""Init the Message Label"""
self.parent = parent
Label.__init__(self, parent)
self.msgText = "Hello World"
self.msgLabel = Label(parent, text=self.msgText)
self.msgLabel.config(height=h, width=w, bg=DEFAULT_BG)
def clear(self):
self.msgText = ""
print(len(self.msgText))
self.msgLabel.config(text=self.msgText)
def newMessage(self, message):
print("about to display <" + message + ">")
self.msgText = message
print(len(self.msgText))
self.msgLabel.config(text=self.msgText)
def show(self, message, sameLine=None):
if (not sameLine) and len(self.msgText) > 0:
self.msgText += '/n'
print("about to show: <" + message + ">")
self.msgText = self.msgText + message
print(len(self.msgText))
self.msgLabel.config(text=self.msgText)
#Root Stuff
if __name__ == "__main__":
app = Tk()
app.title("Message Test")
# this is the start of the application
print("initialise the Message Test")
gMsg = cngMsg(app, 60, 20)
gMsg.pack()
gMsg.newMessage("new message")
gMsg.show("this is a test")
gMsg.show("second test")
app.mainloop()
The debug print messages appear on the console but the application window doesn't display the Label.
GUI programming requires using a non-procedural paradigm since they are user-input driven. The question Tkinter — executing functions over time discusses this and has some sample code.
Personally I have often found it useful when creating GUI apps to think of them as FSMs (Finite State Machines) in which user inputs cause them to change their state.
Here's how to do something similar to what I think you were trying to in your sample code which is based on the #Bryan Oakley's answer to the linked question (updated to Python 3). It also shows the proper way to subclass tkinter classes. In addition it mostly follows the PEP 8 - Style Guide for Python Code guideline, which I strongly suggest your read and start following.
from tkinter import *
DEFAULT_BG = '#f0f0f0'
DEFAULT_MSG_TEXT = "Hello World"
DELAY = 1000 # Milliseconds.
class CngMsg(Label):
"""Message Display label"""
def __init__(self, parent, w, h):
# Intialize with default text and background color.
super().__init__(parent, text=DEFAULT_MSG_TEXT, height=h, width=w, bg=DEFAULT_BG)
def clear(self):
self.config(text='')
def newMessage(self, message):
self.config(text=message)
def show(self, message, same_line=False):
text = self.cget('text') # Get value of current option.
if not same_line and text:
text += '\n'
text += message
self.config(text=text)
class MyApp(Tk):
def __init__(self):
super().__init__()
self.frame = Frame(self)
self.frame.pack()
self.test_msg = CngMsg(self.frame, 60, 20)
self.test_msg.pack()
self.state = 0
self.do_test()
def do_test(self):
if self.state == 0:
self.test_msg.newMessage("start message")
self.state = 1
elif self.state == 1:
self.test_msg.show("this is a test")
self.state = 2
elif self.state == 2:
self.test_msg.show("second test")
self.state = 3
elif self.state == 3:
self.test_msg.clear()
self.test_msg.show("TEST COMPLETED")
self.state = -1 # Enter final state.
elif self.state != -1:
self.quit() # Stop mainloop.
raise RuntimeError("Unknown state encountered")
if self.state != -1: # Not final state?
self.after(DELAY, self.do_test) # Schedule another call.
if __name__ == "__main__":
root = MyApp()
root.title("Message Test")
root.mainloop()
Your cngMsg class is creating two labels. The first is the instance of that class itself. The second is created when you do self.msgLabel = Label(parent, text=self.msgText). This second label is a child of the cngMsg label. Since you do not call pack, place, or grid on that second label it will not appear anywhere.
In your newMessage method you are updating the text in the invisible label instead of the actual label.
You don't need this second label, and in newMessage you should configure itself like this:
def newMessage(self, message):
self.msgText = msgText
self.configure(text=self.msgText)
Similarly, show and clear should be defined in a similar way:
def clear(self):
self.msgText = ""
self.config(text=self.msgText)
def show(self, message, sameLine=None):
if (not sameLine) and len(self.msgText) > 0:
self.msgText += '/n'
self.msgText = self.msgText + message
self.config(text=self.msgText)
Oke i got the problem days ago and someone helped me with treading but my code was really ugly (im totaly new to coding) now i try to make it better and on an smarter way but now my gui get a frezze all time.
i tryed to do it on the way like my last code but it dosent work this time.
What have i to do this time i cant understand it but want understand it.
some Helpful Tips and tricks ?
Or better ways to do it smart, faster, and more powerfull, or mybae the gui more Beautyfule ?
import time
import sys
from tkinter import *
import threading
root = Tk()
root.geometry("600x400")
global start
start = 1
def startUp():
user_input()
thr = threading.Thread(target=user_input)
thr.start()
def user_input():
global nameInput
global start
nameInput = textBox.get("1.0","end-1c")
start = 0
if start < 1:
while True:
apex = ApexLegends("APIKey")
player = apex.player(nameInput)
print("Gesamt Kills: " + player.kills + "\n" + 'Gesamt Damage: ' + player.damage)
time.sleep(3)
else:
print("stop")
anleitung=Label(text="Gib einen Spielernamen ein und drücke Start")
anleitung.pack()
textBox=Text(root, height=1, width=30)
textBox.pack()
startButton=Button(root, height=1, width=10, text="Start", command=lambda:startUp())
startButton.pack()
Tkinter isn't designed to be accessed by more than one thread. Here is an excellent answer by one of the guys who has a very deep understainding of how tcl & tk works (the libraries that tkinter depends on), explaining why this is so.
Callback to python function from Tkinter Tcl crashes in windows
This is the first of the two paragraphs in that answer:
Each Tcl interpreter object (i.e., the context that knows how to run a Tcl procedure) can only be safely used from the OS thread that creates it. This is because Tcl doesn't use a global interpreter lock like Python, and instead makes extensive use of thread-specific data to reduce the number of locks required internally. (Well-written Tcl code can take advantage of this to scale up very large on suitable hardware.)
def startUp():
user_input()
thr = threading.Thread(target=user_input)
thr.start()
This doesn't look right. You're calling user_input() in both the main thread and the child thread. If you only want it to run in the child thread, don't call it that first time.
def startUp():
thr = threading.Thread(target=user_input)
thr.start()
Hi #Trason I've played with your code and I suggest an oo approach.
In the code below I've try to adapt a functional script to your code.
First of all I've use a variable as
self.nameInput = tk.IntVar()
to store the user input on
tk.Entry(w, bg='white', textvariable=self.nameInput).pack()
I've use an Entry widget instead of Text but it should be the same.
Furthermore I use a class to manage thread start and stop operation.
Look, I changed your 'start' variable with 'check' because start is a reserved word
in python thread module.
I tried to recreate the functionality of your code.
Try to import and use your ApexLegends.
import tkinter as tk
import threading
import queue
import datetime
import time
class MyThread(threading.Thread):
def __init__(self, queue,nameInput):
threading.Thread.__init__(self)
self.queue = queue
self.nameInput = nameInput
self.check = True
def stop(self):
self.check = False
def run(self):
while self.check:
# apex = ApexLegends("APIKey")
#player = apex.player(self.nameInput.get())
x = "Gesamt Kills: " + "player.kills" + "\n" + 'Gesamt Damage: ' + "player.damage"+ "\n"
s = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
msg = "{} datetime: {} nameInput {}".format(x,s,self.nameInput.get())
time.sleep(3)
self.queue.put(msg)
class App(tk.Frame):
def __init__(self,):
super().__init__()
self.master.title("Hello World")
self.master.protocol("WM_DELETE_WINDOW",self.on_close)
self.queue = queue.Queue()
self.my_thread = None
self.nameInput = tk.IntVar()
self.init_ui()
def init_ui(self):
self.f = tk.Frame()
w = tk.Frame()
tk.Label(w, text = "Gib einen Spielernamen ein und drücke Start").pack()
tk.Entry(w, bg='white', textvariable=self.nameInput).pack()
w.pack(side=tk.LEFT, fill=tk.BOTH, expand=0)
w = tk.Frame()
tk.Button(w, text="Start", command=self.startUp).pack()
tk.Button(w, text="Stop", command=self.stop_thread).pack()
tk.Button(w, text="Close", command=self.on_close).pack()
w.pack(side=tk.RIGHT, fill=tk.BOTH, expand=0)
self.f.pack(side=tk.LEFT, fill=tk.BOTH, expand=0)
def startUp(self):
if (threading.active_count()!=0):
self.my_thread = MyThread(self.queue,self.nameInput)
self.my_thread.start()
self.periodiccall()
def stop_thread(self):
if(threading.active_count()!=1):
self.my_thread.stop()
def periodiccall(self):
self.checkqueue()
if self.my_thread.is_alive():
self.after(1, self.periodiccall)
else:
pass
def checkqueue(self):
while self.queue.qsize():
try:
ret = self.queue.get(0)
msg = "%s"%(ret)
print(msg)
except queue.Empty:
pass
def on_close(self):
if(threading.active_count()!=0):
if self.my_thread is not None:
self.my_thread.stop()
self.master.destroy()
if __name__ == '__main__':
app = App()
app.mainloop()
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.
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.
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()