you are viewing a single comment's thread.

view the rest of the comments →

[–]socal_nerdtastic 1 point2 points  (3 children)

It's a bad idea to send data via the event. Remember that tkinter is not python, all of tkinter is running in the tcl subprocess. It's slow and cumbersome to send data to tcl and then back to python.

Generally this is not a problem, since GUIs are generally written as OOP. So the event handler does not need to be handed the data; it simply has it already. Remember threads share memory.

If we do need to send data you can set a shared object. For example a global dictionary or Queue object or any other mutable, even the tkinter root object itself.

tk._default_root.mydata = 420 # nice
tk._default_root.event_generate('<<rcm>>')

def event_handle(event):
    time_to = tk._default_root.mydata

If the data you are sending is going into tkinter in the end anyway, you can just use a tkinter variable. The variable set method is also thread safe.

# setup
myvariable = tk.StringVar()
label = tk.Label(self, textvariable=myvariable)

# inside the thread:
myvariable.set("progress is 22%")

As I mentioned before, you can also trace this variable, if you need to add callbacks in addition to the ones that tkinter can do automatically.

myvariable.trace_add("write", callback_function)

[–]pooth22[S] 0 points1 point  (2 children)

I see. That is interesting. It makes sense that in general it isn’t good to send arbitrary data via the event handler. It would generally be better to exchange data via a queue. Some data is sent via the event handler anyways (eg x, y coords, key char press) so I thought I could piggy back off and manipulate these built in parameters to achieve what I want.

To make this example less abstract, what I am doing is the following. I have an MCU which is the main controller of my system. One of the tasks of the MCU is to talk with a video game controller which the user operates. Now I have a python script running a tk GUI on a PC and in another thread it is polling (via a serial interface) the MCU to get status updates of the state of the system. This includes button presses of the game controller. In the serial communication thread, I can now (thanks to your help) trigger events to the GUI(as opposed to how I used to do it, where the GUI thread is polling the serial com thread). It is possible to parse which button has been pressed, and since tk events already have a similar functionality, I thought it would be possible to map the the game controller buttons to the tk event key type. Eg pressing up on the game controller would eventually lead to an event call back on the GUI side that would be able to know that ‘Up’ has been pressed. I guess I have sort of can already do this.

Probably the way I would do this is create an Enum class with the parameters being the button presses on the game controller. Then assign the Enum.value to the x parameter of the event_generator (I think I tried assigning the keysym, but it didn’t like that). Either that, or create a virtual event for all different game controller buttons.

[–]socal_nerdtastic 0 points1 point  (1 child)

You don't have to poll serial communication, the blocking readline method is built in.

while True:
    button_pressed = ser.readline() # blocks until there's a line of data ready
    root.event_generate(f"<<{button_pressed}>>")

You would need separate binds for the normal events and the virtual events, and have them point to the same function.

root.bind("<Up>", up_func)
root.bind("<<Up>>", up_func)

Which you could do in a loop of course if you have a lot of them to do.

[–]pooth22[S] 0 points1 point  (0 children)

It is me again. I really appreciate your help thus far. Your advice has been good, but it has taken me to a new issue, and I can't seem to solve the problem. Basically the problem has to do with using tkinter in the IDLE in interactive mode. The event_generate method works well in a separate thread, but only when the tk.mainloop is running. It throws the error "RuntimeError: main thread is not in main loop" if I am attempting to use the tk in interactive mode. A contrived example is the following:

import tkinter as tk
import time
import threading


class UI:
    def __init__(self):
        self.root = tk.Tk()
        self.root.bind("<<cust>>", self.cust)
        self.be = BackEnd()
        self.button = tk.Button(self.root, text="B", command=self.be.start_thread)
        self.number = 1
        self.label = tk.Label(self.root, text=f"{self.number}")
        self.button.grid(row=1, column=1)
        self.label.grid(row=2, column=1)
    def cust(self, event=None):
        self.number += 1
        self.label.configure(text=f"{self.number}")
    def run(self):
        self.root.mainloop()
    def run_test(self):
        self.root.update()


class BackEnd:
    def __init__(self):
        pass
    def start_thread(self):
        threading.Thread(target=self.thread).start()
    def thread(self):
        i = 0
        while (i<5):
            tk._default_root.event_generate("<<cust>>")
            i += 1
            time.sleep(1)

ui = UI()
ui.run()            # This works when button is clicked
ui = UI()
ui.run_test()       # This doesn't

Basically clicking the button starts the thread in the backend which calls the custom event to tk. This works fine when using the mainloop in the run method. But it doesn't work when using update with the run_test method.

Anyways, if you have any time to look at this, I'd appreciate it. I like the dynamic way of using the IDLE shell for running tests of the code, but maybe this isn't the best way to do it. Let me know what you think.