Python / Tkinter status bar not updating correctly - python

I have developed a simple app in Python (2.7) with Tkinter. But my status bar is only sort of working. Here's the stripped down code:
from Tkinter import *
import os
import sys
def fixFiles():
inputFilePath= input_dir.get()
#Build a list of files in a directory
fileList = os.listdir(inputFilePath)
#Loop through those files, open the file, do something, close the file
for filename in fileList:
infile = open(inputfilepath + "/" + filename,'r')
#Update the status with the filename
status_string = 'Status: Working on file: ' + str(filename)
status.set(status_string)
for line in infile:
#Do some stuff here
infile.close()
class App:
def __init__(self, master):
i = 0
status.set("Status: Press 'Fix Files!'")
statuslabel = Label(master, textvariable=status, relief = RIDGE, width = 65, pady = 5, anchor=W)
bFixFiles = Button(root, text='Fix Files!', command = fixFiles)
bQuit = Button(root, text='Quit', command = root.destroy)
statuslabel.grid(row=i, column = 0, columnspan = 2)
bFixFiles.grid(row=i, column=2, sticky=E)
bQuit.grid(row=i, column=3, sticky=W)
root = Tk()
root.title("FIX Files")
input_dir = StringVar()
status = StringVar()
choice = IntVar()
app = App(root)
root.mainloop()
Currently what's happening is that the status bar reads "Status: Press 'Fix Files!'" until the program is finished looping through the files, at which point it reads "Status: Working on file: XXXXX.txt" (which is the name of the last file to be opened and closed by the program.
I would like the status bar to update with the file name each time the program opens a new file. Any help is appreciated!

The goofy way is to use root.update_idletasks():
#Update the status with the filename
status_string = 'Status: Working on file: ' + str(filename)
status.set(status_string)
root.update_idletasks()
To its credit, it is simple, but it does not really work -- although the statuslabel gets updated, the Quit button is frozen until fixFiles is completed. That's not very GUI-friendly. Here are some more reasons why update and update_idletasks are considered harmful.
So how should we run a long-running task without freezing the GUI?
The key is to make your callback functions end quickly. Instead of having a long-running for-loop, make a function that runs through the innards of the for-loop once. Hopefully that ends quickly enough for the user to not feel the GUI has been frozen.
Then, to replace the for-loop, you could use calls to root.after to call your quick-running function multiple times.
from Tkinter import *
import tkFileDialog
import os
import sys
import time
def startFixFiles():
inputFilePath = tkFileDialog.askdirectory()
# inputFilePath= input_dir.get()
# Build a list of files in a directory
fileList = os.listdir(inputFilePath)
def fixFiles():
try:
filename = fileList.pop()
except IndexError:
return
try:
with open(os.path.join(inputFilePath, filename), 'r') as infile:
# Update the status with the filename
status_string = 'Status: Working on file: ' + str(filename)
status.set(status_string)
for line in infile:
# Do some stuff here
pass
except IOError:
# You might get here if file is unreadable, you don't have read permission,
# or the file might be a directory...
pass
root.after(250, fixFiles)
root.after(10, fixFiles)
class App:
def __init__(self, master):
i = 0
status.set("Status: Press 'Fix Files!'")
statuslabel = Label(
master, textvariable=status, relief=RIDGE, width=65,
pady=5, anchor=W)
bFixFiles = Button(root, text='Fix Files!', command=startFixFiles)
bQuit = Button(root, text='Quit', command=root.destroy)
statuslabel.grid(row=i, column=0, columnspan=2)
bFixFiles.grid(row=i, column=2, sticky=E)
bQuit.grid(row=i, column=3, sticky=W)
root = Tk()
root.title("FIX Files")
input_dir = StringVar()
status = StringVar()
choice = IntVar()
app = App(root)
root.mainloop()
The above begs the question, What should we do if our long-running task has no loop? or if even one pass through the loop requires a long time?
Here is a way to run the long-running task in a separate process (or thread), and have it communicate information through a queue which the main process can periodically poll (using root.after) to update the GUI status bar. I think this design is more easily applicable to this problem in general since it does not require you to break apart the for-loop.
Note carefully that all Tkinter GUI-related function calls must occur from a single thread. That is why the long-running process simply sends strings through the queue instead of trying to call status.set directly.
import Tkinter as tk
import multiprocessing as mp
import tkFileDialog
import os
import Queue
sentinel = None
def long_running_worker(inputFilePath, outqueue):
# Build a list of files in a directory
fileList = os.listdir(inputFilePath)
for filename in fileList:
try:
with open(os.path.join(inputFilePath, filename), 'r') as infile:
# Update the status with the filename
status_string = 'Status: Working on file: ' + str(filename)
outqueue.put(status_string)
for line in infile:
# Do some stuff here
pass
except IOError:
# You might get here if file is unreadable, you don't have read permission,
# or the file might be a directory...
pass
# Put the sentinel in the queue to tell update_status to end
outqueue.put(sentinel)
class App(object):
def __init__(self, master):
self.status = tk.StringVar()
self.status.set("Status: Press 'Fix Files!'")
self.statuslabel = tk.Label(
master, textvariable=self.status, relief=tk.RIDGE, width=65,
pady=5, anchor='w')
bFixFiles = tk.Button(root, text='Fix Files!', command=self.startFixFiles)
bQuit = tk.Button(root, text='Quit', command=root.destroy)
self.statuslabel.grid(row=1, column=0, columnspan=2)
bFixFiles.grid(row=0, column=0, sticky='e')
bQuit.grid(row=0, column=1, sticky='e')
def update_status(self, outqueue):
try:
status_string = outqueue.get_nowait()
if status_string is not sentinel:
self.status.set(status_string)
root.after(250, self.update_status, outqueue)
else:
# By not calling root.after here, we allow update_status to truly end
pass
except Queue.Empty:
root.after(250, self.update_status, outqueue)
def startFixFiles(self):
inputFilePath = tkFileDialog.askdirectory()
# Start long running process
outqueue = mp.Queue()
proc = mp.Process(target=long_running_worker, args=(inputFilePath, outqueue))
proc.daemon = True
proc.start()
# Start a function to check a queue for GUI-related updates
root.after(250, self.update_status, outqueue)
root = tk.Tk()
root.title("FIX Files")
app = App(root)
root.mainloop()

