How to automate a button's action with tkinter? - python

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.

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

I can not figure out why my app menu is still freezing after I have used threading on tkinter

When I run the app, the run button is still responsive for a few seconds before the entire app freezes but the thread continues running. Where is the problem in my code and how do fix it?
import threading
from tkinter import *
from tkinter import ttk
class SubExtractor:
def __init__(self, root):
self.root = root
self._create_layout()
def _create_layout(self):
self.root.title("Sub Extractor")
self._menu_bar()
self.main_frame = ttk.Frame(self.root, padding=(5, 5, 5, 15))
self._video_frame()
self._progress_frame()
self._output_frame()
self.main_frame.grid()
def _menu_bar(self):
self.root.option_add('*tearOff', FALSE)
menubar = Menu(self.root)
self.root.config(menu=menubar)
menu_file = Menu(menubar)
menu_settings = Menu(menubar)
menubar.add_cascade(menu=menu_file, label="File")
menubar.add_cascade(menu=menu_settings, label="Settings")
menu_file.add_command(label="Open", command=self._open_file)
menu_file.add_command(label="Close", command=self._close_window)
menu_settings.add_command(label="Language", command=self._language_settings)
menu_settings.add_command(label="Extraction", command=self._extraction_settings)
def _video_frame(self):
video_frame = ttk.Frame(self.main_frame, borderwidth=2, relief="ridge", width=1000, height=600)
video_frame.grid()
def _progress_frame(self):
progress_frame = ttk.Frame(self.main_frame)
progress_frame.grid(row=1, sticky="W")
self.run_button = ttk.Button(progress_frame, text="Run", command=self._run)
self.run_button.grid(pady=10, padx=30)
self.progress_bar = ttk.Progressbar(progress_frame, orient=HORIZONTAL, length=800, mode='determinate')
self.progress_bar.grid(column=2, row=0)
def _output_frame(self):
output_frame = ttk.Frame(self.main_frame)
output_frame.grid(row=2)
self.text_output_widget = Text(output_frame, width=97, height=12, state="disabled")
self.text_output_widget.grid()
output_scroll = ttk.Scrollbar(output_frame, orient=VERTICAL, command=self.text_output_widget.yview)
output_scroll.grid(column=1, row=0, sticky="N,S")
self.text_output_widget.configure(yscrollcommand=output_scroll.set)
def _close_window(self):
self._stop_run()
self.root.quit()
def _stop_run(self):
self.interrupt = True
self.run_button.configure(text="Run", command=self._run)
def _text_to_output(self, text):
self.text_output_widget.configure(state="normal")
self.text_output_widget.insert("end", f"{text}\n")
self.text_output_widget.see("end")
self.text_output_widget.configure(state="disabled")
def long_running_method(self):
num = 100000
self.progress_bar.configure(maximum=num)
for i in range(0, num):
if self.interrupt:
break
self._text_to_output(f"Line {i} of {num}")
self.progress_bar['value'] += 1
self._stop_run()
def _run(self):
self.interrupt = False
self.run_button.configure(text='Stop', command=self._stop_run)
threading.Thread(target=self.long_running_method).start()
rt = Tk()
SubtitleExtractorGUI(rt)
rt.mainloop()
What I expect to accomplish is for the loop to insert texts in the text widget while I can still freely move and use other buttons on the app.

How can I bind a combobox to a Radiobutton

