So I have a couple of images in a folder and I want to do a little pack opener on tkinter where if I press on a button it randomly opens an Image of that folder and shows it. So I did this:
import os
import random
from PIL import Image
from tkinter import *
def pack():
path ='C:\\Users\\matt\OneDrive\Images\cards'
files = os.listdir(path)
index = random.randrange(0, len(files))
image = Image.open(files[index])
image.show()
pack_button = Button(window,text = " Pack ",fg="white",bg = 'black',command = pack)
pack_button.grid(row = 2,column = 1,padx = 10,pady = 5)
window.mainloop()
The problem is that this function doesn't want to work and it always tells me:
AttributeError: type object 'Image' has no attribute 'open'
Can someone please help me? And does someone know how to do a button out of an image?
Thank you in advance.☺
Assuming you're using Python 3, and you have a folder named card images in the same directory as your Python script, and that folder contains .png images of your playing cards, then the following should work:
import tkinter as tk
def get_random_image_path():
from random import choice
from pathlib import Path
return str(choice(list(Path("card images").glob("*.png"))))
class Application(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.title("Random card")
self.geometry("171x239")
self.resizable(width=False, height=False)
self.image = tk.PhotoImage(file=get_random_image_path())
self.button = tk.Button(self, image=self.image, command=self.assign_new_image)
self.button.pack()
def assign_new_image(self):
self.image = tk.PhotoImage(file=get_random_image_path())
self.button.configure(image=self.image)
def main():
application = Application()
application.mainloop()
return 0
if __name__ == "__main__":
import sys
sys.exit(main())
Note: I just grabbed three public domain card images from Wikimedia Commons. You can download them either in .svg or .png format in different resolutions. In my case I downloaded them as .pngs with a resolution of 171x239, which is why I picked the same dimensions for the tkinter window. Since I've only downloaded three images, sometimes it appears as though clicking the button doesn't seem to do anything, which isn't the case - it's just that, with only three images to choose from, we're likely to pick the same image multiple times in a row.
To get your example running, see a minimal solution below.
For a ready Tkinter program and to avoid PIL use Paul M.'s solution.
import os
import random
from PIL import Image
# assuming that this dir contains only images
# otherwise test if r is an image
img_folder = r'/home/ktw/Desktop/test_img'
def pack():
r = random.choice(os.listdir(img_folder))
image = Image.open(os.path.join(img_folder, r))
image.show()
if __name__=='__main__':
pack()
EDIT:
This should work for you. Just change the path to the full path of your image folder.
choice(os.listdir(img_folder)) gives you only the name of the random file. os.path.join(img_folder, choice(os.listdir(img_folder))) builds an absolute path from the image folder and the random image so Tk.PhotoImage should work.
import tkinter as tk
import os
from random import choice
from pathlib import Path
img_folder = r'/home/ktw/Desktop/images'
def get_random_image_path():
return os.path.join(img_folder, choice(os.listdir(img_folder)))
class Application(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.title("Random card")
self.geometry("171x239")
self.resizable(width=False, height=False)
self.image = tk.PhotoImage(file=get_random_image_path())
self.button = tk.Button(self, image=self.image, command=self.assign_new_image)
self.button.pack()
def assign_new_image(self):
self.image = tk.PhotoImage(file=get_random_image_path())
self.button.configure(image=self.image)
if __name__=='__main__':
application = Application()
application.mainloop()
import tkinter as tk
import os
from random import choice
from pathlib import Path
from PIL import Image
img_folder = r'C:\\Users\\matt\OneDrive\Images\cards'
def get_random_image_path():
return os.path.join(img_folder, choice(os.listdir(img_folder)))
class Application(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.title("Random card")
self.resizable(width=False, height=False)
self.image = tk.PhotoImage(get_random_image_path())
self.button = tk.Button(self, image=self.image, command=self.assign_new_image)
self.button.pack()
def assign_new_image(self):
self.image = tk.PhotoImage(file=get_random_image_path())
self.button.configure(image=self.image)
if __name__=='__main__':
application = Application()
application.mainloop()
and it says this:
File "C:\Users\matt\OneDrive\Images\cards\pack opener.py", line 31, in <module>
application = Application()
File "C:\Users\matt\OneDrive\Images\cards\pack opener.py", line 22, in __init__
self.button = tk.Button(self, image=self.image, command=self.assign_new_image)
File "C:\Users\matt\AppData\Local\Programs\Python\Python310\lib\tkinter\__init__.py", line 2679, in __init__
Widget.__init__(self, master, 'button', cnf, kw)
File "C:\Users\matt\AppData\Local\Programs\Python\Python310\lib\tkinter\__init__.py", line 2601, in __init__
self.tk.call(
_tkinter.TclError: image "C:\\Users\\matt\OneDrive\Images\cards\Vettel.png" doesn't exist
Related
Summary of code purpose: change tk.Button['image'] to either selected file, or screen snip
I'm getting this error:
_tkinter.TclError: image "<PIL.Image.Image image mode=RGB size=600x400 at 0x34045F0>" doesn't exist
But I can't understand why it doesn't exist since if I useprint(self.image_selected) I get
<PIL.Image.Image image mode=RGB size=600x400 at 0x3D16650>
Does it need to be a global variable? Doesn't adding self take care of that?
CODE
import tkinter as tk
from tkinter import filedialog
from PIL import ImageGrab, ImageTk
class MCVE():
def __init__(self, master):
self.master = master
self.ButtonOne = tk.Button(text="start",width=30,height=30,command = lambda: self.GetImg(master))
self.ButtonOne.pack()
self.image_selected = None
def GetImg(self, master):
self.newWin = tk.Toplevel(self.master)
self.ButtonTwo = tk.Button(self.newWin,text="snip", command = lambda: self.Snip(self.master))
self.ButtonThree = tk.Button(self.newWin, text="open", command = lambda: self.FileO(self.master))
self.ButtonTwo.pack()
self.ButtonThree.pack()
def Snip(self, master):
self.image_selected = ImageGrab.grab(bbox=(0,0,600,400))
self.changeImg()
def FileO(self, master):
ret = filedialog.askopenfilename() # filedialog.askopenfilename(initialdir='/home/user/images/')
if ret:
self.image_selected = ImageTk.PhotoImage(file=ret)
self.changeImg()
def changeImg(self):
if self.image_selected:
print(self.image_selected)
#self.ButtonOne['image'] = self.image_selected
self.ButtonOne.config(image=self.image_selected)
def main():
root = tk.Tk()
MCVE(root)
root.mainloop()
if __name__ == '__main__':
main()
I tested the program, and as you stated, the 'snip' button gave me the error. I could fix it by changing the format of the image to a ImageTk.PhotoImage.
def Snip(self, master):
self.image_selected = ImageTk.PhotoImage(ImageGrab.grab(bbox=(0,0,600,400)))
self.changeImg()
I have to say, though, that the photo showed on the button icon after selecting the image was weird, but I guess it was because i tested it with the wrong resolution! ;)
My plotting library needs to be able to show multiple plots at the same time, each of which is represented as a PIL image, and each of which should show up as its own window. The windows should be independent, so closing any one of them should not affect the others, but when all of them have been closed the main loop should exit. This behavior was easy to achieve in qt and wx, but in qt it's proving difficult so far.
Here's the closest I've come so far:
from six.moves import tkinter
from PIL import ImageTk
class Window:
def __init__(self, img):
self.window = tkinter.Toplevel()
self.window.minsize(img.width, img.height)
self.canvas = tkinter.Canvas(self.window, width=img.width, height=img.height)
self.canvas.pack()
self.canvas.configure(background="white")
self.photo = ImageTk.PhotoImage(img)
self.sprite = self.canvas.create_image(0, 0, image=self.photo, anchor=tkinter.NW)
windows = []
for img in imgs:
windows.append(Window(img))
if len(windows) > 0: windows[0].window.mainloop()
This displays an image in each window, and each of those windows can be closed independently. But it also displays an empty root window which needs to be closed for the main loop to exit, and which will cause all windows to close when closed, which is not the behavior I want.
If I replace tkinter.Toplevel() with tkinter.Tk(), then create_image fails for the second window with an obscure "pyimageX does not exist" error message, where X is an incrementing integer.
Will I have to make an invisible root window, and then manually count how many child windows have closed and trigger destruction of the invisible root window when all of them have closed in order to get the behavior I'm looking for? Or is there a simple way to achieve this?
Edit: Just to clarify: My program is not mainly a Tk app. It spends almost all its time doing other stuff, and only temporarily uses Tk in a single function to display some plots. That's why it's important that the main loop exits after the plots have been closed, to the program can resume its normal operation. Think about how show() in matplotlib works for an example of this scenario.
Here is an example of how you might want to do this. This example uses the root window to house a button that will open up all images at the top level.
Make sure you change self.path to your image folder.
import tkinter as tk
import os
class App(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
tk.Button(self, text="Open Images", command=self.open_images).pack()
self.path = ".\RGB"
def open_images(self):
directory = os.fsencode(self.path)
for file in os.listdir(directory):
filename = os.fsdecode(file)
if filename.endswith(".gif"):
print(filename)
top = tk.Toplevel(self)
img = tk.PhotoImage(file="{}\{}".format(self.path, filename))
lbl = tk.Label(top, image=img)
lbl.image = img
lbl.pack()
if __name__ == '__main__':
app = App()
app.mainloop()
Here is my 2nd example where you can hide the root window and when the last top level window is closed the tkinter instance is also destroyed. This is maned with a simple tracking variable.
import tkinter as tk
import os
class App(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.top_level_count = 0
self.path = ".\RGB"
self.open_images()
self.withdraw()
def open_images(self):
directory = os.fsencode(self.path)
for file in os.listdir(directory):
filename = os.fsdecode(file)
if filename.endswith(".gif"):
self.top_level_count += 1
image_top(self, self.path, filename)
def check_top_count(self):
print(self.top_level_count)
if self.top_level_count <= 0:
self.destroy()
class image_top(tk.Toplevel):
def __init__(self, controller, path, filename):
tk.Toplevel.__init__(self, controller)
self.controller = controller
self.protocol("WM_DELETE_WINDOW", self.handle_close)
img = tk.PhotoImage(file="{}\{}".format(path, filename))
lbl = tk.Label(self, image=img)
lbl.image = img
lbl.pack()
def handle_close(self):
self.controller.top_level_count -= 1
self.destroy()
self.controller.check_top_count()
if __name__ == '__main__':
app = App()
app.mainloop()
Ok so here's a couple of classes I came up with to solve this problem:
class ImgRoot(tkinter.Tk):
def __init__(self, imgs):
super(ImgRoot, self).__init__()
for i in imgs:
Window(self, i)
self.withdraw()
self.open=True
self.tick()
def tick(self):
if not self.open:
self.destroy()
self.open=False
self.after(100, self.tick)
def checkin(self):
self.open=True
class Window(tkinter.Toplevel):
def __init__(self, root, img):
super(Window, self).__init__()
self.root=root
self.tick()
self.minsize(img.width, img.height)
self.canvas = tkinter.Canvas(self, width=img.width, height=img.height)
self.canvas.pack()
self.canvas.configure(background="white")
self.photo = ImageTk.PhotoImage(img)
self.sprite = self.canvas.create_image(0, 0, image=self.photo, anchor=tkinter.NW)
def tick(self):
self.root.checkin()
self.after(100, self.tick)
The idea here is to create a main class (ImgRoot) which handles the whole thing. Then, every 0.1 seconds (100 miliseconds), it will check if any of the image windows have told it that they are still alive, and, if not, close. The image windows (Windows) do this by setting the ImgRoot's open attribute to True every 0.1 seconds that they are alive. Here is an example usage:
import tkinter
#above classes go here
ImgRoot(imgs) #imgs is a list as defined in your question
tkinter.mainloop()
print('done') #or whatever you want to do next
I want to make a class that has a picture and it is changed to the next one by mouse click.I'm new to oop, my idea here was to make class similar to real life where there is new class instance for every new picture, is it possible to do it this way? Here is my code
import tkinter as tk
from PIL import Image,ImageTk
class Picture():
_count=1
def __init__(self,window):
self.id=Picture._count
Picture._count+=1
self.img=Image.open(r'C:\ImgArchive\img%s.png' % self.id)
self.pimg = ImageTk.PhotoImage(self.img)
self.lab=tk.Label(window,image=self.pimg)
self.lab.pack()
self.lab.bind('<1>',self.click)
def click(self,event):
self.lab.destroy()
self=self.__init__(window)
window = tk.Tk()
window.title('Album')
window.geometry('1200x900')
pic=Picture(window)
window.mainloop()
It works fine, but i'm not sure that old instances of my class is deleted, are they? And i use self.lab.destroy() because if i dont new picture appears down, like this
instead of this
So why it happens?What is elegant way for it?
Below example produces a simple image viewer tested with path of C:\Users\Public\Pictures\Sample Pictures, let me know if anything's unclear:
import tkinter as tk
from PIL import Image, ImageTk
#required for getting files in a path
import os
class ImageViewer(tk.Label):
def __init__(self, master, path):
super().__init__(master)
self.path = path
self.image_index = 0
self.list_image_files()
self.show_image()
self.bind('<Button-1>', self.show_next_image)
def list_files(self):
(_, _, filenames) = next(os.walk(self.path))
return filenames
def list_image_files(self):
self.image_files = list()
for a_file in self.list_files():
if a_file.lower().endswith(('.jpg', '.png', '.jpeg')):
self.image_files.append(a_file)
def show_image(self):
img = Image.open(self.path + "\\" + self.image_files[self.image_index])
self.img = ImageTk.PhotoImage(img)
self['image'] = self.img
def show_next_image(self, *args):
self.image_index = (self.image_index + 1) % len(self.image_files)
self.show_image()
root = tk.Tk()
mypath = r"C:\Users\Public\Pictures\Sample Pictures"
a = ImageViewer(root, mypath)
a.pack()
root.mainloop()
I am new on Tkinter. I was trying to display two images on my canvas but I couldn't. I tried to achieve this by creating two different files. One will contain all logic behind and the other one will handle the gui. Here is my code so far:
file1.py
from file2 import *
import tkinter as tk
import random
# global variables
w = 'initial'
class start_gui(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self,parent, *args, **kwargs)
# create canvas
self.canvas = tk.Canvas(parent, width=800, height=800, background="green")
self.canvas.pack()
c = Display(self.canvas)
c.current_play(w)
if __name__ == "__main__":
# create main window
root = tk.Tk()
root.geometry("800x800")
start_gui(root)
root.mainloop()
file2.py
import tkinter as tk
from functools import partial
from PIL import ImageTk
from PIL import Image
class Display:
def __init__(self, canv):
self.canvas = canv
def current_play(self, option):
if (option == 'initial'):
self.initial_display()
elif (option == 'n' or option == 's'):
self.ns_display()
def initial_display(self):
# display cat image
self.im = Image.open("cat.gif")
self.photo_image = ImageTk.PhotoImage(self.im)
self.demo = self.canvas.create_image(400, 400, image=self.photo_image, anchor='center')
self.canvas.create_rectangle(50, 25, 150, 75, fill="blue")
self.temp_image = tk.PhotoImage(file="cat.gif")
self.demo2 = self. canvas.create_image(600, 600, image = self.temp_image, anchor='center')
The problem here is that the two image items I created do not show up on the canvas but only the rectangle. Can someone help me with this?
PS: I am using python v 3.4
Another solution: We can make Display inherit from class tk.Canvas
import tkinter as tk
from PIL import ImageTk
from PIL import Image
import random
# global variables
w = 'initial'
class Display(tk.Canvas):
def __init__(self, parent, *args, **kwargs):
tk.Canvas.__init__(self, parent, *args, **kwargs)
def current_play(self, option):
if option == 'initial':
self.initial_display()
elif option == 'n' or option == 's':
self.ns_display()
def initial_display(self):
# display cat image
self.im = Image.open("cat.gif")
self.photo_image = ImageTk.PhotoImage(self.im)
self.demo = self.create_image(400, 400, image=self.photo_image, anchor='center')
self.create_rectangle(50, 25, 150, 75, fill="blue")
self.temp_image = tk.PhotoImage(file="cat.gif")
self.demo2 = self.create_image(600, 600, image = self.temp_image, anchor='center')
class start_gui(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self,parent, *args, **kwargs)
# create canvas
self.canvas = Display(parent, width=800, height=800, background="green")
self.canvas.pack()
self.canvas.current_play(w)
if __name__ == "__main__":
root = tk.Tk()
root.geometry("800x800")
start_gui(root)
root.mainloop()
The problem is one of garbage collection. Your Display object is stored in a local variable inside start_gui.__init__. Once start_gui is constructed, this object is thrown away. The image is an attribute of that object, so it gets garbage-collected. When an image object gets garbage-collected, tkinter is unable to display it.
The simple solution is to keep a permanent reference to Display:
self.display = Display(canvas)
self.display.current_play(w)
Hey I'm following a tutorial, https://www.youtube.com/watch?v=a1Y5e-aGPQ4 , and I can't get it to work properly. I'm trying to add an image when you press on a menu button:
from tkinter import *
from PIL import *
class Window(Frame):
def __init__(self, master = None):
Frame.__init__(self, master)
self.master = master
self.init_Window()
def init_Window(self):
self.master.title("GUI")
self.pack(fill =BOTH, expand=1)
#quitButton = Button(self, text = "Quit", command = self.client_exit)
#quitButton.place(x=0,y=0)
menu = Menu(self.master)
self.master.config(menu=menu)
file=Menu(menu)
file.add_command(label='Save',command= self.client_exit)
file.add_command(label='Exit',command= self.client_exit)
menu.add_cascade(label='File',menu=file)
edit = Menu(menu)
edit.add_command(label='Show Image', command=self.showImg)
edit.add_command(label='Show Text', command=self.showTxt)
menu.add_cascade(label='Edit',menu=edit)
def showImg(self):
load = Image.open('Pic.png')
render = ImageTk.PhotoImage(load)
img = Label(self, image=render)
img.image = render
img.place(x=0,y=0)
def showTxt(self):
text = Label(self,text='Hey')
text.pack
def client_exit(self):
exit()
root = Tk()
root.geometry("400x300")
app = Window(root)
root.mainloop()
I have tried asking around school, StackOverflow, and YouTube for about 3 days now, and nothing has solved my problem, if you need any more info about it please ask. I am getting the error code:
Exception in Tkinter callback
Traceback (most recent call last):
File "/usr/lib/python3.5/tkinter/__init__.py", line 1558, in __call__
return self.func(*args)
File "/root/Desktop/Python Programs/Tkinter.py", line 35, in showImg
load = Image.open('pic.png')
AttributeError: type object 'Image' has no attribute 'open'
You use import * so you don't know if you use tkinter.Image or PIL.Image . And this is why you shouldn't use import *
Try
from PIL import Image, ImageTk
Images are a bit tricky to get right, some tips: Keep the image object global, to avoid garbage collection, avoid Attribute Error (by reading the docs).
In this example I don t use PIL and I load a gif image
#!/usr/bin/python
#-*-coding:utf-8 -*
#Python 3
#must have a valid gif file "im.gif" in the same foldeer
from tkinter import *
Window=Tk()
ListePhoto=list()
ListePhoto.append(PhotoImage(file="im.gif"))
def Try():
Window.title('image')
Window.geometry('+0+0')
Window.configure(bg='white')
DisplayImage()
def DisplayImage():
label_frame=LabelFrame(Window, relief='ridge', borderwidth=12, text="AnImage",
font='Arial 16 bold',bg='lightblue',fg='black')
ListeBouttons=list()#Liste Vide pour les Radiobutton(s)
RadioButton = Radiobutton(label_frame,text="notext",image=ListePhoto[0], indicatoron=0)
RadioButton.grid(row=1,column=1)
label_frame.pack(side="left")
if __name__ == '__main__':
Try()