I'm working on a project to create a GUI application to display images as clickable buttons in Tkinter. I have the functionality completed where the images appear in a scroll-able grid on the GUI. However, when scrolling up or down there is terrible screen tearing with the images. Is there a way to work around this with Tkinter or is this due to python not being the fastest language and graphical operations being somewhat expensive? If I have to rewrite the program in C++ for example to be faster that is fine, but I would like to know if such a measure is necessary before taking that step. I am new to GUI applications so if im making some obvious mistake please let me know.
Thanks for your help ahead of time, code below:
import os
import json
import tkinter as tk
from tkinter import ttk
from math import *
from tkinter import *
from PIL import ImageTk, Image
directory = "IMG DIR HERE!" #<--CHANGE THIS FOR YOUR SYSTEM!
class Img:
def __init__(self,name=-2,img=-2):
self.name = name
self.img = img
def event(args):
canvas.configure(scrollregion = canvas.bbox("all"))
def command():
print("Hello")
def resize_img(raw_img):
std_size = (300,300)
img = raw_img.resize(std_size)
return img
def getimages():
images = os.listdir(directory);
Imgs=[]
exts = ["gif","jpg","png"]
for name in images:
ext = name.split(".")[1]
print(ext)
if ext not in exts:
print("False")
continue
print("True")
raw_img = Image.open(directory + "/"+name)
img = resize_img(raw_img)
img = ImageTk.PhotoImage(img)
img = Img(name,img)
Imgs.append(img)
return Imgs
root= tk.Tk()
root.geometry("1000x400")
root.title("Display Image App")
images = getimages()
#Create A Main Frame
main_frame = Frame(root)
main_frame.pack(fill=BOTH,expand=1)
#Create A Canvas
canvas = Canvas(main_frame)
canvas.pack(side=LEFT, fill=BOTH, expand=1)
#Add A Scrollbar to the Canvas
scrollbar = Scrollbar(main_frame, orient=VERTICAL, command=canvas.yview)
scrollbar.pack(side=RIGHT,fill=Y)
#Configure Canvas
canvas.configure(yscrollcommand=scrollbar.set)
canvas.bind('<Configure>', event)
#Create another Frame inside the Canvas
imageframe = Frame(canvas)
#add that new frame to a window in the canvas
canvas.create_window((0,0), window=imageframe, anchor="nw")
num_images = len(images)
num_images_per_col = 4
num_rows = ceil(num_images/num_images_per_col)
for i in range(num_images_per_col):
imageframe.columnconfigure(i, weight=1)
index=0
for i in range(num_rows):
for j in range(num_images_per_col):
if index > num_images-1:
break
img = images[index].img
button_img = Button(imageframe,image=img, command=command,borderwidth=2)
button_img.grid(row=i,column=j, pady=5)
index += 1
root.mainloop()
So I've come across a work around that works for me where instead of overlaying an image on the button, I draw the images directly on the canvas and then bind a function associated with a click on that image to provide the same functionality. It's not perfect but works for my needs. Thanks to everyone who originally answered.
import os
import json
import tkinter as tk
from tkinter import ttk
from math import *
from tkinter import *
from PIL import ImageTk, Image
import customtkinter as ctk
directory = "IMAGE DIR HERE!" # <-- CHANGE FOR YOUR SYSTEM!
class Img:
def __init__(self,name=-2,img=-2):
self.name = name
self.img = img
def event(args):
canvas.configure(scrollregion = canvas.bbox("all"))
def on_image_click(event,name):
#Do Stuff
print(name)
def resize_img(raw_img):
std_size = (300,300)
img = raw_img.resize(std_size)
return img
def getimages():
images = os.listdir(directory);
Imgs=[]
exts = ["gif","jpg","png"]
for name in images:
ext = name.split(".")[1]
print(ext)
if ext not in exts:
print("False")
continue
print("True")
raw_img = Image.open(directory + "/"+name)
img = resize_img(raw_img)
img = ImageTk.PhotoImage(img)
img = Img(name,img)
Imgs.append(img)
return Imgs
root= ctk.CTk()
root.geometry("1000x400")
root.title("Display Image App")
images = getimages()
#Create A Main Frame
main_frame = Frame(root)
main_frame.pack(fill=BOTH,expand=1)
#Create A Canvas
canvas = Canvas(main_frame)
canvas.pack(side=LEFT, fill=BOTH, expand=1)
#Add A Scrollbar to the Canvas
scrollbar = ctk.CTkScrollbar(main_frame, command=canvas.yview)
scrollbar.pack(side=RIGHT,fill=Y)
#Configure Canvas
canvas.configure(yscrollcommand=scrollbar.set)
canvas.bind('<Configure>', event)
#Create another Frame inside the Canvas
imageframe = Frame(canvas)
#add that new frame to a window in the canvas
canvas.create_window((0,0), window=imageframe, anchor="nw")
num_images = len(images)
num_images_per_col = 4
num_rows = ceil(num_images/num_images_per_col)
for i in range(num_images_per_col):
imageframe.columnconfigure(i, weight=1)
index=0
for i in range(num_rows):
for j in range(num_images_per_col):
if index > num_images-1:
break
image = images[index]
img = image.img
name = image.name
x = j * 320
y = i * 320
img_id = canvas.create_image(x, y, anchor="nw", image=img)
canvas.tag_bind(img_id, '<Button-1>', lambda event, name=name: on_image_click(event,name))
index += 1
root.mainloop()