I have some Radiobuttons. Depending of what Radio button was selected I want to have different Combobox values. I don't know how I can solve the problem. In a further step I want to create further comboboxes which are dependend on the value of the first.
The following code creates the list of user, but it does not show up in the combobox.
For me it is difficult to understand where the right position of functions is, and if I need a lambda function nor a binding.
import tkinter as tk
from tkinter import ttk
import pandas as pd
import os
global version
global df_MA
df_MA = []
class Window(tk.Toplevel):
def __init__(self, parent):
super().__init__(parent)
self.geometry('300x100')
self.title('Toplevel Window')
self.btn = ttk.Button(self, text='Close',command=self.destroy).pack(expand=True)
class App(tk.Tk):
def __init__(self,*args, **kwargs):
super().__init__()
# def load_input_values(self):
def set_department(department):
if department == "produktion":
working_df_complete = path_input_produktion
if department == "service":
working_df_complete = path_input_service
working_df_complete = pd.read_excel(working_df_complete)
working_df_complete = pd.DataFrame(working_df_complete)
'''set worker df'''
df_MA = working_df_complete.loc[:,'MA']
df_MA = list(df_MA.values.tolist())
def select_working_step():
return
'''Define Variable Bereich aofter clicking of radio button '''
'''SEEMS TO ME UNECCESSARY COMPLICATED, but I dont't know how to do it properly. I am no progammer'''
border = 10
spacey = 10
'''paths for input file'''
path_input_produktion = os.path.abspath('input_data\werte_comboboxen_produktion.xlsx')
path_input_service = os.path.abspath('input_data\werte_comboboxen_service.xlsx')
self.geometry('500x600')
'''Variablen for department'''
department = tk.StringVar()
department.set(" ")
'''place Frame department'''
self.rb_frame_abteilung = tk.Frame(self)
'''Radiobuttons for department'''
rb_abteilung_produktion = tk.Radiobutton(self.rb_frame_abteilung, text="Produktion", variable= department,
value="produktion", command= lambda: set_department(department.get()))
rb_abteilung_service = tk.Radiobutton(self.rb_frame_abteilung, text="Service", variable= department,
value="service", command= lambda: set_department(department.get()) )
rb_abteilung_produktion.pack(side="left", fill=None, expand=False, padx=10)
rb_abteilung_service.pack(side="left", fill=None, expand=False, padx =10)
self.rb_frame_abteilung.grid(row=5, column=1, sticky="nw", columnspan=99)
self.label_user = ttk.Label(self, text='user').grid(row=15,
column=15, pady=spacey,padx=border, sticky='w')
self.combobox_user = ttk.Combobox(self, width = 10, value= df_MA)
self.combobox_user.bind("<<ComboboxSelected>>", select_working_step)
self.combobox_user.grid(row=15, column=20, pady=spacey, sticky='w')
if __name__ == "__main__":
app = App()
app.mainloop()
´´´
I rewrote everything using indexes and removing global variables...
#!/usr/bin/python3
import tkinter as tk
from tkinter import ttk
from tkinter import messagebox
class App(tk.Tk):
"""Application start here"""
def __init__(self):
super().__init__()
self.protocol("WM_DELETE_WINDOW", self.on_close)
self.title("Simple App")
self.option = tk.IntVar()
self.departments = ('Produktion','Service')
self.df_MA_1 = ['Peter','Hans','Alfred']
self.df_MA_2 = ['Otto','Friedrich','Tanja']
self.init_ui()
self.on_reset()
def init_ui(self):
w = ttk.Frame(self, padding=8)
r = 0
c = 1
ttk.Label(w, text="Combobox:").grid(row=r, sticky=tk.W)
self.cbCombo = ttk.Combobox(w, values="")
self.cbCombo.grid(row=r, column=c, padx=5, pady=5)
r += 1
ttk.Label(w, text="Radiobutton:").grid(row=r, sticky=tk.W)
for index, text in enumerate(self.departments):
ttk.Radiobutton(w,
text=text,
variable=self.option,
value=index,
command= self.set_combo_values).grid(row=r,
column=c,
sticky=tk.W,
padx=5, pady=5)
r +=1
r = 0
c = 2
b = ttk.LabelFrame(self, text="", relief=tk.GROOVE, padding=5)
bts = [("Reset", 0, self.on_reset, "<Alt-r>"),
("Close", 0, self.on_close, "<Alt-c>")]
for btn in bts:
ttk.Button(b, text=btn[0], underline=btn[1], command = btn[2]).grid(row=r,
column=c,
sticky=tk.N+tk.W+tk.E,
padx=5, pady=5)
self.bind(btn[3], btn[2])
r += 1
b.grid(row=0, column=1, sticky=tk.N+tk.W+tk.S+tk.E)
w.grid(row=0, column=0, sticky=tk.N+tk.W+tk.S+tk.E)
def set_combo_values(self):
print("you have selected {0} radio option".format(self.option.get()))
self.cbCombo.set("")
if self.option.get() == 0:
self.cbCombo["values"] = self.df_MA_1
else:
self.cbCombo["values"] = self.df_MA_2
def on_reset(self, evt=None):
self.cbCombo.set("")
self.option.set(0)
self.set_combo_values()
def on_close(self,evt=None):
"""Close all"""
if messagebox.askokcancel(self.title(), "Do you want to quit?", parent=self):
self.destroy()
def main():
app = App()
app.mainloop()
if __name__ == '__main__':
main()

How to run tkinter's after event alongside another task?

