Displaying image using canvas in Python [duplicate] - python-3.x

This question already has answers here:
tkinter canvas image not displaying
(3 answers)
Closed 4 months ago.
I am getting errors when I want to display photos using canvas and Tkinter.
The code runs properly but when I added a few lines to display the image I got error like this
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.10_3.10.2032.0_x64__qbz5n2kfra8p0\lib\tkinter\__init__.py", line 1921, in __call__
return self.func(*args)
File "C:\Users\Kacper\PycharmProjects\kosmos\[w budowie]guicosmoscatalog.py", line 60, in show
photo_display.pack()
AttributeError: 'int' object has no attribute 'pack'
def show(event):
type = clicked.get()
if type == options_list_planets[0]:
#IMAGE DISPLAYING
canvas2 = Canvas(root, width = 400, height = 400, bg='#171717', bd = 0, highlightthickness=0, relief='ridge')
canvas2.pack()
my_img = ImageTk.PhotoImage(Image.open("Merkury.png"))
canvas2.place(relx = 0.05, rely = 0.1, relheight = 0.6, relwidth = 0.5)
photo_display = canvas2.create_image(225, 210, image=my_img)
photo_display.pack()
#LINES OF CODE RESPONSIBLE FOR DISPLAYING TEXT (IT WORKS)
for widget in pierwszy_frame.winfo_children():
widget.destroy()
for widget in drugi_frame.winfo_children():
widget.destroy()
text_label = tk.Label(pierwszy_frame, text = FULL_MERCURY_DESC)
text_label.pack()
#text2_label = tk.Label(drugi_frame, text = MERCURY_FACT)
#text2_label.pack(side = 'left')

When you create an object on a canvas, it's not a widget and can't be treated as a widget. When you call canvas2.create_image(225, 210, image=my_img), tkinter is going to put the image on the canvas at the given location and then return an integer representing the id of the image.
You are then trying to call pack on this integer, which gives the error 'int' object has no attribute 'pack'
The simply solution is to remove that line of code.
You are also making a very common mistake: you are storing the image reference in a local variable. Python will destroy this variable when the function returns. In order for the image to not be destroyed it needs to be saved as a global variable or as an attribute of some object.
For example, you could attach the image to the canvas2 object like so:
canvas2.my_img = my_img
This is arguably a design flaw in tkinter, but the solution is simple so it's easy to work around.

Related

Animating a gif W/O using PIL

I am currently trying to make life easier by having the code pull all the frames into my code and execute the animation. My current code is:
import time
from tkinter import *
import os
root = Tk()
imagelist = []
for file in os.listdir("MY-DIRECTORY"):
if file.endswith(".gif"):
imagelist.append(PhotoImage(file=str(os.path.join("MY-DIRECTORY", file))))
# extract width and height info
photo = PhotoImage(file=imagelist[0])
width = photo.width()
height = photo.height()
canvas = Canvas(width=width, height=height)
canvas.pack()
# create a list of image objects
giflist = []
for imagefile in imagelist:
photo = PhotoImage(file=imagefile)
giflist.append(photo)
# loop through the gif image objects for a while
for k in range(0, 1000):
for gif in giflist:
canvas.create_image(width / 2.0, height / 2.0, image=gif)
canvas.update()
time.sleep(0.1)
root.mainloop()
When I try to execute the file it gives me this error which I cannot make sense of.
Traceback (most recent call last):
File "C:/Users/Profile/Desktop/folder (2.22.2019)/animation2.py", line 21, in <module>
photo = PhotoImage(file=imagelist[0])
File "C:\Users\Profile\AppData\Local\Programs\Python\Python37-32\lib\tkinter\__init__.py", line 3545, in __init__
Image.__init__(self, 'photo', name, cnf, master, **kw)
File "C:\Users\Profile\AppData\Local\Programs\Python\Python37-32\lib\tkinter\__init__.py", line 3501, in __init__
self.tk.call(('image', 'create', imgtype, name,) + options)
_tkinter.TclError: couldn't open "pyimage1": no such file or directory
Note: I am adapting this code to fit my needs I did not write it.
I ran some print statements to verify the pyimage was being uploaded to the array but I cannot figure out why its saying there is no such file or directory if its already uploaded to the array. Can you all please shine some light.
I found that I was creating an unnecessary array of objects lower in the code. giflist[]. I ultimately resolved the issue by removing it and having the loop use the array that was created earlier in the code imagelist. The following code works now.
import time
from tkinter import *
import os
root = Tk()
imagelist = []
for file in os.listdir("My-Directory"):
if file.endswith(".gif"):
imagelist.append(PhotoImage(file=str(os.path.join("My-Directory", file))))
# Extract width and height info
photo = PhotoImage(file="My-Directory")
width = photo.width()
height = photo.height()
canvas = Canvas(width=width, height=height)
canvas.pack()
# Loop through the gif image objects for a while
for k in range(0, len(imagelist)):
for gif in imagelist:
canvas.create_image(width / 2.0, height / 2.0, image=gif)
canvas.update()
time.sleep(0.1)
root.mainloop()

