How to automate a button's action with tkinter?

Viewed 220

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 Answers

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