tkinter (py3) - change image labels, inside functions, in real time - python

I'm learning to use tkinter, and what I've tried to do is have 4 buttons on a coffee machine, and each button would create a new window, which would show images in order, like a slideshow. What I've done doesn't work, as it only shows the last image in the slideshow, and ignores the rest. In python 2.7, I was able to fix it by printing nothing into the console after every configuration of the label, but it doesn't seem to work. If you could tell me why this happens, and/or how to fix it, it would be greatly appreciated.
(P.S. I know that my code is probably very ugly/inefficient, but bear in mind that I'm very new to tkinter, so I only really care that it works).
def Latte():
global Grind
global Hot_Water
global Cocoa_Poweder
global Steamed_Milk
global Foamed_Milk
global LattePhoto
MakingCoffee=Toplevel(Test, width=200, height=200)
MakingCoffee.wm_title("Latte")
MakingCoffee.iconbitmap("Photos\Latte.ico")
photolabel= Label(MakingCoffee,image=Grind)
photolabel.pack()
time.sleep(2)
photolabel.configure(image=Hot_Water)
time.sleep(2)
photolabel.configure(image=Steamed_Milk)
time.sleep(4)
photolabel.configure(image=Foamed_Milk)
time.sleep(1)
photolabel.configure(image=LattePhoto)
time.sleep(2)
MakingCoffee.destroy()

Here's a small sample of one way to make this work. You can restructure as needed. The list is just so I can iterate through it showing the changes. You don't need all of those globals in your original code. They're already global variables and you're not attempting to reassign them so this a redundancy. You can factor out ~50% of your code at least to be reusable. You can make this a class and make a "factory class" to create different types of slideshows with different types of coffee, and then you could also get rid of the global here as well.
from tkinter import *
import tkinter
import time
Test = tkinter.Tk()
Test.wm_title("Coffee Store")
Test.resizable(0, 0)
americano_images = [
PhotoImage(file='1.gif'),
PhotoImage(file='2.gif'),
PhotoImage(file='3.gif')
]
AFTER = None
AmericanoPhoto= PhotoImage(file="1.gif")
def switch_images(im_list, label, index=0):
global AFTER
label.configure(image=im_list[index])
if index != len(im_list) - 1:
index += 1
else:
index = 0
AFTER = Test.after(1000, lambda: switch_images(im_list, label, index))
def Americano():
MakingCoffee=Toplevel(Test, width=200, height=200)
MakingCoffee.wm_title("Americano")
photolabel= Label(MakingCoffee)
photolabel.pack_propagate(0)
photolabel.pack()
after = switch_images(americano_images, photolabel)
cancel = Button(MakingCoffee, text='Kill Slideshow',
command=lambda: Test.after_cancel(AFTER))
cancel.pack(fill=X, expand=1)
B1= Button(Test, text='BUTTON', command=Americano)
B1.grid(row=0,column=0)
Test.mainloop()

Related

How to solve Problem with Multiprocessing in Tkinter?

Over here I am using multiprocessing to run multiple algorithms in tkinter. At first I tried using threading, but it can't work properly in my program. Below is an idea of my program workflow, it works something like this, but just different functions:
from tkinter import *
from multiprocessing import Process
def SquarFunc(Square):
for i in range(1,1000):
Square.set(str(i**2))
def CubeFunc(Cube):
for i in range(1,1000):
Cube.set(str(i**3))
if __name__ == "__main__":
window= Tk()
Square= StringVar()
Cube= StringVar()
window.geometry("500x500")
A= Label(window, textvariable= Square)
A.place(x=200, y=200)
B= Label(window, textvariable= Cube)
B.place(x=300, y=300)
Squaring= Process(target=SquarFunc, args=(Square, ))
Cubing= Process(target=CubeFunc, args=(Cube, ))
Squaring.start()#Error originates here
Cubing.start()
Squaring.join()
Cubing.join()
window.mainloop()
The error produced is this:
TypeError: cannot pickle '_tkinter.tkapp' object
Anybody knows how to fix this?? thanks in advance!
Here is an example of how to communicate with other processes if using multiprocessing (explanation is in comments, time.sleep is used just for the example because otherwise those loops will complete in a few microseconds):
from tkinter import Tk, StringVar, Label
from multiprocessing import Process, Manager
import time
def square_func(d, name):
for i in range(1, 1000):
# update data in the shared dict
d[name] = i
time.sleep(0.1)
def cube_func(d, name):
for i in range(1, 1000):
# update data in the shared dict
d[name] = i
time.sleep(0.1)
def update_string_vars(d, *variables):
for var in variables:
# get the value from shared dict
value = d[str(var)]
if value is not None:
# set string var to the value
var.set(str(value))
# schedule this to run again
window.after(100, update_string_vars, d, *variables)
# cleanup process upon closing the window in case
# processes haven't finished
def terminate_processes(*processes):
for p in processes:
p.terminate()
if __name__ == "__main__":
window = Tk()
window.geometry("500x500")
# bind the terminator to closing the window
window.bind('<Destroy>', lambda _: terminate_processes(
square_process, cube_process))
square_var = StringVar()
cube_var = StringVar()
Label(window, text='Square:').pack()
Label(window, textvariable=square_var).pack()
Label(window, text='Cube:').pack()
Label(window, textvariable=cube_var).pack()
# create the manager to have a shared memory space
manager = Manager()
# shared dict with preset values as to not raise a KeyError
process_dict = manager.dict({str(square_var): None, str(cube_var): None})
square_process = Process(
target=square_func, args=(process_dict, str(square_var))
)
cube_process = Process(
target=cube_func, args=(process_dict, str(cube_var))
)
square_process.start()
cube_process.start()
# start the updater
update_string_vars(process_dict, square_var, cube_var)
window.mainloop()
Useful:
Sharing state between processes
shortly about tkinter and processes
See also:
I strongly advise against using wildcard (*) when importing something, You should either import what You need, e.g. from module import Class1, func_1, var_2 and so on or import the whole module: import module then You can also use an alias: import module as md or sth like that, the point is that don't import everything unless You actually know what You are doing; name clashes are the issue.
I strongly suggest following PEP 8 - Style Guide for Python Code. Function and variable names should be in snake_case, class names in CapitalCase. Don't have space around = if it is used as a part of keyword argument (func(arg='value')) but have space around = if it is used for assigning a value (variable = 'some value'). Have space around operators (+-/ etc.: value = x + y(except here value += x + y)). Have two blank lines around function and class declarations. Object method definitions have one blank line around them.

GUI status in Python Tkinter

EDIT: Made some changes based on feedback in comments.
I am trying to use a statusTextF function to show a WAIT/READY message on python GUI.
I have defined, at the beginning, when the GUI is drawn.
statusText = True
and then call the following when i want it to wait:
statusText = False
I call statusText as a global variable everywhere I use it, and I have the statusTextF function as shown below:
def statusTextF():
if statusText == True:
statusTitle = tk.Label(root,text="READY")
statusTitle.config(font=statusFont,bg="light green")
statusX = 500
statusY = 450
statusTitle.place(x=statusX,y=statusY)
separation = 45
else:
statusTitle = tk.Label(root,text="WAIT")
statusTitle.config(font=statusFont,bg="light red")
statusX = 500
statusY = 450
statusTitle.place(x=statusX,y=statusY)
separation = 45
I am seeing 'READY' all the time though.
What could be wrong?
Now, it doesn't display anything, and also I get a light red is unknown colour error.
Here's a demonstration of how to do something like you want. It doesn't use a Queue for communication between the main GUI thread and the status updating thread since the amount of information being exchanged between them is so minimal. It instead uses a threading.Lock to control access to a global variable shared between them. Note that it's also implicitly being used to protect updates to the separation global.
Using a queue.Queue to exchange the information ought be fairly easy to implement — should you need one for some reason — since they don't need a separate Lock because they implement "all the required locking semantics" interally.
Note: I've tried to (mostly) follow the PEP 8 - Style Guide for Python Code to make it fairly readable — which I strongly suggest you read (and also follow).
import random
import time
import tkinter as tk
import tkinter.font as tkFont
import threading
class StatusUpdater(threading.Thread):
# # Not really needed since it doesn't do anything except call superclass ctor here.
# def __init__(self, *args, **kwargs):
# super().__init__(*args, **kwargs) # Initialize base class constructor.
def run(self):
global status_flag_lock, status_flag # Must declare these to change their values.
while True:
# Randomly update status_flag.
value = random.randint(0, 100)
with status_flag_lock:
status_flag = bool(value % 2) # True if odd number.
time.sleep(.5) # Pause updating for a little while.
def statusTextF():
global status_flag_lock, separation # Must declare these to change their values.
with status_flag_lock:
if status_flag:
statusTitle.config(text="READY", bg="light green")
separation = 45
else:
statusTitle.config(text="WAIT", bg="pink")
separation = 55
root.after(250, statusTextF) # Continue the polling.
status_flag_lock = threading.Lock() # To control concurrent access.
root = tk.Tk()
root.geometry('600x600')
STATUS_FONT = tkFont.Font(family='Courier', size=8)
STATUS_X, STATUS_Y = 500, 450
status_flag = True
separation = 45
statusTitle = tk.Label(root, text="UNKNOWN", font=STATUS_FONT, bg="gray50")
statusTitle.place(x=STATUS_X, y=STATUS_Y)
status_updater = StatusUpdater(daemon=True)
status_updater.start() # Start updating of status flag.
root.after(250, statusTextF) # Start polling status every 250 millisecs.
root.mainloop()

Pausing from within a tkinter function

I'm trying to create a couple of functions which do things in a sequential order. First they need to open a new window and display a label, then they need to wait for some seconds, then they need to call another function. However, I'm struggling to get the functions to wait, all the methods I've tried (.after, .sleep, .wait_visibility) seem to be ignored and it just skips to the next function call without pausing.
Here's what I have (sorry if it's messy, I'm new to python):
from tkinter import *
import time
root =Tk()
root.geometry('600x600')
def scale_screen(event = None):
global s_screen
s_screen = Toplevel(root)
s_screen.title('Residual Inhibition Tester')
s_screen.geometry('600x600')
s_screen.transient(root)
s_screen.bind('<Return>', sel)
global var
var = IntVar()
scale = Scale(s_screen, variable = var, orient = HORIZONTAL, length = 1000)
scale.focus_set()
scale.pack(anchor=CENTER)
button = Button(s_screen, text="Select", command=sel)
button.pack(anchor=CENTER)
def sel(event = None):
label = Label(s_screen)
selection = "Value = " + str(var.get())
label.config(text = selection)
interval_screen()
def interval_screen():
global i_screen
i_screen = Toplevel(root)
i_screen.geometry('600x600')
i_screen.transient(root)
i_label = Label(i_screen, text = "Please Wait")
i_label.pack(anchor = CENTER)
s_screen.destroy()
i_screen.after(3000, masker_screen)
#time.sleep(3)
#i_screen.after(300,i_label.configure(text="Playing New Masker Noise"))
#root.wait_visibility(window = i_screen)
def masker_screen():
global m_screen
m_screen = Toplevel(root)
m_screen.geometry('600x600')
m_screen.transient(root)
m_label = Label(m_screen, text = "Playing New Masker Noise").pack(anchor = CENTER)
m_screen.after(3000, lambda: scale_screen(event = None))
i_screen.destroy()
b1 = Button(root, command = scale_screen).pack(anchor=CENTER)
root.bind('<Return>', scale_screen)
root.mainloop()
In this example, the program will run but just skip the interval_screen entirely and just do the masker_screen. I'm also not averse to just using one screen and using the .configure methods to change the label text if that's easier.
Thanks!
Without seeing all the ways you tried it, it's impossible to know what you did wrong. In general you should never call time.sleep and you should never call after with just a single argument. Also, when you use after with two arguments, the second argument must be a reference to a function.
The proper way to do this is to have your first function call your second function via after:
def interval_screen():
...
i_screen.after(3000, maker_screen)
def masker_screen():
...
m_screen.after(3000, lambda: scale_screen(event = None))
Note that in your updated question you're using after incorrectly:
m_screen.after(3000, scale_screen(event = None))
You're calling the function scale_screen(...) immediately, and giving the result of that to the after function. If you need to pass arguments to your function you must create another function that does not require arguments. The simplest way to do this is with lambda, though you can also use functools.partial or you can create your own function.

Tkinter gui stops processing (Binary clock)

Since I'm new to Python I hope this is a blindingly obvious question.
I'm coding a binary clock (i.e. shows 1s and 0s, but will ultimately display graphics of large LEDs)
Here is the code which I have used so far:
#Simple binary clock
#Python 3.3.2
from tkinter import *
import time
root=Tk()
root.title("Title")
root.geometry("500x500")
def task():
tme= time.strftime("%H",time.localtime()) + time.strftime("%M",time.localtime()) + time.strftime("%S",time.localtime())
print(tme)
hpos=0
for c in tme: #tme set to HHMMSS, iterate through each digit
col=50+hpos*50 #Set column position
b=format(int(c),'04b') #Covert digit to 4 bit binary
vpos=0
for r in b:
row=50+vpos*50
if r=="1":
label1=Label(root,text="1")
else:
label1=Label(root,text="0")
label1.place(x=col,y=row)
vpos+=1
hpos+=1
root.after(1000,task) #reschedule event, 1000=1sec
root.after(100,task)
root.mainloop()
The issue is this- after leaving to run the code for about 15 minutes it slows down and grinds to a halt. I've tried it on more than one PC to the same effect, I want this to work on a Raspberry Pi but again it has the same result.
I will eventually make the form fill the screen and use graphics in the label widgets- I'm open to suggestions to solving the solution in a different way.
Thanks in advance for any help you are able to offer.
JJ
The line label1 = Label(root, ...) creates a new window each time and places this on top of the previous instance. So over time you are creating more and more windows which gradually consumes your memory.
Instead, create the label once and place it. Then just update it's text property in the task so that it displays the new value in the same window instance.
Also, you can format time in one call time.strftime("%H%M%S",time.localtime())
Example
from Tkinter import *
import sys,time
class App():
def __init__(self, parent):
parent.title("Title")
parent.geometry("500x500")
self.labels = []
self.parent = parent
x,y = 50,50
for index in range(3):
label = Label(parent, text='0')
label.place(x = x, y = y)
self.labels.append(label)
y += 50
self.parent.after(1000, self.Task)
def Task(self):
t = time.strftime("%H:%M:%S", time.localtime())
print(t)
index = 0
for c in t.split(':'):
b = format(int(c), '04b')
self.labels[index].configure(text=b)
index += 1
self.parent.after(1000, self.Task)
def main():
root = Tk()
app = App(root)
root.mainloop()
if __name__=='__main__':
sys.exit(main())
I've attempted to put in the changes as outlined, I've added a separate widget for each bit, initialized them, placed them on the form.
On changing the text of the widgets the form is not updated. I have looked to see if the label widget has an update method, but apparently not. I must be missing something really obvious here.
I have a slimmed-down version of the code here which only displays the last digit of the seconds, what have I missed?:
#Simple binary clock
from tkinter import *
import time
root=Tk()
root.title("Title")
root.geometry("500x500")
def task():
tme=time.strftime("%S",time.localtime()) #testing on just the seconds
tme=tme[1] #testing, just take last digit of seconds
print(tme)
for c in tme: #tme set to HHMMSS, iterate through each digit
b=format(int(c),'04b') #Covert digit to 4 bit binary
print(b)
if b[0]=="1":
label0=Label(root,text="1")
else:
label0=Label(root,text="0")
if b[1]=="1":
label1=Label(root,text="1")
else:
label1=Label(root,text="0")
if b[2]=="1":
label2=Label(root,text="1")
else:
label2=Label(root,text="0")
if b[3]=="1":
label3=Label(root,text="1")
else:
label3=Label(root,text="0")
root.after(1000,task) #reschedule event, 1000=1sec
label0=Label(root, text="*")
label0.place(x=50,y=50)
label1=Label(root, text="*")
label1.place(x=50,y=100)
label2=Label(root, text="*")
label2.place(x=50, y=150)
label3=Label(root, text="*")
label3.place(x=50,y=200)
root.after(1000,task)
root.mainloop()

Python Execution Order with GUI

I'm having some issues with the following code. This is the first time that I'm working with a GUI and it's been a while since I've worked with python as well. When I try to execute the solfield function with the button, it yields no output.
from Tkinter import *
import math
master = Tk()
n = float()
I = float()
def solfield():
pass
label_coils = Label(text='Number of Coils Per Meter', textvariable=n)
label_coils.grid()
coils = Entry(master)
coils.grid()
label_current = Label(text='Current in Amps', textvariable=I)
label_current.grid()
current = Entry(master)
current.grid()
calculate_button = Button(text='Calculate', command=solfield())
calculate_button.grid()
label_bfield = Label(text='B Field in +z Direction')
label_bfield.grid()
label_result = Label(text='solfield')
label_result.grid()
master.title('Coil Gun Simulation')
master.mainloop()
def solfield():
mu0 = math.pi*4e-7
solfield = mu0*n*I
print solfield
Any other tips would be appreciated as well, as there will eventually be much more coding for me to do.
This has been solved. If anyone is interested, here is the code after several fixes were made:
from Tkinter import *
import math
master = Tk()
label_coils = Label(text='Number of Coils Per Meter')
label_coils.grid()
coils = Entry(master)
coils.grid()
label_current = Label(text='Current in Amps')
label_current.grid()
current = Entry(master)
current.grid()
def solfield():
mu0 = math.pi*4e-7
n = float(coils.get())
I = float(current.get())
fieldmag = mu0*n*I
print fieldmag
calculate_button = Button(text='Calculate', command=solfield)
calculate_button.grid()
label_bfield = Label(text='B Field in +z Direction')
label_bfield.grid()
label_result = Label(text='solfield')
label_result.grid()
master.title('Coil Gun Simulation')
master.mainloop()
The problem is here:
calculate_button = Button(text='Calculate', command=solfield())
To pass the function solfield itself as the command, just use its name:
calculate_button = Button(text='Calculate', command=solfield)
What you're doing is calling the function, and then passing the return value of that function as the command.
Since you defined solfield above as do-nothing function, that return value is None, so you're telling calculate_button that its command=None, and it's properly doing nothing.
Meanwhile, as SethMMorton pointed out (but then deleted):
You have two functions named solfield, and you are naming a variable solfield in one of your solfield functions. Remove the empty function (the one with pass), and using a different variable name in the remaining function.
This isn't causing your actual problem, but it's certainly adding to the confusion that makes it harder for you to find the problem. (For example, if you hadn't included the excess empty definition of solfield at all, you would have gotten a NameError in the incorrect line, which would have made things easier to debug.)
Putting it all together, what you should do is:
Get rid of the empty (pass-only) definition of solfield.
Move the real implementation of solfield up above the point where you build the GUI.
Don't name a local variable solfield within the function.
Pass just solfield, not solfield() as the command for calculate_button.

Categories

Resources