tkinter wont display anything when using after() method - python

At first I implemented this code which uses classes and worked fine:
from Tkinter import *
import numpy as np
from PIL import Image,ImageTk
import time
#----------------------------------------------------------------------
class MainWindow():
#----------------
def __init__(self,main,pix):
# canvas for image
self.canvas = Canvas(main, width=424, height=424)
self.canvas.grid(row=0, column=0)
# images
self.im=Image.fromarray(pix.astype('uint8'))
self.photo = ImageTk.PhotoImage(image=self.im)
self.my_images = self.photo
self.my_image_number = 0
# set first image on canvas
self.image_on_canvas = self.canvas.create_image(0, 0, anchor = NW, image = self.my_images )
self.i=0
self.j=0
main.after(1,self.onButton,main,pix)# button to change image
main.update()
#----------------
def onButton(self,main,pix):
print self.i,self.j
if self.j==100:
return
pix[self.i][self.j]=255-pix[self.i][self.j]
self.i+=1
if self.i==100:
self.i=0
self.j+=1
self.im=Image.fromarray(pix.astype('uint8'))
self.photo = ImageTk.PhotoImage(image=self.im)
self.my_images = self.photo
self.canvas.itemconfig(self.image_on_canvas, image = self.my_images)
main.after(1,self.onButton,main,pix)
else:
main.after(0,self.onButton,main,pix)
#----------------------------------------------------------------------
root = Tk()
pix=np.array([[(i,j,255) for i in range(256)] for j in range(255,-1,-1)])
x=MainWindow(root,pix)
root.mainloop()
Later I tried to use the same functions without the class and it looks like this:
from Tkinter import *
import numpy as np
from PIL import Image,ImageTk
import time
def onButton(main,pix):
global i,j
if j==100:
return
pix[i][j]=255-pix[i][j]
i+=1
if i==100:
i=0
j+=1
im=Image.fromarray(pix.astype('uint8'))
photo = ImageTk.PhotoImage(image=im)
my_images = photo
canvas.itemconfig(image_on_canvas, image = my_images)
main.after(1,onButton,main,pix)
else:
main.after(0,onButton,main,pix)
root = Tk()
pix=np.array([[(i,j,255) for i in range(256)] for j in range(255,-1,-1)])
canvas = Canvas(root, width=424, height=424)
canvas.grid(row=0, column=0)
im=Image.fromarray(pix.astype('uint8'))
photo = ImageTk.PhotoImage(image=im)
image_on_canvas = canvas.create_image(0, 0, anchor = NW, image = photo )
i,j=0,0
root.after(1,onButton,root,pix)# button to change image
print "hi"
root.mainloop(f i==100:
i=0
j+=1
im=Image.fromarray(pix.astype('uint8'))
photo = ImageTk.PhotoImage(image=im)
my_images = photo
canvas.itemconfig(image_on_canvas, image = my_images)
main.after(1,onButton,main,pix)
else:
main.after(0,onButton,main,pix)
root = Tk()
pix=np.array([[(i,j,255) for i in range(256)] for j in range(255,-1,-1)])
canvas = Canvas(root, width=424, height=424)
canvas.grid(row=0, column=0)
im=Image.fromarray(pix.astype('uint8'))
photo = ImageTk.PhotoImage(image=im)
image_on_canvas = canvas.create_image(0, 0, anchor = NW, image = photo )
i,j=0,0
root.after(1,onButton,root,pix)# button to change image
print "hi"
root.mainloop()
Why does this not work? This is the first time I am working with tkinter, so I am probably missing something crucial. What do I need to change?

I'm curious why you need to implement such a thing without a class, but first let's look at your problem, namely the OnButton function.
You have already defined global variables i and j. But how about others?
All variables that you are trying to mutate (and keep value) must also be declared as global! This is a root of your problem and as for the answer to the question "why doesn't it work" - #PM 2Ring give you a good refer to photoimage docs.
Note: When a PhotoImage object is garbage-collected by Python (e.g. when you return from a function which stored an image in a local variable), the image is cleared even if it’s being displayed by a Tkinter widget.
try:
from tkinter import *
except ImportError:
from Tkinter import *
import numpy as np
from PIL import Image,ImageTk
def onButton():
global i, j, photo #, pix
print(i, j)
if j == 100:
return
pix[i][j] = 255-pix[i][j]
i += 1
if i == 100:
i = 0
j += 1
photo = ImageTk.PhotoImage(image=Image.fromarray(pix.astype('uint8')))
canvas.itemconfig(image_on_canvas, image=photo)
root.after(1, onButton)
else:
root.after(0, onButton)
root = Tk()
pix=np.array([[(i, j, 255) for i in range(256)] for j in range(255, -1, -1)])
canvas = Canvas(root, width=424, height=424)
canvas.grid(row=0, column=0)
photo = ImageTk.PhotoImage(image=Image.fromarray(pix.astype('uint8')))
image_on_canvas = canvas.create_image(0, 0, anchor=NW, image=photo)
i, j = 0, 0
root.after(1, onButton) # button to change image
print("hi")
root.mainloop()
And if there's no need for this variables outside function - then make'em local and move all relevant code into function!
After all, please, take a time to read this article. Maybe you change your mind about the classes (and about wildcard import).

Related

How to get circles to appear over the video in canvas tkinter?

I have the below code:
import tkinter as tk, threading
from tkinter import *
import imageio
from PIL import Image, ImageTk
from random import *
video_name = "video.mp4" #This is your video file path
video = imageio.get_reader(video_name)
def stream(label):
for image in video.iter_data():
frame_image = ImageTk.PhotoImage(Image.fromarray(image))
label.config(image=frame_image)
label.image = frame_image
def circle():
global circ
x = randint(0, 299)
y = randint(0, 299)
diameter = randint(10, 100)
circ = canvas.create_oval(x, y, x + diameter, y + diameter, tags="circle")
canvas.tag_raise(circ)
if __name__ == "__main__":
root = tk.Tk()
canvas = Canvas(root, bg="green")
canvas.pack(expand=True, fill=BOTH)
my_label = tk.Label(canvas)
my_label.pack()
b = Button(canvas, text="Circle", command=circle)
b.pack()
thread = threading.Thread(target=stream, args=(my_label,))
thread.daemon = 1
thread.start()
root.mainloop()
It works fine, and the circles appear, but they go behind the video playing. How can I make the circles appear on top of the video?
Thanks!
You will need to use a text item on the canvas rather than a label. The canvas does not allow you to draw on top of widgets embedded in or on the canvas.

How do I delete the Label I click on?

I have been working on a project where I use labels that I want to disappear on click, but it only deletes the last label that was created. Here's my code:
from tkinter import *
import tkinter
import random
from PIL import Image, ImageTk
from functools import partial
width1=1280
height1=720
canvas = tkinter.Canvas(width=width1,height=height1, bg = 'white')
canvas.pack()
def clicked(*args):
label.destroy()
def square():
global label
global img
sq_time = random.randrange(4000,6000)
x = random.randrange(100,width1-40,40)
y = random.randrange(40,height1-40,40)
label = Label(canvas, image = img)
label.place(x = x , y = y)
label.bind("<Button-1>",partial(clicked))
canvas.after(sq_time, square)
img = ImageTk.PhotoImage(Image.open('froggy.png'))
square()
mainloop()
froggy.png is a image that I have saved in the same folder as the code. Can someone tell me how do I delete the label that was clicked?
In tkinter event handler functions are automatically passed an event object argument that, among other things, has an attribute that identifies the widget that triggered them. This means you can use that instead of creating a partial to get the information needed.
from tkinter import *
import tkinter
import random
from PIL import Image, ImageTk
width1 = 1280
height1 = 720
canvas = tkinter.Canvas(width=width1, height=height1, bg='white')
canvas.pack()
def clicked(event):
event.widget.destroy()
def square():
global label
global img
sq_time = random.randrange(4000, 6000)
x = random.randrange(100, width1-40, 40)
y = random.randrange(40, height1-40, 40)
label = Label(canvas, image = img)
label.place(x=x, y=y)
label.bind("<Button-1>", clicked)
canvas.after(sq_time, square)
img = ImageTk.PhotoImage(Image.open('froggy.png'))
square()
mainloop()
def on_click():
label.after(1000, label.destroy)
Button(win, text="Delete", command=on_click).pack()

Get access to a variable method to end threading loop?

I have question about how to call some variables, in this case from the method inside exportCsv that belongs to back_Gui class. I want to use the variables self.msg_ and self.opstat in the method __update from class _Gui to stop the reproduction of the gift and promt out the window to save the file. When I run the code and press the button it iterates in an infinite loop because the variable is not passing to. Also, I try to aggregate some threading to not to freeze the window when the button is pressed. Any solution?.
Also, I think these variables I mention would be inside the try but what happens in the except?. Do I have to create more variables to avoid infinite looping on the gift?
from tkinter import *
import tkinter as tk
from tkinter import filedialog
import sqlite3
import pandas as pd
from PIL import Image, ImageTk
import time
import threading
class back_Gui: #Superclass
'''Class that handle the data'''
def __init__(self, db_name = 'database.db'):
self.db_name = db_name
self.msg_ = None
self.opstat = None
.
.
.
def exportCSV(self):
df_to = self.df
try:
export_file = filedialog.asksaveasfilename(defaultextension='.csv')
df_to.to_csv(export_file, index=False, header=True)
except:
pass
#These are the variables that I need
self.msg_ = "Done."
self.opstat = -1
class _Gui(back_Gui): #Subclass
def __init__(self, window):
'''Gui of the windw tk '''
self.wind = window #child
super().__init__()
self.text_font = ('Helvetica', '10', 'bold')
self.Button_(self.wind)
def Button_(self, wind):
"""Run the gift while the csv is being generated"""
#Button
b1 = Button(self.wind, text="random",
font=self.text_font,
command=self.job_genCsv,
).grid(row=1, column=5, padx=5, pady=5 ,sticky=W)
def frame_maps(self, wind):
'''Frame Containter'''
self.frame = LabelFrame(self.wind, text='Hail Hydra', font=self.text_font, height = 500, width = 1300, bd=4)
self.frame.grid(row=2, columnspan=20, sticky=W+E)#, columnspan=3, padx=25, pady=25)
# create the canvas, size in pixels
self.canvas = Canvas(self.frame, width=1300, height=500, bg='white')
# pack the canvas into a frame/form
self.canvas.grid(row=0, columnspan=20, sticky=N, ipadx=20, ipady=20)#
# load the .gif image file
#Here it has to be use the self because is local variable
self.current_image_number = 0
file="cain.gif"
info = Image.open(file)
self.frames = info.n_frames
self.gif1 = [PhotoImage(file=file, format=f"gif -index {i}") for i in range(self.frames)]
def __update(self):
#self.job_genCsv()
''' update the gift '''
self.frame.update_idletasks()#update
if self.opstat >= 0.0:
#msg = self.image_on_canvas #
# next image
self.current_image_number += 1
# return to first image
if self.current_image_number == self.frames: #len(self.images):
self.current_image_number = 0
# change image on canvas
self.canvas.itemconfig(self.update, image=self.gif1[self.current_image_number])
_ = threading.Timer(0, self.__update).start()
print("loop")
else:
if self.msg_ == "Done.":
self.update = self.canvas.create_image(250, 50, anchor=NW, image=self.gif1[self.current_image_number])
del self.msg_
#control variable restablished
self.opstat = 0
print("ends")
def job_genCsv(self):
'''Runs his job and call frame_maps '''
self.frame_maps(self)
_ = threading.Timer(0, self.exportCSV).start()
_ = threading.Timer(0, self.__update).start()
if __name__ == '__main__':
window = Tk()
application = _Gui(window)
window.mainloop()
I'm getting this error:
File "C:\Users\Documents\run.py", line 214, in __update
if self.opstat >= 0.0:
AttributeError: '_Gui' object has no attribute 'opstat'

How to add pictures to a Tkinter Treeview as values?

How can I add picture to ttk Treeview? Example Code:
from tkinter import *
from ttk import *
from tkinter import ttk
import json
from PIL import Image, ImageTk
okno = Tk()
def maketable():
style = ttk.Style(okno)
style.configure("TVS.Treeview", rowheight=40)
tv = Treeview(okno, style="TVS.Treeview")
tv['columns'] = ('LName', 'Pic')
tv.heading("#0", text='Jméno', anchor='w')
tv.column("#0", anchor="w", width=200)
tv.heading('LName', text='Příjmení')
tv.column('LName', anchor='center', width=200)
tv.heading('Pic', text='Obrazek')
tv.column('Pic', anchor='center', width=200)
dbf = open("xxx.json", 'r')
db = json.loads(dbf.read())
for i in range(0, len(db)):
root_pic1 = Image.open(db[i]["Pic"])
root_pic2 = ImageTk.PhotoImage(root_pic1)
tv.insert('', 'end', image=root_pic2, text=db[i]['Name'], values=(db[i]['LName']))
tv.pack()
def main():
okno.mainloop()
if __name__ == '__main__':
maketable()
main()
I want the "Pic" Column to have pictures - this means a picture shouldn't be in #0 because it is a value. In db[i]["Pic"] there is a file path. In the values=() I can't add images—it doesn't show anything.
EDIT: Thanks for solution but doesnt work on multiple images:
for i in range(0, len(db)):
tv._image = Image
tv._image = Image.open(db[i]["Pic"])
tv._image.thumbnail((100, 200), PIL.Image.ANTIALIAS)
tv._image = ImageTk.PhotoImage(tv._image)
tv.insert('', 'end', image=tv._image, values=(db[i]['Name'], db[i]['LName']))
tv.pack()
Like #Nae and #furas pointed out, local variables are removed when function finishes and your images get garbage collected.
you would need to do something like:
tv._image = Image.open('test.png')
tv._image = ImageTk.PhotoImage(tv._image)
for i in range(0, len(db)):
tv.insert('', 'end', image=tv._image, text=db[i]['Name'], values=(db[i]['LName']))
or define them outside the function. It would be ideal if you were using classes and you can call self.root_pic = ...
EDIT: As for more images, you would need to create a list:
tv._images = []
for i in range(0, len(db)):
image = Image.open(db[i]["Pic"])
image = ImageTk.PhotoImage(image)
image.thumbnail((100, 200), PIL.Image.ANTIALIAS) # resize
tv._images.append(image)
tv.insert('', 'end', image=tv._images[-1], text=db[i]['Name'], values=(db[i]['LName']))

Tkinter images crashing

So I made a script in python with Tkinter and the thing is that the first Tkinter window pops up without problems but when the code goes to the second window it says :
_tkinter.TclError: image "pyimage1" doesn't exist
and I didn't find anything that helped me, could someone help me please ?
Here is the code :
from Tkinter import *
from PIL import ImageTk, Image
def choose():
global name, chosen
name = name1.get()
chosen = chosen1.get()
print name
print chosen
root0.quit()
root0 = Tk()
name1 = Entry(root0)
name1.pack()
chosen1 = Entry(root0)
chosen1.pack()
Button(root0, text="ENTER", command=choose).pack()
root0.mainloop()
root = Tk()
img = ImageTk.PhotoImage(Image.open('person1.png'))
panel1 = Label(root, image = img)
panel1.pack(side="left")
img2 = ImageTk.PhotoImage(Image.open('person2.png'))
panel2 = Label(root, image = img2)
panel2.pack(side="right")
root.mainloop()
by the way, the python version is 2.7
This is a side effect of using 2 roots (Tk() instances). The images default to associate with the first root window. The quick fix is to provide the image with the correct root:
img2 = ImageTk.PhotoImage(Image.open('person2.png'), master=root)
The proper fix is to never use more than one Tk(). Put all your code into Frame instances, and then destroy one and load the other when the time is right:
import Tkinter as tk
def choose():
global name, chosen
name = name1.get()
chosen = chosen1.get()
print name
print chosen
frame0.destroy() # kill this frame
frame1.pack() # open new frame
root = tk.Tk()
frame0 = tk.Frame(root)
name1 = tk.Entry(frame0)
name1.pack()
chosen1 = tk.Entry(frame0)
chosen1.pack()
tk.Button(frame0, text="ENTER", command=choose).pack()
frame1 = tk.Frame(root)
img = ImageTk.PhotoImage(Image.open('person1.png'))
panel1 = tk.Label(frame1, image = img)
panel1.pack(side="left")
img2 = ImageTk.PhotoImage(Image.open('person2.png'))
panel2 = tk.Label(frame1, image = img2)
panel2.pack(side="right")
#start the program
frame0.pack() # load frame0
root.mainloop()
Note I also moved you away from the evil wildcard imports (from module import *).

Categories

Resources