How to automate a button's action with tkinter?

I would like to achieve, when I click on the "Autopaging" button each page from the PDF gets displayed for 1s. Now when I click it, its loops through the list of pages, jumps to the last page, and doesn't display any other page.

To the auto_read function I placed print(pagenumber) to see when its looping through the pages.

from PIL import ImageTk, Image
from pdf2image import convert_from_path
import time

class SuReader:
    def __init__(self, root):
        self.root = root
        self.next_but = Button(root, text='>>', command=lambda: self.next_page())
        self.prev_but = Button(root, text='<<', command=self.prev_page)
        self.automate_but = Button(root, text='Autopaging', command=self.auto_read)
        self.next_but.grid(row=1, column=2)
        self.prev_but.grid(row=1, column=0)
        self.automate_but.grid(row=1, column=1)
        self.display_pages()


    def get_pages(self):
        self.pages = convert_from_path('book.pdf', size=(700, 600))
        self.photos = []
        for i in range(len(self.pages)):
            self.photos.append(ImageTk.PhotoImage(self.pages[i]))


    def display_pages(self):
        self.get_pages()
        self.page_number = 0
        self.content = Label(self.root, image=self.photos[self.page_number])
        self.content.grid(column=0,row=0,sticky='NSEW', columnspan=3)
        print(len(self.photos))


    def next_page(self):
        # self.page_number += 1
        self.content.destroy()
        self.content = Label(self.root, image=self.photos[self.page_number])
        self.content.grid(column=0,row=0,sticky='NSEW', columnspan=3)
        self.page_number += 1



    def prev_page(self):
        self.page_number -= 1
        print(self.page_number)
        self.content.destroy()
        self.content = Label(self.root, image=self.photos[self.page_number])
        self.content.grid(column=0,row=0,sticky='NSEW', columnspan=3)


    def auto_read(self):
        for i in range(len(self.photos)):
            time.sleep(1)
            print(self.page_number)
            self.next_page()


root = Tk()
root.title('Book Reader')
program = SuReader(root)
root.mainloop()

Here I just pass it to the next_page function, but even if I define properly it doesn't work.

1 answer

  • answered 2020-08-11 00:38 Michael Guidry

    time.sleep is blocking mainloop and I believe you are also having a garbage collection problem. I rewrote your code and solved your problems. I solved other problems you were about to have, as well. The changes are commented in the script.

    import tkinter as tk
    from PIL import ImageTk, Image
    from pdf2image import convert_from_path
    
    class App(tk.Tk):
        WIDTH  = 700
        HEIGHT = 640
        TITLE  = 'Book Reader'
        
        def __init__(self, **kwargs):
            tk.Tk.__init__(self, **kwargs)
            
            #get_pages and display_pages are useless just do it all in the constructor
            self.pages  = convert_from_path('book.pdf', size=(700, 600))
            self.photos = {}
            for i in range(len(self.pages)):
                #the image might get garbage collected if you don't do it this way
                self.photos[f'photo_{i}'] = ImageTk.PhotoImage(self.pages[i])
                
            self.auto     = tk.IntVar()
            self.was_auto = False
            
            self.page_number = 0
            self.content     = tk.Label(self, image=self.photos[f'photo_{self.page_number}'])
            self.content.grid(column=0, row=0, sticky='nsew', columnspan=3)
            
            tk.Button(self, text='<<', command=self.prev_page).grid(row=1, column=0, sticky='w')
            tk.Checkbutton(self, text='auto-paging', variable=self.auto, command=self.auto_page).grid(row=1, column=1)
            tk.Button(self, text='>>', command=self.next_page).grid(row=1, column=2, sticky='e')
            
            self.grid_columnconfigure(0, weight=1)
            self.grid_rowconfigure(0, weight=1)
            self.grid_columnconfigure(2, weight=1)
            
        def next_page(self):
            #stop a lingering `after` call from affecting anything if we unchecked auto-paging
            if self.auto.get() == 0 and self.was_auto:
                self.was_auto = False
                return
                
            self.page_number += 1
            #never be out of range
            self.page_number = self.page_number % len(self.pages)
            #you don't have to remake the Label every time, just reassign it's image property
            self.content['image'] = self.photos[f'photo_{self.page_number}']
            
            #time.sleep(1) replacement ~ non-blocking 
            if self.auto.get() == 1:
                self.after(1000, self.next_page)
    
        def prev_page(self):
            self.page_number -= 1
            #never be out of range
            if self.page_number < 0:
                self.page_number += len(self.pages)
            self.content['image'] = self.photos[f'photo_{self.page_number}']
            
        def auto_page(self):
            if self.auto.get() == 1:
               #allows us to catch a lingering `after` call if auto-paging is unchecked
               self.was_auto = True
               self.next_page()
                
    
    if __name__ == '__main__':
        app = App()
        app.title(App.TITLE)
        app.geometry(f'{App.WIDTH}x{App.HEIGHT}')
        app.resizable(width=False, height=False)
        app.mainloop()