Getting spinner working without threading - python-3.x

I am trying to get a spinner working, but without using threads (I had a working version, but it gave me erratic behaviour. After reading online people told me that it's best to avoid threading with tkinter). So I wanted to give the after function a try.
The goal is to have a spinner pop up before starting something is done and have it disappear once the 'something' is done. Here is a minimalistic example of what I have:
import tk
from time import sleep
class Spinner(tk.Toplevel):
def __init__(self, master, text = None, barsize = 10, speed = 100, spinnerSize = 50):
super().__init__(master = master)
self.barsize = barsize
self.speed = speed
self.size = spinnerSize
self.done = False
self.progress = 0
self.forward = True
if text is None:
self.text = 'Something is happening'
else:
self.text = text
self.title(self.text)
self.out = tk.Label(master = self,
text = self.spinnerCalc(),
height = 1,
borderwidth = 0,
width = 80,
bg = self['background'])
self.out.pack()
self.update_self()
def update_self(self):
print(self.progress)
if self.forward:
if self.progress == self.size - self.barsize:
self.forward = False
self.progress -= 1
else:
self.progress += 1
else:
if self.progress == 0:
self.forward = True
self.progress += 1
else:
self.progress -= 1
self.out.config(text = self.spinnerCalc())
if self.done:
self.grab_release()
self.destroy()
else:
# print('entered continue update')
self.update()
# print('step 2')
self.grab_set()
# print('step 3')
self.after(self.speed, self.update_self)
# print('step 4')
def spinnerCalc(self):
#returns something like |------█████--------------|
bar = '|'
barsize = self.barsize
size = self.size
for i in range (self.progress):
bar += '-'
for i in range (barsize):
bar += '\u2588'
for i in range (size-barsize-self.progress):
bar += '-'
bar += '|'
return bar
def stop(self):
self.done = True
root = tk.Tk()
def spin():
spinner = Spinner(root)
a_bunch_of_stuff_happening(10)
spinner.stop()
def a_bunch_of_stuff_happening(amount):
count = 0
for i in range(100000000):
count += i
print(count)
tk.Button(master = root,
text = 'Press me',
command = spin).pack()
tk.Button(master = root,
text = 'Close me',
command = root.destroy).pack()
root.mainloop()
The problem is that the update_self is not running. I do not want to mainloop the Spinner as that will block my a_bunch_of_stuff_happening() function. The a_bunch_of_stuff_happening is also a complicated function which changes attributes of the main root class.
I am stuck with what else to try without going into threading again (which was a big pain last time). Anyone have advice what else I could try to get this to work?

Related

PyQt 6. Progress bar freezes mainwindow

I am writing program with PyQT6 lib.
In program I have some big calculations (class in 2nd .py file), which take some time. I implemented progress bar and now I am trying to prevent freeze of Gui. I find what I need to use threads and signals to communicate with bar. But it doesn't work, as i can see. Program behavior isn't clear for me because on Linux Ubuntu 18 this program works fine with out freeze.
Can smb help me? What am I doing wrong?
here code example:
mainWindow.py
`
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.resize(800, 150)
self.n_times_clicked = 0
self.setWindowTitle("Выгрузка отчета риска")
self.button = QPushButton("Загрузить файл типа 'Scens'")
self.button.clicked.connect(self.the_button_was_clicked)
self.button.adjustSize()
self.button1 = QPushButton("Загрузить файл типа 'Зоны'")
self.button1.clicked.connect(self.the_button_was_clicked_second)
self.button1.adjustSize()
self.button2 = QPushButton("Переформатировать и выгрузить")
self.button2.clicked.connect(self.btn2_was_clicked)
self.button2.adjustSize()
self.button3 = QPushButton("Переформатировать и выгрузить")
self.button3.clicked.connect(self.btn3_was_clicked)
self.button3.adjustSize()
self.progressbar = QProgressBar()
self.progressbar.setValue(0)
self.input1 = QLineEdit("")
self.input1.setPlaceholderText("Введите путь до файла: C:\\user\\file.docx")
self.input2 = QLineEdit("")
self.input2.setPlaceholderText("Введите путь до файла: C:\\user\\file.docx")
layout = QGridLayout()
layout.addWidget(self.button, 0, 0)
layout.addWidget(self.input1, 0, 1)
layout.addWidget(self.button2, 0, 2)
layout.addWidget(self.button1, 1, 0)
layout.addWidget(self.input2, 1, 1)
layout.addWidget(self.button3, 1, 2)
layout.addWidget(self.progressbar, 2, 0, 2, 3)
self.progressbar.setHidden(True)
layout.setRowStretch(2, 1)
container = QWidget()
container.setLayout(layout)
self.setCentralWidget(container)
def the_button_was_clicked_second(self):
#some code
return fname
def the_button_was_clicked(self):
#some code
return fname
def btn2_was_clicked(self):
self.button2.setEnabled(False)
if get_file_extension(self.input1.text()) == 0:
if Path(self.input1.text()).exists():
self.progressbar.setHidden(False)
saveFile = QFileDialog.getSaveFileName(self, 'Save File', "Новый отчет.docx", "Word файл (*.docx)")
if saveFile[0] != "":
input_data = self.input1.text()
check = 0
check = worker.edit_excel(input_data, saveFile[0])
self.input1.clear()
if check == 1:
msg = QMessageBox()
msg.setWindowTitle("Ошибка")
msg.setText("Структура выбраного файла не соответсвует структуре 'Scens'.")
msg.setIcon(QMessageBox.Icon.Critical)
a = msg.exec()
else:
msg = QMessageBox()
msg.setWindowTitle("Готово!")
msg.setText("Word файл успешно создан")
msg.setIcon(QMessageBox.Icon.Information)
a = msg.exec()
self.progressbar.setValue(0)
self.button2.setEnabled(True)
self.progressbar.setHidden(True)
else:
print("no file selected")
else:
msg = QMessageBox()
msg.setWindowTitle("Ошибка")
msg.setText("Вы выбрали несуществующий файл")
msg.setIcon(QMessageBox.Icon.Critical)
a = msg.exec()
else:
msg = QMessageBox()
msg.setWindowTitle("Ошибка")
msg.setText("Вы выбрали файл неверного расширения. Допустимые файлы *.xls и *.xlsx")
msg.setIcon(QMessageBox.Icon.Critical)
a = msg.exec()
self.input1.clear()
def btn3_was_clicked(self):
#some code
def signal_accept(self, msg):
if int(msg) > 100:
self.progressbar.setValue(100)
else:
self.progressbar.setValue(int(msg))
if self.progressbar.value() == 100:
# self.progressbar.setValue(0)
self.button2.setEnabled(True)
app = QApplication(sys.argv)
window = MainWindow()
worker = Excel_redactor.Excel()
worker._signal.connect(window.signal_accept)
worker.start()
window.show()
sys.exit(app.exec())
`
and in second file I have calculations:
`
class Excel(QThread):
_signal = pyqtSignal(int)
def __init__(self):
super(Excel, self).__init__()
def __del__(self):
self.wait()
def another_e(self, chislo):
power = int(str(chislo)[-2] + str(chislo)[-1])
value_new = float(str(chislo * pow(10, power))[:-2])
return value_new
def sort_dict(self, dict):
dictionary_keys = list(dict.keys())
sorted_dict = {dictionary_keys[x]: sorted(
dict.values())[x] for x in range(len(dictionary_keys))}
return sorted_dict
def edit_excel(self, excel_path, word_path):
#some code
j = 0
step = all_rows / 100
percent = 1
for row in range(2, all_rows-2):
if row % step < 1.0 and row % step >= 0.0:
percent += 1
self._signal.emit(percent)
#some code
return 0
`
I tried to move QThread .start inside btn2_was_clicked func, but in that case gui doesnt work if i do "huge calculations" more than 1 time

How can I bind a function to the return key when my Tk frames are generated?

I am trying to bind the function validateSubmit to the enter key so the test may be taken at faster speeds, however I cannot figure out where or what to try to bind it to. Inside of the main at the bottom, I commented out the bind that I feel I get the closest results with, however I've been at this long enough to know that its currently beyond my scope of knowledge. This program is my foray into OOP. I also receive a strange error from validateSubmit, however it seems to be caused by tkinter's internal type-casting and has not seemed to do anything more than echo an error back in the terminal.
##########################################################################################
#Imports
import tkinter as tk
from tkinter import ttk
from tkinter import Tk
from random import randint
##########################################################################################
class Storage():
def __init__(self):
self.timeChoice = 0
self.gameScore = 0
self.answer = 0
self.highScore = 0
def set_timeChoice(self,x):
self.timeChoice = x
def set_gameScore(self,x):
self.gameScore = x
def set_answer(self,x):
self.answer = x
def set_highScore(self,x):
self.highScore = x
def save_highScore(self):
timeChoiceVar = str(self.timeChoice)
with open('data' + timeChoiceVar + '.txt',mode='w') as file:
file.write(str(self.highScore))
def get_highScore(self):
timeChoiceVar = str(self.timeChoice)
try:
with open('data' + timeChoiceVar + '.txt',mode='r') as file:
self.highScore = file.read()
except:
with open('data' + timeChoiceVar + '.txt',mode='w') as file:
file.write('0')
##########################################################################################
class AdditionApp(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self._frame = None
self.switch_frame(SetTimePage)
def switch_frame(self, frame_class):
new_frame = frame_class(self)
if self._frame is not None:
self._frame.destroy()
self._frame = new_frame
self._frame.grid(column=0,row=0,sticky=tk.W)
##########################################################################################
class SetTimePage(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master)
self.timeVar = tk.IntVar()
data.set_timeChoice(60)
time_frame = ttk.LabelFrame(self,text=' Test Timer Selection ')
time_frame.grid(column=0,row=0,padx=6,pady=8,sticky='WE')
radio1 = tk.Radiobutton(time_frame,text='1 Minute',variable=self.timeVar,value=60,command=lambda: data.set_timeChoice(self.timeVar.get()))
radio1.grid(column=0,row=0,sticky=tk.W)
radio1.select()
radio2 = tk.Radiobutton(time_frame,text='2 Minutes',variable=self.timeVar,value=120,command=lambda: data.set_timeChoice(self.timeVar.get()))
radio2.grid(column=0,row=1,sticky=tk.W)
radio5 = tk.Radiobutton(time_frame,text='5 Minutes',variable=self.timeVar,value=300,command=lambda: data.set_timeChoice(self.timeVar.get()))
radio5.grid(column=0,row=2,sticky=tk.W)
radio10 = tk.Radiobutton(time_frame,text='10 Minutes',variable=self.timeVar,value=600,command=lambda: data.set_timeChoice(self.timeVar.get()))
radio10.grid(column=0,row=3,sticky=tk.W)
start_button = ttk.Button(self,text=' Start ',command=lambda: master.switch_frame(TestPage))
start_button.grid(column=0,row=1,sticky='WE',pady=4)
##########################################################################################
class TestPage(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master)
self.answer = tk.IntVar()
self.scoreVar = 0
data.set_gameScore(0)
data.get_highScore()
self.test_frame = tk.Label(self,text=data.timeChoice)
self.test_frame.grid(column=3,row=0,sticky=tk.N)
self.score_frame = tk.Label(self,text='Score: %d' % data.gameScore)
self.score_frame.grid(column=3,row=1,sticky=tk.N)
self.high_score_frame = tk.Label(self,text='High Score: %s' % data.highScore)
self.high_score_frame.grid(column=3,row=2,sticky=tk.N)
self.solve_frame = ttk.LabelFrame(self,text=' Solve: ')
self.solve_frame.grid(column=0,row=1,columnspan=2,padx=12,sticky=tk.W)
self.equation_label = tk.Label(self.solve_frame,text='')
self.equation_label.grid(column=0,row=0,padx=6,sticky=tk.W)
self.answer_box = ttk.Entry(self.solve_frame,width=2,textvariable=self.answer)
self.answer_box.grid(column=1,row=0,padx=12,sticky=tk.W)
self.answer_box.bind('<Return>', self.validateSubmit)
self.back_button = ttk.Button(self.solve_frame,text=' Back ',command=lambda: master.switch_frame(SetTimePage))
self.back_button.grid(column=3,row=0,padx=12,sticky=tk.N)
self.countdown(data.timeChoice)
def countdown(self, remaining = None):
if remaining is not None:
self.remaining = remaining
self.generateEquation()
if self.remaining < 0:
self.test_frame.configure(text=" Time's up! ")
self.submit_answer.configure(state='disabled')
self.answer_box.configure(state='disabled')
else:
self.test_frame.configure(text="Seconds remaining: %d" % self.remaining)
self.high_score_frame.configure(text='High Score: %s' % data.highScore)
self.submit_answer.bind('<Return>', self.validateSubmit)
self.remaining = self.remaining - 1
self.after(1000, self.countdown)
def generateEquation(self):
self.x = randint(0,10)
self.y = randint(0,10)
self.z = 0
self.equation_label.configure(text='%d + %d' % (self.x , self.y))
self.answer_box.delete(0,3)
self.answer_box.focus()
self.submit_answer = ttk.Button(self.solve_frame,text=' Submit ',command=self.validateSubmit)
self.submit_answer.grid(column=2,row=0,padx=12,sticky=tk.E)
def validateSubmit(self,event=None):
while self.remaining >= 0:
self.z = self.answer.get()
if self.x + self.y == self.z:
self.scoreVar += 1
data.set_gameScore(self.scoreVar)
self.score_frame.configure(text='Score: %d' % data.gameScore)
self.generateEquation()
if int(data.highScore) < data.gameScore:
data.set_highScore(data.gameScore)
data.save_highScore()
elif self.x + self.y != self.z :
self.generateEquation()
##########################################################################################
if __name__ == "__main__":
data = Storage()
program = AdditionApp()
program.title(' Addition Test ')
#program.bind('<Return>', TestPage.validateSubmit)
program.mainloop()
data.save_highScore
If you want the user to be able to press return after entering an answer instead of pressing the Submit button, then the binding needs to go on the entry widget since that is the widget with keyboard focus.
self.answer_box.bind('<Return>', self.validateSubmit)
Also, you need to make sure that validateSubmit can accept the event option that is passed in. Since you want to use it both for a binding and a button click, the easiest way to do that is to make the event argument optional:
def validateSubmit(self, event=None):

How to safely quit a Toplevel() window on a thread

I want to include a spinner in my program to show some indication that program is doing something when some heavy background calculations are being made. Here is the spinner I came up with, but I am having trouble quitting it safely. I am not sure how else I can make my idea work, and this is my latest code for it. Anyone have any hint on how I can implement this functionality?
import tkinter as tk
from threading import Thread
from time import sleep
class Spinner_top(tk.Toplevel):
def __init__(self, master, text = None, barsize = 10, speed = 0.10, spinnerSize = 50):
super().__init__(master = master)
self.barsize = barsize
self.speed = speed
self.size = spinnerSize
self.done = False
if text is None:
self.text = 'Program is thinking, thus progress is moving'
else:
self.text = text
self.title(self.text)
self.out = tk.Label(master = self,
height = 1,
borderwidth = 0,
width = 80,
bg = self['background'])
self.out.pack()
self.update()
self.thread = Thread(target = self.fill_self)
self.thread.start()
def fill_self(self):
print('start called')
# print(self)
# print('update2')
forward = True
progress = 0
print('entered while loop')
while True:
msg = self.spinnerCalc(progress)
print('message changed')
self.out.configure(text = msg)
print('message updated')
self.update()
print('updated self')
if forward:
if progress == self.size - self.barsize:
forward = False
progress -= 1
else:
progress += 1
else:
if progress == 0:
forward = True
progress += 1
else:
progress -= 1
print(self.done)
if self.done:
break
sleep(self.speed)
return
def spinnerCalc(self, progress):
bar = '|'
barsize = self.barsize
size = self.size
for i in range (progress):
bar += '-'
for i in range (barsize):
bar += '\u2588'
for i in range (size-barsize-progress):
bar += '-'
bar += '|'
return bar
def stop(self):
print('stop called')
self.done = True
self.thread.join()
print('got pass join()')
self.quit()
self.destroy()
root = tk.Tk()
spinner = [None]
def start():
if spinner[0] is None:
spinner[0] = Spinner_top(root,'Program is thinking')
def stop():
if spinner[0] is not None:
spinner[0].stop()
spinner[0] = None
def experiment():
if spinner[0] is None:
spinner[0] = Spinner_top(root,'Try something')
spinner[0].stop()
tk.Button(root,
text = 'start spinner',
command = start).pack()
tk.Button(root,
text = 'stop spinner',
command = stop).pack()
tk.Button(root,
text='experiment',
command = experiment).pack()
tk.Button(root,
text = 'quit',
command = root.destroy).pack()
root.mainloop()
I am running into 2 problems with this:
1. When starting using the 'start spinner' button, the program freezes up.
2. When starting with the 'experiment' button, the code cannot get to print('message updated') line.
#trying to use the spinner using after as per #Nae suggestion (resulted in more problems :/
class Spinner_top(tk.Toplevel):
def __init__(self, master, text = None, barsize = 10, speed = 100, spinnerSize = 50):
super().__init__(master = master)
self.barsize = barsize
self.speed = speed
self.size = spinnerSize
self.progress = 0
self.done = False
if text is None:
self.text = 'Program is thinking, thus progress is moving'
else:
self.text = text
self.title(self.text)
self.out = tk.Label(master = self,
height = 1,
borderwidth = 0,
width = 80,
bg = self['background'])
self.out.pack()
self.fill_self()
def fill_self(self):
print('start called')
# print(self)
# print('update2')
self.forward = True
progress = 0
print('entered while loop')
def foo():
msg = self.spinnerCalc(progress)
print('message changed')
self.out.configure(text = msg)
print('message updated')
self.update()
print('updated self')
if self.forward:
if self.progress == self.size - self.barsize:
self.forward = False
self.progress -= 1
else:
self.progress += 1
else:
if progress == 0:
self.forward = True
self.progress += 1
else:
self.progress -= 1
# print(self.done)
if not self.done:
self.after(self.speed, func = foo)
foo()
return
def spinnerCalc(self, progress):
bar = '|'
barsize = self.barsize
size = self.size
for i in range (progress):
bar += '-'
for i in range (barsize):
bar += '\u2588'
for i in range (size-barsize-progress):
bar += '-'
bar += '|'
return bar
def stop(self):
print('stop called')
self.done = True
self.quit()
self.destroy()
After trying to figure this out, I have just resorted to simple spinner. Better than nothing and technically does the job. If anyone finds a way to make this spinner idea work with an updating gui though, I'd appreciate it.
class Simple_Spinner(tk.Toplevel):
def __init__(self,master,text=None):
super().__init__(master)
self.grab_set()
self.protocol("WM_DELETE_WINDOW", self.do_nothing)
if text is None:
text = 'Program is processing'
tk.Label(self,text=text).pack()
self.update()
def do_nothing(self):
return
def stop(self):
self.grab_release()
self.destroy()
If you can't measure the progress and just want to show it's busy, and user is not supposed to do other things meanwhile, just change the window mouse cursor to a watch:
>>> import tkinter
>>> root = tkinter.Tk()
>>> root.configure(cursor='watch')
and revert in the end with
>>> root.configure(cursor='arrow')
you can adapt this to your code and is about as simple as I can think.

After text widget opens, tkinter GUI crashes/does not respond whenever its closed

Right now, after I press the 'Time Range' button and call the calculateTime function, the text widget would appear with the results that I've inserted into it. However, after that, whenever I close the GUI window, the program would freeze and I'll have to forced quit it. This is my code:
import tkinter
from tkinter import *
import math
from tkinter import messagebox
class MyClass(tkinter.Frame):
def __init__(self, *args, **kwargs):
tkinter.Frame.__init__(self, *args, **kwargs)
#Setting up frame and widgets
vcmd1 = (self.register(self.__vcmd1), '%P', '%S')
vcmd2 = (self.register(self.__vcmd2), '%P')
vcmd3 = (self.register(self.__vcmd3), '%P', '%S')
label_iso = Label(self,text="Isotope A, Element")
label_vol = Label(self, text="Voltage")
label_range = Label(self, text="Charge Range")
label_iso.grid(row=0, column=0, sticky=E)
label_vol.grid(row=1, column=0, sticky=E)
label_range.grid(row=2, column=0, sticky=E)
self.entry1 = tkinter.Entry(self, validate="key", validatecommand=vcmd1)
self.entry2 = tkinter.Entry(self, validate="key", validatecommand=vcmd2)
self.entry3 = tkinter.Entry(self, validate="key", validatecommand=vcmd3)
self.entry1.grid(row=0, column=1)
self.entry2.grid(row=1, column=1)
self.entry3.grid(row=2, column=1)
def __vcmd1(self, P, S):
validString = 'qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM,1234567890'
if not S in validString:
return False
if "," in P:
if (len(P) - 1) > len(P.replace(",","")):
return False
messagebox.showinfo("Error", "Expected Form: ex. 133,Cs")
else:
return True
def __vcmd2(self, P):
if P == '':
return True
try:
float(P)
return True
except ValueError:
messagebox.showinfo("Error", "Entry is not a float or integer")
return False
def __vcmd3(self, P, S):
if "," in P:
if len(P.split(",")) > 2:
return False
a = P.split(",")[0]
b = P.split(",")[1]
if a != '' and b != '':
try:
int(a)
int(b)
except ValueError:
messagebox.showinfo("Error", "Expected form: ex. 1,12")
return False
else:
return True
class TimeGenerator:
def __init__(self,master):
frame = MyClass(master)
frame.grid(columnspan=2)
button = Button(root, text='Time Range', command=self.calculateTime)
button.grid(row=3, columnspan=2)
self.text = Text(root)
self.iso = frame.entry1
self.vol = frame.entry2
self.r = frame.entry3
def calculateTime(self):
x = 5
if self.r.get() == "" or self.iso.get() == "" or self.vol.get() == "":
messagebox.showinfo("Error", "No field can be empty")
return None
self.iso = self.iso.get().replace(" ", "")
list = []
for e in self.iso.split(","):
list.append(e)
f = open("/Users/LazyLinh/PycharmProjects/mass.mas12.txt", "r")
i = 0
while (i < 40):
header = f.readline()
i += 1
self.mass = 0
#iterate through text file
for line in f:
line = line.strip()
columns = line.split()
if (list[0] == columns[3]):
if (list[1].lower() == columns[4].lower()):
if (len(columns) == 16):
self.mass = float(columns[13].replace("#","")) + float(columns[14].replace("#",""))
else:
self.mass = float(columns[12].replace("#","")) + float(columns[13].replace("#",""))
#Calculation
self.r = self.r.get().replace(" ", "")
tup = tuple(int(x) for x in self.r.split(","))
list = []
for q in range(tup[0], tup[1] + 1):
y = (x * math.sqrt(self.mass * 1.6605402e-27 / (2 * q * float(self.vol.get())))) * 10e6
list.append(y)
i = tup[0]
#inserting to text widget
for time in list:
self.text.insert("end", "%d: %s\n" % (i, time))
i = i + 1
self.text.pack()
root = Tk()
b = TimeGenerator(root)
root.mainloop()
I've tried to searched up on this topic, but I'm not really using any weird update() function, and text shows up after the function is finished, so how likely that it is an event loop problem? Am I also doing something wrong that could cause this problem?
Thank you!
You have widgets in the root window that use both pack and grid. You cannot do this. Within a given container (root window, frame, etc) you can use one or the other, but not both.
The reason your program freezes is due to pack and grid fighting to gain control of the layout. When you pack the text widget it causes a change in the size and/or position of other widgets in the root window. That triggers grid to try to re-layout the widgets it is responsible for. This triggers pack to try to re-layout the widgets it is responsible for, and on and on until the end of time.
My guess is that you need to use grid with self.text since you use grid everywhere else.

Tkinter freezing - threads? Progressbar?

I wrote some code that creates progressbars that update when a json file is changed (by a different program). The idea is that this code will be combined with a much larger project to give the user information about the json file as it is being written.
My Problem: If I activate one of the progressbars, the entire GUI freezes. That progressbar will work just fine but I can't start any of the others.
My Plan: I've read up on tkinter and python and I believe that what I want is for each progressbar to operate on a different thread. I tried that; but it's still freezing. And the Quit button won't work properly either. Any suggestions? Or an easier way to approach this??
Here is my code (sorry for the length):
import threading
import time
from Tkinter import *
import json
import Queue
from time import sleep
master = Tk()
#somewhere accessible to both:
callback_queue = Queue.Queue()
#see (thread_working.py) for debugging help
######ProgressBar Code (my_progressbar.py)
class Meter(Frame):
#make a progress bar widget
def __init__(self, master, fillcolor = 'darkblue', text='', value=0.0, **kw):
Frame.__init__(self, master, bg='lightgray', width=350, height=20)
self.configure(**kw)
self._c = Canvas(self, bg=self['bg'], width=self['width'], height=self['height'], highlightthickness=0, relief='flat', bd=0)
self._c.pack(fill='x', expand=1)
self._r = self._c.create_rectangle(0,0,0, int(self['height']), fill=fillcolor, width=0)
self._t = self._c.create_text(int(self['width'])/2, int(self['height'])/2, text='')
self.set(value)
def set(self, value=0.0):
text = str(int(round(100 * value))) + ' %'
self._c.coords(self._r, 0, 0, int(self['width']) * value,int(self['height']))
self._c.itemconfigure(self._t, text=text)
self.update()
progbarlock = False # start with the prograssbar marked as not occupied
class guibuild:
def __init__(self):
guibuild.progbarlock = False
self.progbar = Meter(theframe) #makes the progressbar object
self.progbar.set(0.0) #sets the initial value to 0
self.progbar.pack(side=LEFT)
self.mybutton = Button(theframe, text="My Button", command = self.interval).pack(side = LEFT)
def stop_progbar(self):
self.progbar.stop()
def interval(self):
if guibuild.progbarlock == False:
counter = 0
#sleep(5) #slow down the loop
guibuild.progbarlock = True
i = float(0) #i is going to be the value set on the progressbar
while i <= 1.0: #will stop at 100%
the_file = open("sample.json")
data = json.load(the_file)
curr = data["curr_line"]
total = data["total_lines"]
if counter == total:
print "stop" #for debug purposes
self.stop_progbar
#pass
elif curr == counter:
#print "curr = counter" #debug
pass
elif curr == counter+1:
i += 1.0/total
#print i #debug
self.progbar.set(i) #apply the new value of i to the progressbar
print "the progressbar should reflect", str(int(round(i*100))) +"%progress right now"
print "the counter will increase"
counter += 1
#print counter #debug purposes
self.stop_progbar
#print "test"
else:
print "something is wrong - running.json is not available"
time.sleep(5)
guibuild.progbarlock = False
##########################################################################################################
def create_bar():
guibuild()
######Make the actual GUI
#master = Tk()
global theframe #makes the frame object global
theframe = Frame(master)
theframe.pack()
frame2 = Frame(master)
frame2.pack(side=BOTTOM)
quitbutton = Button(frame2, text="Quit", fg = "darkred", command = master.quit).pack(side=LEFT)
#original command was theframe.quit, original location was theframe (vs master)
##############Threading Stuff#####################
beginbutton = Button(theframe, text="Make Bar", command =create_bar).pack(side = BOTTOM)
def my_thread(func_to_call_from_main_thread):
callback_queue.put(guibuild)
#this must be here and below
def from_main_thread_nonblocking():
while True:
try:
callback = callback_queue.get(True) #doesn't block #was false
except Queue.Empty: #raised when queue is empty
break
callback()
#this allows for it to be activated several times
threading.Thread(target=guibuild).start()
while True:
master.mainloop()
master.destroy()
from_main_thread_nonblocking()
master.destroy()
sample.json looks like this:
{
"curr_line": 1,
"total_lines": 5
}
Edit: I got this fixed but found a new bug. Will post the corrected code once the bug is fixed in case anyone comes looking for an answer and finds this.
I fixed all of the bugs and want to share this answer for any future searchers. As #BryanOakley said, Tkinter does not work with threads. I researched some more and decided to delete all of my while loops and make use of the Tkinter after() method. Below is the modified part of my code.
class guibuild:
def __init__(self):
self.master = master
guibuild.progbarlock = False
self.progbar = Meter(theframe) #makes the progressbar object
self.progbar.set(0.0) #sets the initial value to 0
self.progbar.pack(side=LEFT)
self.counter = 0
self.i = float(0) #i is the value set to the progressbar
self.mybutton = Button(theframe, text="My Button", command = self.interval).pack(side = LEFT)
def stop_progbar(self):
self.progbar.stop()
def interval(self):
if guibuild.progbarlock == False:
guibuild.progbarlock = True
the_file = open("sample_running.json")
data = json.load(the_file)
curr = data["curr_line"]
command = data["curr_line_text"]
total = data["total_lines"]
print self.counter
if self.i == 1.0:
self.stop_progbar
print "100% - process is done"
self.master.after_cancel(self.interval)
elif self.counter == total:
print "stop" #for debug purposes
self.i = 1.0
self.master.after(5000, self.interval)
elif curr == self.counter:
print self.counter
print self.i
self.master.after(5000, self.interval)
elif curr == self.counter+1:
self.i += 1.0/total
print self.i #debug
self.progbar.set(self.i) #apply the new value of i to the progressbar
print "the progressbar should reflect", str(int(round(self.i*100))) +"%progress right now"
print "the counter will increase"
self.counter += 1
print self.counter #debug purposes
self.stop_progbar
self.master.after(5000, self.interval)
else:
print "something is wrong - running.json is not available"
self.master.after(5000, self.interval)
guibuild.progbarlock = False
Note that the call for self.master.after() needs to occur after every if statement - this is so that self.master.after_cancel() works when it is invoked. Cheers!

Resources