Cryptic error Closing Toplevel window Python 3 Canvas

No idea what the comments are on this. But, never mind, I fixed it myself.
Took a while to figure out that Python is really sloppy and leaves timers running when you destroy the process completely. Then a while later, you get a blow up when it comes out of the timer and has nowhere to go. That's a very basic thing. You clean up after yourself in a destroy operation. Python does not.
I am able to open and close my toplevel window while keeping the main window going but I get a large, cryptic error message when I close the child window. This happens both when I close it via the X or the defined button in the child window. If I comment out the timer event (self.ater), the error messages do not come up. But I have to have that to update the form.
The error message is coming up on the next timer pop. It is not destroyed and pops but there is nowhere to go any longer.
I can't make the error message show up exactly right. Did the best I could.
Here is the error message block, then the code to create and close the child window.
Exception in Tkinter callback
Traceback (most recent call last):
File "/usr/lib/python3.4/tkinter/init.py", line 1536, in call
return self.func(*args)
File "/usr/lib/python3.4/tkinter/init.py", line 585, in callit
func(*args)
File "/home/pi/IltiShares/#Mikes Files/#Python3 Source/GPS Data Show 11-02-2016.py", line 630, in UpdateSky
t_canvas.itemconfig(zz, text=str(self.counter))
File "/usr/lib/python3.4/tkinter/init.py", line 2419, in itemconfigure
return self._configure(('itemconfigure', tagOrId), cnf, kw)
File "/usr/lib/python3.4/tkinter/init.py", line 1313, in _configure
self.tk.call(_flatten((self._w, cmd)) + self._options(cnf))
_tkinter.TclError: invalid command name ".1959370864.1959118928.1959118192.1959118800"
def ShowSky(self):
global t_canvas
global zz
fontsize = 8
labelwidth = 15
textwidth = 17
self.counter += 1
t = Toplevel(self)
t.wm_title("Sky Map or SV's")
frame2 = Frame(t)
frame2.config(height = 400, width = 400) # These are pixels
frame2.config(relief = RIDGE)
frame2.pack(side = LEFT, anchor= 'ne')
t_canvas = Canvas(frame2, height = 300, width = 300, borderwidth = 2,
bg= '#b3ffff', relief = GROOVE)
t_canvas.pack()
self.waiting = StringVar()
self.waiting.set('.')
zz = t_canvas.create_text(50, 50, text = '01')
Button(frame2, text='Close', width = 10, bg = '#FFc0c0', command=t.destroy).pack()
self.after(1000, self.UpdateSky)
pass
def UpdateSky(self):
global t_canvas
global zz
self.counter += 1
# Test movement and text change.
t_canvas.itemconfig(zz, text=str(self.counter))
t_canvas.coords(zz,self.counter,60)
self.after(8000, self.UpdateSky)
pass

