对关键事件使用 pynput 而不是 tkinter

using pynput for key events instead of tkinter

这是我的整个程序。我想从将 tkinter 与 gui 一起使用更改为对代码的 get_key 事件部分使用 pynput no gui。谁能告诉我要使用什么代码?此代码是一台会说话的自动售货机,它从机器内容列表中读取,这是一个由自动售货机公司更新的文件。

我不想使用图形用户界面,因为没有显示器。是覆盆子。

from gtts import gTTS
import pygame
from io import BytesIO
import sys
import time
import csv

pygame.init()
if sys.version_info[0] == 3:
    # for Python3
    from tkinter import *   
else:
    # for Python2
    from Tkinter import *

def say(text):
    tts = gTTS(text=text, slow=False, lang='en-us', lang_check=False)
    fp = BytesIO()
    tts.write_to_fp(fp)
    fp.seek(0)
    pygame.mixer.init()
    pygame.mixer.music.load(fp)
    pygame.mixer.music.play()



def load_list():
    with open(r"/home/pi/VendyLogProject/vendylist.csv", mode="r") as infile:
        return sorted(list(csv.reader(infile)))

def refresh_list():
    global vl, vl2, baseposition
    new_items = load_list()
    if vl != new_items:
        vl = new_items
        vl2 = [item[0] for item in vl]
        baseposition = vl[0]






vl = load_list()

vl2 = [item[0] for item in vl]

baseposition = vl[-1] # so when reading through it reads entry 0 first, then 1




def current(event=None):
        global baseposition # baseposition was defined outside of the function, therefore we call global
        say(baseposition[1]+baseposition[0])



def back(event=None):
        global baseposition
        currentposition = vl.index(baseposition)
        if currentposition == 0: 
                baseposition = vl[-1]
                say(baseposition[1]+baseposition[0])

        else:
                previousposition = int(currentposition) - 1 # previousposition is previous position
                baseposition = vl[previousposition]
                say(baseposition[1]+baseposition[0])



def forward(event=None):
        global baseposition
        currentposition = vl.index(baseposition)
        if currentposition == (len(vl) - 1):
                baseposition = vl[0]
                say(baseposition[1]+baseposition[0])
        else:
                nextposition = int(currentposition) + 1 # nextposition is next position
                baseposition = vl[nextposition]
                say(baseposition[1]+baseposition[0])


def readnumber(int):
           global vl
           for item in vl:
            global baseposition
            currentposition = vl.index(baseposition)
            if int == item[0]:
                      baseposition = vl[vl.index(item)]
                      say(baseposition[1]+baseposition[0])


def help():
           say("Welcome to Vendy log! Use the plus and minus keys to go through the list of snacks or push a number to hear its contents!")







root = Tk()
prompt = '      VendyLog      '
label1 = Label(root, text=prompt, width=len(prompt))
label1.pack()

#keys buffer
keybuf = []

def test_after():
           if keybuf:
                      num = ''.join(keybuf)
                      keybuf.clear()




def get_key(event):
           keybuf.append(event.char)
           event.char = ''.join(keybuf)
           root.after(500,test_after)
           if event.char == '-':
                      back()
           elif event.char == '+':
                      forward()
           elif event.char == '.':
                      current()
           elif event.char in vl2:
                      readnumber(event.char)
           elif event.char == '00':
                      help()
           elif event.char == '462.':
                sys.exit()

def refresh_list_and_enqueue_next_refresh():
    refresh_list()
    root.after(60000, refresh_list_and_enqueue_next_refresh)




refresh_list_and_enqueue_next_refresh()                                            

root.bind_all('<Key>', get_key)

root.mainloop()

我编辑了这条评论,因为在我玩过 OpenGLPygame 之后,我找到了如何使用 pynputtkinter.

这是我编写的代码示例,用于测试它是否有效。

# // Imports
import tkinter, pynput
from tkinter import messagebox

