I'm new to python and can't get a test program functioning. The goal was to keep rotating an image until it reaches the desired angle using tkinter GUI. The program runs fine but after reaching to the desired angle the console shows the following exception.
Traceback (most recent call last):
File "/usr/lib/python3.8/tkinter/__init__.py", line 1883, in __call__
return self.func(*args)
File "/usr/lib/python3.8/tkinter/__init__.py", line 804, in callit
func(*args)
StopIteration
And the code is :
import tkinter as tk
from PIL import ImageTk
from PIL import Image
import time
class SimpleApp(object):
def __init__(self, master, filename, **kwargs):
self.master = master
self.filename = filename
self.canvas = tk.Canvas(master, width=500, height=500)
self.canvas.pack()
self.update = self.draw().__next__
master.after(100, self.update)
def draw(self):
image = Image.open(self.filename)
angle = 0
while (angle<90):
tkimage = ImageTk.PhotoImage(image.rotate(angle))
canvas_obj = self.canvas.create_image(
250, 250, image=tkimage)
self.master.after_idle(self.update)
yield
angle += 5
time.sleep(0.1)
root = tk.Tk()
app = SimpleApp(root, 'cat.jpg')
root.mainloop()
Thanks in advance.
This is happening because when you reach the desired angle, the code doesn't enter your while loop, meaning no yield statement is encountered. Thus when you call self.draw().__next__, there is no next element to retrieve.
If you want to keep the current code, simply adding an else statement with a yield afterwards would solve your error
def draw(self):
image = Image.open(self.filename)
angle = 0
while (angle<90):
tkimage = ImageTk.PhotoImage(image.rotate(angle))
canvas_obj = self.canvas.create_image(
250, 250, image=tkimage)
self.master.after_idle(self.update)
yield
angle += 5
time.sleep(0.1)
else:
yield
However I see no point in using the next and yield statements in the function you've made, and would probably have done something like the below instead
import tkinter as tk
from PIL import ImageTk
from PIL import Image
import time
class SimpleApp(object):
def __init__(self, master, filename, **kwargs):
self.master = master
self.filename = filename
self.canvas = tk.Canvas(master, width=500, height=500)
self.canvas.pack()
self.image = Image.open(self.filename)
self.angle = 0
self.master.after(100, self.draw)
def draw(self):
while self.angle < 90:
tkimage = ImageTk.PhotoImage(self.image.rotate(angle))
canvas_obj = self.canvas.create_image(
250, 250, image=tkimage)
self.angle += 5
self.master.after(100, self.draw)
root = tk.Tk()
app = SimpleApp(root, 'cat.jpg')
root.mainloop()
Related
Trying to make a flappy bird clone and am having troubles with spawning in the object that will be the bird. I have tried to fix it by myself but I cannot think of why it isn't working. Tried to find if there was any fix online and I came up empty. I don't understand what the issue is and why it isn't spawning. I can't think of how to fix it. my code is here.
import tkinter
class Menu:
_BUTTON_HEIGHT = 2
_BUTTON_WIDTH = 10
_PLAY_BUTTON = "Start"
_EXIT_BUTTON = "Exit"
def __init__(self):
""""""
self.canvas = tkinter.Tk()
self.play_button = None
self.exit_button = None
self.starting_game_new = None
def canvas_create(self):
"""Creates a window for the frame to be displayed"""
self.canvas.geometry("500x700")
def menu_buttons(self):
""""""
self.play_button = tkinter.Button(self.canvas,
command=self.start_game,
text=Menu._PLAY_BUTTON,
width=Menu._BUTTON_WIDTH,
height=Menu._BUTTON_HEIGHT)
self.play_button.pack()
self.exit_button = tkinter.Button(self.canvas,
command=self.exit_game,
text=Menu._EXIT_BUTTON,
width=Menu._BUTTON_WIDTH,
height=Menu._BUTTON_HEIGHT)
self.exit_button.pack()
def start_game(self):
self.play_button.pack_forget()
self.exit_button.pack_forget()
self.starting_game_new = GameFrame(canvas=self.canvas)
self.starting_game_new.create_frame()
self.starting_game_new.create_bird()
def exit_game(self):
self.canvas.destroy()
class GameFrame:
_FRAME_HEIGHT = 700
_FRAME_WIDTH = 500
_FRAME_COLOUR = "dodger blue"
_X_BIRD = 200
_X_BIRD_2 = 250
_Y_BIRD = 150
_Y_BIRD_2 = 200
_BIRD_COLOUR = "gold2"
def __init__(self, canvas):
""""""
self.game_frame = None
self.canvas = canvas
self.bird = None
def create_frame(self):
"""Creates the game canvas and background"""
self.game_frame = tkinter.Canvas(self.canvas,
width=GameFrame._FRAME_WIDTH,
height=GameFrame._FRAME_HEIGHT,
background=GameFrame._FRAME_COLOUR)
self.game_frame.pack()
def create_bird(self):
self.bird = self.canvas.create_rectangle(GameFrame._X_BIRD,
GameFrame._Y_BIRD,
GameFrame._X_BIRD_2,
GameFrame._Y_BIRD_2,
fill=GameFrame._BIRD_COLOUR)
def main():
start = Menu()
start.canvas_create()
start.menu_buttons()
start.canvas.mainloop()
if __name__ == '__main__':
main()
Error code here:
Exception in Tkinter callback Traceback (most recent call last): File "(file)init.py", line 1883, in call return self.func(*args) File "", line 50, in start_game self.starting_game_new.create_bird() File "D:/Downloads/flappybirdgame-main/aewf.py", line 82, in create_bird self.bird = self.canvas.create_rectangle(GameFrame._X_BIRD, File "", line 2345, in getattr return getattr(self.tk, attr) AttributeError: '_tkinter.tkapp' object has no attribute 'create_rectangle'
The method create_rectangle is a method on your canvas. Your variable self.canvas is not a canvas, it's the root window.
You need to create a tkinter Canvas widget in order to draw a rectangle. It looks like you're doing so in this line:
self.game_frame = tkinter.Canvas(self.canvas, ...)
So, you need to call create_rectangle on this widget instead of self.canvas:
self.bird = self.game_frame.create_rectangle(...)
I am trying to get an array of a custom tkinter object (it is a GUI window that shows a video stream).
I am running into errors with the code I tried to write.
I made a function to make the custom object and tried to make an array of those objects.
I am getting the following error:
File "array_videoFeeds.py", line 60, in
app = FeedCams(root) File "array_videoFeeds.py", line 8, in init
self.initialize() File "array_videoFeeds.py", line 54, in initialize
self.b = self.videowindow() File "array_videoFeeds.py", line 11, in videowindow
self.videowindow = window NameError: name 'window' is not defined
Here is my python code:
import tkinter as Tk
import numpy as np
class FeedCams(Tk.Frame):
def __init__(self,parent):
Tk.Frame.__init__(self, parent)
self.parent = parent
self.initialize()
def videowindow(self):
self.videowindow = window
self.cap = cap
self.width = self.cap.get(cv2.CAP_PROP_FRAME_WIDTH)
self.height = self.cap.get(cv2.CAP_PROP_FRAME_HEIGHT)
self.interval = 20 # Interval in ms to get the latest frame
# Create canvas for image
self.canvas = tk.Canvas(self.window, width=self.width, height=self.height)
# self.canvas.grid(row=0, column=0)
# Update image on canvas
self.update_image()
dynamic_windows.append(videowindow)
def update_image(self):
# Get the latest frame and convert image format
self.image = cv2.cvtColor(self.cap.read()[1], cv2.COLOR_BGR2RGB) # to RGB
self.image = Image.fromarray(self.image) # to PIL format
self.image = ImageTk.PhotoImage(self.image) # to ImageTk format
# Update image
self.canvas.create_image(0, 0, anchor=tk.NW, image=self.image)
# Repeat every 'interval' ms
self.videowindow.after(self.interval, self.update_image)
def initialize(self):
'''
Draw the GUI
'''
self.parent.title("RUN ON START TEST")
self.parent.grid_rowconfigure(1,weight=1)
self.parent.grid_columnconfigure(1,weight=1)
self.frame = Tk.Frame(self.parent)
self.frame.pack(fill=Tk.X, padx=5, pady=5)
# Create a array of videos
self.a = np.zeros((3,3))
for i in range(0,self.a.shape[0]):
for j in range(0,self.a.shape[1]):
self.b = self.videowindow()
self.b.grid(row=i, column= j)
# Start the main program here
if __name__ == "__main__":
root=Tk.Tk()
app = FeedCams(root)
root.mainloop()
How can I successfully make this work?
UPDATE:
I changed the line of code based on one of the replies to self.a = np.zeros((3,3)).
There error has been updated in the question.
I want to do something that should be rather simple, but I'm struggling to make it work.
Basically I have 2 Tkinter windows (canvas_tk and control_tk).
In canvas_tk I want to show an image and draw a circle on top of it.
In control_tk I have an Entry to input the radius of the circle to be drawn.
In my code the critical line is at the very bottom of the code:
self.panel = tk.Label(canvas_tk, image=image)
If I make the label in the control_tk or if I dont specify the parent, it actually works fine and draws the circles in the control_tk window
However I want the circles to be drawn in the canvas_tk window
self.panel = tk.Label(image=image) # Works
self.panel = tk.Label(control_tk, image=image) # Works
self.panel = tk.Label(canvas_tk, image=image) # Fails
Here's my minimal code:
import tkinter as tk
from PIL import Image, ImageTk, ImageDraw
control_tk = tk.Tk()
canvas_tk = tk.Tk()
control_tk.geometry("300x300")
canvas_tk.geometry("900x900")
class Drawing:
def __init__(self):
# Numerical entry with a variable traced with callback to "on_change" function
self.radius_var = tk.IntVar()
self.radius_var.trace_variable("w", self.on_change)
tk.Entry(control_tk, textvariable=self.radius_var).grid()
# Initialize image and panel
self.image = Image.new('RGB', (1000, 1000))
self.panel = None
# mainloop for the two tkinter windows
control_tk.mainloop()
canvas_tk.mainloop()
def on_change(self, *args):
print("Value changed") # to check that function is being called
draw = ImageDraw.Draw(self.image)
draw.ellipse((50-self.radius_var.get(), 50-self.radius_var.get(),
50+self.radius_var.get(), 50+self.radius_var.get()),
outline='blue', width=3)
image = ImageTk.PhotoImage(self.image)
if self.panel: # update the image
self.panel.configure(image=image)
self.panel.image = image
else: # if it's the first time initialize the panel
self.panel = tk.Label(canvas_tk, image=image)
self.panel.image = image
self.panel.grid(sticky="w")
Drawing()
And the traceback error basically complains about image not existing
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\lab\AppData\Local\Programs\Python\Python37\lib\tkinter\__init__.py", line 508, in get
return self._tk.getint(value)
_tkinter.TclError: expected integer but got ""
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "C:\Users\lab\AppData\Local\Programs\Python\Python37\lib\tkinter\__init__.py", line 1705, in __call__
return self.func(*args)
File "C:/GIT/142-277-00_pyScuti2/test.py", line 29, in on_change
draw.ellipse((50-self.radius_var.get(), 50-self.radius_var.get(),
File "C:\Users\lab\AppData\Local\Programs\Python\Python37\lib\tkinter\__init__.py", line 510, in get
return int(self._tk.getdouble(value))
_tkinter.TclError: expected floating-point number but got ""
Value changed
Value changed
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\lab\AppData\Local\Programs\Python\Python37\lib\tkinter\__init__.py", line 1705, in __call__
return self.func(*args)
File "C:/GIT/142-277-00_pyScuti2/test.py", line 38, in on_change
self.panel = tk.Label(canvas_tk, image=image)
File "C:\Users\lab\AppData\Local\Programs\Python\Python37\lib\tkinter\__init__.py", line 2766, in __init__
Widget.__init__(self, master, 'label', cnf, kw)
File "C:\Users\lab\AppData\Local\Programs\Python\Python37\lib\tkinter\__init__.py", line 2299, in __init__
(widgetName, self._w) + extra + self._options(cnf))
_tkinter.TclError: image "pyimage1" doesn't exist
First of all, you should not use multiple Tk() instances. Second, you better not using trace() on tracking input change, use bind('<Return>', ...) to trigger drawing after user enters value and press Enter key. Below is a modified code based on yours:
import tkinter as tk
from PIL import Image, ImageTk, ImageDraw
control_tk = tk.Tk()
control_tk.geometry("300x300+800+600")
canvas_tk = tk.Toplevel() # use Toplevel instead of Tk
canvas_tk.geometry("900x900+10+10")
class Drawing:
def __init__(self):
# Numerical entry with a variable traced with callback to "on_change" function
self.radius_var = tk.IntVar()
entry = tk.Entry(control_tk, textvariable=self.radius_var)
entry.grid()
# binding Enter key on entry to trigger the drawing
entry.bind('<Return>', self.on_change)
# Initialize image and panel
self.image = Image.new('RGB', (1000, 1000))
self.panel = None
# mainloop for the main window
control_tk.mainloop()
def on_change(self, *args):
try:
radius = self.radius_var.get()
except:
print('Invalid number')
return
print("Value changed") # to check that function is being called
draw = ImageDraw.Draw(self.image)
draw.ellipse((50-radius, 50-radius, 50+radius, 50+radius),
outline='blue', width=3)
image = ImageTk.PhotoImage(self.image)
if self.panel:
self.panel.configure(image=image)
else:
self.panel = tk.Label(canvas_tk, image=image)
self.panel.grid(sticky='w')
self.panel.image = image
Drawing()
I have two questions:
I want to make several rectangles, moving randomly. I am at a point where i
can do it with one rectangle but i don't get it how to multiply them.
I am a beginner so i have copied this example and modified it in my favor but i don't know exactly why i have to write everytime the "self" and the "init". It seems to be common to name those parameters in this manner.
I looked both questions up several times but didn't find a satisfying answer.
here the code:
from tkinter import *
from tkinter.ttk import *
from random import *
class simulation:
def __init__(self, anzahl, master = None):
self.master = master
self.canvas = Canvas(master, width= 2736, height= 1824)
self.rectangle = self.canvas.create_rectangle(500, 380, 515, 395, fill = "black")
self.canvas.pack()
self.movement()
def movement(self):
self.canvas.move(self.rectangle, randint(-10,10), randint(-10,10))
self.canvas.after(100, self.movement)
if __name__ == "__main__":
master = Tk()
master.title("Simulation")
simulation = simulation(master)
mainloop()
maybe this will help you, make an object for each player and the canvas packed ones in order not to hide other players ...
from tkinter import *
from random import *
class simulation:
def __init__(self, master , canvas , color):
self.master = master
self.canvas = canvas
self.rectangle = canvas.create_rectangle(500, 380, 515, 395, fill=color)
def movement(self):
canvas.move(self.rectangle, randint(-10,10), randint(-10,10))
self.canvas.after(100, self.movement)
if __name__ == "__main__":
master = Tk()
canvas = Canvas(master, width=2736, height=1824)
canvas.pack()
master.title("Simulation")
player1 = simulation(master, canvas,"red")
player2 = simulation(master,canvas, "black")
player1.movement()
player2.movement()
mainloop()
Please run the following example.
I have created a progress bar for my application, and by pressing the "Open" button a progress bar pops up. However, the progress bar does not fill up and it appears that the script is halt at
bar.set(i)
when function ProgressBarLoop is called.
from Tkinter import Tk, Frame, BOTH, Label, Toplevel, Canvas, Button
import thread
import time
class ProgressBar:
def __init__(self, parent, width, height):
master = Toplevel(parent)
master.protocol('WM_DELETE_WINDOW', self.hide )
self.master = master
self.master.overrideredirect(True)
ws = self.master.winfo_screenwidth()
hs = self.master.winfo_screenheight()
w = (True and ws*0.2) or 0.2
h = (True and ws*0.15) or 0.15
x = (ws/2) - (w/2)
y = (hs/2) - (h/2)
self.master.geometry('%dx%d+%d+%d' % (width, height * 2.5, x, y))
self.mode = 'percent'
self.ONOFF = 'on'
self.width = width
self.height = height
self.frame = None
self.canvas = None
self.progressBar = None
self.backgroundBar = None
self.progressformat = 'percent'
self.label = None
self.progress = 0
self.createWidget()
self.frame.pack()
self.set(0.0) # initialize to 0%
def createWidget(self):
self.frame = Frame(self.master, borderwidth = 1, relief = 'sunken')
self.canvas = Canvas(self.frame)
self.backgroundBar = self.canvas.create_rectangle(0, 0, self.width, self.height, fill = 'darkblue')
self.progressBar = self.canvas.create_rectangle(0, 0, self.width, self.height, fill = 'blue')
self.setWidth()
self.setHeight()
self.label = Label(self.frame, text = 'Loading...', width = 20)
self.label.pack(side = 'top') # where text label should be packed
self.canvas.pack()
def setWidth(self, width = None):
if width is not None:
self.width = width
self.canvas.configure(width = self.width)
self.canvas.coords(self.backgroundBar, 0, 0, self.width, self.height)
self.setBar() # update progress bar
def setHeight(self, height = None):
if height is not None:
self.height = height
self.canvas.configure(height = self.height)
self.canvas.coords(self.backgroundBar, 0, 0, self.width, self.height)
self.setBar() # update progress bar
def set(self, value):
if self.ONOFF == 'off': # no need to set and redraw if hidden
return
if self.mode == 'percent':
self.progress = value
self.setBar()
return
def setBar(self):
self.canvas.coords(self.progressBar,0, 0, self.width * self.progress/100.0, self.height)
self.canvas.update_idletasks()
def hide(self):
if isinstance(self.master, Toplevel):
self.master.withdraw()
else:
self.frame.forget()
self.ONOFF = 'off'
def configure(self, **kw):
mode = None
for key,value in kw.items():
if key=='mode':
mode = value
elif key=='progressformat':
self.progressformat = value
if mode:
self.mode = mode
def ProgressBarLoop(window, bar):
bar.configure(mode = 'percent', progressformat = 'ratio')
while(True):
if not window.loading:
break
for i in range(101):
bar.set(i)
print "refreshed bar"
time.sleep(0.001)
bar.hide()
class Application(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.pack(fill = BOTH, expand = True)
parent.geometry('%dx%d+%d+%d' % (100, 100, 0, 0))
Button(parent, text = "Open", command = self.onOpen).pack()
def onOpen(self, event = None):
self.loading = True
bar = ProgressBar(self, width=150, height=18)
thread.start_new_thread(ProgressBarLoop, (self, bar))
while(True):
pass
root = Tk()
Application(root)
root.mainloop()
EDIT:
After trying dano's answer, it works but I get the following error:
Exception in thread Thread-1:
Traceback (most recent call last):
File "/mnt/sdev/tools/lib/python2.7/threading.py", line 551, in __bootstrap_inner
self.run()
File "/mnt/sdev/tools/lib/python2.7/threading.py", line 504, in run
self.__target(*self.__args, **self.__kwargs)
File "/home/jun/eclipse/connScript/src/root/nested/test.py", line 88, in ProgressBarLoop
bar.set(i)
File "/home/jun/eclipse/connScript/src/root/nested/test.py", line 61, in set
self.setBar()
File "/home/jun/eclipse/connScript/src/root/nested/test.py", line 64, in setBar
self.canvas.coords(self.progressBar,0, 0, self.width * self.progress/100.0, self.height)
File "/mnt/sdev/tools/lib/python2.7/lib-tk/Tkinter.py", line 2178, in coords
self.tk.call((self._w, 'coords') + args)))
RuntimeError: main thread is not in main loop
The problem is that you're running an infinite loop immediately after you start the thread:
def onOpen(self, event = None):
self.loading = True
bar = ProgressBar(self, width=150, height=18)
thread.start_new_thread(ProgressBarLoop, (self, bar))
while(True): # Infinite loop
pass
Because of this, control never returns to the Tk event loop, so the progress bar can never be updated. The code should work fine if you remove the loop.
Also, you should use the threading module instead of the thread module, as suggested in the Python docs:
Note The thread module has been renamed to _thread in Python 3. The
2to3 tool will automatically adapt imports when converting your
sources to Python 3; however, you should consider using the high-level
threading module instead.
Putting it altogether, here's what onOpen should look like:
def onOpen(self, event = None):
self.loading = True
bar = ProgressBar(self, width=150, height=18)
t = threading.Thread(target=ProgressBarLoop, args=(self, bar))
t.start()
Edit:
Trying to update tkinter widgets from multiple threads is somewhat finicky. When I tried this code on three different systems, I ended up getting three different results. Avoiding loops in the threaded method helped avoid any errors:
def ProgressBarLoop(window, bar, i=0):
bar.configure(mode = 'percent', progressformat = 'ratio')
if not window.loading:
bar.hide()
return
bar.set(i)
print "refreshed bar"
i+=1
if i == 101:
# Reset the counter
i = 0
window.root.after(1, ProgressBarLoop, window, bar, i)
But really, if we're not using an infinite loop in ProgressBarLoop anyway, there's really no need to use a separate thread at all. This version of ProgressBarLoop can be called in the main thread, and the GUI won't be blocked for any noticeable amount of time.