Search code examples
pythontkintertkinter-entry

tkinter Entry Field Auto Update


With every left mouse click, self.LEFT_MB_Counter increments so the value is always changing. I want the value in self.LEFT_MB_Counter to be displayed in the entry field self.left_MB_entry but I'm unable to achieve this.

How can I get the entry field to always update and display the current value in self.LEFT_MB_Counter?

from win32api import GetKeyState
import tkinter.ttk
import tkinter


class MainApplication:
    """Class that creates the widgets and window."""
    def __init__(self, master):
        """Method that creates the class constructor."""
        self.master = master
        self.var = tkinter.IntVar(value=0)   
        self.left_MB_entry = self.Entry(self.var)
        self.left_MB_entry.grid()

    def Entry(self, text_var, justify="center"):
        """Method that defines a default entry field."""
        entry = tkinter.ttk.Entry(self.master, textvariable=text_var, justify=justify)
        return entry

class MouseCounter:
    """Class that counts mouse button clicks."""
    def __init__(self):
        """Method that creates the class constructor."""
        self.LEFT_MB = 0x01  # Virtual-key code from Microsoft for LEFT MButton
        self.Old_State_LEFT_MB = GetKeyState(self.LEFT_MB)  # LEFT MButton Down = -127 or -128, MButton Up = 0 or 1
        self.LEFT_MB_Counter = 0  # Initialize to 0

    def count(self):
        # Following block of code monitors LEFT MButton
        New_State_LEFT_MB = GetKeyState(self.LEFT_MB)
        if New_State_LEFT_MB != self.Old_State_LEFT_MB:  # Button state changed
            self.Old_State_LEFT_MB = New_State_LEFT_MB
            print(New_State_LEFT_MB)
            if New_State_LEFT_MB < 0:
                self.LEFT_MB_Counter += 1
                print("Count:", self.LEFT_MB_Counter)
                print('Left Button Pressed')
            else:
                print('Left Button Released')
        root.after(1, self.count)


root = tkinter.Tk()
root.style = tkinter.ttk.Style()
#  ('winnative', 'clam', 'alt', 'default', 'classic', 'vista', 'xpnative')
root.style.theme_use("clam")
APP = MainApplication(root)  # Create object instance of the user interface
root.after(0, MouseCounter().count())
root.mainloop()  # Display the user interface

Solution

  • Solution overview

    The root of the problem is simply that the instance of MouseCounter has no reference to the application, and thus can't affect anything in the application. This isn't anything unique to tkinter, it's just a fundamental python principle. To change an object you need a reference to an object.

    Once you make sure that the instance of MouseCounter has a reference to the instance of MainApplication, the problem becomes fairly trivial to solve.

    Solution details

    The first thing you need to do is properly associate var with the entry widget. Your code is passing it as a positional argument which is not the proper way to do it. You need to assign the variable to the textvariable attribute:

        self.var = tkinter.IntVar(value=0)   
        self.left_MB_entry = self.Entry(textvariable=self.var)
    

    Next, you need to make sure that MouseCounter is able to be passed an instance of the main application:

    class MouseCounter:
        """Class that counts mouse button clicks."""
        def __init__(self, master):
            self.master = master
            ...
    

    When you create the instance, pass in APP as the instance of MainApplication:

    APP = MainApplication(root)
    mc = MouseCounter(APP) 
    root.after(0, mc.count)
    

    Next, you simply need to update var from your counter:

    def count(self):
        ...
            if New_State_LEFT_MB < 0:
                ...
                self.master.var.set(self.LEFT_MB_Counter)
                ...
    

    Note: you have a slight misunderstanding of how after works. It doesn't affect the code, but if you're going to use it, you should use it properly.

    Consider this code:

    root.after(0, MouseCounter().count())
    

    It is functionally identical to the following:

    result = MouseCounter().count()
    root.after(0, None)
    

    after requires a reference to a function, not an actual function call. You need to remove the trailing parenthesis:

    root.after(0, MouseCounter().count)
    

    Even better would be to create the instance of MouseCounter and save a reference so that it doesn't get reaped by the garbage collector:

    counter = MouseCounter()
    root.after(0, counter.count)