Search code examples
pythontkinterpython-imaging-libraryscreenphoto

Python Tkinter Images Tearing On Scroll


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()

Solution

  • 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()