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.
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 am new to python, the above figure is two UI, I was trying to insert the two tab from the right side UI into the left side UI. But I had met some error and no idea how to solve.
Now below is the original coding of the left side UI
import tkinter as tkk
from tkinter import *
from tkinter import messagebox
import os.path
import hashlib
import sys
import time
import getpass
from tkinter import filedialog
import platform
import getpass
import os,sys
import tkinter.font as font
from tkinter import ttk
from tkinter import StringVar
import Consts
import shutil
class Page(tkk.Frame):
def __init__(self, *args, **kwargs):
tkk.Frame.__init__(self, *args, **kwargs)
def show(self):
self.lift()
class Page1(Page):
def __init__(self, *args, **kwargs):
Page.__init__(self, *args, **kwargs,bg='white')
my_system=platform.uname()
#computer information
comsys=my_system.system
comnode=my_system.node
comrel=my_system.release
comver=my_system.version
commac=my_system.machine
compro=my_system.processor
comuser=getpass.getuser()
label1 = tkk.Label(self, text="System: "+comsys,bg='white')
label1.grid(row=1,column=1,pady=10,sticky='w')
label2 = tkk.Label(self, text="Computer Name: "+comnode,bg='white')
label2.grid(row=2,column=1,pady=10,sticky='w')
label3 = tkk.Label(self, text="Release: "+comrel,bg='white')
label3.grid(row=3,column=1,pady=10,sticky='w')
label4 = tkk.Label(self, text="Version: "+comver,bg='white')
label4.grid(row=4,column=1,pady=10,sticky='w')
label5 = tkk.Label(self, text="Machine: "+commac,bg='white')
label5.grid(row=5,column=1, pady=10,sticky='w')
label6 = tkk.Label(self, text="Processor: "+compro,bg='white')
label6.grid(row=6,column=1, pady=10,sticky='w')
label7 = tkk.Label(self, text="Username: "+comuser,bg='white')
label7.grid(row=7,column=1,pady=10,sticky='w')
#computer usage hold first, no idea how to do
class Page2(Page):
def __init__(self, *args, **kwargs):
Page.__init__(self, *args, **kwargs,bg='white')
tabControl=ttk.Notebook(self)
qsFrame = ttk.Frame(tabControl)
fsFrame = ttk.Frame(tabControl)
csFrame = ttk.Frame(tabControl)
#tab
tabControl.add(qsFrame, text='Quick Scan')
tabControl.add(fsFrame, text='Full Scan')
tabControl.add(csFrame, text='Custom Scan')
tabControl.pack(expand=1,fill="both")
class Page3(Page):
def __init__(self, *args, **kwargs):
Page.__init__(self, *args, **kwargs,bg='white')
label = tkk.Label(self, text="This is page 3")
label.grid(row=2,column=1)
def mytools():
total, used, free = shutil.disk_usage("/")
print("Total:%d GB" %(total // (2**30)))
print("Used:%d GB" %(used // (2**30)))
print("Free:%d GB" %(free // (2**30)))
if free <= total/2:
clean = os.popen('Cleanmgr.exe/ sagerun:1').read()
#print(clean)
def btn1():
if __name__ =="__main__":
mytools()
button1=ttk.Button(self,text="Clean Up",command=btn1)
button1.grid(row=3,column=2)
class Page4(Page):
def __init__(self, *args, **kwargs):
Page.__init__(self, *args, **kwargs,bg='white')
label = tkk.Label(self, text="This is page 4")
label.grid(row=2,column=1)
class MainView(tkk.Frame):
def __init__(self, *args, **kwargs):
tkk.Frame.__init__(self, *args, **kwargs)
p1 = Page1(self)
p2 = Page2(self)
p3 = Page3(self)
p4 = Page4(self)
buttonframe = tkk.Frame(self)
container = tkk.Frame(self,bg='white')
buttonframe.pack(side="left", fill="x", expand=False)
container.pack(side="left", fill="both", expand=True)
buttonframe.grid_rowconfigure(0,weight=1)
buttonframe.grid_columnconfigure(0,weight=1)
container.grid_rowconfigure(0,weight=1)
container.grid_columnconfigure(0,weight=1)
p1.place(in_=container, x=0, y=0, relwidth=1, relheight=1)
p2.place(in_=container, x=0, y=0, relwidth=1, relheight=1)
p3.place(in_=container, x=0, y=0, relwidth=1, relheight=1)
p4.place(in_=container, x=0, y=0, relwidth=1, relheight=1)
stats_btn = tkk.PhotoImage(file='C:/FYP/SecuCOM2022/icon&pic/stats.png')
scanner_btn = tkk.PhotoImage(file='C:\FYP\SecuCOM2022\icon&pic\scanner.png')
speedup_btn = tkk.PhotoImage(file='C:\FYP\SecuCOM2022\icon&pic\speedup.png')
settings_btn = tkk.PhotoImage(file='C:\FYP\SecuCOM2022\icon&pic\settings.png')
#logo
logo=tkk.PhotoImage(file="C:\FYP\SecuCOM2022\icon&pic\g (1).png")
label=tkk.Label(buttonframe,image=logo)
label.grid(row=0,column=0, padx=10,pady=10)
logo.image = logo
b1 = tkk.Button(buttonframe, image=stats_btn, command=p1.show, borderwidth=0)
b2 = tkk.Button(buttonframe, image=scanner_btn, command=p2.show, borderwidth=0)
b3 = tkk.Button(buttonframe, image=speedup_btn, command=p3.show, borderwidth=0)
b4 = tkk.Button(buttonframe, image=settings_btn, command=p4.show, borderwidth=0)
b1.image = stats_btn
b2.image = scanner_btn
b3.image = speedup_btn
b4.image = settings_btn
b1.grid(row=1,column=0,padx=10,pady=10)
b2.grid(row=2,column=0,padx=10,pady=10)
b3.grid(row=3,column=0,padx=10,pady=10)
b4.grid(row=4,column=0,padx=10,pady=10)
if __name__ == "__main__":
root= Tk()
main = MainView(root)
main.pack(side="top", fill="both", expand=True)
main.grid_rowconfigure(0,weight=1)
main.grid_columnconfigure(0,weight=1)
root.title("SecuCOM2022")
root.geometry("600x300")
root.maxsize(600,375)
root.minsize(600,375)
root.iconbitmap('C:\FYP\SecuCOM2022\icon&pic\g.png')
root.mainloop()
root.mainloop()
#GUI end`
Next below here is the right side UI coding, there are multiple files for it. I just show files related with the UI only.
Consts.py
ENTRY_WIDTH = 50
FileReportTab.py
from tkinter import filedialog
from tkinter import messagebox
from tkinter import ttk
from tkinter import StringVar
import time
import os.path
import sys
from VTPackage import Consts
class FileReportTab:
def __init__(self, root, frame, vtClient):
self.root = root
self.frame = frame
self.vtClient = vtClient
self.mainVTURLframe = ttk.LabelFrame(frame, text=' File report')
self.mainVTURLframe.grid(column=0, row=1, padx=8, pady=4)
ttk.Label(self.mainVTURLframe, text="Progress:").grid(column=0, row=1, sticky='W') # <== right-align
self.progressBar = ttk.Progressbar(self.mainVTURLframe, orient='horizontal', length=300, mode='determinate')
self.progressBar.grid(column=1, row=1)
ttk.Label(self.mainVTURLframe, text="File path:").grid(column=0, row=2, sticky='W') # <== right-align
self.filePath = StringVar()
filePathEntry = ttk.Entry(self.mainVTURLframe, width=Consts.ENTRY_WIDTH, textvariable=self.filePath, state='readonly')
filePathEntry.grid(column=1, row=2, sticky='W')
ttk.Label(self.mainVTURLframe, text="Status:").grid(column=0, row=3, sticky='W') # <== right-align
self.status = StringVar()
statusEntry = ttk.Entry(self.mainVTURLframe, width=Consts.ENTRY_WIDTH, textvariable=self.status, state='readonly')
statusEntry.grid(column=1, row=3, sticky='W')
ttk.Label(self.mainVTURLframe, text="Positive Indications:").grid(column=0, row=4, sticky='W') # <== right-align
self.positiveIndications = StringVar()
positiveIndicationsEntry = ttk.Entry(self.mainVTURLframe, width=Consts.ENTRY_WIDTH, textvariable=self.positiveIndications, state='readonly')
positiveIndicationsEntry.grid(column=1, row=4, sticky='W')
ttk.Label(self.mainVTURLframe, text="SHA1:").grid(column=0, row=5, sticky='W') # <== right-align
self.sha1 = StringVar()
sha1Entry = ttk.Entry(self.mainVTURLframe, width=Consts.ENTRY_WIDTH, textvariable=self.sha1, state='readonly')
sha1Entry.grid(column=1, row=5, sticky='W')
ttk.Label(self.mainVTURLframe, text="SHA256:").grid(column=0, row=6, sticky='W') # <== right-align
self.sha256 = StringVar()
sha256Entry = ttk.Entry(self.mainVTURLframe, width=Consts.ENTRY_WIDTH, textvariable=self.sha256, state='readonly')
sha256Entry.grid(column=1, row=6, sticky='W')
chooseFileButton = ttk.Button(self.mainVTURLframe, text="Choose File", width=40, command=self._scanFile).grid(column=1, row=0)
self.scanCheckingTimeInterval = 25000 # This is the amount of time we are going to wait before asking VT again if it already processed our scan request
for child in self.mainVTURLframe.winfo_children():
child.grid_configure(padx=4, pady=2)
def showResults(self, results):
try:
#self.file_Path = self.filePath
self.sha1.set(results["sha1"])
self.sha256.set(results["sha256"])
self.positiveIndications.set(results["positives"])
if results["positives"] == 0:
messagebox.showwarning("Analysis Info","File is Safe.\nOur Scanners found nothing Malicious")
elif results["positives"] <= 5:
messagebox.showwarning("Analysis Alert", "Given File may be Malicious")
elif results["positives"] >= 5:
messagebox.showwarning("Analysis Alert", f"Given File is Malicious.\nAdvice you remove the file from your System!")
res = messagebox.askyesno("Analysis Alert","The given file is highly Malicious.\nDo you want to Delete it permanently?")
if res == 1:
print("Attemting to delete file...")
time.sleep(1)
os.remove(self.filePath1)
#if os.PathLike(_scanFile.filePath):
# os.remove(self.filePath)
else:
print("This file cannot be deleted. Please do not use the fie. It's Malicious")
except Exception as e:
messagebox.showerror('Error', e)
def checkStatus(self):
try:
self.scanResult = self.vtClient.get_file_report(self.scanID)
print(self.scanResult)
if self.scanResult["response_code"] == -2: # By reading the next line, you can understand what is the meaning of the -2 response ode
self.status.set("Scanning...")
self.progressBar['value'] = self.progressBar['value'] + 5
self.root.update_idletasks()
self.mainVTURLframe.after(self.scanCheckingTimeInterval, self.checkStatus)
else:
self.hasScanFinished = True
self.showResults(self.scanResult)
self.status.set("Finished!")
self.progressBar['value'] = 100
except Exception as e:
if "To much API requests" in str(e):
pass
def _scanFile(self):
try:
self.progressBar['value'] = 0
self.filePath1 = filedialog.askopenfilename(initialdir="/", title="Select file for VT", filetypes=(("EXE files", "*.exe"), ("all files", "*.*")))
if (self.filePath): # Only if the user chose a file, we will want to continue the process
self.filePath.set(self.filePath1)
self.status.set("Sending file...")
self.progressBar['value'] = 10
self.root.update_idletasks()
self.scanID = self.vtClient.scan_file(self.filePath1)
self.hasScanFinished = False
if not self.hasScanFinished:
self.scanResult = self.vtClient.get_file_report(self.scanID)
print(self.scanResult)
self.checkStatus()
# We could have been using time.sleep() or time.wait(), but then our UI would get stuck.
# by using after, we are initiating a callback in which does not blocks our event loop
except Exception as e:
messagebox.showerror('Error', e)
URLreportTab.py
from tkinter import ttk
from tkinter import StringVar
from VTPackage import Consts
class URLreportTab:
def __init__(self, root, frame, vtClient):
self.root = root
self.frame = frame
self.mainVTURLframe = ttk.LabelFrame(frame, text=' URL report tab!')
# using the tkinter grid layout manager
self.mainVTURLframe.grid(column=0, row=0, padx=8, pady=4)
ttk.Label(self.mainVTURLframe, text="URL:").grid(column=0, row=0, sticky='W') # What does sticky does? Sticky sayes where to stick the label to : N,S,E,W
urlEntry = ttk.Entry(self.mainVTURLframe, width=Consts.ENTRY_WIDTH)
urlEntry.grid(column=1, row=0, sticky='E')
ttk.Label(self.mainVTURLframe, text="Positive Indications:").grid(column=0, row=1, sticky='W') # <== right-align
Positive = StringVar()
PositiveEntry = ttk.Entry(self.mainVTURLframe, width=Consts.ENTRY_WIDTH, textvariable=Positive, state='readonly')
PositiveEntry.grid(column=1, row=1, sticky='W')
ttk.Label(self.mainVTURLframe, text="Detections:").grid(column=0, row=2, sticky='W') # <== right-align
detections = StringVar()
detectionsEntry = ttk.Entry(self.mainVTURLframe, width=Consts.ENTRY_WIDTH, textvariable=detections, state='readonly')
detectionsEntry.grid(column=1, row=2, sticky='W')
self.notificationFrame = ttk.LabelFrame(self.frame, text=' Notifications', width=40)
# using the tkinter grid layout manager
self.notificationFrame.grid(column=0, row=1, padx=8, pady=10, sticky='W')
ttk.Label(self.notificationFrame, text="Errors:").grid(column=0, row=0, sticky='W') # <== increment row for each
Error = StringVar()
ErrorEntry = ttk.Entry(self.notificationFrame, width=Consts.ENTRY_WIDTH, textvariable=Error, state='readonly')
ErrorEntry.grid(column=1, row=0, sticky='W')
def _cleanErrorMessage(): # We could have been doing this without a function, but it is more neat that way
Error.set("")
def _getReport():
# the _ notation before a function means that this function is internal to the class only. As python cannot really prevent you from using it outside the class (as C# for example) the notation is being used to warn other developers not to call this function outside the class
try:
_cleanErrorMessage() # Starting with cleaning the error message bar
if not urlEntry.get():
print('Please enter a URL')
Error.set("Please enter a URL!")
return
urlToCheck = urlEntry.get()
response = vtClient.get_url_report(urlToCheck)
print(response)
Positive.set(response["positives"])
scans = response["scans"]
findings = set()
for key, value in scans.items():
if value["detected"]:
findings.add(value["result"])
detections.set(",".join([str(finding) for finding in findings]))
except Exception as e:
print(e)
Error.set(e)
checkURLinVTButton = ttk.Button(self.mainVTURLframe, text='Check Now!', command=_getReport).grid(column=2, row=0)
# Instead of setting padding for each UI element, we can just iterate through the children of the main UI object.
for child in self.mainVTURLframe.winfo_children():
child.grid_configure(padx=4, pady=2)
for child in self.notificationFrame.winfo_children():
child.grid_configure(padx=4, pady=2)
VTApp.py
import tkinter as tk
import configparser
from tkinter import Menu
from tkinter import ttk
from tkinter import messagebox
from VTPackage import URLreportTab
from VTPackage import FileReportTab
from VTPackage import VTClient
config = configparser.ConfigParser()
config.read('config.ini')
class VTApp:
def __init__(self):
# Loading the config file
self.config = configparser.ConfigParser()
self.config.read('config.ini')
self.virusTotalAPIkey = config['VirusTotal']['apiKey']
self.vtClient = VTClient.VTClient(self.virusTotalAPIkey)
self.root = tk.Tk()
self.root.title("Virus Total UI")
self.menuBar = Menu()
self.root.config(menu=self.menuBar)
self.fileMenu = Menu(self.menuBar, tearoff=0)
self.fileMenu.add_command(label="New")
self.fileMenu.add_separator()
self.menuBar.add_cascade(label="File", menu=self.fileMenu)
if not self.vtClient.is_API_key_valid():
messagebox.showerror('Error', "API key is not valid! Check your config file")
def _quit():
self.root.quit() # The app will exist when this function is called
self.root.destroy()
exit()
self.fileMenu.add_command(label="Exit", command=_quit) # command callback
self.tabControl = ttk.Notebook(self.root) # Create Tab Control
self.urlFrame = ttk.Frame(self.tabControl)
self.urlTab = URLreportTab.URLreportTab(self.root, self.urlFrame, self.vtClient)
self.tabControl.add(self.urlFrame, text='URL')
self.fileFrame = ttk.Frame(self.tabControl)
self.fileTab = FileReportTab.FileReportTab(self.tabControl, self.fileFrame, self.vtClient)
self.tabControl.add(self.fileFrame, text='File')
self.tabControl.pack(expand=1, fill="both") # Pack to make visible
def start(self):
self.root.mainloop()
Main.py
from VTPackage import VTApp
vtApp = VTApp.VTApp()
vtApp.start()
This is the original code, Sorry for the spacing error, I copy&paste from vsc and it seem like the got some spacing error after Class. So basically this is the original code and I try like import VTApp and code inside class Page2 like
vtApp = VTApp.VTApp()
vtApp.start()
and change some coding in the VTApp.py but it doesn't work.... Does anyone know how to make the script works? I been trying and trying for a week and still couldn't get the solution.
You cannot move a widget from one window to another in tkinter. You will have to recreate the tab in the other window.
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()
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!! :)
In the following example I'm unable to write to the other tabs aside from the open-by-default Log tab:
#!/usr/bin/python
import os, sys
import threading
import time
from itertools import count
import tkinter
from tkinter import *
from tkinter import tix
import tkinter.tix
from tkinter.constants import *
import traceback, tkinter.messagebox
from tkinter import ttk
TCL_DONT_WAIT = 1<<1
TCL_WINDOW_EVENTS = 1<<2
TCL_FILE_EVENTS = 1<<3
TCL_TIMER_EVENTS = 1<<4
TCL_IDLE_EVENTS = 1<<5
TCL_ALL_EVENTS = 0
class GUI(threading.Thread):
def __init__(self):
""" thread init
defines some vars and starts stuff when the class is called (gui=GUI())
"""
self.root=tkinter.tix.Tk()
z = self.root.winfo_toplevel()
z.wm_title('minimal example')
if z.winfo_screenwidth() <= 800:
z.geometry('790x590+10+10')
else:
z.geometry('890x640+10+10')
frame1 = self.MkMainNotebook()
frame2 = self.MkMainStatus()
frame1.pack(side=TOP, expand=1, fill=BOTH, padx=4, pady=4)
frame2.pack(side=BOTTOM, fill=X)
z.wm_protocol("WM_DELETE_WINDOW", lambda self=self: self.stop())
threading.Thread.__init__(self)
def run(self):
""" thread start
kick starts the main loop when the thread start()
"""
self.root.mainloop()
def stop(self):
""" escape plan
Exits gui thread
"""
self._stop()
raise SystemExit
def MkMainStatus(self):
""" status bar
"""
top = self.root
w = tkinter.tix.Frame(top, relief=tkinter.tix.RAISED, bd=1)
self.exitbutton = tkinter.Button(w, text='Exit GUI Thread', width=20, command=lambda self=self: self.stop())
self.exitbutton.grid(row=0, column=0, sticky=E, padx=3, pady=3)
return w
def MkMainNotebook(self):
""" the tabs frame
defines the tabs
"""
top = self.root
w = tkinter.tix.NoteBook(top, ipadx=5, ipady=5, options="""
tagPadX 6
tagPadY 4
borderWidth 2
""")
top['bg'] = w['bg']
w.add('log', label='Log', underline=0,
createcmd=lambda w=w, name='log': self.MkLog(w, name))
w.add('pro', label='Progress', underline=0,
createcmd=lambda w=w, name='pro': self.MkProgress(w, name))
w.add('set', label='Settings', underline=0,
createcmd=lambda w=w, name='set': self.MkSettings(w, name))
return w
def MkSettings(self, nb, name):
""" TODO: settings tab
"""
w = nb.page(name)
options="label.width %d label.anchor %s entry.width %d" % (10, tkinter.tix.E, 13)
settings_scr_win = tix.ScrolledWindow(w, width=400, height=400)
settings_scr_win.pack(side=tkinter.tix.TOP, padx=2, pady=2, fill='both', expand=1)
self.SettingsFrame = settings_scr_win.window
def MkProgress(self, nb, name):
""" TODO: progress tab
"""
w = nb.page(name)
options = "label.padX 4"
progress_scr_win = tix.ScrolledWindow(w, width=400, height=400)
progress_scr_win.pack(side=tkinter.tix.TOP, padx=2, pady=2, fill='both', expand=1)
self.ProgressFrame = progress_scr_win.window
def MkLog(self, nb, name):
""" log
"""
w = nb.page(name)
options = "label.padX 4"
log_scr_win = tix.ScrolledWindow(w, width=400, height=400)
log_scr_win.pack(side=tkinter.tix.TOP, padx=2, pady=2, fill='both', expand=1)
self.LogFrame = log_scr_win.window
def main(argv):
""" main function
Keyword arguments:
args[0] --
args[1] --
Returns:
None
"""
#GUI
gui=GUI()
gui.start()
time.sleep(1) # give it a sec to draw the gui...
tix.Label(gui.LogFrame, text=("log")).pack()
tix.Label(gui.SettingsFrame, text=("settings")).pack()
tix.Label(gui.ProgressFrame, text=("progress")).pack()
return None
if __name__ == "__main__":
sys.exit(main(sys.argv))
The console traceback:
Traceback (most recent call last):
File "C:\minimal example.py", line 132, in <module>
sys.exit(main(sys.argv))
File "C:\minimal example.py", line 126, in main
tix.Label(gui.SettingsFrame, text=("settings")).pack()
AttributeError: 'GUI' object has no attribute 'SettingsFrame'
This happens whenever I try to create widgets in gui.SettingsFrame or gui.ProgressFrame.
If I increase the time.sleep(1) in main() and click through the tabs before the tix.Label part starts, the code works since now the tabs command were invoked.
So, how do I declare the gui.SettingsFrame and gui.ProgressFrame in advance ? can I pass through the tabs in the code before I reach the tix.Label in main() ?
Note: I need it threaded, it's part of a larger program that does a dozen different things and is both multi threaded and has several processes.
Thanks
Edit 1:
I could add methods to the class instead of referring to the frames:
def print_log(self, text):
""" log printer
"""
tix.Label(self.LogFrame, text=(text)).pack()
def print_progress(self, text):
""" log printer
"""
tix.Label(self.ProgressFrame, text=(text)).pack()
def print_settings(self, text):
""" log printer
"""
tix.Label(self.SettingsFrame, text=(text)).pack()
and call them in main:
#tix.Label(gui.LogFrame, text=("log")).pack()
gui.print_log("log")
#tix.Label(gui.SettingsFrame, text=("settings")).pack()
gui.print_settings("settings")
#tix.Label(gui.ProgressFrame, text=("progress")).pack()
gui.print_progress("progress")
but the results are the same:
Traceback (most recent call last):
File "C:\minimal example.py", line 150, in <module>
sys.exit(main(sys.argv))
File "C:\minimal example.py", line 143, in main
gui.print_settings("settings")
File "C:\minimal example.py", line 124, in print_settings
tix.Label(self.SettingsFrame, text=(text)).pack()
AttributeError: 'GUI' object has no attribute 'SettingsFrame'
Edit 2: a very nice quick and easy fix by Bryan Oakley :
#!/usr/bin/python
import os, sys
import threading
import time
from itertools import count
import tkinter
from tkinter import *
from tkinter import tix
import tkinter.tix
from tkinter.constants import *
import traceback, tkinter.messagebox
from tkinter import ttk
TCL_DONT_WAIT = 1<<1
TCL_WINDOW_EVENTS = 1<<2
TCL_FILE_EVENTS = 1<<3
TCL_TIMER_EVENTS = 1<<4
TCL_IDLE_EVENTS = 1<<5
TCL_ALL_EVENTS = 0
class GUI(threading.Thread):
def __init__(self):
""" thread init
defines some vars and starts stuff when the class is called (gui=GUI())
"""
self.root=tkinter.tix.Tk()
z = self.root.winfo_toplevel()
z.wm_title('minimal example')
if z.winfo_screenwidth() <= 800:
z.geometry('790x590+10+10')
else:
z.geometry('890x640+10+10')
frame1 = self.MkMainNotebook()
frame2 = self.MkMainStatus()
frame1.pack(side=TOP, expand=1, fill=BOTH, padx=4, pady=4)
frame2.pack(side=BOTTOM, fill=X)
z.wm_protocol("WM_DELETE_WINDOW", lambda self=self: self.stop())
threading.Thread.__init__(self)
def run(self):
""" thread start
kick starts the main loop when the thread start()
"""
self.root.mainloop()
def stop(self):
""" escape plan
Exits gui thread
"""
self._stop()
raise SystemExit
def MkMainStatus(self):
""" status bar
"""
top = self.root
w = tkinter.tix.Frame(top, relief=tkinter.tix.RAISED, bd=1)
self.exitbutton = tkinter.Button(w, text='Exit GUI Thread', width=20, command=lambda self=self: self.stop())
self.exitbutton.grid(row=0, column=0, sticky=E, padx=3, pady=3)
return w
def MkMainNotebook(self):
""" the tabs frame
defines the tabs
"""
top = self.root
w = tkinter.tix.NoteBook(top, ipadx=5, ipady=5, options="""
tagPadX 6
tagPadY 4
borderWidth 2
""")
top['bg'] = w['bg']
w.add('log', label='Log', underline=0)
#createcmd=lambda w=w, name='log': self.MkLog(w, name))
w.add('pro', label='Progress', underline=0)
#createcmd=lambda w=w, name='pro': self.MkProgress(w, name))
w.add('set', label='Settings', underline=0)
#createcmd=lambda w=w, name='set': self.MkSettings(w, name))
#log_it = w.subwidget('log')
#pro_it = w.subwidget('pro')
#set_it = w.subwidget('set')
self.MkLog(w, 'log')
self.MkProgress(w, 'pro')
self.MkSettings(w, 'set')
return w
def MkSettings(self, nb, name):
""" TODO: settings tab
"""
w = nb.page(name)
options="label.width %d label.anchor %s entry.width %d" % (10, tkinter.tix.E, 13)
settings_scr_win = tix.ScrolledWindow(w, width=400, height=400)
settings_scr_win.pack(side=tkinter.tix.TOP, padx=2, pady=2, fill='both', expand=1)
self.SettingsFrame = settings_scr_win.window
def MkProgress(self, nb, name):
""" TODO: progress tab
"""
w = nb.page(name)
options = "label.padX 4"
progress_scr_win = tix.ScrolledWindow(w, width=400, height=400)
progress_scr_win.pack(side=tkinter.tix.TOP, padx=2, pady=2, fill='both', expand=1)
self.ProgressFrame = progress_scr_win.window
def MkLog(self, nb, name):
""" log
"""
w = nb.page(name)
options = "label.padX 4"
log_scr_win = tix.ScrolledWindow(w, width=400, height=400)
log_scr_win.pack(side=tkinter.tix.TOP, padx=2, pady=2, fill='both', expand=1)
self.LogFrame = log_scr_win.window
def print_log(self, text):
""" log printer
"""
tix.Label(self.LogFrame, text=(text)).pack()
def print_progress(self, text):
""" log printer
"""
tix.Label(self.ProgressFrame, text=(text)).pack()
def print_settings(self, text):
""" log printer
"""
tix.Label(self.SettingsFrame, text=(text)).pack()
def main(argv):
""" main function
Keyword arguments:
args[0] --
args[1] --
Returns:
None
"""
#GUI
gui=GUI()
gui.start()
time.sleep(1) # give it a sec to draw the gui...
tix.Label(gui.LogFrame, text=("log")).pack()
#gui.print_log("log")
tix.Label(gui.SettingsFrame, text=("settings")).pack()
#gui.print_settings("settings")
tix.Label(gui.ProgressFrame, text=("progress")).pack()
#gui.print_progress("progress")
return None
if __name__ == "__main__":
sys.exit(main(sys.argv))
Thanks !
Edit 3: The following is a thread safe implementation. I added a bonus timer:
#!/usr/bin/python
import os, sys
import threading
import queue
from queue import Empty
import time
from itertools import count
import tkinter
from tkinter import *
from tkinter import tix
import tkinter.tix
from tkinter.constants import *
import traceback, tkinter.messagebox
from tkinter import ttk
TCL_DONT_WAIT = 1<<1
TCL_WINDOW_EVENTS = 1<<2
TCL_FILE_EVENTS = 1<<3
TCL_TIMER_EVENTS = 1<<4
TCL_IDLE_EVENTS = 1<<5
TCL_ALL_EVENTS = 0
class GUI(threading.Thread):
def __init__(self):
""" thread init
defines some vars and starts stuff when the class is called (gui=GUI())
"""
self.root=tkinter.tix.Tk()
z = self.root.winfo_toplevel()
z.wm_title('minimal example')
if z.winfo_screenwidth() <= 800:
z.geometry('790x590+10+10')
else:
z.geometry('890x640+10+10')
frame1 = self.MkMainNotebook()
frame2 = self.MkMainStatus()
frame1.pack(side=TOP, expand=1, fill=BOTH, padx=4, pady=4)
frame2.pack(side=BOTTOM, fill=X)
z.wm_protocol("WM_DELETE_WINDOW", lambda self=self: self.stop())
threading.Thread.__init__(self)
def run(self):
""" thread start
kick starts the main loop when the thread start()
"""
self.root.mainloop()
def stop(self):
""" escape plan
Exits gui thread
"""
self._stop()
raise SystemExit
def MkMainStatus(self):
""" status bar
"""
top = self.root
w = tkinter.tix.Frame(top, relief=tkinter.tix.RAISED, bd=1)
self.status = tkinter.tix.Label(w, anchor=E, bd=1)
self.exitbutton = tkinter.Button(w, text='Exit GUI Thread', width=20, command=lambda self=self: self.stop())
self.print_queue=queue.Queue()
self.print_label()
self.status.grid(row=0, column=0, sticky=W, padx=3, pady=3)
self.exitbutton.grid(row=0, column=1, sticky=E, padx=3, pady=3)
return w
def print_label(self):
""" listner
listner
"""
rate=0.5 # seconds to re-read queue; 0.5=half a second, 1=a full second
counter = count(0, rate)
def update_func():
secs= str(counter.__next__())
try:
self.status.config(text=str("%s(secs): Processing queue..." % (secs.split('.'))[0]), fg=str("red"))
a = tix.Label(self.LogFrame, text=(self.print_queue.get(False)))
except Empty:
self.status.config(text=str("%s(secs): Waiting for queue..." % (secs.split('.'))[0]), fg=str("black"))
self.status.after(int(rate*1000), update_func)
else:
a.pack()
a.after(int(rate*1000), update_func)
update_func()
def MkMainNotebook(self):
""" the tabs frame
defines the tabs
"""
top = self.root
w = tkinter.tix.NoteBook(top, ipadx=5, ipady=5, options="""
tagPadX 6
tagPadY 4
borderWidth 2
""")
top['bg'] = w['bg']
w.add('log', label='Log', underline=0)
self.MkLog(w, 'log')
w.add('pro', label='Progress', underline=0)
self.MkProgress(w, 'pro')
w.add('set', label='Settings', underline=0)
self.MkSettings(w, 'set')
return w
def MkSettings(self, nb, name):
""" TODO: settings tab
"""
w = nb.page(name)
options="label.width %d label.anchor %s entry.width %d" % (10, tkinter.tix.E, 13)
settings_scr_win = tix.ScrolledWindow(w, width=400, height=400)
settings_scr_win.pack(side=tkinter.tix.TOP, padx=2, pady=2, fill='both', expand=1)
self.SettingsFrame = settings_scr_win.window
def MkProgress(self, nb, name):
""" TODO: progress tab
"""
w = nb.page(name)
options = "label.padX 4"
progress_scr_win = tix.ScrolledWindow(w, width=400, height=400)
progress_scr_win.pack(side=tkinter.tix.TOP, padx=2, pady=2, fill='both', expand=1)
self.ProgressFrame = progress_scr_win.window
def MkLog(self, nb, name):
""" log
"""
w = nb.page(name)
options = "label.padX 4"
log_scr_win = tix.ScrolledWindow(w, width=400, height=400)
log_scr_win.pack(side=tkinter.tix.TOP, padx=2, pady=2, fill='both', expand=1)
self.LogFrame = log_scr_win.window
def main(argv):
""" main function
Keyword arguments:
args[0] --
args[1] --
Returns:
None
"""
#GUI
gui=GUI()
gui.start()
gui.print_queue.put("log")
time.sleep(10) # timed release test
gui.print_queue.put("timed release test")
return None
if __name__ == "__main__":
sys.exit(main(sys.argv))
It's probably the simplest method for implementing a threaded tkinter gui to a console application. The gui.print_queue.put() replaces the print() and the rate the gui updates can be modified in print_label(self) by adjusting the rate variable.
Enjoy !
For one, Tkinter is not thread safe. You can't access the tk widgets from more than one thread. Doing so yields unpredictable behavior (though one can usually predict that it will cause crashes).
As for the other question -- how to declare gui.SettingsFrame in advance -- I'm not sure how to answer it without stating the obvious. To "declare" it you must create it, and to create it you must call the method that creates it. And you must do this before using gui.SettingsFrame as an argument to some other method.
Why are you creating a label in a frame from outside of the method where the frame is created? I think the solution to that problem is to move the creation of the "settings" label from Main to MkSettings (and likewise for the "progress" and "log" labels).