Python Forum
[Tkinter] Call a class method from another (Tk GUI) class? - Printable Version

+- Python Forum (https://python-forum.io)
+-- Forum: Python Coding (https://python-forum.io/forum-7.html)
+--- Forum: GUI (https://python-forum.io/forum-10.html)
+--- Thread: [Tkinter] Call a class method from another (Tk GUI) class? (/thread-23654.html)



Call a class method from another (Tk GUI) class? - Marbelous - Jan-10-2020

I am trying to create a simple timed event class that I can use to trigger repeating events in other programs (e.g.: data logger). I know that since Python functions (methods) are first class objects they can be passed around easily, stored in lists or dicts, etc. so I thought that it would be easy to just store a function as a class attribute and call it from there when needed. But, I'm having trouble calling the stored function. In my App class (Tk) I need to call the function in App.run_events() but if I try without the parenthesis it does nothing and if I add them the program hangs. I suspect I'm missing something obvious but after spending the last decade coding in LabVIEW my brain might just be corrupted in some way.

The issue is down between the lines of ###########s...

If I'm doing something "un-pythonic" and you feel there's a better way to tackle this please feel free to say so. I'm finally back to Python in my main job so I want to move forward doing things the right way.

Thanks All,
K

#! /usr/bin/env python

import tkinter as tk
import time

class Event(object):
    """ Event Class for timing repetitive events. """

    event_list = []

    def __init__(self, name, interval, method_to_call):
        self.name = name
        self.interval = interval # The time delay (in seconds) between subsequent triggers of this event.
        self.method_to_call = method_to_call # The function to be executed when this event is triggered.
        self.init_time = time.time() # Keep the timestamp of when the event was initialized for later use.
        self.last_time = self.init_time
        Event.event_list.append(self)

    def __str__(self):
        return f"EVENT -- Name: {self.name}, Interval: {self.interval}, Method Called: {self.method_to_call.__name__}."

    def check_event_ready(self):
        """ Test whether an event interval has elapsed and it's ready to run again. """

        if time.time() - self.last_time >= self.interval:
            self.last_time = time.time()
            return True
        else:
            return False


class App():
    def __init__(self):
        self.root = tk.Tk()

        self.event_listbox = tk.Listbox(width=50, height=25)
        self.event_listbox.pack()

        self.status_listbox = tk.Listbox(width=50, height=5)
        self.status_listbox.pack()

        self.setup() # Load up the Events to run.
        self.run_events() # Start the Event checking loop.
        self.root.mainloop() # Start the Tk GUI.

    def setup(self):
        """ Setup the events with their interval timing and function to execute. """

        ev1 = Event("Collect Data", 2, self.collect_data)
        ev2 = Event("Update Network", 5, self.update_network)
        for e in Event.event_list:
            print(e)

#####################################################################################################################
    def run_events(self):
        """ Spins a fast loop (using Tk.root.after()) to constantly check whether it's time to trigger an event. """

        for event in Event.event_list:
            if event.check_event_ready():
                self.event_listbox.insert(tk.END, event.name)
                event.method_to_call ### HOW DO I CALL THIS METHOD???
                print(event.method_to_call.__name__) # It prints just fine.
######################################################################################################################

        self.root.after(10, self.run_events) # Repeat at 10mS resolution.

    def collect_data(self):
        self.status_listbox.insert(tk.END, "Collect Data Running")
        time.sleep(500) # Add time delay to see if I need to use threading.

    def update_network(self):
        self.status_listbox.insert(tk.END, "Update Network Running")
        time.sleep(500)

app = App()



RE: Call a class method from another (Tk GUI) class? - Marbelous - Jan-10-2020

Looks like I got ahead of myself and caused the problem. Blush It was the time.sleep() functions I added to the methods I wanted to call through the events that were causing the program to hang. I knew I would eventually need to thread the functions running under Tk so I should have left the calls to sleep() out until I had the basics working. I keep forgetting the python's sleep() function is in seconds, NOT milli-seconds so of course it looked like the program was hanging when it was working fine.

If you want to run the code just change the 500 in time.sleep(500) to a small number of seconds and you can see that the functions are being called but then tie up the thread. Time to start working with the threading and multi-processor modules...


RE: Call a class method from another (Tk GUI) class? - woooee - Jan-10-2020

Instead of time.sleep, use tkinter's after method.


RE: Call a class method from another (Tk GUI) class? - Marbelous - Jan-15-2020

Thanks for your reply! I'm using .after() in the Event class to poll for when an event is ready to run but I can't use it to replace sleep since sleep is only a simulation for testing. In the real code, an event like collect_data will cause real hardware to scan a bunch of signals and it will actually take a few seconds. I don't want the GUI to go unresponsive so I'm pretty sure the best solution is threading (or multi-processing). I'm working on that now... Slowly. Wall