I am trying to create a tkinter GUI for a script which performs some task. The task is triggered by clicking a start button, and I would like to add a dynamic label showing that the task is "in progress" by displaying:
"Working." → "Working.." → "Working..."
I referred to this post and wrote the following script. Here I used a progress bar to represent my "task", and was expecting the status label to change (as stated above) WHILE the progress bar is updating.
import tkinter as tk
class UI:
def __init__(self):
self.root = tk.Tk()
self.root.title('Hello World')
self.prog_Label = tk.Label(self.root, text='Progress')
self.prog_Label.grid(row=0, column=0, sticky=tk.W, padx=20, pady=(10, 0))
self.prog_Bar = tk.ttk.Progressbar(self.root)
self.prog_Bar.configure(value=0, mode='determinate', orient='horizontal')
self.prog_Bar.grid(row=1, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), padx=20, pady=5)
self.exe_Btn = tk.Button(self.root, text='Start', padx=15, command=self.run, relief='groove')
self.exe_Btn.grid(row=2, column=0, padx=80, pady=(40, 20), sticky=tk.E)
self.prog_Label = tk.Label(self.root, text='Status:-')
self.prog_Label.grid(row=3, column=0, sticky=tk.W, padx=20, pady=10)
self.root.mainloop()
def run(self):
self.update_status('Working')
n = 0
self.prog_Bar.configure(value=n, maximum=100000, mode='determinate', orient='horizontal')
for i in range(100000):
n += 1
self.prog_Bar.configure(value=n)
self.prog_Bar.update_idletasks()
def update_status(self, status=None):
if status is not None:
current_status = 'Status: ' + status
else:
current_status = self.prog_Label['text']
if current_status.endswith('...'):
current_status = current_status.replace('...', '')
else:
current_status += '.'
self.prog_Label['text'] = current_status
self.prog_Label.update_idletasks()
self._status = self.root.after(1000, self.update_status)
if __name__ == '__main__':
ui = UI()
However, the program behaves in a way that, when the start button is clicked, although the status label changes from '-' to 'Working' immediately, it only starts to add the dots after the progress bar reaches the end.
Is there a way to modify it so as to achieve my purpose?
I changed your structure a little so your task is now in its own class, instead of sleeping you would perform the task there. I added a thread for the task as i assumed that it would need its own process, this stops the application freezing as it would block the main UI loop.
import threading
import time
import tkinter as tk
import tkinter.ttk as ttk
class Task:
def __init__(self):
self.percent_done = 0
threading.Thread(target = self.run).start()
def run(self):
while self.percent_done < 1:
self.percent_done += 0.1
# Do your task here
time.sleep(0.5)
self.percent_done = 1
class Application():
def __init__(self):
self.root = tk.Tk()
self.root.title("Window Title")
self.task = None
self.label_dots = 0
self.prog_bar = tk.ttk.Progressbar(self.root)
self.prog_bar.configure(value=0, maximum=100, mode='determinate', orient='horizontal')
self.prog_bar.grid(row=1, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), padx=20, pady=5)
self.run_btn = tk.Button(self.root, text='Start', padx=15, command=self.start_task, relief='groove')
self.run_btn.grid(row=2, column=0, padx=80, pady=(40, 20), sticky=tk.E)
self.prog_label = tk.Label(self.root, text='Status: -')
self.prog_label.grid(row=3, column=0, sticky=tk.W, padx=20, pady=10)
def start_task(self):
self.task = Task()
self.update_ui()
def update_ui(self):
# Percent is between 0 and 1
if 0 < self.task.percent_done < 1:
status = "Working"
self.label_dots += 1
self.label_dots = self.label_dots % 4
else:
status = "Finished"
self.label_dots = 0
self.prog_bar.configure(value=self.task.percent_done * 100)
label_text = "Status: - " + status + ("." * self.label_dots)
self.prog_label.config(text = label_text)
if status != "Finished":
self.root.after(1000, self.update_ui)
Application().root.mainloop()

Use start and stop function with same button in Tkinter