Related

PyInstaller .exe keeps restarting itself

I made something in python to test my python skills. I tried converting it to an .EXE (the main python file itself) but it keeps starting itself over and over again: https://youtu.be/GItuZkD8nfo
it keeps starting new windows over & over again.
# Imports
from asyncio.log import logger
from asyncio.windows_events import NULL
from base64 import b64encode
from os import remove, mkdir
from shutil import rmtree
from shutil import move as movefile
from art import text2art
from anonfile import AnonFile
anon = AnonFile()
from discord_webhook import DiscordWebhook
from tkinter import NE, TOP, Entry, Label, Tk, Button, PhotoImage, messagebox, StringVar
import PyInstaller.__main__
from multiprocessing import freeze_support
freeze_support()
# Warn user to install python!
messagebox.showinfo("Python!", "Make sure you have python v3.10 installed before continuing")
# create temp folder
try:
mkdir('temp')
except FileExistsError:
pass
#labels / buttons to remove later
labels = []
#terminal art ;)
Art=text2art("Cappuccino")
print(Art)
#define GUI class
class MyFirstGUI:
def __init__(self, master):
self.master = master
#define title
master.title("Cappuccino")
#buttons
self.greet_button = Button(master, image=photo,highlightthickness=0, borderwidth=0,relief='flat',command=self.build)
self.quit = Button(master, image=quit_Image,highlightthickness=0, borderwidth=0,relief='flat', command=self.quit)
#pack the buttons
self.greet_button.pack(side="bottom")
self.quit.pack(side=TOP, anchor=NE)
#entry box
e1 = Entry(master, width=50, justify='center', textvariable=webhook)
e1.pack(padx=10, pady=100)
def quit(self):
#quit function
rmtree('temp')
exit()
def build(self):
#build function
for label in labels: label.destroy()
webhook_encoded = str(b64encode(webhook.get().encode('utf-8'))).replace("b'", "").replace("'", "")
try:
webhookSender = DiscordWebhook(url=webhook.get(), content='Cappucino Test!', username='Cappucino')
response = webhookSender.execute()
except:
print('ERROR! Most likely caused by: No internet connection or an invalid webhook!')
label = Label( root, image=error, highlightthickness=0, borderwidth=0,relief='flat' )
label.pack()
labels.append(label)
return
filename = anon.download("private", path='temp')
try:
f = open('temp/py1.py', 'r', encoding='utf-8')
except FileNotFoundError:
label = Label( root, image=error, highlightthickness=0, borderwidth=0,relief='flat' )
label.pack()
labels.append(label)
print('ERROR! Python file not found.')
return
code = f.read()
try:
l2 = open('temp/py2.py', 'w', encoding='utf-8')
except FileNotFoundError:
pass
l2.write(code.replace('Webhook_Here', webhook_encoded))
l2.close()
f.close()
error = False
try:
PyInstaller.__main__.run([
'temp/logger2.py',
'--onefile',
'--noconsole',
'--log-level=INFO',
'--icon=NONE',
])
except:
label = Label( root, image=error, highlightthickness=0, borderwidth=0,relief='flat' )
label.pack()
labels.append(label)
print('ERROR! PyInstaller failed converting the logger to an .EXE!')
return
movefile('dist/p2.exe', 'a.exe')
remove('logger2.spec')
try:
rmtree('temp/__pycache__')
rmtree('build')
rmtree('dist')
except OSError as e:
print("Error: %s - %s." % (e.filename, e.strerror))
remove('temp/py1.py')
remove('temp/py2.py')
#define root
root = Tk()
#define webhook
webhook = StringVar()
#configure background
root.configure(bg="#1e1e1e")
#configure icon
root.wm_iconbitmap('Cappuccino.ico')
#define window height and width
root.geometry("600x400")
#error text
photo = PhotoImage(file = "Untitled.png")
quit_Image = PhotoImage(file = "quit.png")
error = PhotoImage(file = "Error.png")
#gui?
my_gui = MyFirstGUI(root)
#loop
root.mainloop()
I tried adding
from multiprocessing import freeze_support
freeze_support()
but that did not work at all. I was expecting the program to work like normal.
Running the program from the normal .py file works just fine!