# // Global variables
# // If we define variable type I found that will speed up execution a little
root:object = tkinter.Tk()
app_title:str = "Tkinter and Pynput"
app_size:tuple = (300, 150)
listener_stop:bool = False


# // Logics Keyboard
class Keyboard:
    # On button pressed
    # On my case I found the "Fn" button from laptop is not triggered...
    @staticmethod
    def Pressed(key) -> bool:
        # If listener_stop is True then stop listening
        if listener_stop: print("Keyboard Events are stoped!"); return False
        # Else show pressed key
        else: print(f"Keyboard pressed: {key}")

    # On button released
    @staticmethod
    def Released(key) -> None:
        print(f"Keyboard released: {key}")

    # Listen keybboard buttons
    @staticmethod
    def Listener() -> None:
        k_listen = pynput.keyboard.Listener(on_press=Keyboard.Pressed,
                                            on_release=Keyboard.Released
                                            )
        k_listen.start()


# // Logics Mouse
class Mouse:
    # On move
    @staticmethod
    def Move(x, y) -> bool:
        # If listener_stop is True then stop listening
        if listener_stop: print("Mouse Events are stoped!"); return False
        else: print(f"Mouse: Moved to {x}x{y}")

    # On scroll
    @staticmethod
    def Scroll(x, y, dx, dy) -> None:
        where = "down" if dy < 0 else "up"
        print(f"Mouse: Scrolled {where} at {x}x{y}")

    # On click
    # On my case I found mouse wheel press is not triggered...
    @staticmethod
    def Click(x, y, button, pressed) -> None:
        action = "pressed" if pressed else "released"
        print(f"Mouse: {button} was {action} at {x}x{y}")

    # Listen keybboard buttons
    @staticmethod
    def Listener() -> None:
        m_listen = pynput.mouse.Listener(on_move=Mouse.Move,
                                         on_click=Mouse.Click,
                                         on_scroll=Mouse.Scroll
                                         )
        m_listen.start()


# // Logics Define GUI
class MainApp:
    def __init__(self, master):
        self.master = master

        # Create tkinter interface
        self.X = (self.master.winfo_screenwidth() - app_size[0]) // 2
        self.Y = (self.master.winfo_screenheight() - app_size[1]) // 2
        self.master.wm_title(app_title)
        self.master.wm_geometry(f"{app_size[0]}x{app_size[1]}+{self.X}+{self.Y}")

        # Magic hapen here :P
        self.Screen(self.master)
        self.InputEvents()

    # Define Screen Informations
    def Screen(self, root) -> None:
        # Set the main frame
        self.frm_main = tkinter.Frame(root)
        self.frm_main.pack(expand=True, fill="x", side="top")

        # Defain frame components
        self.title = tkinter.Label(self.frm_main, text=app_title, font=("Comic Sans MS", 18, "bold"), fg="tomato")
        self.title.pack(expand=False, fill="x", side="top")

    # Input events
    def InputEvents(self) -> None:
        Keyboard.Listener()
        Mouse.Listener()

    # Safe Quit
    def SafeQuit(self, master:object = root) -> None:
        global listener_stop

        if messagebox.askokcancel(f"{app_title} Quit", f"Are you shore you want to quit {app_title}?"):
            # We need to make shore if the window was closed and
            # listening event are still runing, then make them stop
            # You will see in fact they are not stoped (from console point view)
            # for that reason we check listener_stop on Mouse Move and on Keyboard Key press.
            # If for some reason the app is quit and listener has not stoped then on first action did will stop
            # Mouse move after app quit >> Mouse listener will stop
            # Keyboard button pressed after app quit >> Keyboard listener will stops
            if listener_stop == False:
                listener_stop = True
                print("Events Listening are stoped!")
            master.destroy()

# // Run if this is tha main file
if __name__ == "__main__":
    app:object = MainApp(root)
    root.protocol("WM_DELETE_WINDOW", app.SafeQuit)
    root.mainloop()

我再次更新了代码,停止监听键盘和鼠标事件。

PS:它们的更新版本可以是found on my github