TypeError: draw() missing 1 required positional argument: 'screen'

I've looked around on here for solutions to this problem with no avail. Maybe you can help, I can't see the error.
Here's the traceback:
File "copy.py", line 171, in <module>
main()
File "copy.py", line 128, in main
game.draw(screen)
File "copy.py", line 59, in draw
snake.draw(screen)
TypeError: draw() missing 1 required positional argument: 'screen'
The code referenced in the trace back is below
def main():
pygame.init()
winWidth, winHeight = 800, 800
screen = pygame.display.set_mode([winWidth, winHeight])
pygame.display.set_caption("Exploring mouse, keys, and event loops with Pygame")
running = 1
bgcolor = (0,0,0)
screen.fill(bgcolor)
game = SnaketheGame(winWidth, winHeight)
game.draw(screen)
##########
class SnaketheGame:
def __init__(self, winWidth, winHeight):
self.snake = snake
self.snakeMoving = False
self.growingSnake = False
self.food = food
def draw(self, screen):
self.snake.draw(screen)
for food in self.food:
food.draw(screen)
I've looked through other answers and made sure that I was calling an instance of the class first. I just can't figure out what I'm doing wrong here. Please help!
If I'm reading this correctly, I believe that when you are calling game = SnaketehGame(winWidth, winHeight), you aren't actually initializing the game. Because you haven't defined snake and food, it's probably not initializing correctly, so when you try to call game.draw, you don't actually have a game yet, so the screen input is being used in place of self. This is why you are getting the error that you don't have the positional argument of screen: screen is being used as self.
To fix this, fix your initialization to define snake and food.

How to make functions that draw lines in canvas (tkinter 3.x) properly

I am making a program that draws a line(you decide where is the beggining and end of it with the sliders/scales), problem is im getting these errors(That i wish i understood) when i press the psy Button(code below the errors) :
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\python351\lib\tkinter\__init__.py", line 1549, in __call__
return self.func(*args)
File "C:/Users/Koteu/PycharmProjects/guji/fsd.py", line 23, in creat
cans.create_line(ar1,ar2,br1,br2)
File "C:\python351\lib\tkinter\__init__.py", line 2331, in create_line
return self._create('line', args, kw)
File "C:\python351\lib\tkinter\__init__.py", line 2319, in _create
*(args + self._options(cnf, kw))))
_tkinter.TclError: bad screen distance ".14855536.14855504"
Process finished with exit code 0
anyways, the code :
import os
import sys
from tkinter import *
root = Tk()
app=Frame(root)
root.geometry("1200x1200")
ar1 = Scale(root,from_=0,to=600)
ar2= Scale(app,from_=0,to=600,deafultvar=0)#app instead of root because the button for unknown to me reason
#wouldn't appear in GUI otherwise
br1= Scale(root,from_=0,to=600)
br2= Scale(root,from_=0,to=600)
cans = Canvas(root,width = 500,height = 500)
cans.create_line(600,50,0,50) #This has nothing to do with the actual program by my understanding
def creat():
cans.create_line(ar1,ar2,br1,br2)#< this is what causes the problem i don't understand
psy=Button(root,command=creat,text="karole")
psy.pack()
cans.pack()
ar1.pack()
ar2.pack()
br1.pack()
br2.pack()
mainloop()
also, if that helps, im using py345
cans.create_line(x0, y0, ...) takes an even number of integer coordinates as positional args. You passed widgets, which were turned into their string identifiers. In ".14855536.14855504", '.' represents root, '14855536' is the canvas, and '14855504' is the scale ar1. Instead you need to use the .get() method on the scales to get their integer values. The following works.
from tkinter import *
root = Tk()
root.geometry("1200x1200")
ar1 = Scale(root,from_=0,to=600)
ar2= Scale(root,from_=0, to=600)
br1= Scale(root,from_=0, to=600)
br2= Scale(root,from_=0, to=600)
cans = Canvas(root, width=500, height=500)
def creat():
cans.create_line(ar1.get(), ar2.get(), br1.get(), br2.get())
psy=Button(root, command=creat, text="karole")
ar1.pack()
ar2.pack()
br1.pack()
br2.pack()
psy.pack()
cans.pack()
root.mainloop()
A couple of other fixes: the defaultvar option is not valid and caused an error; mainloop() instead of root.mainloop() caused tk to create a second Tk object, which is a bad idea.
EDIT: added the code that works.

