I am just getting started with tkinter widgets and am trying to create Checkbuttons with both images and text labels. Unfortunately, this cannot be done with basic Checkbutton(..., image= , text= ) as setting the image suppresses the text.
Here's a silly yet reproducible example of what I'm trying to do.
from tkinter import Tk, Frame, Checkbutton, Label
from PIL import ImageTk, Image
import requests
def getImgFromUrl(url): # using solution from : https://stackoverflow.com/a/18369957/2573061
try:
r = requests.get(url, stream=True)
pilImage = Image.open(r.raw)
phoImage = ImageTk.PhotoImage(pilImage)
return phoImage
except Exception as e:
print('Error ' + repr(e) )
return None
class AnimalPicker(Frame):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.master.title("Animal Picker")
def main():
root = Tk()
root.geometry("250x450+300+300")
app = AnimalPicker()
imageUrls = ['http://icons.iconarchive.com/icons/martin-berube/flat-animal/64/dachshund-icon.png',
'http://icons.iconarchive.com/icons/iconka/meow/64/cat-walk-icon.png',
'http://icons.iconarchive.com/icons/sonya/swarm/64/Unicorn-icon.png']
labels = ['Dog','Cat','Unicorn']
images = [getImgFromUrl(x) for x in imageUrls]
for i in range(len(images)):
cb = Checkbutton(root, text=labels[i], image = images[i])
cb.pack(anchor = 'w', padx=5,pady=5)
root.mainloop()
if __name__ == '__main__':
main()
Gives us:
But I would like to have the labels ("Cat", "Dog", "Unicorn") either underneath or to the side of the images.
It's also important that the solution work for an arbitrary number of Checkbuttons, as in the for loop above.
Should I be using Grid and stacking these Checkbuttons next to Labels? I've managed to avoid learning it so far. Or is it easy to do with Pack?
I would like to have the labels ("Cat", "Dog", "Unicorn") either underneath or to the side of the images.
As suggested in Bryan's comment, this can simply be done by configuring compound option of Checkbutton.
Simply replace:
cb = Checkbutton(root, text=labels[i], image = images[i])
with:
cb = Checkbutton(root, text=labels[i], image = images[i], compound='left')
or simply set compound option to an element of ['bottom', 'center', 'left', 'right', 'top'] which is by default None:
cb['compound'] = 'top'
Below example produces a simple example for most windows users:
import tkinter as tk
from PIL import Image, ImageTk
root = tk.Tk()
mypath = r"C:\Users\Public\Pictures\Sample Pictures\Koala.jpg"
img = Image.open(mypath)
glb_img = ImageTk.PhotoImage(img)
tk.Checkbutton(root, text="Koala", image=glb_img, compound='top').pack()
root.mainloop()
Also it's worth noting that importing PIL is redundant for .png format, one could simply use either tk.PhotoImage(data=image_data) or tk.PhotoImage(file=image_file).
Related
So, my goal is to create a sort of slideshow within Tkinter. I have a list of images like Images = ["1.png", "2.png", ...], I want to be able to iterate through the list and display each image in a Tkinter window, the concept is simple and as follows:
Display Image 1
30 Second Delay
Display Image 2
30 Second Delay
Display Image 3
I have managed to iterate the images using a button press, however, I do not want to have to click a button as it is meant to imitate a slideshow, I also attempted looping a function but the time.sleep() function does not delay in the correct way because of how Tkinter behaves.
I managed to achieve the above using mostly source code from here, and I would appreciate a little hand achieving the above.
My Code:
from tkinter import *
from PIL import ImageTk, Image
Window = Tk()
Window.geometry("1920x1080")
Window.resizable(0, 0)
Label1 = Label(Window)
Label1.pack()
Images = iter(["1.png", "2.png", "3.png", "4.png", "5.png",
"6.png", "7.png", "8.png", "9.png", "10.png"])
def Next_Image(Val):
try:
Image1 = next(Images)
except StopIteration:
return
Image1 = ImageTk.PhotoImage(Image.open(Image1))
Label1.Image = Image1
Label1["image"] = Image1
Button1 = Button(text = "Next image", command =
lambda:Next_Image(1))
Button1.place(x = 50, y = 50)
Next_Image(1)
Window.mainloop()
I also attempted to use .after(), however, it did not display each image, it skipped from the first image to the last straight away with the compounded delay.
for x in range(1, 11):
Window.after(1000, lambda : Next_Image(1))
You need to create a function that gets the image off of the list and displays it, and then uses after to call itself again in a second. Your main program needs to call this exactly once, and then it will run until it runs out of things to do.
Here's a working example that uses a text string for simplicity, but it should be obvious how to modify it to use images.
import tkinter as tk
images = iter(["1.png", "2.png", "3.png", "4.png", "5.png",
"6.png", "7.png", "8.png", "9.png", "10.png"])
def next_image():
try:
image = next(images)
label.configure(text=image)
root.after(1000, next_image)
except StopIteration:
return
root = tk.Tk()
label = tk.Label(root, width = 40, height=4)
label.pack()
next_image()
root.mainloop()
You can use .after() to switch image periodically:
from itertools import cycle
...
# use cycle() instead of iter()
Images = cycle([f"{i}.png" for i in range(1, 5)])
...
def next_image():
# use next() to get next image in the cycle list
Label1.image = ImageTk.PhotoImage(file=next(Images))
Label1['image'] = Label1.image
# switch image again after 1 second
Label1.after(1000, next_image)
next_image() # start the loop
Window.mainloop()
This worked, Thank you #acw1668 and #Bryan Oakley.
from tkinter import *
from PIL import ImageTk, Image
Window = Tk()
Window.geometry("1920x1080")
Window.resizable(0, 0)
Label1 = Label(Window)
Label1.pack()
Images = iter(["1.png", "2.png", "3.png", "4.png", "5.png", "6.png",
"7.png", "8.png", "9.png", "10.png"])
def Next_Image(Val):
try:
Image1 = next(Images)
except StopIteration:
return
Image1 = ImageTk.PhotoImage(Image.open("BuyingConfig\\" + Image1))
Label1.Image = Image1
Label1["image"] = Image1
Window.after(3000, lambda:Next_Image(1))
Window.after(0, lambda:Next_Image(1))
Window.mainloop()
I'm trying to put a background image in my tkinter project, but despite of several attempts it's simply not showing up.
Here is the code:
import tkinter
from PIL import ImageTk,Image
show_screen = tkinter.Tk()
show_screen.geometry('900x900')
show_screen.title("LEARNTECH OPE")
img_show = Image.open("C:\PYTHON IDE\RemoteProctoring_Featured.png")
show_image = ImageTk.PhotoImage(img_show)
show_label = tkinter.Label(show_screen,font=("Arial Bold",10),fg="blue",
text="FILL THE NECESSARY DETAILS GIVEN BELOW")
show_label.place(x=600,y=0)
enter_field = tkinter.Entry(show_screen,width=50)
enter_field.place(x=600,y=200)
def clicked():
ref = "Welcome" + enter_field.get()
show_label.configure(text=ref)
show_button = tkinter.Button(show_screen,text="CLICK TO EXIT",
fg="green",command=clicked).place(x=600,y=400)
show_screen.mainloop()
As I said in a comment, images can only be displayed as part of some tkinter widget. Below is an example of doing that by putting the image in a Label. I also added code to resize the image to fill the window (aka "screen" in your code).
import tkinter
from PIL import ImageTk, Image
WIDTH, HEIGHT = 900, 900
IMG_PATH = r"C:\PYTHON IDE\RemoteProctoring_Featured.png" # Note 'r' prefix.
show_screen = tkinter.Tk()
show_screen.geometry('{}x{}'.format(WIDTH, HEIGHT))
show_screen.title("LEARNTECH OPE")
# Place background image on a Label widget.
tmp_img = Image.open(IMG_PATH).resize((WIDTH, HEIGHT), Image.ANTIALIAS)
bkg_img = ImageTk.PhotoImage(tmp_img)
bkg_label = tkinter.Label(show_screen, image=bkg_img)
bkg_label.img = bkg_img # Keep a reference in case this code put is in a function.
bkg_label.place(relx=0.5, rely=0.5, anchor='center') # Place in center of window.
enter_field = tkinter.Entry(show_screen,width=50)
enter_field.place(x=600,y=200)
show_label = tkinter.Label(show_screen,font=("Arial Bold",10),fg="blue",
text="FILL THE NECESSARY DETAILS GIVEN BELOW")
show_label.place(x=600,y=0)
def clicked():
ref = "Welcome " + enter_field.get()
show_label.configure(text=ref)
show_button = tkinter.Button(show_screen,text="CLICK TO EXIT",
fg="green",command=clicked)
show_button.place(x=600,y=400)
show_screen.mainloop()
The following project is supposed to show a message when clicking a certain colored button. But, whenever I execute the program it shows blank(white) buttons in the correct alignment, etc. Due to some reason the images are not loaded.
In future, I plan to add different images hence testing with colored image created in Paint and not in-built commands to show the color.
I will add the result below after the code.
Edit: All images are 100x100 pixels created in Microsoft Paint.I have tried other modules like PIL but to no avail.
# importing the module
import tkinter
import tkinter.messagebox
from tkinter import *
# importing the module
# initialising tkinter
class window(Frame):
def __init__(self,master = None):
Frame.__init__(self,master)
self.master = master
# initialising tkinter
# creating the window
root = Tk()
app = window(root)
root.geometry("350x350")
# creating the window
# colours
WHITE = (255,255,255)
BLACK = (0,0,0)
BLUE = (0,0,255)
RED = (255,0,0)
# colours
# image
red_image = "red.png"
blue_image = "blue.png"
yellow_image = "yellow.png"
green_image = "green.png"
# image
# creating a button function
def create_button(x,y,color,color2,picture):
click = Button(root, image = PhotoImage(picture), width= 150, height=150, command = lambda : tkinter.messagebox.showinfo( "Hello Python", "This is " + color))
click.image = PhotoImage(picture)
click.grid( row = x, column = y)
# creating a button function
create_button(0,0,'red','pink',red_image)
create_button(0,2,'blue','lightblue',blue_image)
create_button(2,0,'green','lightgreen',green_image)
create_button(2,2,'yellow','lightyellow',yellow_image)
# starting the widget
root.mainloop()
# starting the widget
There are two issues in your code:
You passed filename to PhotoImage() without using file keyword: PhotoImage(picture) should be PhotoImage(file=picture)
You did not keep the reference of the image assigned to button, but another instance of image
Below is the updated create_button() function that fixes the issues:
def create_button(x, y, color, color2, picture):
image = PhotoImage(file=picture)
click = Button(root, image=image, width=150, height=150, command=lambda: tkinter.messagebox.showinfo("Hello Python", "This is "+color))
click.image = image
click.grid(row=x, column=y)
For adding image in Button you have not use appropriate keywords.
Here is a simple example for you to add image in button
from tkinter import *
from tkinter.ttk import *
# creating tkinter window
root = Tk()
# Adding widgets to the root window
Label(root, text = 'Image adding', font =( 'Verdana',15)).pack(side = TOP, pady = 10)
# Creating a photoimage object to use image
photo = PhotoImage(file = "C:\Gfg\circle.png")
# here, image option is used to
# set image on button
Button(root, text = 'Click Me !', image = photo).pack(side = TOP)
root.mainloop()
I think it may help you
I'm trying to make a windows style image viewer that can display multiple images at once in an icon or thumbnail format (like when you do View > Layout > Large Icons on windows) in tkinter. However, the folder I want to open contains a couple of hundred images and it's way to slow to load them all and will probably use too much memory anyway. Therefore I'm trying to lazy load images based on if they are visible (i.e: if the user has scrolled down enough to see it). Here's what I have so far:
from tkinter import *
from tkinter import filedialog
from PIL import Image
from PIL import ImageTk
import glob
import datetime
import os
class ImageListViewer(Frame):
def __init__(self, root, img_paths, *args, **kwargs):
self.img_paths = img_paths
self.root = root
self.canvas = Canvas(root)
self.scroll_y = Scrollbar(root, orient="vertical", command=self.canvas.yview)
self.frame = Frame(self.canvas, bg='red')
self.max_w, self.max_h = 200, 200
cols = 3
loading_img = Image.open('loading.png')
loading_img = self.fit_img(loading_img, self.max_w, self.max_h)
loading_photo = ImageTk.PhotoImage(loading_img)
# group of widgets
for i, path in enumerate(img_paths):
label = Label(self.frame, image=loading_photo, width=self.max_w, height=self.max_h, borderwidth=2, relief="sunken")
label.photo = loading_photo
# label = Label(self.frame, text=str(i), width=self.max_w, height=self.max_h, borderwidth=2, relief="sunken")
label.index = i
label.has_img = False
label.grid(row=i-i%cols, column=i%cols)
self.canvas.create_window(0, 0, anchor='nw', window=self.frame)
self.canvas.update_idletasks()
self.canvas.configure(scrollregion=self.canvas.bbox('all'),
yscrollcommand=self.scroll_intercepter)
self.canvas.pack(fill='both', expand=True, side='left')
self.scroll_y.pack(fill='y', side='right')
self.root.bind('h', self.debug)
super().__init__(root)
def scroll_intercepter(self, a, b):
for label in self.labels_in_view():
if not label.has_img:
self.lazy_load_img(label)
self.scroll_y.set(a, b)
def lazy_load_img(self, label):
img = Image.open(self.img_paths[label.index])
img = self.fit_img(img, self.max_w, self.max_h)
photo = ImageTk.PhotoImage(img)
label.config(image = photo)
label.photo = photo
# label.config(bg = 'red')
label.has_img = True
def labels_in_view(self, show=False):
if show:
print('Root:', self.root.winfo_width(), self.root.winfo_height())
for label in self.frame.winfo_children():
label_x = label.winfo_rootx() #- label.winfo_width()
label_y = label.winfo_rooty() #- label.winfo_height()
if show:
print(label.index, label_x, label_y)
x_in = 0 <= label_x < self.root.winfo_width()
y_in = 0 <= label_y < self.root.winfo_height()
if x_in and y_in:
yield label
def debug(self, event):
print([l.index for l in self.labels_in_view(show=True)], '\n')
#staticmethod
def fit_img(img, width, height, keep_aspect=True):
if keep_aspect:
w, h = img.size
ratio = min(width/w, height/h)
image = img.resize((int(w*ratio), int(h*ratio)))
else:
image = img.resize((width, height))
return image
if __name__ == "__main__":
root = Tk()
root.title("Image List Viewer")
root.geometry('400x400')
paths = glob.glob('*.JPG')
ImageListViewer(root, paths).pack()
root.mainloop()
The labels_in_view method tries to only return labels (which contain the images) that are displayed. Each label is originally loaded with a dummy 'loading' image. You can also see I've binded 'h' to display some extra debugging info.
Here's the problem, past tens of images it completely hangs. If one were to scroll to the end of all the images, it tries to load all the intermediate images from top to bottom and not only the ones that are viewable.
This is certainly because of how I intercept the scrollbar call back to then lazy load but I cannot figure out any other way of doing it. Is there a better way? Maybe a better library too, tkinter is rather slow although I'd rather not restart from scratch?
Any help is greatly appreciated! Thanks.
EDIT: Images should only be loaded once because of the label.has_img flag that is set to true once the image has been loaded.
I can't seem to get the picture to show up on screen while having a toplevel frame on top of my main one (root). This one is just called "frame". I have circled the outline of the tkinter Frame on the included photo in this post. When I resize the picture, the green frame outline changes, but the picture itself won't show up.
I also tried to pack it on my main root window, with success, which suggests it's a toplevel window issue. I just don't know what it is. Any ideas?
Here is my code:
def show_topLevelWindow():
from tkinter import ttk
print("Entered to show results")
window_linkedin = Toplevel(root)
window_linkedin.geometry('1000x590')
frame = Frame(window_linkedin)
frame.pack()
error_frame = tkinter.Frame(frame, highlightbackground="green", highlightcolor="green", highlightthickness=1)
error_label = Label(frame, text="It appears there are no results for the selected country")
error_label.config(font=("Helvetica Neue", 20))
im_error = Image.open("./ressources/images_gui/aw_snap.png")
im_error = im_error.resize((500, 500), Image.ANTIALIAS)
im_error = ImageTk.PhotoImage(file = "./ressources/images_gui/aw_snap.png")
im_error_label = Label(frame, image=im_error)
try:
if ....:
....unimportant code ....
else:
error_label.pack(in_=error_frame)
im_error_label.pack(in_=error_frame)
error_frame.pack(anchor="center")
except Exception as e:
error_label.pack(in_=error_frame)
im_error_label.pack(in_=error_frame)
error_frame.pack(anchor="center")
Packed image shows as blank
The single most important issue you are having is your image is not being saved for reference. if you add global im_error to the very top of your function your image will be visible.
That said there are some issues with your code you should correct.
First: Do not import in a function. Instead write all your imports at the top of your code.
Second: I am not sure why you are doing .pack(in_=error_frame). This is not something one would ever really need. Just make sure that your label is already assigned to the correct frame. The in_ argument is rarely used and probably most people never use it. I have been on here for two years now and this is the first time I have seen anyone use that argument.
Third: You have not shown your imports for Tkinter however based on how you have written you code it looks like you have done:
import tkinter
from tkinter import *
This is overkill and is not a good idea. Just do import tkinter as tk and make sure you use tk. prefix where it applies.
Here is your code remade:
import tkinter.ttk as ttk
import tkinter as tk
from PIL import ImageTk, Image
def show_toplevel_window():
global im_error
window_linkedin = tk.Toplevel(root)
window_linkedin.geometry('1000x590')
frame = tk.Frame(window_linkedin)
frame.pack()
error_frame = tk.Frame(frame, highlightbackground="green", highlightcolor="green", highlightthickness=1)
error_frame.pack()
error_label = tk.Label(frame, font=("Helvetica Neue", 20), text="It appears there are no results for the selected country")
error_label.pack()
im_error = Image.open("./ressources/images_gui/aw_snap.png")
im_error = im_error.resize((500, 500), Image.ANTIALIAS)
im_error = ImageTk.PhotoImage(file = "./ressources/images_gui/aw_snap.png")
im_error_label = tk.Label(error_frame, image=im_error)
im_error_label.pack()
root = tk.Tk()
show_toplevel_window()
root.mainloop()