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!! :)
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 a gui with two tabs and would like to switch back and forth between the tabs while keeping the content of the tabs. If I do this all in one function, then it works fine, but since I have more in the individual functions. I have outsourced them. Now I have bound the individual functions to the tabs. But here I have the problem that I overwrite the tabs with every change. But I would like to switch between the tabs without overwriting the entries. I understand why I overwrite the input. After all, I call the individual functions with rountine and overwrite them that way. Unfortunately I do not know how to do it better.
import tkinter as tk
from tkinter import ttk
class display():
def __init__(self):
self.root = tk.Tk()
self.root.geometry('400x300')
self.root.title('Notebook Demo')
def gui(self):
# create a notebook
self.notebook = ttk.Notebook(self.root)
self.notebook.place(rely=0.0, relheight=1, relwidth=1)
# create frames
self.frame1 = ttk.Frame(self.notebook, width=400, height=280)
self.frame2 = ttk.Frame(self.notebook, width=400, height=280)
self.frame1.place(rely=0.0, relheight=1, relwidth=1)
self.frame2.place(rely=0.0, relheight=1, relwidth=1)
# add frames to notebook
self.notebook.add(self.frame1, text='General Information')
self.notebook.add(self.frame2, text='Profile')
self.notebook.bind("<<NotebookTabChanged>>", self.routine)
self.gu1()
self.root.update_idletasks()
self.root.mainloop()
def routine(self,event):
if self.notebook.index("current") == 1:
self.gu1()
else:
self.gu2()
def gu1(self):
button1 = tk.Button(self.frame1, text="test1", command=self.changeText)
button1.grid(column=0, row=0)
self.label1 = tk.Label(self.frame1, text="test1")
self.label1.grid(column=1, row=0)
self.root.mainloop()
def gu2(self):
button2 = tk.Button(self.frame2, text="test2", command=self.changeText2)
button2.grid(column=0, row=0)
self.label2 = tk.Label(self.frame2, text="test2")
self.label2.grid(column=1, row=0)
self.root.mainloop()
def changeText(self):
self.label1["text"] = "change1"
def changeText2(self):
self.label2["text"] = "change2"
if __name__ == "__main__":
display().gui()
All problem is that you use .gu1() and .gu2() in routine().
You should run .gu1() and .gu2() only once - at start in gui().
def gui(self):
# ... code ...
self.notebook.bind("<<NotebookTabChanged>>", self.routine)
self.gu1() # create at start
self.gu2() # create at start
def routine(self,event):
if self.notebook.index("current") == 1:
#self.gu1() # DON'T DO THIS
print('selected tab 1')
else:
#self.gu2() # DON'T DO THIS
print('selected tab 2')
Working code:
tkinter should use only one mainloop()
import tkinter as tk
from tkinter import ttk
class display():
def __init__(self):
self.root = tk.Tk()
self.root.geometry('400x300')
self.root.title('Notebook Demo')
def gui(self):
# create a notebook
self.notebook = ttk.Notebook(self.root)
self.notebook.place(rely=0.0, relheight=1, relwidth=1)
# create frames
self.frame1 = ttk.Frame(self.notebook, width=400, height=280)
self.frame2 = ttk.Frame(self.notebook, width=400, height=280)
self.frame1.place(rely=0.0, relheight=1, relwidth=1)
self.frame2.place(rely=0.0, relheight=1, relwidth=1)
# add frames to notebook
self.notebook.add(self.frame1, text='General Information')
self.notebook.add(self.frame2, text='Profile')
self.notebook.bind("<<NotebookTabChanged>>", self.routine)
self.gu1()
self.gu2()
self.root.update_idletasks()
self.root.mainloop()
def routine(self,event):
if self.notebook.index("current") == 1:
#self.gu1() # DON'T DO THIS
print('selected tab 1')
else:
#self.gu2() # DON'T DO THIS
print('selected tab 2')
def gu1(self):
button1 = tk.Button(self.frame1, text="test1", command=self.changeText)
button1.grid(column=0, row=0)
self.label1 = tk.Label(self.frame1, text="test1")
self.label1.grid(column=1, row=0)
def gu2(self):
button2 = tk.Button(self.frame2, text="test2", command=self.changeText2)
button2.grid(column=0, row=0)
self.label2 = tk.Label(self.frame2, text="test2")
self.label2.grid(column=1, row=0)
def changeText(self):
self.label1["text"] = "change1"
def changeText2(self):
self.label2["text"] = "change2"
if __name__ == "__main__":
display().gui()
EDIT:
If you have to create some widgets in tabs with delays (ie. to wait for user's values in other widgets) then you should use boolean variables (True/False) to run it only once in routine()
def gui(self):
# ... code ...
self.gu1_created = False
self.gu2_created = False
self.notebook.bind("<<NotebookTabChanged>>", self.routine)
def routine(self,event):
if self.notebook.index("current") == 1:
if not self.gu1_created:
self.gu1()
self.gu1_created = True
print('selected tab 1')
else:
if not self.gu2_created:
self.gu2()
self.gu2_created = True
print('selected tab 2')
In the below code snippet (in the convert function) the status label is not getting set to "Processing" before the time_consuming_func is run. Rather it gets updated after the function. I'm using the label to indicate the program is executing the time consuming function so this defeats the purpose of having a status label.
I noticed that if I put a breakpoint just after self.status.set("Processing") the label does get updated.
What can I do to have the status label change correctly i.e. before the time_consuming_func ?
import os
import time
import tkinter as tk
from tkinter import filedialog as fd
from tkinter import ttk
from tkinter.messagebox import showerror
from typing import Any, Tuple
WINDOW_SIZE = "550x300"
FILETYPES = (("All files", "*.*"),)
def time_consuming_func():
time.sleep(10)
print("DONE")
class NotebookTab(ttk.Frame):
def __init__(
self,
master: ttk.Notebook,
io_callbacks: Tuple[Any, Any],
**kwargs: Any,
) -> None:
if kwargs:
super().__init__(master, **kwargs)
else:
super().__init__(master)
self.ipath = tk.StringVar(self)
self.opath = tk.StringVar(self)
self.status = tk.StringVar(self, value="Idle")
assert len(io_callbacks) == 2
self.input_callback: Any = io_callbacks[0]
self.output_callback: Any = io_callbacks[1]
self.create_widgets()
def open_input(self) -> None:
if self.input_callback == fd.askopenfilename:
_input = self.input_callback(filetypes=FILETYPES)
else:
_input = self.input_callback()
if _input:
path = os.path.abspath(_input)
self.ipath.set(path)
def open_output(self) -> None:
if self.output_callback == fd.asksaveasfilename:
_output = self.output_callback(filetypes=FILETYPES)
else:
_output = self.output_callback()
if _output:
path = os.path.abspath(_output)
self.opath.set(path)
def convert(self) -> None:
inpath = self.ipath.get()
outpath = self.opath.get()
self.status.set("Processing")
#import pdb; pdb.set_trace()
try:
time_consuming_func()
# Set status to done and clear boxes
#self.status.set("Idle")
self.ipath.set("")
self.opath.set("")
except Exception:
self.status.set("ERROR")
showerror(
title="Error",
message="An unexpected error occurred."
"Close window or press OK to view traceback",
)
raise
def create_widgets(self) -> None:
statuslbl = tk.Label(self, text="Status:")
statuslbl.place(relx=0.7, rely=0.7, anchor="e")
self.statusval = tk.Label(self, textvariable=self.status)
self.statusval.place(relx=0.85, rely=0.7, anchor="e")
inputpath = tk.Entry(self, textvariable=self.ipath)
inputpath.update()
inputpath.focus_set()
inputpath.place(y=10, x=10, relwidth=0.70, height=20)
outputpath = tk.Entry(self, textvariable=self.opath)
outputpath.update()
outputpath.focus_set()
outputpath.place(y=50, x=10, relwidth=0.70, height=20)
# Buttons
open_input_button = ttk.Button(self, text="Input", command=self.open_input)
open_output_button = ttk.Button(self, text="Output", command=self.open_output)
convert_button = ttk.Button(self, text="Convert", command=self.convert)
open_input_button.pack(anchor="e", padx=20, pady=10)
open_output_button.pack(anchor="e", padx=20, pady=10)
convert_button.place(relx=0.3, rely=0.7, anchor=tk.CENTER)
def main() -> None:
# Root window
root = tk.Tk()
root.title("Converter")
root.resizable(True, True)
root.geometry(WINDOW_SIZE)
tab_parent = ttk.Notebook(root)
file_tab = NotebookTab(
tab_parent,
(fd.askopenfilename, fd.asksaveasfilename),
)
dir_tab = NotebookTab(
tab_parent,
(fd.askdirectory, fd.askdirectory),
)
tab_parent.add(file_tab, text="File")
tab_parent.add(dir_tab, text="Directory")
tab_parent.pack(expand=1, fill="both")
root.mainloop()
if __name__ == "__main__":
main()
Get familiar with the concept of threading. I made changes in line 8 and 65. However, this means that your GUI is reactive after launching the thread, which comes with a couple of risks.
import os
import time
import tkinter as tk
from tkinter import filedialog as fd
from tkinter import ttk
from tkinter.messagebox import showerror
from typing import Any, Tuple
from threading import Thread
WINDOW_SIZE = "550x300"
FILETYPES = (("All files", "*.*"),)
def time_consuming_func():
time.sleep(10)
print("DONE")
class NotebookTab(ttk.Frame):
def __init__(
self,
master: ttk.Notebook,
io_callbacks: Tuple[Any, Any],
**kwargs: Any,
) -> None:
if kwargs:
super().__init__(master, **kwargs)
else:
super().__init__(master)
self.ipath = tk.StringVar(self)
self.opath = tk.StringVar(self)
self.status = tk.StringVar(self, value="Idle")
assert len(io_callbacks) == 2
self.input_callback: Any = io_callbacks[0]
self.output_callback: Any = io_callbacks[1]
self.create_widgets()
def open_input(self) -> None:
if self.input_callback == fd.askopenfilename:
_input = self.input_callback(filetypes=FILETYPES)
else:
_input = self.input_callback()
if _input:
path = os.path.abspath(_input)
self.ipath.set(path)
def open_output(self) -> None:
if self.output_callback == fd.asksaveasfilename:
_output = self.output_callback(filetypes=FILETYPES)
else:
_output = self.output_callback()
if _output:
path = os.path.abspath(_output)
self.opath.set(path)
def convert(self) -> None:
inpath = self.ipath.get()
outpath = self.opath.get()
self.status.set("Processing")
#import pdb; pdb.set_trace()
try:
t = Thread(target=time_consuming_func)
t.start()
# time_consuming_func()
# Set status to done and clear boxes
#self.status.set("Idle")
self.ipath.set("")
self.opath.set("")
except Exception:
self.status.set("ERROR")
showerror(
title="Error",
message="An unexpected error occurred."
"Close window or press OK to view traceback",
)
raise
def create_widgets(self) -> None:
statuslbl = tk.Label(self, text="Status:")
statuslbl.place(relx=0.7, rely=0.7, anchor="e")
self.statusval = tk.Label(self, textvariable=self.status)
self.statusval.place(relx=0.85, rely=0.7, anchor="e")
inputpath = tk.Entry(self, textvariable=self.ipath)
inputpath.update()
inputpath.focus_set()
inputpath.place(y=10, x=10, relwidth=0.70, height=20)
outputpath = tk.Entry(self, textvariable=self.opath)
outputpath.update()
outputpath.focus_set()
outputpath.place(y=50, x=10, relwidth=0.70, height=20)
# Buttons
open_input_button = ttk.Button(self, text="Input", command=self.open_input)
open_output_button = ttk.Button(self, text="Output", command=self.open_output)
convert_button = ttk.Button(self, text="Convert", command=self.convert)
open_input_button.pack(anchor="e", padx=20, pady=10)
open_output_button.pack(anchor="e", padx=20, pady=10)
convert_button.place(relx=0.3, rely=0.7, anchor=tk.CENTER)
def main() -> None:
# Root window
root = tk.Tk()
root.title("Converter")
root.resizable(True, True)
root.geometry(WINDOW_SIZE)
tab_parent = ttk.Notebook(root)
file_tab = NotebookTab(
tab_parent,
(fd.askopenfilename, fd.asksaveasfilename),
)
dir_tab = NotebookTab(
tab_parent,
(fd.askdirectory, fd.askdirectory),
)
tab_parent.add(file_tab, text="File")
tab_parent.add(dir_tab, text="Directory")
tab_parent.pack(expand=1, fill="both")
root.mainloop()
if __name__ == "__main__":
main()
Edit
if you want parts of your code to be executed before launching the thread, and other parts when its done, i suggest putting the parts (e.g. clearing the entry) at the end in your time_consuming_func(). You have other options, such as getting returns from the thread and handle those, which is a bit more complicated.
You can just add self.update() before try and except in convert function.
The problem is when self.status.set() is executed, it does its job perfectly, but you have used time.sleep(10) which cause delay in whole python interpreter, which could be solved by using .after method.
self.update() works because it updates the window before executing time.sleep(), so updates the whole window.
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()
I set up a weather application. I create two Threads, first Thread1 grab weather condition and second Thread2 set background Image but when I click button tkinter widget goes disappear, after hover it's appears. I think there is some issue with thread. can I've understand?
My code:
import requests
from tkinter import *
from bs4 import BeautifulSoup
from threading import Thread
from io import BytesIO
from PIL import ImageTk, Image
class gui(Tk):
def __init__(self):
super().__init__()
self.call_weather_True = False
self.icon_link = ''
self.text = ''
self.weather = ''
self.icon_set = ''
self.back_icon = ''
# title of app
self.title('AccWeather')
self.geometry('700x500')
# API
self.api = 'vvfhMQuqmjOgcq1ctGqgmH55bqMPVH3c'
# main canvas with image
self.main = Label(self)
self.main.place(relx=0, rely=0, relwidth=1, relheight=1)
# button and input
self.button_frame = Frame(self.main)
self.button_frame.place(relx=.1, rely=.1, relwidth=.8)
self.city = Entry(self.button_frame, bd=0, font=3, fg='black', width=50)
self.city.pack(side=LEFT)
Button(self.button_frame, text='Get Weather', bg='DodgerBlue', font=5, bd=1, command=self.get_weather_call).pack(side=RIGHT)
self.put_out = Label(self.main, font=20, anchor='nw', bg='white', justify='left', bd=5) # border = bd
self.put_out.place(relx=.1, rely=.4, relwidth=.5, relheight=.5)
def get_weather_call(self):
self.call_weather_True = True
city = self.city.get()
url = 'https://images.unsplash.com/photo-1451154488477-d20dee2a4e46?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=753&q=80'
thread_background_img = Thread(target=self.background_icon, args=(url,))
thread2 = Thread(target=self.get_weather_call, args=[city])
thread2.start()
thread_background_img.start()
def get_weather(self, city):
# autocomplete location
try:
auto_url = f"http://dataservice.accuweather.com/locations/v1/cities/autocomplete?apikey={self.api}&q=" + city
data = requests.get(auto_url).json()
check = ''
for i in data:
for a in i:
if a=='ServiceUnavailable':
check = True
if check:
self.put_out['text'] = 'Error: '+data['Message']
else:
try:
key = data[0]['Key']
city_name = ', '.join([data[0]['LocalizedName'], data[0]['Country']['LocalizedName']])
api = requests.get(f"http://dataservice.accuweather.com/currentconditions/v1/{key}?apikey={self.api}").json()
self.icon_link = api[0]['Link']
temp = api[0]['Temperature']['Metric']['Value']
self.text = api[0]['WeatherText']
self.weather = f'City: {city_name}\nTemperature (c): {int(temp)}\nCondition: {self.text}'
self.put_out['text'] = self.weather
except Exception as e:
self.put_out['text'] = e
except Exception as e:
print(e)
def background_icon(self, url):
img_data = requests.get(url).content
self.back_icon = ImageTk.PhotoImage(Image.open(BytesIO(img_data)))
self.main['image'] = self.back_icon
if __name__ == '__main__':
start = gui()
start.mainloop()
I spend a day to figure out this but I'd found.why tkinter button,entry disappear?
I've refactoring your code, why you use thread?
Anyway, if I've understand what you want to do below a working script, the main problem is that Api return an Authorization Failed.
#!/usr/bin/python3
import tkinter as tk
from tkinter import ttk
from tkinter import messagebox
import requests
import json
from PIL import ImageTk, Image
from io import BytesIO
class App(tk.Tk):
"""Docstring about this class"""
def __init__(self):
super().__init__()
self.protocol("WM_DELETE_WINDOW", self.on_exit)
self.geometry('800x400')
s = "{0}".format('AccWeather')
self.title(s)
self.text = tk.StringVar()
self.code = tk.StringVar()
self.message = tk.StringVar()
self.reference = tk.StringVar()
self.init_ui()
def init_ui(self):
img = self.set_backgroundn()
f = ttk.Label(image=img)
f.image = img
ttk.Label(f, text="Search a city!").pack()
self.txText = ttk.Entry(f, textvariable=self.text).pack(side=tk.TOP, fill=tk.X, expand=0)
ttk.Label(f, textvariable=self.code).pack(fill=tk.X,padx=2,pady=2)
ttk.Label(f, textvariable=self.message).pack(fill=tk.X,padx=2,pady=2)
ttk.Label(f, textvariable=self.reference).pack(fill=tk.X,padx=2,pady=2)
f.pack(fill=tk.BOTH, expand=1)
w = tk.Frame()
tk.Button(w, text="Get Weather", width=8, command=self.get_weather).pack()
tk.Button(w, text="Close", width=8, command=self.on_exit).pack()
f.pack(side=tk.LEFT, fill=tk.BOTH, expand=1)
w.pack(side=tk.RIGHT, fill=tk.BOTH, padx=5, pady=5, expand=0)
self.set_backgroundn()
def get_weather(self,):
api = 'vvfhMQuqmjOgcq1ctGqgmH55bqMPVH3c'
auto_url = "http://dataservice.accuweather.com/locations/v1/cities/autocomplete?apikey={0}&q={1}".format(api,self.text.get())
response = requests.get(auto_url)
d = response.json()
self.code.set(d['Code'])
self.message.set(d['Message'])
self.reference.set(d['Reference'])
def set_backgroundn(self,):
url = 'https://images.unsplash.com/photo-1451154488477-d20dee2a4e46?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=753&q=80'
img = requests.get(url).content
return ImageTk.PhotoImage(Image.open(BytesIO(img)))
def on_exit(self):
"""Close all"""
if messagebox.askokcancel(self.title(), "Do you want to quit?", parent=self):
self.destroy()
if __name__ == '__main__':
app = App()
app.mainloop()