Tkinter Python Window Crashing When Slider Updated [duplicate] - python

My little brother is just getting into programming, and for his Science Fair project, he's doing a simulation of a flock of birds in the sky. He's gotten most of his code written, and it works nicely, but the birds need to move every moment.
Tkinter, however, hogs the time for its own event loop, and so his code won't run. Doing root.mainloop() runs, runs, and keeps running, and the only thing it runs is the event handlers.
Is there a way to have his code run alongside the mainloop (without multithreading, it's confusing and this should be kept simple), and if so, what is it?
Right now, he came up with an ugly hack, tying his move() function to <b1-motion>, so that as long as he holds the button down and wiggles the mouse, it works. But there's got to be a better way.

Use the after method on the Tk object:
from tkinter import *
root = Tk()
def task():
print("hello")
root.after(2000, task) # reschedule event in 2 seconds
root.after(2000, task)
root.mainloop()
Here's the declaration and documentation for the after method:
def after(self, ms, func=None, *args):
"""Call function once after given time.
MS specifies the time in milliseconds. FUNC gives the
function which shall be called. Additional parameters
are given as parameters to the function call. Return
identifier to cancel scheduling with after_cancel."""

The solution posted by Bjorn results in a "RuntimeError: Calling Tcl from different appartment" message on my computer (RedHat Enterprise 5, python 2.6.1). Bjorn might not have gotten this message, since, according to one place I checked, mishandling threading with Tkinter is unpredictable and platform-dependent.
The problem seems to be that app.start() counts as a reference to Tk, since app contains Tk elements. I fixed this by replacing app.start() with a self.start() inside __init__. I also made it so that all Tk references are either inside the function that calls mainloop() or are inside functions that are called by the function that calls mainloop() (this is apparently critical to avoid the "different apartment" error).
Finally, I added a protocol handler with a callback, since without this the program exits with an error when the Tk window is closed by the user.
The revised code is as follows:
# Run tkinter code in another thread
import tkinter as tk
import threading
class App(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self.start()
def callback(self):
self.root.quit()
def run(self):
self.root = tk.Tk()
self.root.protocol("WM_DELETE_WINDOW", self.callback)
label = tk.Label(self.root, text="Hello World")
label.pack()
self.root.mainloop()
app = App()
print('Now we can continue running code while mainloop runs!')
for i in range(100000):
print(i)

When writing your own loop, as in the simulation (I assume), you need to call the update function which does what the mainloop does: updates the window with your changes, but you do it in your loop.
def task():
# do something
root.update()
while 1:
task()

Another option is to let tkinter execute on a separate thread. One way of doing it is like this:
import Tkinter
import threading
class MyTkApp(threading.Thread):
def __init__(self):
self.root=Tkinter.Tk()
self.s = Tkinter.StringVar()
self.s.set('Foo')
l = Tkinter.Label(self.root,textvariable=self.s)
l.pack()
threading.Thread.__init__(self)
def run(self):
self.root.mainloop()
app = MyTkApp()
app.start()
# Now the app should be running and the value shown on the label
# can be changed by changing the member variable s.
# Like this:
# app.s.set('Bar')
Be careful though, multithreaded programming is hard and it is really easy to shoot your self in the foot. For example you have to be careful when you change member variables of the sample class above so you don't interrupt with the event loop of Tkinter.

This is the first working version of what will be a GPS reader and data presenter. tkinter is a very fragile thing with way too few error messages. It does not put stuff up and does not tell why much of the time. Very difficult coming from a good WYSIWYG form developer. Anyway, this runs a small routine 10 times a second and presents the information on a form. Took a while to make it happen. When I tried a timer value of 0, the form never came up. My head now hurts! 10 or more times per second is good enough for me. I hope it helps someone else. Mike Morrow
import tkinter as tk
import time
def GetDateTime():
# Get current date and time in ISO8601
# https://en.wikipedia.org/wiki/ISO_8601
# https://xkcd.com/1179/
return (time.strftime("%Y%m%d", time.gmtime()),
time.strftime("%H%M%S", time.gmtime()),
time.strftime("%Y%m%d", time.localtime()),
time.strftime("%H%M%S", time.localtime()))
class Application(tk.Frame):
def __init__(self, master):
fontsize = 12
textwidth = 9
tk.Frame.__init__(self, master)
self.pack()
tk.Label(self, font=('Helvetica', fontsize), bg = '#be004e', fg = 'white', width = textwidth,
text='Local Time').grid(row=0, column=0)
self.LocalDate = tk.StringVar()
self.LocalDate.set('waiting...')
tk.Label(self, font=('Helvetica', fontsize), bg = '#be004e', fg = 'white', width = textwidth,
textvariable=self.LocalDate).grid(row=0, column=1)
tk.Label(self, font=('Helvetica', fontsize), bg = '#be004e', fg = 'white', width = textwidth,
text='Local Date').grid(row=1, column=0)
self.LocalTime = tk.StringVar()
self.LocalTime.set('waiting...')
tk.Label(self, font=('Helvetica', fontsize), bg = '#be004e', fg = 'white', width = textwidth,
textvariable=self.LocalTime).grid(row=1, column=1)
tk.Label(self, font=('Helvetica', fontsize), bg = '#40CCC0', fg = 'white', width = textwidth,
text='GMT Time').grid(row=2, column=0)
self.nowGdate = tk.StringVar()
self.nowGdate.set('waiting...')
tk.Label(self, font=('Helvetica', fontsize), bg = '#40CCC0', fg = 'white', width = textwidth,
textvariable=self.nowGdate).grid(row=2, column=1)
tk.Label(self, font=('Helvetica', fontsize), bg = '#40CCC0', fg = 'white', width = textwidth,
text='GMT Date').grid(row=3, column=0)
self.nowGtime = tk.StringVar()
self.nowGtime.set('waiting...')
tk.Label(self, font=('Helvetica', fontsize), bg = '#40CCC0', fg = 'white', width = textwidth,
textvariable=self.nowGtime).grid(row=3, column=1)
tk.Button(self, text='Exit', width = 10, bg = '#FF8080', command=root.destroy).grid(row=4, columnspan=2)
self.gettime()
pass
def gettime(self):
gdt, gtm, ldt, ltm = GetDateTime()
gdt = gdt[0:4] + '/' + gdt[4:6] + '/' + gdt[6:8]
gtm = gtm[0:2] + ':' + gtm[2:4] + ':' + gtm[4:6] + ' Z'
ldt = ldt[0:4] + '/' + ldt[4:6] + '/' + ldt[6:8]
ltm = ltm[0:2] + ':' + ltm[2:4] + ':' + ltm[4:6]
self.nowGtime.set(gdt)
self.nowGdate.set(gtm)
self.LocalTime.set(ldt)
self.LocalDate.set(ltm)
self.after(100, self.gettime)
#print (ltm) # Prove it is running this and the external code, too.
pass
root = tk.Tk()
root.wm_title('Temp Converter')
app = Application(master=root)
w = 200 # width for the Tk root
h = 125 # height for the Tk root
# get display screen width and height
ws = root.winfo_screenwidth() # width of the screen
hs = root.winfo_screenheight() # height of the screen
# calculate x and y coordinates for positioning the Tk root window
#centered
#x = (ws/2) - (w/2)
#y = (hs/2) - (h/2)
#right bottom corner (misfires in Win10 putting it too low. OK in Ubuntu)
x = ws - w
y = hs - h - 35 # -35 fixes it, more or less, for Win10
#set the dimensions of the screen and where it is placed
root.geometry('%dx%d+%d+%d' % (w, h, x, y))
root.mainloop()

Related

Tkinter progress bar not working properly

I am trying to add a progress bar to my window until some work is being done. But it is not working properly. I want it to keep moving until the work is done but it just moves rapidly and then stops. Also if I try to minimize or close the progress window it just hangs and stops responding.
Can anyone help me how can I do it properly? Here is my code.
import time
from tkinter import ttk
from tkinter import *
numbers = []
def main():
main_window = Tk()
app = info(main_window)
main_window.mainloop()
class info:
def __init__(self, root):
# start = timer()
self.error_str = ''
self.root1 = root
self.root1.title('LOADING......')
self.root1.geometry("380x200")
self.root1.eval('tk::PlaceWindow . center')
self.root1.resizable(width=False, height=False)
self.root1.configure(background='white')
progress = ttk.Progressbar(self.root1, orient=HORIZONTAL,
length=380, mode='determinate')
progress.place(x=0, y=100)
i = 20
for x in range(1, 50):
numbers.append(x * 2)
print(numbers)
progress['value'] = i
self.root1.update_idletasks()
time.sleep(0.1)
i = i + 40
self.root = root
self.root.title('Second window')
self.root.geometry('1350x800+0+0')
frame1 = Frame(self.root, bg='#7877a5')
frame1.place(x=0, y=0, width=1350, height=150)
title = Label(frame1, text="Second Window", font=("Times New Roman", 40, "bold", "italic"),
bg='#7877a5',
fg='white')
title.place(x=380, y=45)
if __name__ == '__main__':
main()
Generally speaking you shouldn't call time.sleep() in a tkinter application because it interferes with the GUI's mainloop() and will make your program hang or freeze. Use the universal widget method after() instead.
Lastly you need to specify a maximum value for the Progressbar so its indicator scales properly relatively to the values of i you are setting its value to. The default for maximum is only 100, which your code was greatly exceeding in the for x loop.
Here's the code that needs to change in info.__init__(). The two lines changed have # ALL CAPS comments:
progress = ttk.Progressbar(self.root1, orient=HORIZONTAL,
length=380, mode='determinate',
maximum=(48*40)+20) # ADDED ARGUMENT.
progress.place(x=0, y=100)
i = 20
for x in range(1, 50):
numbers.append(x * 2)
print(numbers)
progress['value'] = i
self.root1.update_idletasks()
self.root1.after(100) # Delay in millisecs. # REPLACED TIME.SLEEP() CALL.
i = i + 40

Tkinter pyowm not updating

I have several widgets, or more correctly, Labels, that update perfectly when a method finishing with self.after() is called in __init__.
That's not the case with this Label here, which gets the weather Information from OWM (OpenWeatherMap) through pyowm, and should be updated every specific amount of time, defined in the after() function.
I've tried almost everything in my knowledge, though I'm pretty newbie to Python. I've searched google for days, but no working solution was found, or not that I could make it work.
I've tried to put the after function in every method, even __init__.
The trimmed down main() for debug and the Weather Class follows:
import tkinter as tk
from Weather import Meteo
def displayMeteo(win):
# Place Meteo portlet
meteoHandler = Meteo(win, bg='black', fg='white')
meteoHandler.pack(side='top', anchor='ne')
def main():
# Set the screen definition, root window initialization
root = tk.Tk()
root.configure(background='black')
width, height = root.winfo_screenwidth(),
root.winfo_screenheight()
root.geometry("%dx%d+0+0" % (width, height))
label = tk.Label(root, text="Monitor Dashboard", bg='black',
fg='red')
label.pack(side='bottom', fill='x', anchor='se')
# Display portlet
displayMeteo(root)
# Loop the GUI manager
root.mainloop(0)
###############################
# MAIN SCRIPT BODY PART #
###############################
if __name__ == "__main__":
main()
And the class:
import pyowm
import tkinter as tk
from PIL import Image, ImageTk
class Meteo(tk.Label):
def __init__(self, parent=None, **params):
tk.Label.__init__(self, parent)
self.API = pyowm.OWM('API KEY', config_module=None,
language='it', subscription_type=None)
self.location = self.API.weather_at_place('Rome,IT')
self.weatherdata = self.location.get_weather()
self.weather = str(self.weatherdata.get_detailed_status())
self.dictionary = {'poche nuvole': 'Parzialmente Nuvoloso',
'cielo sereno': 'Sereno', 'nubi sparse': 'Nuvoloso',
'temporale con pioggia': 'Temporale', 'pioggia leggera':
'Pioggerella'}
self.Display()
def Temperatur(self):
self.Temp = tk.StringVar()
self.tempvalue = self.weatherdata.get_temperature('celsius')
self.temperature = str(self.tempvalue.get('temp'))
self.Temp.set(self.temperature)
self.after(3000, self.Temperatur)
def WeatherInfo(self):
self.Weather = tk.StringVar()
self.weather = str(self.weatherdata.get_detailed_status())
if self.weather in self.dictionary:
self.weather = self.dictionary[self.weather]
self.weatherstring = self.weather.title()
self.Weather.set(self.weatherstring)
self.after(3000, self.WeatherInfo)
def Display(self):
self.Temperatur()
self.WeatherInfo()
self.configure(text=self.Weather.get() + ", " + self.Temp.get() + "°", bg='black',
fg='#FFFF96', font=("arial, 35"))
self.after(3000, self.Display)
Now, what should happen is the widget updating every 3 secs (just to debug), although I know even if updating works it won't change every 3 seconds, 'cause you know...weather doesn't change every 3 secs.
As done in suggestion of Idlehands the problem was not in the Label or the updating.
The Code, if written that way, would call .get_weather only once, thus creating a stagnating variable. I added a method that updates the pyowm parsing and now evertyhing's working fine!

Tkinter background colour issue

I have a script that has a Tkinter module in it that i would like to change the background color in 3min intervals e.g green for 3mins then orange then red.
I have the code to display the green but can't get it to change.
When I make a function in my code it gets a few different errors including
'root not defined, global name "root" no defined' although it is.
Also on a side note kill the Tk display after 15 mins so once all 3 colours have been though.
from __future__ import absolute_import
from . import BasePlugin
import os, sys
import time
from Tkinter import *
def Orange (*args,**kwargs):
root.config(background="Orange")
def Red(*args,**kwargs):
root.config(background="Red")
class dis(BasePlugin):
def execute(self, msg, unit, address, when, printer, print_copies):
mseg = str('%s - %s' % (msg, unit))
root = Tk()
root.title('label')
txt = Label(root, font= 'times 20 bold', bg='Green')
txt.config(text= mseg)
txt.pack(fill=BOTH, expand=0)
root.after(10,Orange)
root.after(10,Red)
root.mainloop(0)
PLUGIN = dis
I have also tried
from __future__ import absolute_import
from . import BasePlugin
import os, sys
import time
from Tkinter import *
def Orange (*args,**kwargs):
txt.config(background="Orange")
def Red(*args,**kwargs):
txt.config(background="Red")
class dis(BasePlugin):
def execute(self, msg, unit, address, when, printer, print_copies):
mseg = str('%s - %s' % (msg, unit))
root = Tk()
root.title('label')
txt = Label(root, font= 'times 20 bold', bg='Green')
txt.config(text= mseg)
txt.pack(fill=BOTH, expand=0)
txt.after(10,Orange)
txt.after(10,Red)
root.mainloop(0)
PLUGIN = dis
If I place root = Tk() anywhere else I get a small gray TK box that I don't want.
P.S I know that it's set to 10 seconds that's only so I can test it
There are (at least) four problems with your code, but that's difficult to tell, since you are not showing us all the details. In particular, you never seem to call execute, but I'll assume that this happens somewhere else, maybe via the super class...
root is defined inside execute, thus to access it in your callback functions, you either have to make it global, or a member of the dis instance, or put the callback functions inside execute
the delay in after is in milliseconds, so using 10 the colours will switch instantaneously, which is probably not the best setup for testing
as it stands, both after callbacks are executed at the exact same time; either put one at the end of the other callback function, or use different times
you change the background of the root panel, while in fact you want to change the background of the txt Label
For example, you could try like this (minimal stand-alone example)
class dis:
def execute(self):
def orange():
txt.config(bg="Orange")
root.after(2000, red)
def red():
txt.config(bg="Red")
root.after(2000, kill)
def kill():
root.destroy()
root = Tk()
txt = Label(root, text="some text", font='times 20 bold', bg='Green')
txt.pack(fill=BOTH, expand=0)
root.after(2000, orange)
root.mainloop()
dis().execute()
Or shorter, just using a bunch of lambda:
class dis:
def execute(self):
root = Tk()
txt = Label(root, text="some text", font='times 20 bold', bg='Green')
txt.pack(fill=BOTH, expand=0)
root.after(2000, lambda: txt.config(bg="Orange"))
root.after(4000, lambda: txt.config(bg="Red"))
root.after(6000, root.destroy)
root.mainloop()
dis().execute()
Or a little more generic using a list
class dis():
def __init__(self):
mseg = ("test message")
self.color_list=["green", "orange", "red"]
self.ctr=0
root = Tk()
root.title('label')
self.txt = Label(root, font= 'times 20 bold', width=20)
self.txt.config(text= mseg)
self.txt.pack(fill=BOTH, expand=0)
self.change_color()
root.mainloop()
def change_color(self):
self.txt.config(background=self.color_list[self.ctr])
self.ctr += 1
if self.ctr > 2:
self.ctr=0
self.txt.after(500, self.change_color)
PLUGIN = dis()

bring window to top level

I want to present several questions, one after another. The first question is shown as I like, with the cursor set in the entry field. Then I destroy the window and call the function again to create a new window. This time the window is not shown in the front and therefore I first have to click on the screen in order to have the cursor set to the entry field. Also the escape key does not work until I click on the screen to bring the window to the top. I'd be very happy for your help!
Thank you in advance!
Here's my code:
from Tkinter import *
def text_input_restricted(fn,question, nr_letters, limit, len_min, len_max,keys, justify):
class MyApp():
def validate(root, S):
return all(c in keys for c in S)
def __init__(self, q= None):
#save response after "next"-button has been clicked
def okClicked():
lines = e.get()
if len_min < len(lines) < len_max:
lines = unicode(lines).encode('utf-8')
datFile = open(fn, "a")
datFile.write(" '%s'"%(lines))
datFile.close()
self.root.destroy()
self.root = Tk()
vcmd = (self.root.register(self.validate), '%S')
#quit if escape-key has been pressed
self.root.bind('<Escape>', lambda q: quit())
#colors
color = '#%02x%02x%02x' % (200, 200, 200)
self.root.configure(bg=color)
#set window size to screen size
RWidth=MAXX
RHeight=MAXY
self.root.geometry(("%dx%d")%(RWidth,RHeight))
#remove buttons (cross, minimize, maximize)
self.root.overrideredirect(1)
#remove title
self.root.title("")
#item
labelWidget = Label(self.root,text=question, font=("Arial", int(0.02*MAXX)), bd=5, bg=color, justify="center")
labelWidget.place(x=0, y=RHeight/40,width=RWidth)
#"next"-button
ok_width = RWidth/15
ok_height = RWidth/15
okWidget = Button(self.root, text= "next", command = okClicked, font=("Arial",int(0.015*MAXX)), bd=5, justify="center")
okWidget.place(x=RWidth/2-ok_width/2,y=13*RHeight/40, width=ok_width,height=ok_height)
def callback(sv):
c = sv.get()[0:limit]
sv.set(c)
sv = StringVar()
width=nr_letters * int(0.02*MAXX)*1.3
sv.trace("w", lambda name, index, mode, sv=sv: callback(sv))
e = Entry(self.root, textvariable=sv,font=("Arial", int(0.02*MAXX)),justify=justify,validate="key", validatecommand=vcmd)
e.place(x=RWidth/2-width/2, y=9*RHeight/40, width=width)
#show cursor
e.focus_set()
self.root.mainloop()
MyApp()
MAXX=1366
MAXY=768
fn = "D:/test.dat"
text_input_restricted(fn = fn, question=u"f for female, m for male", nr_letters=1, limit =1, len_min =0, len_max=2, keys = 'fm', justify="center")
text_input_restricted(fn = fn, question="How old are you?", nr_letters=2,limit=2, len_min = 1, len_max = 3, keys = '1234567890',justify="center")
In Tk you use the raise command to bring a window to the front of the Z-order. However, raise is a keyword in Python so this has been renamed to lift. Provided your application is still the foreground application you can call the lift() method on a toplevel widget. If the application is not the foreground application then this will raise the window but only above other windows from the same application. On Windows this causes the taskbar icon for your application to start flashing.
You might do better to destroy the contents of the toplevel and replace them. Or even better - create a number of frames holding each 'page' of your application and toggle the visibility of each frame by packing and pack_forgetting (or grid and grid forget). This will avoid loosing the focus completely - you can just set the focus onto the first widget of each frame as you make it visible.

Making python/tkinter label widget update?

I'm working on getting a python/tkinter label widget to update its contents. Per an earlier thread today, I followed instructions on how to put together the widgets. At runtime, however, the label widget does NOT change contents, but simply retains its original content. As far as I can tell, decrement_widget() is never called at all. Any ideas?
def snooze (secs):
"""
Snoozes for the given number of seconds. During the snooze, a progress
dialog is launched notifying the
"""
root = Tkinter.Tk()
prompt = 'hello'
label1 = Tkinter.Label(root, text=prompt, width=len(prompt))
label1.pack()
remaining = secs
def decrement_label ():
text = "Snoozing %d sec(s)" % remaining
remaining -= 1
label1.config(text=text, width=100)
label1.update_idletasks()
for i in range(1, secs + 1):
root.after(i * 1000, decrement_label )
root.after((i+1) * 1000, lambda : root.destroy())
root.mainloop()
You'll want to set the label's textvariable with a StringVar; when the StringVar changes (by you calling myStringVar.set("text here")), then the label's text also gets updated. And yes, I agree, this is a strange way to do things.
See the Tkinter Book for a little more information on this:
You can associate a Tkinter variable with a label. When the contents of the variable changes, the label is automatically updated:
v = StringVar()
Label(master, textvariable=v).pack()
v.set("New Text!")
I think you're getting a "referenced before assignment" error because Python thinks remaining is in the local scope.
In Python 3, you can say nonlocal remaining. But in Python 2, I don't believe there's a way to refer to a non-local, non-global scope. This worked for me:
remaining = 0
def snooze (secs):
"""
Snoozes for the given number of seconds. During the snooze, a progress
dialog is launched notifying the
"""
global remaining
root = Tkinter.Tk()
prompt = 'hello'
label1 = Tkinter.Label(root, text=prompt, width=len(prompt))
label1.pack()
remaining = secs
def decrement_label ():
global remaining
text = "Snoozing %d sec(s)" % remaining
remaining -= 1
label1.config(text=text, width=100)
label1.update_idletasks()
for i in range(1, secs + 1):
root.after(i * 1000, decrement_label )
root.after((i+1) * 1000, lambda : root.destroy())
root.mainloop()
import tkinter
from tkinter import *
# just init some vars
remaining = 0
secs = 0
root = tkinter.Tk()
prompt = StringVar()
def snooze (secs):
"""
Snoozes for the given number of seconds. During the snooze, a progress
dialog is launched notifying the
"""
def decrement_label ():
global remaining, prompt
remaining -= 1
prompt.set('Snoozing %d sec(s)' % remaining)
label1.update_idletasks()
if not remaining:
print("end ... ")
root.destroy()
global remaining
prompt.set("hello")
label1 = tkinter.Label(root, textvariable=prompt, width=30)
label1.pack()
remaining = secs
for i in range(1, secs + 1):
root.after(i * 1000, decrement_label )
snooze(10)
root.mainloop()
To update text in a label you can try the following:
from tkinter import *
root = Tk()
root.title("Title")
root.geometry('300x300')
def clear_text(self):
txtE.delete(0, 'end')
def new_label(event=None):
Entree = txtE.get()
lbl1['text'] = Entree.title()
clear_text(txtE)
lbl1 = Label(root, text='Hello There')
lbl1.pack()
txtE = Entry(root)
txtE.focus()
txtE.pack()
Button(root, text='Enter', command=new_label).pack()
Button(root, text='Quit', command=root.destroy).pack(side=BOTTOM)
root.bind('<Return>', new_label)
root.mainloop()
I think you have to call snooze(secs) function
After that if your code again not works try this
Set a variable
Variable = StringVar()
In the label widget you can set "textvariable" argument to the above mentioned "Variable".
Eg:
label1 = Label(root,textvariable = Variable).pack()
And you can update by setting a new value to "Variable"
Eg:Variable.set("hi")
Hope you got it !!!

Categories