With the help of the command button, I am able to disconnect the frame in Tkinter. But is there any way which helps to use the same button to start also?
import tkinter as tk
counter = 0
def counter_label(label):
def count():
global counter
counter+=1
label.config(text=counter)
label.after(1000, count)
count()
root = tk.Tk()
root.title("Counting Seconds")
label = tk.Label(root, fg="green")
label.pack()
counter_label(label)
button = tk.Button(root, text='Stop', width=25, command=root.destroy)
button.pack()
root.mainloop()
Suggestions will be grateful
You could simple use an if/else statement to check if the buttons text is Start or Stop then change a Boolean variable that controls the counter while also update the text on the button.
import tkinter as tk
counter = 0
active_counter = False
def count():
if active_counter:
global counter
counter += 1
label.config(text=counter)
label.after(1000, count)
def start_stop():
global active_counter
if button['text'] == 'Start':
active_counter = True
count()
button.config(text="Stop")
else:
active_counter = False
button.config(text="Start")
root = tk.Tk()
root.title("Counting Seconds")
label = tk.Label(root, fg="green")
label.pack()
button = tk.Button(root, text='Start', width=25, command=start_stop)
button.pack()
root.mainloop()
Here is an OOP example as well:
import tkinter as tk
class App(tk.Tk):
def __init__(self):
super().__init__()
self.title("Counting Seconds")
self.counter = 0
self.active_counter = False
self.label = tk.Label(self, fg="green")
self.label.pack()
self.button = tk.Button(self, text='Start', width=25, command=self.start_stop)
self.button.pack()
def count(self):
if self.active_counter:
self.counter += 1
self.label.config(text=self.counter)
self.label.after(1000, self.count)
def start_stop(self):
if self.button['text'] == 'Start':
self.active_counter = True
self.count()
self.button.config(text="Stop")
else:
self.active_counter = False
self.button.config(text="Start")
if __name__ == "__main__":
App().mainloop()
This code is overly complicated (my answer), I suggest improving it. But it shows how one could use the same button for both start and stop as well as keeping most of your code.
import tkinter as tk
def counter_label(label):
a = 0
label.config(text=str(a))
def count():
nonlocal a
label.config(text=str(a))
a += 1
label.after(1000, count)
return count
def start_stop(root, btn_text, counter):
first = True
def call():
nonlocal first
if first:
counter()
first = False
btn_text.set('Stop')
else:
root.destroy()
return call
root = tk.Tk()
root.title("Counting Seconds")
label = tk.Label(root, fg="green")
label.pack()
counter = counter_label(label)
btn_text = tk.StringVar()
button = tk.Button(root, textvariable=btn_text, width=25, command=start_stop(root, btn_text, counter))
btn_text.set('Start')
button.pack()
root.mainloop()

Python GUI interface not opening widget?

I'm trying to create a widget that's like a to do list: but my program doesn't seem to run for some reason. It doesn't give an error, but it doesn't work..?
would anyone know what's wrong with my code?
from Tkinter import *
import tkFont
class App:
def getTasks(self):
return self.todo
def getCompleted(self):
return self.done
def __init__(self, master):
self.todo = todo.todoList()
self.master = master
self.frame - Frame(master)
self.frame.grid()
self.saveButton = Button(self.frame, text="Save", command=self.save)
self.saveButton.grid()
self.restoreButton = Button(self.frame, text="Restore", command=self.res)
self.restoreButton.grid(row=0, column=1)
self.addButton = Button(self.frame, text="Add", command = self.add)
self.addButton.grid(row=0, column=2)
self.doneButton = Button(self.frame, text = "Done", command = self.done)
self.doneButton.grid(row=0, column=3)
self.button = Button(self.frame, text="QUIT", command=self.quit)
self.button.grid(row=0, column=4)
label = Label(self.frame, text="New Task: ")
label.grid()
self.entry = Entry(self.frame)
self.entry.grid(row=0, column=4)
frame1 = LabelFrame(self.frame, text="Tasks")
frame1.grid(columnspam = 5)
self.tasks = Listbox(frame1)
self.task.grid()
frame2=LabelFrame(self.frame, text="Completed")
frame2.grid(columnspan=5)
self.completed= Listbox(frame2)
self.completed.grid()
def save(self):
self.todo.saveList("tasks.txt")
def restore(self):
self.todo.restoreList("tasks.txt")
items = self.todo.getTasks()
self.tasks.delete(0, END)
for item in items:
self.tasks.insert(END, item)
items = self.todo.getCompleted()
self.completed.delete(0,END)
for item in items:
self.completed.insert(END,item)
def add(self):
task = self.entry.get()
self.todo.addTask(task)
self.tasks.insert(END,task)
def done(self):
selection = self.tasks.curselection()
if len(selection) == 0:
return
task = self.tasks.get(selection[0])
self.todo.completeTask(task)
self.tasks.delete(selection[0])
self.completed.insert(END,task)
would anyone know what the error is?
You need to actually make an instance of App and an instance of Tkinter.Tk:
...
if __name__ == '__main__':
root = Tk()
app = App(root)
root.mainloop()
Note, I haven't tried the rest of your code, so I don't know if it will work, but this should at least start giving you errors to work with.

Categories

Resources