Home > other >  Update label continuously while accessing data from a for loop inside a button's callback funct
Update label continuously while accessing data from a for loop inside a button's callback funct

Time:05-26

I think this one is the kind of problem that will make me change much of my code.

The program is basically a genetic algorithm that finds the best solutions of a given function and plots them over generations passed.

It only has some inputs and the run button. The run button uses a for loop to output the data in two lists and then plots them

def run(self):
     
     b = []
     a = []
     self.ga = Ga()
     for i in range(self.generations):
         
         ga.run(self.v.get())
         b.append(ga.best().fitness) 
         a.append(ga.fitness_average)
     self.graph(a,b)

Is there a way I can access the data being parsed into the lists during run time and use them to continuously update a Label ? The algorithm can take some time to run and I thought showing some values on the screen while running would be a nice feature.

CodePudding user response:

Sorry for the SUUPER long wait, internet went out for several hours

Hopefully the comments give you enough of an understanding to plop it into your code and get it all to work how you want, but if they don't, please feel free to ask for assistance, it's what i'm here for

Anyway, i assume your "run" function is a part of a class because of self so make sure to change the target for the thread to fit

#----------------- NEED THESE BEFORE EVERYTHING ELSE BUT DON'T NEED THEM EXACTLY HERE(but can get rid of time import if you dont want to use it)
import threading
import time
gettingData = True
#----------------- NEED THESE BEFORE EVERYTHING ELSE BUT DON'T NEED THEM EXACTLY HERE(but can get rid of time import if you dont want to use it)want to use it

def run(self):
     
    b = []
    a = []
    self.ga = Ga()
    while gettingData:
      time.sleep(1) #without this, it will be looping as soon as it finishes, which may use too many resources
      ga.run(self.v.get())
      b.append(ga.best().fitness) 
      a.append(ga.fitness_average)
      self.graph(a,b)

#threading stuff, just starts a new thread and continues along with its day
thread = threading.Thread(target=whateverThisClassIsCalled.run) 
thread.start()

#continue with other code you want to use as the while loop in "run" will now be constantly running

And if you want to stop the loop at any point:

gettingData = False #this stops the while loop which will allow your thread to run through the function to completion
#you can add a thread.join() after gettingData if you wish to wait for the thread to close(it to stop appending and graphing etc) before executing code coming after

CodePudding user response:

While multi-threading can be a way out for tkinter apps, tkinter itself is not thread-safe, and events or calls into tkinter made from other threads may cause strange errors.

This can be run in a single thread - the catch is that tkinter won't , by default, update anything on screen, until your long run function is over and control is resumed to the tkinter loop.

However, all that is needed to update the screen at any time is to call the .update() method on your widgets, or top-level widgets.

Since your function do calculations, it would not be a nice design to hard-code "window.update()" calls inside it. Instead, a design where an optional "update" callback is passed into your "run" function will be more elegant, and allow the same function to be run with other interfaces as text user interface program in the terminal, other gui toolkit like Qt or GTK, or even a web application.

I'd love to write "working code" of this example, but since you did not post a self contained example, I can't take my time to write a tkinter UI with the labels to update from scratch.

As I was writing the snippet bellow I saw your "run" is supposed to be a method. Please post self contained minimal, complete, executable examples, not out of context functions, when asking questions. If you app is assembled as a class, then update may be a method, instead of a callback passed as paremeter as I described above.

The idea goes like:

import tkinter as tk

class Mistery:
    def __init__(mistery_instance):
 
        # code to create the main window assigned
        # to "selfroot", the needed widgets, and an 
        # action that will trigger the "run"
        # function
        self.root = tkinter.Tk()  
        """you did not show your code. A lot of 
 over 15 year old documentation suggest inheriting
 your own class
 from "tk.Tk" and build your app on that class - 
that definetelly is not a good 
thing to do: there are _hundreds_ of methods on
 a tkinter class that would 
name-clash with your methods and you would never
 know it. Just associate
 your widet as an 
attribute as in the line above
        """
        self.label1 = ...
        self.label2 = ...
        # code to create the remainder of the UI
        # and at some point, create a callback to execute "self.run"
        ...
    

 
    def update(self,a, b):
        #code to set the display entries in label1 and label2
        ...
        root.update()  # <- this ensures the window is repainted with the new values


    def run(self, update_interval=100):
        
        b = []
        a = []
        self.ga = Ga()
        for i in range(self.generations):
            
            ga.run(self.v.get())
            b.append(ga.best().fitness) 
            a.append(ga.fitness_average)
            if i % update_interval == 0:
                self.update(a, b)
        self.graph(a,b)

m = Mistery()
tkinter.mainloop()
  • Related