I am running Tkinter 3.5 on Win machine and when I run this code, I get two windows . I was expecting just one . BTW, I got the code form the web.It is working fine, except that bothers me the second(in backgorund) window .
Basically is a widget to navigate trough different windows(pages) with buttons .
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
try:
import Tkinter as tk # Python2
except ImportError:
import tkinter as tk # Python3
class Wizard(tk.Toplevel):
def __init__(self, npages, master=None):
self.pages = []
self.current = 0
tk.Toplevel.__init__(self)
self.attributes('-topmost', True)
for page in range(npages):
self.pages.append(tk.Frame(self))
self.pages[0].pack(fill='both', expand=1)
self.__wizard_buttons()
def onQuit(self):
pass
def __wizard_buttons(self):
for indx, frm in enumerate(self.pages):
btnframe = tk.Frame(frm, bd=1, bg='#3C3B37')
btnframe.pack(side='bottom', fill='x')
nextbtn = tk.Button(btnframe, bd=0, bg='#F2F1F0', activebackground='#F58151', highlightcolor='red', cursor='hand2', text="Siguiente >>", width=10, command=self.__next_page)
nextbtn.pack(side='right', anchor='e', padx=5, pady=5)
if indx != 0:
prevbtn = tk.Button(btnframe, bd=0, bg='#F2F1F0', activebackground='#F58151', highlightcolor='red', cursor='hand2', text="<< Atras", width=10, command=self.__prev_page)
prevbtn.pack(side='right', anchor='e', padx=5, pady=5)
if indx == len(self.pages) - 1:
nextbtn.configure(text="Terminar", bd=0, bg='#F2F1F0', activebackground='#F58151', highlightcolor='red', cursor='hand2', command=self.close)
def __next_page(self):
if self.current == len(self.pages):
return
self.pages[self.current].pack_forget()
self.current += 1
self.pages[self.current].pack(fill='both', expand=1)
def __prev_page(self):
if self.current == 0:
return
self.pages[self.current].pack_forget()
self.current -= 1
self.pages[self.current].pack(fill='both', expand=1)
def add_page_body(self, body):
body.pack(side='top', fill='both', padx=6, pady=12)
def page(self, page_num):
try:
page = self.pages[page_num]
except KeyError("Pagina Invalida! : %s" % page_num):
return 0
return page
def close(self):
if self.validate():
self.master.iconify()
print (' TK Wizard finished... ')
self.destroy()
self.master.destroy()
def validate(self):
return 1
if __name__ == "__main__":
root = tk.Tk()
root.title(' TK Wizards ')
wizard = Wizard(npages=3, master=root)
wizard.minsize(400, 350)
page0 = tk.Label(wizard.page(0), text='Pagina 1: ...Bienvenido al Wizard de TK !')
page1 = tk.Label(wizard.page(1), text='Pagina 2: Acepta las condiciones de la WTFPL ?')
page2 = tk.Label(wizard.page(2), text='Pagina 3: Felicitaciones, nada no se ha instalado correctamente.')
wizard.add_page_body(page0)
wizard.add_page_body(page1)
wizard.add_page_body(page2)
root.mainloop()
The additional, blank window is the root window. Add a call to
root.withdraw()
Just underneath the root.title(' TK Wizards ') line, should do the trick
In the main area, you create a tkinter object, which will produce a window:
root = tk.Tk()
Then, in the Wizard class's __init__, you create a Toplevel object:
tk.Toplevel.__init__(self)
So you're really creating the GUI in this new Toplevel window. You can change your program to create the app in the root window, which requires changing the Wizard class to inherit from the default object and changing the program to act on a saved self.master root object wherever it used to act on the Wizard object (which is no longer a Toplevel object).
try:
import Tkinter as tk # Python2
except ImportError:
import tkinter as tk # Python3
class Wizard(object):
def __init__(self, npages, master=None):
self.pages = []
self.current = 0
self.master = master
self.master.attributes('-topmost', True)
for page in range(npages):
self.pages.append(tk.Frame(self.master))
self.pages[0].pack(fill='both', expand=1)
self.__wizard_buttons()
def onQuit(self):
pass
def __wizard_buttons(self):
for indx, frm in enumerate(self.pages):
btnframe = tk.Frame(frm, bd=1, bg='#3C3B37')
btnframe.pack(side='bottom', fill='x')
nextbtn = tk.Button(btnframe, bd=0, bg='#F2F1F0', activebackground='#F58151', highlightcolor='red', cursor='hand2', text="Siguiente >>", width=10, command=self.__next_page)
nextbtn.pack(side='right', anchor='e', padx=5, pady=5)
if indx != 0:
prevbtn = tk.Button(btnframe, bd=0, bg='#F2F1F0', activebackground='#F58151', highlightcolor='red', cursor='hand2', text="<< Atras", width=10, command=self.__prev_page)
prevbtn.pack(side='right', anchor='e', padx=5, pady=5)
if indx == len(self.pages) - 1:
nextbtn.configure(text="Terminar", bd=0, bg='#F2F1F0', activebackground='#F58151', highlightcolor='red', cursor='hand2', command=self.close)
def __next_page(self):
if self.current == len(self.pages):
return
self.pages[self.current].pack_forget()
self.current += 1
self.pages[self.current].pack(fill='both', expand=1)
def __prev_page(self):
if self.current == 0:
return
self.pages[self.current].pack_forget()
self.current -= 1
self.pages[self.current].pack(fill='both', expand=1)
def add_page_body(self, body):
body.pack(side='top', fill='both', padx=6, pady=12)
def page(self, page_num):
try:
page = self.pages[page_num]
except KeyError("Pagina Invalida! : %s" % page_num):
return 0
return page
def close(self):
if self.validate():
self.master.iconify()
print (' TK Wizard finished... ')
self.destroy()
self.master.destroy()
def validate(self):
return 1
if __name__ == "__main__":
root = tk.Tk()
root.title(' TK Wizards ')
wizard = Wizard(npages=3, master=root)
wizard.master.minsize(400, 350)
page0 = tk.Label(wizard.page(0), text='Pagina 1: ...Bienvenido al Wizard de TK !')
page1 = tk.Label(wizard.page(1), text='Pagina 2: Acepta las condiciones de la WTFPL ?')
page2 = tk.Label(wizard.page(2), text='Pagina 3: Felicitaciones, nada no se ha instalado correctamente.')
wizard.add_page_body(page0)
wizard.add_page_body(page1)
wizard.add_page_body(page2)
root.mainloop()
Related
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.
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()
This is to be a desktop application for opening multiple small databases and running queries on them. So far I've written some code for opening forms as necessary. Is this a good way to do it? Also - the code shown opens two copies of each form - what am I doing wrong? It's my first attempt at Python and I'm a rudimentary programmer so simple answers would be of most help please. TIA (Python 3.9.6)
link_1.py
from tkinter import Tk
import link_2
root = Tk()
class ClassLink_1:
#if another function in the class is called, the __init__ function is run at start up
def __init__(self):
print("in link_1 __init__ instruction")
#the call_function function can be called at start up, or not, and will act accordingly
def call_function(self):
print("in call_function")
#the line below is run at start up whether or not another function in the class is called
print("in link_1")
root.withdraw() #hides the blank form at start up
#if __name__ == "__main__":
#the line below shows the link_2 form, whether or not the if __name__==__main__ condition is used as its condition
link_2.ClassLink_2(root).__init__(root)
#link_3.ClassLink_3(root).__init__(root)
#the line below runs call_function on start up to print text
ClassLink_1().call_function()
root.mainloop()
link_2.py
from tkinter import Tk, Button
from tkinter import * #for Toplevel
import link_3
root = Tk()
class ClassLink_2:
def __init__(self, master):
self.openNewWindow()
def openNewWindow(self):
newWindow = Toplevel(root) #creates a top level widget with the parent root (first parameter)
newWindow.title("Title opened from link_1")
newWindow.geometry("500x500")
label = Label(newWindow, text ="Opened from link_1").grid(row=1, column=1)
self.add_button = Button(newWindow, text="in ClassLink_2", command= self.do_add)
self.add_button.grid(row=3, column=1)
def do_add(self):
print("button pressed")
link_3.ClassLink_3(root).__init__(root)
root.withdraw() #hides the blank form at start up
link_3.py
from tkinter import Tk, Button
from tkinter import * #for Toplevel
root = Tk()
class ClassLink_3:
def __init__(self, master):
self.openNewWindow()
def openNewWindow(self):
newWindow = Toplevel(root) #creates a top level widget with the parent root (first parameter)
newWindow.title("Title opened from link_2")
newWindow.geometry("500x500")
label = Label(newWindow, text ="Opened from link_2").grid(row=1, column=1)
self.add_button = Button(newWindow, text="in ClassLink_3", command= self.do_add)
self.add_button.grid(row=3, column=1)
def do_add(self):
print("button pressed")
# link_4.ClassLink_4(root).__init__(root) this file has not yet been made
root.withdraw() #hides the blank form at start up
This is a proposed solution, can be expanded as needed. Constructive suggestions for improvement of the structure or code would be appreciated. TIA. I've left in the details in case they are of use to anyone.
link_1
from tkinter import Tk
import link_2
root = Tk()
class ClassLink_1:
def __init__(self):
print("in link_1 __init__ instruction")
root.withdraw() #hides the blank form at start up
link_2.ClassLink_2(root).openNewWindow(0)
root.mainloop()
link_2
from tkinter import Tk, Button
from tkinter import *
import link_3
root = Tk()
class ClassLink_2:
root.withdraw() #hides the blank form at start up
class_var_1 = 0
inst_var_1 = 0
def __init__(self, incoming_inst_var_1):
pass
def openNewWindow(self, inst_var_1_to_open):
newWindow = Toplevel(root)
newWindow.title("Title opened from link_1")
newWindow.geometry("500x500")
label = Label(newWindow, text ="Opened from link_1").grid(row=1, column=1)
self.add_button = Button(newWindow, text="in ClassLink_2", command= self.do_add)
self.add_button.grid(row=3, column=1)
self.add_button = Button(newWindow, text="increment class_var_1", command= self.inc_class_var_1)
self.add_button.grid(row=5, column=1)
self.inst_var_1 = inst_var_1_to_open
def do_add(self):
print("button pressed")
link_3.ClassLink_3(root).openNewWindow(0)
def inc_class_var_1(self):
ClassLink_2.class_var_1 += 2
self.inst_var_1 += 1
print("self.class_var_1 = " + (str)(self.class_var_1))
print("self.inst_var_1 = " + (str)(self.inst_var_1))
link_3
from tkinter import Tk, Button
from tkinter import *
from tkinter.ttk import Combobox
import tkinter.scrolledtext as tkscrolled
# import link_4 <filename of next form>
root = Tk()
class ClassLink_3:
class_var_1 = 0
inst_var_1 = 0
# ------------------------------- START CLASS CONTROLS -----------------------------
root.withdraw()
def __init__(self, incoming_inst_var_1):
pass
def openNewWindow(self, inst_var_1_to_open):
new_window = Toplevel(root)
self.widget_factory(new_window)
self.inst_var_1 = inst_var_1_to_open
def do_add(self):
print("button pressed")
# link_4.ClassLink_4(root).openNewWindow(0) # <filename of next form>
# ---------------------------------- END CLASS CONTROLS -----------------------------
# -------------------------------------- START CALCS --------------------------------------
def inc_class_var_1(self):
ClassLink_3.class_var_1 += 2
self.inst_var_1 += 1
print("self.class_var_1 = " + (str)(self.class_var_1))
print("self.inst_var_1 = " + (str)(self.inst_var_1))
# ---------------------------------------- END CALCS --------------------------------------
# ---------------------------------------- START FACTORY SHOPS-----------------------------------------
def widget_factory(self, new_window):
self.shop_window(new_window)
self.shop_labels(new_window)
self.shop_buttons(new_window)
self.shop_menu(new_window)
self.shop_listbox(new_window)
self.shop_combobox(new_window)
self.shop_radiobuttons(new_window)
self.shop_checkbuttons(new_window)
self.shop_entries(new_window)
self.shop_canvas(new_window)
self.shop_scale(new_window)
self.shop_scrollbars(new_window)
self.shop_textbox(new_window)
self.shop_menu(new_window)
pass
# ------------------------
def shop_window(self, new_window):
new_window.title("Title opened from link_2")
new_window.geometry("800x900")
def shop_labels(self, new_window):
self.label_1 = Label(new_window, text ="Opened from link_2").grid(row=1, column=1)
def shop_buttons(self, new_window):
self.button_1 = Button(new_window, text="in ClassLink_3", command= self.do_add)
self.button_1.grid(row=3, column=1)
self.button_2 = Button(new_window, text="increment class_var_1", command= self.inc_class_var_1)
self.button_2.grid(row=5, column=1)
def shop_listbox(self, new_window):
data=("one", "two", "three", "four")
self.listbox_1 = Listbox(new_window, height=5, selectmode='multiple')
for num in data:
self.listbox_1.insert(END,num)
self.listbox_1.grid(row=7, column=1)
def shop_combobox(self, new_window):
data=("one", "two", "three", "four")
self.combobox_1 = Combobox(new_window, values=data)
self.combobox_1.grid(row=8, column=3)
def shop_radiobuttons(self, new_window):
var_1 = IntVar()
var_1.set(1)
self.rad_btn_1 = Radiobutton(new_window, text="male", variable=var_1, value=1)
self.rad_btn_2 = Radiobutton(new_window, text="female", variable=var_1, value=2)
self.rad_btn_1.grid(row=9, column=1)
self.rad_btn_2.grid(row=9, column=2)
def shop_checkbuttons(self, new_window):
var_1 = IntVar()
var_2 = IntVar()
self.checkbtn_1 = Checkbutton(new_window, text = "Cricket", variable = var_1)
self.checkbtn_2 = Checkbutton(new_window, text = "Tennis", variable = var_2)
self.checkbtn_1.grid(row=10, column=1)
self.checkbtn_2.grid(row=10, column=2)
def shop_entries(self, new_window):
self.entry_1 = Entry(new_window, width = 20)
self.entry_1.insert(0,'Username')
self.entry_1.grid(row= 11, column=2)
self.entry_2 = Entry(new_window, width = 15)
self.entry_2.insert(0,'password')
self.entry_2.grid(row= 12, column=2)
#might want to place on a frame, see example https://coderslegacy.com/python/list-of-tkinter-widgets/
def shop_canvas(self, new_window):
canvas = Canvas(new_window, bg= 'white', width = 260,height = 260) #this is the background for the figure
# left and top of figure is from [x from left, y from top] of canvas right and bottom of figure from [x from left, y from top] of canvas
coordinates = 20, 50, 210, 230
arc = canvas.create_arc(coordinates, start=0, extent=250, fill="blue") #start is from E, extent is in degrees CCW
arc = canvas.create_arc(coordinates, start=250, extent=50, fill="red")
arc = canvas.create_arc(coordinates, start=300, extent=60, fill="yellow")
canvas.grid(row=2, column=1)
def shop_scale(self, new_window):
self.scale_1 = Scale(new_window, from_=0, to=10, orient=VERTICAL)
self.scale_1.grid(row=15, column=2)
self.scale_2 = Scale(new_window, from_=0, to=10, orient = HORIZONTAL)
self.scale_2.grid(row=15, column=3)
def shop_scrollbars(self, new_window):
self.scroll_vert = Scrollbar(new_window)
self.scroll_vert.grid(row=19, column=3)
self.listbox_3 = Listbox(new_window, yscrollcommand = self.scroll_vert.set )
for line in range(1, 100):
self.listbox_3.insert(END, "Number " + str(line))
self.listbox_3.grid(row=19 , column=2)
self.scroll_vert.config(command = self.listbox_3.yview)
def shop_textbox(self, new_window):
#make a frame with parent new_window
self.frame_textbox_1 = Frame(new_window)
self.frame_textbox_1.grid(row=1, column=5)
#make the textbox with parent frame
self.textbox_1 = Text(self.frame_textbox_1)
self.textbox_1.config(wrap=NONE, width=15, height=8) #width, height are characters x rows permitted wrap values should be WORD CHAR, or NONE
#make the X scrollbar with parent frame
self.scroll_text_horiz = Scrollbar(self.frame_textbox_1, orient="horizontal")
self.scroll_text_horiz.config(command = self.textbox_1.xview)
#make the Y scrollbar with parent frame
self.scroll_text_vert = Scrollbar(self.frame_textbox_1, orient="vertical")
self.scroll_text_vert.config(command = self.textbox_1.yview)
#pack the scrollbars before the texbox to allow them to fill the frame width and height
self.scroll_text_horiz.pack(side=BOTTOM, fill=X)
self.scroll_text_vert.pack(side=RIGHT, fill=Y)
self.textbox_1.pack(fill="y")
#connect the scrollbars to the textbox
self.textbox_1["xscrollcommand"] = self.scroll_text_horiz.set
self.textbox_1["yscrollcommand"] = self.scroll_text_vert.set
message = "the quick brown fox"
self.textbox_1.insert(1.1, message)
# ----------------------------------------------
def shop_menu(self, new_window):
print("in shop menu1")
menubar = Menu(new_window)
print("in shop_menu2")
file = Menu(menubar, tearoff=0)
file.add_command(label="New")
file.add_command(label="Open")
file.add_command(label="Save")
file.add_command(label="Save as...")
file.add_command(label="Close")
file.add_separator()
file.add_command(label="Exit", command=new_window.quit)
menubar.add_cascade(label="File", menu=file)
edit = Menu(menubar, tearoff=0)
edit.add_command(label="Undo")
edit.add_separator()
edit.add_command(label="Cut")
edit.add_command(label="Copy")
edit.add_command(label="Paste")
edit.add_command(label="Delete")
edit.add_command(label="Select All")
menubar.add_cascade(label="Edit", menu=edit)
help = Menu(menubar, tearoff=0)
help.add_command(label="About")
menubar.add_cascade(label="Help", menu=help)
new_window.config(menu=menubar)
print("in shop menu3")
# --------------------------------------- END FACTORY SHOPS---------------------------------------
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()
I get an error similar to: invalid command name ".91418712.91418792" when I click the Quit button in my program. I have googled this but an unsure how to fix the issue.
The issue apparently is that my thread is trying to update GUI elements that no longer exists which is why I put a sleep in before doing the destroy.
The following is closely related to my issue _tkinter.TclError: invalid command name ".4302957584"
A condensed version of the code:
#!/usr/bin/python
import Adafruit_GPIO as GPIO
import Adafruit_GPIO.FT232H as FT232H
from time import sleep
from threading import Thread
import Tkinter as tk
import tkFont
# Temporarily disable the built-in FTDI serial driver on Mac & Linux platforms.
FT232H.use_FT232H()
# Create an FT232H object that grabs the first available FT232H device found.
device = FT232H.FT232H()
d0 = 0
d1 = 1
device.setup(d0, GPIO.IN)
device.setup(d1, GPIO.IN)
class Application():
def __init__(self):
self.root = tk.Tk()
self.root.title("Show Voltage")
self.root.grid()
self.createWidgets()
self.stop = False
self.watcher = Thread(target = self.watcher, name="watcher")
self.watcher.start()
self.root.mainloop()
def changeBackground(self, cell):
if cell == 0:
self.la_d0.config(bg="green")
elif cell == 1:
self.la_d1.config(bg="green")
def returnBackground(self, cell):
if cell == 0:
self.la_d0.config(bg="black")
elif cell == 1:
self.la_d1.config(bg="black")
def quit(self):
self.stop = True
self.watcher.join()
sleep(0.3)
self.root.destroy()
def watcher(self):
while not self.stop:
self.wa_d0 = device.input(d0)
self.wa_d1 = device.input(d1)
if self.wa_d0 == GPIO.HIGH:
self.changeBackground(0)
else:
self.returnBackground(0)
if self.wa_d1 == GPIO.HIGH:
self.changeBackground(1)
else:
self.returnBackground(1)
sleep(0.0125)
def createWidgets(self):
self.font = tkFont.Font(family='Helvetica', size=50, weight='bold')
self.bt_font = tkFont.Font(family='Helvetica', size=18, weight='bold')
self.fr_d0 = tk.Frame(height=100, width=100)
self.la_d0 = tk.Label(self.fr_d0, text="d0", bg="black", fg="red", font=self.font)
self.fr_d1 = tk.Frame(height=100, width=100)
self.la_d1 = tk.Label(self.fr_d1, text="d1", bg="black", fg="red", font=self.font)
self.fr_quit = tk.Frame(height=40, width=200)
self.bt_quit = tk.Button(self.fr_quit, text="Quit!", bg="white", command=self.quit, font=self.bt_font)
self.fr_d0.grid(row=0, column=0)
self.fr_d0.pack_propagate(False)
self.la_d0.pack(fill="both", expand=1)
self.fr_d1.grid(row=0, column=2)
self.fr_d1.pack_propagate(False)
self.la_d1.pack(fill="both", expand=1)
self.fr_quit.grid(row=1, column=0, columnspan=3)
self.fr_quit.pack_propagate(False)
self.bt_quit.pack(fill="both", expand=1)
app = Application()
not sure exactly what fixed it but this no longer creates the error
#!/usr/bin/python
import Adafruit_GPIO as GPIO
import Adafruit_GPIO.FT232H as FT232H
from time import sleep
from threading import Thread
import Tkinter as tk
import tkFont
# Temporarily disable the built-in FTDI serial driver on Mac & Linux platforms.
FT232H.use_FT232H()
# Create an FT232H object that grabs the first available FT232H device found.
device = FT232H.FT232H()
d0 = 0
d1 = 1
device.setup(d0, GPIO.IN)
device.setup(d1, GPIO.IN)
class Application():
def __init__(self):
self.root = tk.Tk()
self.root.title("Show Voltage")
self.root.grid()
self.createWidgets()
self.stop = False
self.watcherThread = Thread(target = self.watcher, name="watcher")
self.watcherThread.start()
self.root.mainloop()
def changeBackground(self, cell):
if cell == 0:
self.la_d0.config(bg="green")
elif cell == 1:
self.la_d1.config(bg="green")
def returnBackground(self, cell):
if cell == 0:
self.la_d0.config(bg="black")
elif cell == 1:
self.la_d1.config(bg="black")
def destroy(self):
self.root.destroy()
def quit(self):
self.stop = True
self.watcherThread.join()
self.root.after(300, self.destroy)
def watcher(self):
while not self.stop:
self.wa_d0 = device.input(d0)
self.wa_d1 = device.input(d1)
if self.wa_d0 == GPIO.HIGH:
self.changeBackground(0)
else:
self.returnBackground(0)
if self.wa_d1 == GPIO.HIGH:
self.changeBackground(1)
else:
self.returnBackground(1)
sleep(0.0125)
def createWidgets(self):
self.font = tkFont.Font(family='Helvetica', size=50, weight='bold')
self.bt_font = tkFont.Font(family='Helvetica', size=18, weight='bold')
self.fr_d0 = tk.Frame(height=100, width=100)
self.la_d0 = tk.Label(self.fr_d0, text="d0", bg="black", fg="red", font=self.font)
self.fr_d1 = tk.Frame(height=100, width=100)
self.la_d1 = tk.Label(self.fr_d1, text="d1", bg="black", fg="red", font=self.font)
self.fr_quit = tk.Frame(height=40, width=200)
self.bt_quit = tk.Button(self.fr_quit, text="Quit!", bg="white", command=self.quit, font=self.bt_font)
self.fr_d0.grid(row=0, column=0)
self.fr_d0.pack_propagate(False)
self.la_d0.pack(fill="both", expand=1)
self.fr_d1.grid(row=0, column=2)
self.fr_d1.pack_propagate(False)
self.la_d1.pack(fill="both", expand=1)
self.fr_quit.grid(row=1, column=0, columnspan=3)
self.fr_quit.pack_propagate(False)
self.bt_quit.pack(fill="both", expand=1)
app = Application()
thank you all for all the input!! :)