Why is my threaded process still freezing?

I'm writing an app using tkinter for the GUI, and I want an indeterminate progress bar to be going back and forth while the main function is running, which sometimes takes a few seconds, depending on user input. Normally, the whole program freeze while the main function is running, so I am trying to establish a threaded process for the progress bar so it moves while main() is doing its thing (both functions are called withinscan_and_display()).
from tkinter import filedialog
from tkinter import *
from tkinter.ttk import Progressbar
from main import main
import graphs
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from PIL import ImageTk, Image
import threading
root = Tk()
launch_frame = Frame(root)
button_frame = Frame(root)
graph_frame = Frame(root, height=1000, width=1200)
# log directory button/text
logDirButton = Button(master=launch_frame, text='Select log storage location...',
command=lambda: get_directory(log_text), width=22)
log_text = Text(master=launch_frame, height=1, width=25)
logDirButton.grid(row=1, column=0)
log_text.grid(row=1, column=1)
# scan directory button/text
dirButton = Button(master=launch_frame, text="Select scan directory...", command=lambda: get_directory(t), width=22)
t = Text(master=launch_frame, height=1, width=25)
dirButton.grid(row=2, column=0)
t.grid(row=2, column=1)
# main scan button
mainButton = Button(master=launch_frame, text="SCAN!", state=DISABLED,
width=50, height=10, bg='#27b355')
mainButton.grid(row=3, column=0, columnspan=2)
# progress bar
progress = Progressbar(launch_frame, orient=HORIZONTAL, length=100, mode='indeterminate')
progress.grid(row=4, column=0, columnspan=2)
launch_frame.grid(row=0, column=0, sticky=NW)
def get_directory(text):
# first clear form if it already has text
try:
text.delete("1.0", END)
except AttributeError:
pass
directory = filedialog.askdirectory()
# store the first directory for later specific reference
text.insert(END, directory)
# disable scan button until user has given necessary info to run (log storage location, scan directory)
enable_scan_button(log_text, t)
return directory
def enable_scan_button(logText, dirText):
if logText.get("1.0", END) != '\n' and dirText.get('1.0', END) != '\n':
mainButton['state'] = NORMAL
mainButton['command'] = lambda: scan_and_display()
else:
mainButton['state'] = DISABLED
def scan_and_display():
threading.Thread(target=bar_start).start()
# get scan directory and log directory from text fields
log_directory = log_text.get("1.0", END)[:-1]
scan_directory = t.get("1.0", END)[:-1]
# store the initial scan directory for later reference
top_dir = scan_directory
# runs the main scan function. Passes scan_directory and log_directory arguments
data, scanDate = main(log_directory, scan_directory)
display(scan_directory, data, scanDate, top_dir)
def bar_start():
print("BAR START CALLED")
progress.start(10)
With this setup (and various other configurations I've trieD), the bar still freezes while main() is doing it's thing, and I need it to move to indicate to the user that something is happening.
You need to thread the long-running function, not the progressbar.
def scan_and_display():
# get scan directory and log directory from text fields
# It's best to do all tkinter calls in the main thread
log_directory = log_text.get("1.0", END)[:-1]
scan_directory = t.get("1.0", END)[:-1]
th = threading.Thread(target=bar_start, args=(log_directory, scan_directory))
th.start()
progress.start()
def bar_start(log_directory, scan_directory):
print("BAR START CALLED")
# store the initial scan directory for later reference
top_dir = scan_directory
# runs the main scan function. Passes scan_directory and log_directory arguments
data, scanDate = main(log_directory, scan_directory)
display(scan_directory, data, scanDate, top_dir)
progress.stop()
EDIT: here's a MCVE:
import tkinter as tk
from tkinter.ttk import Progressbar
import time
from threading import Thread
def long_running_function(arg1, arg2, result_obj):
"""accept some type of object to store the result in"""
time.sleep(3) # long running function
result_obj.append(f'DONE at {int(time.time())}')
root.event_generate("<<LongRunningFunctionDone>>") # trigger GUI event
def long_func_start():
x = 'spam'
y = 'eggs'
t = Thread(target=long_running_function, args=(x,y,data))
t.start()
result.config(text='awaiting result')
progress.start()
def long_func_end(event=None):
progress.stop()
result.config(text=f"process finished with result:\n{data[-1]}")
root = tk.Tk()
root.geometry('200x200')
btn = tk.Button(root, text='start long function', command=long_func_start)
btn.pack()
progress = Progressbar(root, orient=tk.HORIZONTAL, length=100, mode='indeterminate')
progress.pack()
result = tk.Label(root, text='---')
result.pack()
root.bind("<<LongRunningFunctionDone>>", long_func_end) # tell GUI what to do when thread ends
data = [] # something to store data
root.mainloop()

Integrating a click-Terminal in Tkinter?

I wrote a CLI with click. Now I'm wondering if it would be possible to integrate this into a GUI with Tkinter.
I know there are ways to embed a console into Tkinter, but I want to specifically know whether it is possible my CLI in it (with 'reasonable' effort).
So I have a Script:
#cli.command()
def myfunction()
print("My Stuff")
And now I want to build a Tkinter GUI, where I have a command line, so that if I type myfunction there it calls and executes this myfunction
Is that possible or should I just take the code underneath the CLI and build a Tkinter App with it, that is independent from the CLI?
For fun, I just made this tkinterify, which does what you're asking.
import tkinter
import click
import sys
from io import StringIO
def tkinterify(cli_group, app_name="Tkinterified App"):
# Create and configure root
root = tkinter.Tk()
root.wm_title(app_name)
tkinter.Grid.rowconfigure(root, 0, weight=1)
tkinter.Grid.columnconfigure(root, 0, weight=1)
# Create and configure frame
frame = tkinter.Frame(root)
frame.grid(row=0, column=0, sticky="nsew")
frame.columnconfigure(0, weight=1)
frame.columnconfigure(1, weight=1)
frame.columnconfigure(2, weight=1)
frame.rowconfigure(0, weight=1)
frame.rowconfigure(1, weight=1)
initial_output = "Valid commands:\n"
initial_command_name_list = list(cli_group.commands.keys())
for available_command_name in initial_command_name_list:
initial_output = initial_output + " " + available_command_name + "\n"
initial_output = initial_output + "Ready for input."
# Some GUI widgets
run_string = tkinter.StringVar()
entry_run = tkinter.Entry(root, textvariable=run_string, width=50)
scrollbar_widget = tkinter.Scrollbar(root)
text_widget = tkinter.Text(root)
def clear_callback():
# Because the text widget is usually disabled, we have to explicitly enable it before we can write to it.
text_widget.config(state='normal')
text_widget.delete(1.0, tkinter.END)
text_widget.insert(tkinter.END, initial_output)
text_widget.config(state='disabled')
def run_callback():
command_args = []
try:
command_parts = run_string.get().split()
command_name = command_parts[0]
except IndexError:
return
if len(command_parts) > 1:
command_args = command_parts[1:]
if command_name:
try:
# Redirect stdout so we can read the output into a string for display within out GUI
real_stdout = sys.stdout
fake_stdout = StringIO()
sys.stdout.flush()
sys.stdout = fake_stdout
# Obtain list of available commands
available_commands = cli_group.commands
command_name_list = list(cli_group.commands.keys())
if command_name in command_name_list:
try:
# Make a fake context in which to run the command
context = available_commands[command_name].make_context("tkinter", command_args)
# Invoke the command within the fake context
available_commands[command_name].invoke(context)
except click.exceptions.UsageError as e:
print(e)
print(initial_output)
else:
print("Command not found.\n")
print(initial_output)
# Put stdout back
sys.stdout.flush()
sys.stdout = real_stdout
sys.stdout.flush()
output_string = fake_stdout.getvalue()
fake_stdout.close()
# Update the text output widget
text_widget.config(state='normal')
text_widget.delete(1.0, tkinter.END)
text_widget.insert(tkinter.END, output_string)
text_widget.config(state='disabled')
except IndexError:
pass
# More GUI widgets
button_run = tkinter.Button(root, text="Run", command=run_callback)
button_clear = tkinter.Button(root, text="Clear", command=clear_callback)
text_widget.delete(1.0, tkinter.END)
text_widget.insert(tkinter.END, initial_output)
entry_run.grid(row=0, column=0, sticky="new")
button_run.grid(row=0, column=1, sticky="n")
button_clear.grid(row=0, column=2, sticky="n")
text_widget.grid(row=1, column=0, columnspan=2, sticky="nsew")
scrollbar_widget.grid(row=1, column=2, sticky="ns")
scrollbar_widget.config(command=text_widget.yview)
text_widget.config(yscrollcommand=scrollbar_widget.set)
text_widget.config(state='disabled')
root.mainloop()
I've put it on github as https://github.com/rbricheno/tkinterify with an example as follows:
import click
from tkinterify import tkinterify
#click.group()
def cli():
pass
#click.command()
def my_function():
print("My Stuff")
#click.command()
def my_other_function():
print("More Things")
cli.add_command(my_function)
cli.add_command(my_other_function)
tkinterify(cli)
You may need to modify it if your scripts have large output or are complicated in other ways. I haven't tested how it handles arguments. Hopefully this gives you an idea how to do what you want.
The main trick is to add our click commands to a Group. Once they're added, we can easily get a reference to them using the dictionary in the_group.commands

tkinter progress bar won't update when called from an other app

I am developing a GUI with tkinter to manage images in a database (import file, load file, query, ...)
When a new directory and its sub-directories are scanned for new images to put in the database, a dedicated GUI is launched:
It consists of a Text widget where the name of the directory currently analysed is printed, and a progress-bar showing the progress of the scan.
When I call this GUI alone, the progressbar updates and progresses correctly as long as I use update() after each change in the progressbar. On the other hand,
the Text widget is corretly updating even if I do no use update.
However, the progress bar does not update as it should when I call it from the main GUI, while the Text widget updates correctly.
I hope someone can help!
Below is the code for the progressbar GUI. I am using Python 3.6.
from tkinter.filedialog import *
from tkinter.ttk import *
class ScanDirectoryForJPG(Tk):
"""
Inherited from the Tk class
"""
def __init__(self, parent, Path=None):
Tk.__init__(self, parent)
self.parent = parent
self.PathDicom = Path
if self.Path == None:
self.Path = askdirectory(title='Select a directory to scan')
self.title('Scan {} for JPG files'.format(self.Path))
self.status_string = 'Scanning the content of {} folder\n'.format(self.Path)
self.initialize_gui()
self.scan_directory()
def initialize_gui(self):
# Style
self.style = Style()
self.style.theme_use('vista')
# Main window
self.grid()
self.grid_columnconfigure([0], weight=1)
self.grid_rowconfigure([0], weight=1)
# Status
self.status_label = Text(self)
self.status_label.grid(row=0, column=0, sticky='NSEW')
self.status_label.insert(END, 'Looking for JPG files in {}\n'.format(self.Path))
# Progress Bar
self.p = DoubleVar()
self.progress_bar = Progressbar(self, orient='horizontal', mode='determinate', variable=self.p, maximum=100)
self.p.set(0)
self.progress_bar.grid(row=1, column=0, rowspan=1, sticky='EW')
def scan_directory(self):
"""
"""
number_of_files = sum([len(files) for r, d, files in os.walk(self.Path)])
count = 0
for dirName, subdirList, fileList in os.walk(self.Path):
self.status_label.insert(END, '\t-exploring: {}\n'.format(dirName))
self.update()
for filename in fileList:
count += 1
value = count / number_of_files * self.progress_bar['maximum']
if value >= (self.progress_bar['value'] + 1):
# update the progress bar only when its value is increased by at least 1 (avoid too much updates of the progressbar)
self.p.set(self.progress_bar['value'] + 1)
self.update()
file = os.path.join(dirName, filename)
# if the file is a JPG, load it into the database
# ...
# ...
# ..
self.status_label.insert(END, 'FINISH\n')
self.update()
if __name__ == '__main__':
app = ScanDirectoryForJPG(None, Path='D:\Data\Test')
app.mainloop()
print('App closed')
If you have to call update() in tkinter you are doing it wrong.
In this case you have a loop that walks the directory tree. For the whole time your code is within that loop you are not processing events promptly so you have to call update() all the time.
Instead, capture the output of the os.walk or simply collect the toplevel directory contents and then use after to process a single item at a time, passing the iterator or list along so once you process a single item, you call after again to process the next one. This way the mainloop will handle UI events promptly with your directory tree processing being queued as events along with everything else. You should fine the application more responsive once you rework it in this manner.
Example
To demonstrate this, os.walk returns a generator so we can use after events to schedule each directory as each call of next(generator) yields the next directory with its files.
To monitor the progress we need some way to count the number of directories or files to be visited and if this demo is used for a whole filesystem that is where the app will appear to freeze. This could be broken into event-based code too to prevent this effect.
I used after(10, ...) to have it show the effect but for maximum speed, use after_idle instead.
import sys
import os
import tkinter as tk
import tkinter.ttk as ttk
from tkinter.filedialog import askdirectory
class App(ttk.Frame):
def __init__(self, parent, title):
#tk.Frame.__init__(self, parent)
super(App, self).__init__(parent)
parent.wm_withdraw()
parent.wm_title(title)
self.create_ui()
self.grid(sticky = "news")
parent.wm_protocol("WM_DELETE_WINDOW", self.on_destroy)
parent.grid_rowconfigure(0, weight=1)
parent.grid_columnconfigure(0, weight=1)
parent.wm_deiconify()
def create_ui(self):
textframe = ttk.Frame(self)
self.text = text = tk.Text(textframe)
vs = ttk.Scrollbar(textframe, orient=tk.VERTICAL, command=text.yview)
text.configure(yscrollcommand=vs.set)
text.grid(row=0, column=0, sticky=tk.NSEW)
vs.grid(row=0, column=1, sticky=tk.NS)
textframe.grid_columnconfigure(0, weight=1)
textframe.grid_rowconfigure(0, weight=1)
textframe.grid(row=0, column=0, columnspan=2, sticky=tk.NSEW)
self.progressvar = tk.IntVar()
self.progress = ttk.Progressbar(self, variable=self.progressvar)
test_button = ttk.Button(self, text="Walk", command=self.on_walk)
exit_button = ttk.Button(self, text="Exit", command=self.on_destroy)
self.progress.grid(row=1, column=0, sticky=tk.NSEW)
test_button.grid(row=1, column=0, sticky=tk.SE)
exit_button.grid(row=1, column=1, sticky=tk.SE)
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1)
def on_destroy(self):
self.master.destroy()
def on_walk(self):
root = askdirectory()
self.walk(root)
def walk(self, root=None):
if root:
# this is potentially costly, but how to find the number of files to be examined?
count = sum([len(files) for (root,dirs,files) in os.walk(root)])
self.text.delete("1.0", "end")
self.progress.configure(maximum=count)
self.progressvar.set(0)
walker = os.walk(root)
self.after(100, self.do_one, walker)
def do_one(self, walker):
try:
root,dirs,files = next(walker)
for file in files:
self.text.insert(tk.END, os.path.join(root, file), "PATH", "\n", "")
self.text.see(tk.END)
self.progressvar.set(self.progressvar.get() + 1)
self.after(10, self.do_one, walker)
except StopIteration:
pass
def main(args):
root = tk.Tk()
app = App(root, "Walk directory tree")
root.mainloop()
if __name__ == '__main__':
sys.exit(main(sys.argv))