_tkinter.TclError:unknown option "23" Code works with few object, but not with many

To start off I am super new at using Tkinter,
The problem I am having is that my code works if I have only one of the object type. It will interact correctly if it is the only one of that tag type. So if I have one 'boat' and 100 'shells' each time it executes, it does so correctly.
The code detects if there is collision between two objects and then changes the currently selected item's color to a random. So as long as there is only one tag type currently it will work correctly. So if I click and drag the 'boat' into a 'shell' it will switch it's color. Then if I take 1 of the 100 'shell's and do the same I get this error.
I do not understand why it works correctly when there is only one object of a given type and to interacts a infinite amount of other objects but when there is more than one of a tag type it fails.
It correctly selects the id number for the selected object so I am just lost right now and appreciate any help.
Follows is the error I receive and the code I am using. It is just the vital parts needed to preform the needed task. The collision code is the same as in the code though.
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Python34\lib\tkinter\__init__.py", line 1487, in __call__
return self.func(*args)
File "H:/Charles Engen/PycharmProjects/Battleship/BattleShipGUI.py", line 112, in on_token_motion
self.collision_detection(event)
File "H:/Charles Engen/PycharmProjects/Battleship/BattleShipGUI.py", line 85, in collision_detection
self.canvas.itemconfig(current_token, outline=_random_color())
File "C:\Python34\lib\tkinter\__init__.py", line 2385, in itemconfigure
return self._configure(('itemconfigure', tagOrId), cnf, kw)
File "C:\Python34\lib\tkinter\__init__.py", line 1259, in _configure
self.tk.call(_flatten((self._w, cmd)) + self._options(cnf))
_tkinter.TclError: unknown option "22"
import tkinter as tk
from random import randint
HEIGHT = 400
WIDTH = 680
def _random_color():
'''Creates a random color sequence when called'''
random_color = ("#"+("%06x" % randint(0, 16777215)))
return random_color
class Board(tk.Tk):
'''Creates a Board Class'''
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.menu_item = tk.Menu(self)
self.file_menu = tk.Menu(self.menu_item, tearoff=0)
self.file_menu.add_command(label='New', command=self.new_game)
self.file_menu.add_command(label='Exit', command=self.quit)
self.config(menu=self.file_menu)
self.canvas = tk.Canvas(width=WIDTH, height=HEIGHT)
self.canvas.pack(fill='both', expand=True)
# adds variable that keeps track of location
self._token_location_data = {"x": 0, "y": 0, "item": None}
def _create_object(self, coord, fcolor, color, token_name):
'''Creates an object with a tag, each object is assigned the ability to be clicked and dragged'''
(x, y) = coord
if token_name == 'boat':
points = [10+x, 10+y, 20+x, 20+y, 110+x, 20+y, 120+x, 10+y, 80+x,
10+y, 80+x, 0+y, 60+x, 0+y, 60+x, 10+y]
self.canvas.create_polygon(points, outline=fcolor, fill=color, width=3, tag=token_name)
elif token_name == 'shell':
self.canvas.create_oval(0+x, 0+y, 10+x, 10+y, outline=fcolor, fill=color, width=3, tag=token_name)
self.canvas.tag_bind(token_name, '<ButtonPress-1>', self.on_token_button_press)
self.canvas.tag_bind(token_name, '<ButtonRelease-1>', self.on_token_button_press)
self.canvas.tag_bind(token_name, '<B1-Motion>', self.on_token_motion)
def collision_detection(self, event):
'''This function tracks any collision between the boat and shell objects'''
# I will upgrade this to take any object collision
token = self.canvas.gettags('current')[0]
current_token = self.canvas.find_withtag(token)
x1, y1, x2, y2 = self.canvas.bbox(token)
overlap = self.canvas.find_overlapping(x1, y1, x2, y2)
for item in current_token:
for over in overlap:
if over != item:
# Changes the color of the object that is colliding.
self.canvas.itemconfig(current_token, outline=_random_color())
# The following three functions are required to just move the tokens
def on_token_button_press(self, event):
'''Adds ability to pick up tokens'''
# Stores token item's location data
self._token_location_data['item'] = self.canvas.find_closest(event.x, event.y)[0]
self._token_location_data['x'] = event.x
self._token_location_data['y'] = event.y
def on_token_button_release(self, event):
'''Adds ability to drop token'''
# Resets the drag
self._token_location_data['item'] = self.canvas.find_closest(event.x, event.y)
self._token_location_data['x'] = event.x
self._token_location_data['y'] = event.y
def on_token_motion(self, event):
'''Adds ability to keep track of location of tokens as you drag them'''
# Computes how much the object has moved
delta_x = event.x - self._token_location_data['x']
delta_y = event.y - self._token_location_data['y']
# move the object the appropriate amount
self.canvas.move(self._token_location_data['item'], delta_x, delta_y)
# record the new position
self._token_location_data['x'] = event.x
self._token_location_data['y'] = event.y
# Detects collision between objects
self.collision_detection(event)
def new_game(self):
'''Creates new game by deleting the current canvas and creating new objects'''
# Deletes current canvas
self.canvas.delete('all')
# runs the create board mechanism
self._generate_board()
# adds code to create a shell and boat on the screen from the appropriate function
for i in range(1):
self._create_object((410, 15*i), _random_color(), _random_color(), 'boat')
for i in range(4):
for j in range(10):
self._create_object((590+(i*10), 15*j), _random_color(), _random_color(), 'shell')
def main():
app = Board()
app.mainloop()
if __name__ == '__main__':
main()
To run the code, I removed the call to the non-existent ._generate_board. After this, I get the same error but with unknown option '3'.
The exception is caused by passing to self.canvas.itemconfig a tuple of ids, which you misleadingly call current_token, instead of a tag or id. A singleton is tolerated because of the _flatten call, but anything more becomes an error. I am rather sure that it is not a coincidence that '3' is the second member of the shell tuple. Passing token instead stops the exception. Also, there should be a break after itemconfig is called the first time.
With this, however, the shells are treated as a group, and the bounding box incloses all shells and overlap includes all shells. This is why moving a single shell away from the others is seen as a collision. At this point, all shells are randomized to a single new color if one is moved. To fix this, token should be set to the single item set in on_token_button_press, instead of a tag group. This implements your todo note. Here is the result.
def collision_detection(self, event):
'''Detect collision between selected object and others.'''
token = self._token_location_data['item']
x1, y1, x2, y2 = self.canvas.bbox(token)
overlap = self.canvas.find_overlapping(x1, y1, x2, y2)
for over in overlap:
if over != token:
# Changes the color of the object that is colliding.
self.canvas.itemconfig(token, outline=_random_color())
break
A minor problem is that you do the tag binds for 'shell' for each shell (in _create_object. Instead, put the tag binds in new so they are done once.
for tag in 'boat', 'shell':
self.canvas.tag_bind(tag, '<ButtonPress-1>', self.on_token_button_press)
self.canvas.tag_bind(tag, '<ButtonRelease-1>', self.on_token_button_press)
self.canvas.tag_bind(tag, '<B1-Motion>', self.on_token_motion)

Resources