Related
The aim of my code is to create a window with labels, each representing a sensor. The data comes from the USB port in a table of 0s&1s and depending on the value it colours the labels accordingly.
The goal is supposed to look like this:
I am unsure of how to pass the data from the port to the function in real time without recreating the window as a whole each time, as I only want it to change the drawn labels (and their colours). Therefore, I was wondering if anyone could point me in the right direction or give me a suggestion of what I can do to make this work.
The code creating my main window & labels:
class MainWindow (qt.QMainWindow):
def __init__(self):
super().__init__()
self.count = 0
self.j = 0
self.i = 0
self.screen()
self.making()
def screen(self):
self.setWindowTitle("Bee Counter")
self.showMaximized()
def making(self):
for i in values: #Iterates over the list of data which comes from the port.
if (i == 1):
self.label = qt.QLabel(self)
self.label.setStyleSheet("background-color: green; border: 1px solid black;")
self.move_label() #Creates multiple labels with the colour green.
else:
self.label = qt.QLabel(self)
self.label.setStyleSheet("background-color: red; border: 1px solid black;")
self.move_label() #Creates multiple labels with the colour red.
self.count +=1
def move_label(self):
self.label.resize(A, A)
self.label.setAlignment(Qt.AlignmentFlag.AlignCenter)
if self.count%2==0:
k=20
self.label.move(X0+self.j,k)
self.j=self.j+X_STEP
self.label.setText(f"{self.count}")
else:
k=90
self.label.move(X0+self.i,k)
self.label.setText(f"{self.count}")
self.i=self.i+X_STEP
self.label.show()
if __name__ == '__main__':
app = qt.QApplication(sys.argv)
w = MainWindow()
w.show()
app.exec()
The code getting the data from the port:
ser = serial.Serial(
port='COM3',\
baudrate=115200,\
parity=serial.PARITY_NONE,\
stopbits=serial.STOPBITS_ONE,\
bytesize=serial.EIGHTBITS)
print("<Succesfully connected to: " + ser.portstr)
while 1:
if ser.inWaiting()>0:
line = ser.readline()
line = line.decode('utf-8')
line = [char for char in line if char=="1" or char=="0"] #Gets data in a form of a table of 0s & 1s.
print(line)
time.sleep(0.01)
ser.close()
P.S. Excuse my perhaps very obvious question, I simply can not wrap my head around it :)
The concept is based on the wrong premise: making should only create the labels (and keep a reference to them), while another function should be responsible for their update.
Since the data rate is quite fast and the display object very simple, it's probably better to use a custom widget instead of continuously set the style sheet.
class DisplayWidget(QWidget):
state = False
def __init__(self, index):
super().__init__()
self.index = str(index)
self.setFixedSize(32, 32)
def setState(self, state):
if self.state != state:
self.state = state
self.update()
def paintEvent(self, event):
qp = QPainter(self)
qp.setBrush(Qt.green if self.state else Qt.red)
qp.drawRect(self.rect().adjusted(0, 0, -1, -1))
qp.drawText(self.rect(), Qt.AlignCenter, self.index)
Now, the "viewer" is a custom widget that is able to create a predefined grid of display widgets and updates them when necessary.
You can provide a default field count, or just ignore that, since the function that updates the data is also capable to update the grid whenever the field count doesn't match.
class SerialViewer(QWidget):
def __init__(self, fieldCount=None):
super().__init__()
layout = QGridLayout(self)
layout.setAlignment(Qt.AlignCenter)
self.widgets = []
if isinstance(fieldCount, int) and fieldCount > 0:
self.createGrid(fieldCount)
def createGrid(self, fieldCount, rows=2):
while self.widgets:
self.widgets.pop(0).deleteLater()
rows = max(1, rows)
count = 0
columns, rest = divmod(fieldCount, rows)
if rest:
columns += 1
for column in range(columns):
for row in range(rows):
widget = DisplayWidget(count)
self.layout().addWidget(widget, row, column)
self.widgets.append(widget)
count += 1
if count == fieldCount:
break
def updateData(self, data):
if len(data) != len(self.widgets):
self.createGrid(len(data))
for widget, state in zip(self.widgets, data):
widget.setState(state)
As you can see, instead of using resize() or move(), I'm using a layout manager that is automatically able to place (and eventually resize) the widgets. Remember, fixed geometries are almost always discouraged. Also note that widgets should not be directly added to a QMainWindow, but set for its central widget.
The thread is implemented in a QThread subclass, using a custom signal that is emitted whenever new data is available:
class SerialThread(QThread):
dataReceived = pyqtSignal(object)
def run(self):
ser = serial.Serial(
port='COM3',
baudrate=115200,
parity=serial.PARITY_NONE,
stopbits=serial.STOPBITS_ONE,
bytesize=serial.EIGHTBITS)
self.keepRunning = True
while self.keepRunning:
if ser.inWaiting() > 0:
line = ser.readline()
line = line.decode('utf-8')
self.dataReceived.emit(
list(int(char) for char in line if char in '01')
)
# note that the indentation level of sleep() is *outside*
# of the "if" otherwise it may temporarily block the loop
# in case there is no data available
time.sleep(0.01)
def stop(self):
self.keepRunning = False
self.wait()
Finally, the main window, from which we can start or stop the serial communication:
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
central = QWidget()
self.setCentralWidget(central)
self.startButton = QPushButton('Start')
self.stopButton = QPushButton('Stop', enabled=False)
self.serialViewer = SerialViewer(32)
layout = QGridLayout(central)
layout.addWidget(self.startButton)
layout.addWidget(self.stopButton, 0, 1)
layout.addWidget(self.serialViewer, 1, 0, 1, 2)
self.serialThread = SerialThread()
self.startButton.clicked.connect(self.start)
self.stopButton.clicked.connect(self.serialThread.stop)
self.serialThread.dataReceived.connect(
self.serialViewer.updateData)
self.serialThread.finished.connect(self.stopped)
def start(self):
self.startButton.setEnabled(False)
self.stopButton.setEnabled(True)
self.serialThread.start()
def stopped(self):
self.startButton.setEnabled(True)
self.stopButton.setEnabled(False)
Notes:
Qt already provides an asynchronous class for serial communication that already supports signals, QSerialPort;
the above codes use the old enum syntax, for Qt6 you need the full enum names (Qt.GlobalColor.red, Qt.AlignmentFlag.AlignCenter, etc);
you will probably need a further check for the serial connection before starting the while loop in run();
There has to be a way to handle repaint events with PyQt. I can't imagine there woulnd't be. I got carried away for a minute, but here is where I'm concluding my attempt. Hopefully this is of some help in anyway, I woulnd't expand on the queue. The main thread needs to come back to repaint it I think. The only issue is awaiting the serial's data, if there even is almost no wait time, I would expect it to hang, and leave the reciever flying through loops and burning up the CPU
import threading
import queue
q = queue.Queue()
def painter():
while True:
if ser.inWaiting()>0:
line = ser.readline()
line = line.decode('utf-8')
line = [char for char in line if char=="1" or char=="0"]
print(line)
time.sleep(0.01)
ser.close()
#stylesheet = "background-color: {}; border: 1px solid black;"
#color = "red" if i else "green"
#label.setStyleSheet(stylesheet.format(color))
class MainWindow (qt.QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Bee Counter")
self.showMaximized()
def build_table(self, width=16, height=2):
labels = [qt.QLabel(self) for _ in range(width*height)]
for label in labels:
label.setStyleSheet(style.format("red"))
threading.Thread(target=painter).start()
I am writing my first python app for the last 20 years. I have a problem with the tag_add() function of the Tkinter Text widget. Adding a tag just works the first time, but not the second time. I checked with tag_names() if my tag got deleted on unchecking the "Highlight Errors" checkbutton. And it is deleted. It is even re-added on checking the checkbutton again, but the text is not colored on second attempt.
Anyone an idea?
As its the first python code for years, do you have feedback to the way I have implemented and structured it? (Sorry cant get away from CamelCase)
Thanks in advance
SLi
from Tkinter import Tk, BOTH, END, N, W, S, TOP, BOTTOM, INSERT, LEFT, RIGHT, SUNKEN, RAISED, X, Y, PanedWindow, Frame, LabelFrame, Scrollbar, Checkbutton, Entry, Button, Label, Text, Menu, IntVar
from ScrolledText import ScrolledText
class Application(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.parent = parent
self.initializeUiVariables()
self.doWindowSetup()
self.createWidgets()
self.doColorSetup()
def doWindowSetup(self):
self.parent.title('PACE Client Log Viewer')
self.screenWidth = self.parent.winfo_screenwidth()
self.screenHeight = self.parent.winfo_screenheight()
desiredWindowWidth = (self.screenWidth * 1.0)
desiredWindowHeight = (self.screenHeight * 1.0)
x = (self.screenWidth / 2) - (desiredWindowWidth / 2)
y = (self.screenHeight / 2) - (desiredWindowHeight / 2)
self.parent.geometry('%dx%d+%d+%d' % (desiredWindowWidth, desiredWindowHeight, x, y))
def initializeUiVariables(self):
self.fontSize = 12
self.highlightErrors = IntVar()
def createWidgets(self):
panedWindow = PanedWindow(sashrelief=RAISED)
panedWindow.pack(fill=BOTH, expand=1)
self.wText = ScrolledText(panedWindow)
self.wText.config(font=("consolas", self.fontSize))
self.wText.pack(side=LEFT, fill=BOTH, expand=True)
panedWindow.add(self.wText, minsize=(self.screenWidth * 0.75))
self.wText.insert(END, "2018-09-28 11:15:03 GMT - my.app.id (ERROR): Class:CertChecker:error: No certificate loaded. Load certificate before continuing.\n2018-09-28 11:15:07 GMT - my.app.id (INFO): Class:PerformInitialization: begin - version 0.3.10")
frameToolbar = Frame(panedWindow, padx=10)
frameToolbar.pack(side=LEFT, fill=BOTH, expand=True)
panedWindow.add(frameToolbar)
# Highlight Options
frameHighlightOptions = LabelFrame(frameToolbar, text="Highlight", padx=5, pady=5)
frameHighlightOptions.pack(side=TOP, fill=BOTH)
cbErrors = Checkbutton(frameHighlightOptions, text="Errors", anchor=W, padx=5, justify=LEFT, variable=self.highlightErrors, command=self.onHighlightErrors)
cbErrors.pack(side=TOP, fill=X)
def doColorSetup(self):
self.wText.tag_config("highlightError", background="#EE2C2C", foreground="#FFFFFF") # red
def onHighlightErrors(self):
if self.highlightErrors.get() == 0:
self.wText.tag_delete("highlightError")
else:
self.highlightRow("error", "highlightError")
def highlightRow(self, pattern, tag):
self.highlightPattern(pattern, tag, True)
def highlightPattern(self, pattern, tag, highlightRow=False):
start = self.wText.index("1.0")
end = self.wText.index(END)
self.wText.mark_set("matchStart", start)
self.wText.mark_set("matchEnd", start)
self.wText.mark_set("searchLimit", end)
count = IntVar()
while True:
index = self.wText.search(pattern, "matchEnd","searchLimit", count=count, regexp=True, nocase=True)
if index == "": break
if count.get() == 0: break # degenerate pattern which matches zero-length strings
if highlightRow:
row, col = index.split('.')
self.wText.mark_set("matchStart", "%s.%s" % (int(row), 0))
else:
self.wText.mark_set("matchStart", index)
if highlightRow:
lineEndIndex = self.wText.search("\n", index, "searchLimit", count=count, regexp=False, nocase=False)
row, col = lineEndIndex.split('.')
self.wText.mark_set("matchEnd", lineEndIndex)
else:
self.wText.mark_set("matchEnd", "%s+%sc" % (index, count.get()))
self.wText.tag_add(tag, "matchStart", "matchEnd")
def main():
root = Tk()
ex = Application(root)
root.mainloop()
if __name__ == '__main__':
main()
When you delete the tag, you destroy all of the information associated with that tag. The next time you add the tag it has no colors associate with it, so you can't see the tag.
Instead of deleting the tag, you should just remove it from the text.
Replace this:
self.wText.tag_delete("highlightError")
... with this:
self.wText.tag_remove("highlightError", "1.0", "end")
I have created a checkbox. I want the program prints 'python' when I tick it in created checkbox.
but it doesn't work...I don't get error but it doesn't print 'python'
please help.
this is my code
#!/usr/bin/python3
from tkinter import *
class Checkbar(Frame):
def __init__(self, parent=None, picks=[], side=LEFT, anchor=W):
Frame.__init__(self, parent)
self.vars = []
for pick in picks:
var = IntVar()
chk = Checkbutton(self, text=pick, variable=var)
chk.pack(side=side, anchor=anchor, expand=YES)
self.vars.append(var)
def state(self):
return map((lambda var: var.get()), self.vars)
def __getitem__(self,key):
return self.vars[key]
if __name__ == '__main__':
root = Tk()
lng = Checkbar(root, ['Python', 'Ruby', 'Perl', 'C++'])
tgl = Checkbar(root, ['English','German'])
lng.pack(side=TOP, fill=X)
tgl.pack(side=LEFT)
lng.config(relief=GROOVE, bd=2)
def allstates():
## print(lng[0])
if lng[0] == 1:
print ('python')
Button(root, text='Quit', command=root.quit).pack(side=RIGHT)
Button(root, text='Peek', command=allstates).pack(side=RIGHT)
root.mainloop()
lng[0] is an IntVar, not an int, so it's never equal to one.
You need to use the get method to compare the value of the variable to 1:
def allstates():
if lng[0].get() == 1:
print ('python')
Another solution is to change the __getitem__ method of the Checkbar so that it returns the value of the variable instead of the variable itself as I proposed in answer to Python Form: Using TKinter --> run script based on checked checkboxes in GUI
def __getitem__(self, key):
return self.vars[key].get()
In this case, you don't have to change the allstates function.
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!
I have successfully created a threading example of a thread which can update a Progressbar as it goes. However doing the same thing with multiprocessing has so far eluded me.
I'm beginning to wonder if it is possible to use tkinter in this way. Has anyone done this?
I am running on OS X 10.7. I know from looking around that different OS's may behave very differently, especially with multiprocessing and tkinter.
I have tried a producer which talks directly to the widget, through both namespaces and event.wait, and event.set. I have done the same thing with a producer talking to a consumer which is either a method or function which talks to the widget. All of these things successfully run, but do not update the widget visually. Although I have done a get() on the IntVar the widget is bound to and seen it change, both when using widget.step() and/or widget.set(). I have even tried running a separate tk() instance inside the sub process. Nothing updates the Progressbar.
Here is one of the simpler versions. The sub process is a method on an object that is a wrapper for the Progressbar widget. The tk GUI runs as the main process. I also find it a little odd that the widget does not get destroyed at the end of the loop, which is probably a clue I'm not understanding the implications of.
import multiprocessing
from tkinter import *
from tkinter import ttk
import time
root = Tk()
class main_window:
def __init__(self):
self.dialog_count = 0
self.parent = root
self.parent.title('multiprocessing progess bar')
frame = ttk.Labelframe(self.parent)
frame.pack(pady=10, padx=10)
btn = ttk.Button(frame, text="Cancel")
btn.bind("<Button-1>", self.cancel)
btn.grid(row=0, column=1, pady=10)
btn = ttk.Button(frame, text="progress_bar")
btn.bind("<Button-1>", self.pbar)
btn.grid(row=0, column=2, pady=10)
self.parent.mainloop()
def pbar(self, event):
name="producer %d" % self.dialog_count
self.dialog_count += 1
pbar = pbar_dialog(self.parent, title=name)
event = multiprocessing.Event()
p = multiprocessing.Process(target=pbar.consumer, args=(None, event))
p.start()
def cancel(self, event):
self.parent.destroy()
class pbar_dialog:
toplevel=None
pbar_count = 0
def __init__(self, parent, ns=None, event=None, title=None, max=100):
self.ns = ns
self.pbar_value = IntVar()
self.max = max
pbar_dialog.pbar_count += 1
self.pbar_value.set(0)
if not pbar_dialog.toplevel:
pbar_dialog.toplevel= Toplevel(parent)
self.frame = ttk.Labelframe(pbar_dialog.toplevel, text=title)
#self.frame.pack()
self.pbar = ttk.Progressbar(self.frame, length=300, variable=self.pbar_value)
self.pbar.grid(row=0, column=1, columnspan=2, padx=5, pady=5)
btn = ttk.Button(self.frame, text="Cancel")
btn.bind("<Button-1>", self.cancel)
btn.grid(row=0, column=3, pady=10)
self.frame.pack()
def set(self,value):
self.pbar_value.set(value)
def step(self,increment=1):
self.pbar.step(increment)
print ("Current", self.pbar_value.get())
def cancel(self, event):
self.destroy()
def destroy(self):
self.frame.destroy()
pbar_dialog.pbar_count -= 1
if pbar_dialog.pbar_count == 0:
pbar_dialog.toplevel.destroy()
def consumer(self, ns, event):
for i in range(21):
#event.wait(2)
self.step(5)
#self.set(i)
print("Consumer", i)
self.destroy()
if __name__ == '__main__':
main_window()
For contrast, here is the threading version which works perfectly.
import threading
from tkinter import *
from tkinter import ttk
import time
root = Tk()
class main_window:
def __init__(self):
self.dialog_count = 0
self.parent = root
self.parent.title('multiprocessing progess bar')
frame = ttk.Labelframe(self.parent)
frame.pack(pady=10, padx=10)
btn = ttk.Button(frame, text="Cancel")
btn.bind("<Button-1>", self.cancel)
btn.grid(row=0, column=1, pady=10)
btn = ttk.Button(frame, text="progress_bar")
btn.bind("<Button-1>", self.pbar)
btn.grid(row=0, column=2, pady=10)
self.parent.mainloop()
def producer(self, pbar):
i=0
while i < 101:
time.sleep(1)
pbar.step(1)
i += 1
pbar.destroy()
def pbar(self, event):
name="producer %d" % self.dialog_count
self.dialog_count += 1
pbar = pbar_dialog(self.parent, title=name)
p = threading.Thread(name=name, target=self.producer, args=(pbar,))
p.start()
#p.join()
def cancel(self, event):
self.parent.destroy()
class pbar_dialog:
toplevel=None
pbar_count = 0
def __init__(self, parent, ns=None, event=None, title=None, max=100):
self.ns = ns
self.pbar_value = IntVar()
self.title = title
self.max = max
pbar_dialog.pbar_count += 1
if not pbar_dialog.toplevel:
pbar_dialog.toplevel= Toplevel(parent)
self.frame = ttk.Labelframe(pbar_dialog.toplevel, text=title)
#self.frame.pack()
self.pbar = ttk.Progressbar(self.frame, length=300, variable=self.pbar_value)
self.pbar.grid(row=0, column=1, columnspan=2, padx=5, pady=5)
btn = ttk.Button(self.frame, text="Cancel")
btn.bind("<Button-1>", self.cancel)
btn.grid(row=0, column=3, pady=10)
self.frame.pack()
self.set(0)
def set(self,value):
self.pbar_value.set(value)
def step(self,increment=1):
self.pbar.step(increment)
def cancel(self, event):
self.destroy()
def destroy(self):
self.frame.destroy()
pbar_dialog.pbar_count -= 1
if pbar_dialog.pbar_count == 0:
pbar_dialog.toplevel.destroy()
pbar_dialog.toplevel = None
def automatic(self, ns, event):
for i in range(1,100):
self.step()
if __name__ == '__main__':
main_window()
Doing something similar, I ended up having to use a combination of threads and processes - the GUI front end had two threads: one for tkinter, and one reading from a multiprocessing.Queue and calling gui.update() - then the back-end processes would write updates into that Queue
This might be a strange approach, but it works for me. Copy and paste this code to a file and run it to see the result. It's ready to run.
I don't have the patience to explain my code right now, I might edit it another day.
Oh, and this is in Python 2.7 I started programming two months ago, so I have not idea if the difference is relevant.
# -*- coding: utf-8 -*-
# threadsandprocesses.py
# Importing modules
import time
import threading
import multiprocessing
import Tkinter as tki
import ttk
class Master(object):
def __init__(self):
self.mainw = tki.Tk()
self.mainw.protocol("WM_DELETE_WINDOW", self.myclose)
self.mainw.title("Progressbar")
self.mainw.geometry('300x100+300+300')
self.main = tki.Frame(self.mainw)
self.RunButton = ttk.Button(self.main, text='Run',
command=self.dostuff)
self.EntryBox = ttk.Entry(self.main)
self.EntryBox.insert(0, "Enter a number")
self.progress = ttk.Progressbar(self.main,
mode='determinate', value=0)
self.main.pack(fill=tki.BOTH, expand=tki.YES)
self.progress.pack(expand=tki.YES)
self.EntryBox.pack(expand=tki.YES)
self.RunButton.pack()
print "The Master was created"
def dostuff(self):
print "The Master does no work himself"
data = range(int(self.EntryBox.get()))
S = Slave(self, data)
print "The Master created a Slave to do his stuff"
print "The Slave gets told to start his work"
S.start()
def myclose(self):
self.mainw.destroy()
return
def nextstep(self):
print "Good job, Slave, I see the result is"
print Master.results.get()
class Slave(threading.Thread):
def __init__(self, guest, data):
print "This is the Slave."
print "Nowdays, Work is outsourced!"
self.data = data
self.guest = guest
threading.Thread.__init__(self)
def run(self):
print "The Slave is outsourcing his work to Calcualte inc."
time.sleep(1)
Outsourcing = Calculate()
Results = Outsourcing.run(self.guest, self.data)
return Results
# unwrapping outside a class
def calc(arg, **kwarg):
return Calculate.calculate(*arg, **kwarg)
class Calculate(object):
def run(self, guest, data):
print"This is Calculate inc. ... how can I help you?"
time.sleep(1)
maximum = int(guest.EntryBox.get())
guest.progress.configure(maximum=maximum, value=0)
manager = multiprocessing.Manager()
queue = manager.Queue()
lock = manager.Lock()
print "Things are setup and good to go"
# Counting the number of available CPUs in System
pool_size = multiprocessing.cpu_count()
print "Your system has %d CPUs" % (pool_size)
# Creating a pool of processes with the maximal number of CPUs possible
pool = multiprocessing.Pool(processes=pool_size)
Master.results = pool.map_async(calc, (zip([self]*len(data), [lock]*len(data),
[queue]*len(data), data)))
for job in range(1, maximum+1):
queue.get() # this is an abuse I think, but works for me
guest.progress.configure(value=job)
# Properly close and end all processes, once we're done
pool.close()
pool.join()
print "All done"
guest.nextstep()
return
def calculate(self, lock, queue, indata):
lock.acquire()
print 'Reading values and starting work'
lock.release()
time.sleep(3) # some work
results = indata # The works results
lock.acquire()
print 'Done'
lock.release()
queue.put("Finished!")
return results
if __name__ == '__main__':
TheMaster = Master()
TheMaster.mainw.mainloop()