call back not being called in tkinter bind

Here's my problem, when keyboard focus is on KeyboardEntry, KeysPressed() should happen, when focus leaves, toggle() should happen.
KeysPressed has a while loop such as:
while Flag:
#Whatever
toggle just toggles the flag
Flag= not Flag
The idea is when focus leaves KeyComboEntry, KeysPressed() stops running
This does not happen, at all matter of fact, toggle is not called
my
# Program by Fares Al Ghazy started 20/5/2017
# Python script to assign key combinations to bash commands, should run in the background at startup
# this program is meant to release bash code, it is obviously non-system agnostic and only works linux systems that use BASH
# is one file which only creates the GUI, another file is needed to use the info taken by this program
FileName = 'BinderData.txt'
import tkinter as tk
from ComboDetect import ComboDetector
from _thread import start_new_thread
# Create a class to get pressed keys and print them
KeyManager = ComboDetector()
# Class that creates GUI and takes info to save in file
class MainFrame(tk.Tk):
# variable to store pressed keys
KeyCombination = ""
testcounter = 1
Flag = True
#function to toggle Flag
def Toggle(self):
print("toggle")
self.Flag = not self.Flag
# function to write to file
def SaveFunction(self, e1, e2, FileName):
file = open(FileName, "a")
combo = e1.get() + '\n'
performed = e2.get() + '\n'
file.write(combo)
file.write(performed)
file.close()
def KeysPressed(self, Entry, KeyCombination):
Entry.config(state="normal")
#Entry.insert(tk.END, "Test")
while self.Flag:
print("test "+str(self.testcounter))
self.testcounter = self.testcounter + 1
KeyCombination = str(KeyManager.getpressedkeys())
Entry.delete(0, tk.END)
Entry.insert(tk.END, KeyCombination)
# constructor
def __init__(self, FileName, **kwargs):
tk.Tk.__init__(self, **kwargs)
# create GUI to take in key combinations and bash codes, then save them in file
root = self # create new window
root.wm_title("Key binder") # set title
# create labels and text boxes
KeyComboLabel = tk.Label(root, text="Key combination = ")
KeyComboEntry = tk.Entry(root)
# Bind function to entry
KeyComboEntry.bind('<FocusIn>', lambda e: start_new_thread(self.KeysPressed, (KeyComboEntry, self.KeyCombination)))
KeyComboEntry.bind('<FocusOut>', lambda f:self.toggle, ())
ActionLabel = tk.Label(root, text="Command to be executed = ")
ActionEntry = tk.Entry(root)
# place widgets in positions
KeyComboLabel.grid(row=0, column=0, sticky=tk.E)
ActionLabel.grid(row=1, column=0, sticky=tk.E)
KeyComboEntry.grid(row=0, column=1)
ActionEntry.grid(row=1, column=1)
# create save button
SaveButton = tk.Button(root, text="save",
command=lambda: self.SaveFunction(KeyComboEntry, ActionEntry, FileName))
SaveButton.grid(row=2, column=2, sticky=tk.E)
app = MainFrame(FileName)
app.mainloop()

Categories