0

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
martineau
  • 119,623
  • 25
  • 170
  • 301
probat
  • 1,422
  • 3
  • 17
  • 33
  • This doesn't do what you think it does: `root.after(0, MouseCounter().count())`. It runs `MouseCounter().count()` _immediately_, and then schedules `None` to be run in zero milliseconds. – Bryan Oakley Feb 25 '18 at 21:27
  • If you want to be helpful, you should post a reply with suggestions to fix the issue... instead of just trying to point out what is wrong. `root.after(0, MouseCounter().count()` runs after `root.mainloop()` and it is working. It calls the method `count()` and inside the method there is `root.after(1, self.count)` which calls itself so it keeps running which is my desired functionality so the program is constantly looking for mouse clicks. It is replacing a while loop I had. If you would like to respond, please be specific in what to change and why. Thank you. – probat Feb 25 '18 at 21:58
  • _"root.after(0, MouseCounter().count() runs after root.mainloop()"_ - no, it doesn't. The `()` after `count` causes it to run immediately. – Bryan Oakley Feb 25 '18 at 22:02
  • 1
    Another question: are you expecting this to count all clicks system-wide, or only clicks that happen when this program has the focus? – Bryan Oakley Feb 25 '18 at 22:04
  • After method was implemented based on this: [link] (https://stackoverflow.com/questions/25753632/tkinter-how-to-use-after-method) It is stated the after method is after mainloop. – probat Feb 25 '18 at 22:04
  • All click events system wide. – probat Feb 25 '18 at 22:05
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/165792/discussion-between-jmm5351-and-bryan-oakley). – probat Feb 25 '18 at 22:08
  • 1
    Read that answer more closely. `after(0, foo())` runs foo _immediately_. `after(0, foo)` schedules `foo` to run in the future. See the difference? That's not the main problem, however. – Bryan Oakley Feb 25 '18 at 22:08
  • fixed removed parenthesis. What is the main problem? The solution that martineau seems to work fine. Also, it would be much easier to chat in a room if you would accept. – probat Feb 25 '18 at 22:12

3 Answers3

2

One option is to make LEFT_MB_Counter an IntVar that can be used directly by the Entry:

from multiprocessing import Process, Pipe
import time
from win32api import GetKeyState
import tkinter.ttk
import tkinter


class MainApplication(object):
    """Class that creates the widgets and window."""
    def __init__(self, master):
        """Method that creates the class constructor."""
        self.master = master
        self.mc = MouseCounter(master)
        root.after(0, self.mc.count)
        self.left_MB_entry = self.Entry(self.mc.LEFT_MB_Counter)
        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, master):
        """Method that creates the class constructor."""
        self.master = master
        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 = tkinter.IntVar(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.set(self.LEFT_MB_Counter.get() + 1)
                print("Count:", self.LEFT_MB_Counter.get())
                print('Left Button Pressed')
            else:
                print('Left Button Released')
        self.master.after(1, self.count)


root = tkinter.Tk()
root.style = tkinter.ttk.Style()
root.style.theme_use("clam")
APP = MainApplication(root) 

root.mainloop()  # Display the user interface
FJSevilla
  • 3,733
  • 1
  • 13
  • 20
2

You can easily do it by updating the IntVar attribute named APP.var of the MainApplication instance in the MouseCounter.count() method.

Here's a modified version of your code showing how to do this:

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, variable):
        """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
        self.variable = variable
        self.variable.set(self.LEFT_MB_Counter)

    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
                self.variable.set(self.LEFT_MB_Counter)
                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
mouse_counter = MouseCounter(APP.var)  # Create an instance outside of mainloop.
root.after(0, mouse_counter.count)
root.mainloop()  # Display the user interface
martineau
  • 119,623
  • 25
  • 170
  • 301
2

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)
Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685
  • `var` was properly associated with the Entry widget. In the method Entry I had `Entry(self.master, textvariable=text_var`. I created an instance of `MouseCounter`. I removed the parenthesis as you shown. I also updated the method `count` as you shown, but it doesn't work. Thanks for your help. Class `MouseCounter` does not have master in it which is why I think it failed. I passed master to the class and it still failed. – probat Feb 25 '18 at 22:31
  • @jmm5351: I apologize. I've updated my answer to show how to pass the instance of `MainApplication` to `MouseCounter`. The core of the answer still is to properly associate the variable with the entry, then update the variable. You were not properly associating the variable, because the first positional parameter is the parent of the widget, not the variable. – Bryan Oakley Feb 25 '18 at 22:49
  • @jmm5351: I've tried to address all your concerns. The solution is conceptually very simple, but unfortunately it involves several small changes to your program. – Bryan Oakley Feb 25 '18 at 22:55
  • Thank you greatly for your suggestions and working with me. I fixed each method that created a default widget so the first positional parameter is a reference to the parent of the widget `self.master`. I also made all the other changes you specified and indeed it works. – probat Feb 26 '18 at 00:32
  • @BryanOakley A very nice and detailed explanation that helped me a lot. Congratulations – Konstantinos Chertouras Mar 16 '20 at 12:32