0
votes

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.

1
You can't access tkinter from more than one thread. If you search this site you'll find many questions about tkinter and threads, all with similar answers.Bryan Oakley
That makes sense @Bryan. What I want to do is this but I'm having trouble formatting my code correctly. Any tips?Isa
@BryanOakley I was using this stack overflow question as a template, that's where I first got the idea to use threadsIsa

1 Answers

0
votes

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!