Search code examples
pythonuser-interfacetkinterprintingflush

Display python (constantly changing) 'print output' to tkinter GUI textbox (flush?)


I am new to tkinter and I coded a live cps counter that displays your current cps, the cps value is therefore constantly changing. All I want to do Is to display this output in the tkinter GUI or an other GUI instead of my pycharm outpout box.

This is the output I get, note that the cps value is changing constantly

10.4 cps

In my code there is this code line that prints the cps:

print(raw_raw/sec, 'cps', end="")

Is there any way I can make this line 'print' in the tkinter GUI?

There is also this line:

print("\r", end="")

That makes the cps count stay in the same line by replacing the previous line to not print an infinite list of cps values

full code:

from pynput.mouse import Listener
import time

raw_clicks = 0
sec = 0.6

def on_click(x, y, button, pressed):
    global start
    listener.stop()
    start = time.time()
    print('')

with Listener(on_click=on_click) as listener:
    listener.join()

while True:
    def on_click(x, y, button, pressed):
            global raw_clicks
            global sec
            global start
            raw_clicks = raw_clicks + 1
            if time.time() - start > sec:
                listener.stop()
                raw_raw = (raw_clicks / 2)
                raw_raw = round(raw_raw/sec, 1)
                print("\r", end="")
                print(raw_raw, 'cps', end="")
                raw_clicks = 0
                start = time.time()

    with Listener(on_click=on_click) as listener:
        listener.join()

Thanks for your help


Solution

  • I made an example displaying Your cpi in cmdline, tkinter and pyqt5.

    I simplified Your cpi counter and made it in class. Sorry to change Your code completely, but like that it's more suitable to work with multiple output interfaces. In principle, You can use Your code, but it must not be blocking.

    Let's start with file click_counter.py, it will hold ClickCounter class, which is our back-end, counting clicks in user defined interval (default is 1 second).

    from threading import Thread, Event
    from typing import Callable
    
    from pynput.mouse import Listener
    
    
    class ClickCounter(Thread):
        """Click counter, counting clicks / interval"""
        click_count = 0
        stopped = Event()
    
        def __init__(self, callback: Callable, interval: float = 1.0) -> None:
            super().__init__()
            self.interval = interval
            self.callback = callback
            self.listener = Listener(on_click=self.click_counter)
    
        def run(self) -> None:
            """Start mouse click listener and timer"""
            self.listener.start()
    
            while not self.stopped.wait(self.interval):
                # Call callback with click counter value, after interval expires
                self.callback(self.click_count)
                # Reset counter
                self.click_count = 0
    
        def click_counter(self, x: float, y: float, button: int, pressed: bool) -> None:
            """Count clicks"""
            if pressed:
                # Count when mouse button is pressed
                self.click_count += 1
    
        def cancel(self) -> None:
            """Cancel counter timer"""
            # Stop timer
            self.stopped.set()
            # Stop listener
            self.listener.stop()
    

    Then we can define different output interfaces.
    Let's start with cps_cmdline.py, which displays cps in command line:

    import signal
    from time import sleep
    
    from click_counter import ClickCounter
    
    
    def print_counter(count):
        print("\r", end="")
        print(count, 'cps', end="")
    
    
    click_counter = ClickCounter(print_counter)
    click_counter.start()
    
    stopped = False
    
    def exit_app(*args):
        """Close counter and app"""
        global stopped, click_counter
        click_counter.cancel()
        stopped = True
    
    # Register kill signals
    signal.signal(signal.SIGINT, exit_app)
    signal.signal(signal.SIGTERM, exit_app)
    
    while not stopped:
        # Just run until stopped
        sleep(0.1)
    

    To display cps in tkinter, use code in file cps_tkinter.py:

    import tkinter as tk
    
    from cps.click_counter import ClickCounter
    
    
    class Window(tk.Frame):
    
        def __init__(self, master=None):
            """Create label and StringVar holding its text"""
            super().__init__(master)
            self.master = master
            self.pack(fill=tk.BOTH, expand=1)
            self.cps_text = tk.StringVar(value="0 cps")
            self.cps_label = tk.Label(self, textvariable=self.cps_text)
            self.cps_label.place(relx=0.5, rely=0.5, anchor='center')
    
        def print_counter(self, count):
            """Thread safe variable set"""
            self.after(0, self.cps_text.set, f"{count} cps")
    
    if __name__ == "__main__":
        root = tk.Tk()
        app = Window(root)
        root.wm_title("CPS counter")
        root.geometry("100x100")
    
        # Create and start counter
        click_counter = ClickCounter(app.print_counter)
        click_counter.start()
    
        # Start tkinter app
        root.mainloop()
        # tkinter app is over, cancel click_counter
        click_counter.cancel()
    

    And last but not least cps_qt5.py:

    import sys
    
    from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt
    from PyQt5.QtWidgets import QApplication, QWidget, QLabel, QHBoxLayout
    
    from cps.click_counter import ClickCounter
    
    
    class Window(QWidget):
        sig_print_counter = pyqtSignal(int)
    
        def __init__(self, parent=None):
            """Create label and StringVar holding its text"""
            super().__init__(parent)
            self.cps_label = QLabel()
            self.cps_label.setText("0 cps")
            self.resize(100, 100)
            layout = QHBoxLayout()
            layout.setAlignment(Qt.AlignHCenter)
            layout.addWidget(self.cps_label)
            self.setLayout(layout)
            self.sig_print_counter.connect(self.print_counter)
    
        @pyqtSlot(int)
        def print_counter(self, count):
            """Thread safe label text set"""
            self.cps_label.setText(f"{count} cps")
    
    
    if __name__ == "__main__":
        app = QApplication(sys.argv)
        window = Window()
        window.show()
    
        # Create and start counter
        click_counter = ClickCounter(window.sig_print_counter.emit)
        click_counter.start()
    
        app.exec()
        # Qt app is over, cancel click_counter
        click_counter.cancel()