Захват ключей с помощью TKinter по этому сценарию - PullRequest
3 голосов
/ 16 июня 2020

Я хотел бы либо зафиксировать все нажатия клавиш, либо связать нажатие клавиши с кнопкой. В настоящее время в этой игре нет никаких действий пользователя, кроме нажатия кнопок. Я хотел бы назначить каждой кнопке одну букву клавиатуры. Я также играл с pynput, но поскольку программа уже использует TKinter, похоже, я смогу выполнить sh с его функциями.

У меня может быть метод on_press в основном классе Game, который затем вызывает соответствующую функцию для каждой клавиши (так же, как пользователь нажимает клавишу), или, возможно, есть лучший способ.

Большинство примеров, которые я видел, относятся к объекту, созданному из класса tkinter, но в этом случае он удален из моей основной программы на несколько уровней.

Это игра, которую я получил с GitHub и адаптирую к моим предпочтениям. Так что я стараюсь как можно меньше структурно его изменить.

В Graphics.py я вижу этот код:

class GraphWin(tk.Canvas):

    def __init__(self, title="Graphics Window", width=200, height=200):
        master = tk.Toplevel(_root)
        master.protocol("WM_DELETE_WINDOW", self.close)
        tk.Canvas.__init__(self, master, width=width, height=height)
        self.master.title(title)
        self.pack()
        master.resizable(0,0)
        self.foreground = "black"
        self.items = []
        self.mouseX = None
        self.mouseY = None
        self.bind("<Button-1>", self._onClick)  #original code
        self.height = height
        self.width = width
        self._mouseCallback = None
        self.trans = None

    def _onClick(self, e):
        self.mouseX = e.x
        self.mouseY = e.y
        if self._mouseCallback:
            self._mouseCallback(Point(e.x, e.y)) 

Основная программа выглядит примерно так:

def main():
    # first number is width, second is height
    screenWidth = 800
    screenHeight = 500
    mainWindow = GraphWin("Game", screenWidth, screenHeight)
    game = game(mainWindow)
    mainWindow.bind('h', game.on_press())  #<---- I added this 
    #listener = keyboard.Listener(on_press=game.on_press, on_release=game.on_release)
    #listener.start()
    game.go()
    #listener.join()
    mainWindow.close()

if __name__ == '__main__':
    main()

Я добавил тестовую функцию в Класс игры, и в данный момент он не стреляет.

def on_press(self):
    #print("key=" + str(key))
    print( "on_press")
    #if key == keyboard.KeyCode(char='h'):
    #    self.hit()

Кнопки настраиваются следующим образом:

def __init__( self, win ): 
    # First set up screen
    self.win = win
    win.setBackground("dark green")
    xmin = 0.0
    xmax = 160.0
    ymax = 220.0
    win.setCoords( 0.0, 0.0, xmax, ymax )
    self.engine = MouseTrap( win )

затем позже ...

self.play_button = Button( win, Point(bs*8.5,by), bw, bh, 'Play')
self.play_button.setRun( self.play )
self.engine.registerButton( self.play_button )

И, наконец, код кнопки находится в guiengine.py

class Button:

    """A button is a labeled rectangle in a window.
    It is activated or deactivated with the activate()
    and deactivate() methods. The clicked(p) method
    returns true if the button is active and p is inside it."""

    def __init__(self, win, center, width, height, label):
        """ Creates a rectangular button, eg:
        qb = Button(myWin, Point(30,25), 20, 10, 'Quit') """ 

        self.runDef = False
        self.setUp( win, center, width, height, label )

    def setUp(self, win, center, width, height, label):
        """ set most of the Button data - not in init to make easier
        for child class methods inheriting from Button.
        If called from child class with own run(), set self.runDef""" 

        w,h = width/2.0, height/2.0
        x,y = center.getX(), center.getY()
        self.xmax, self.xmin = x+w, x-w
        self.ymax, self.ymin = y+h, y-h
        p1 = Point(self.xmin, self.ymin)
        p2 = Point(self.xmax, self.ymax)
        self.rect = Rectangle(p1,p2)
        self.rect.setFill('lightgray')
        self.rect.draw(win)
        self.label = Text(center, label)
        self.label.draw(win)
        self.deactivate()

    def clicked(self, p):
        "Returns true if button active and p is inside"
        return self.active and \
               self.xmin <= p.getX() <= self.xmax and \
               self.ymin <= p.getY() <= self.ymax

    def getLabel(self):
        "Returns the label string of this button."
        return self.label.getText()

    def activate(self):
        "Sets this button to 'active'."
        self.label.setFill('black')
        self.rect.setWidth(2)
        self.active = True

    def deactivate(self):
        "Sets this button to 'inactive'."
        self.label.setFill('darkgrey')
        self.rect.setWidth(1)
        self.active = False

    def setRun( self, function ):
        "set a function to be the mouse click event handler"
        self.runDef = True
        self.runfunction = function

    def run( self ):
       """The default event handler.  It either runs the handler function
       set in setRun() or it raises an exception."""
       if self.runDef:
           return self.runfunction()
       else:
           #Neal change for Python3
           #raise RuntimeError, 'Button run() method not defined'
           raise RuntimeError ('Button run() method not defined')
           return False  # exit program on error

Запрошен дополнительный код:

class Rectangle(_BBox):

    def __init__(self, p1, p2):
        _BBox.__init__(self, p1, p2)

    def _draw(self, canvas, options):
        p1 = self.p1
        p2 = self.p2
        x1,y1 = canvas.toScreen(p1.x,p1.y)
        x2,y2 = canvas.toScreen(p2.x,p2.y)
        return canvas.create_rectangle(x1,y1,x2,y2,options)

    def clone(self):
        other = Rectangle(self.p1, self.p2)
        other.config = self.config
        return other

class Point(GraphicsObject):
    def __init__(self, x, y):
        GraphicsObject.__init__(self, ["outline", "fill"])
        self.setFill = self.setOutline
        self.x = x
        self.y = y

    def _draw(self, canvas, options):
        x,y = canvas.toScreen(self.x,self.y)
        return canvas.create_rectangle(x,y,x+1,y+1,options)

    def _move(self, dx, dy):
        self.x = self.x + dx
        self.y = self.y + dy

    def clone(self):
        other = Point(self.x,self.y)
        other.config = self.config
        return other

    def getX(self): return self.x
    def getY(self): return self.y

Обновление

Некоторые примечания, которые я поместил в комментарии: Он использует это: http://mcsp.wartburg.edu/zelle/python/graphics.py График Джона Зелле c .py.

http://mcsp.wartburg.edu/zelle/python/graphics/graphics.pdf - общие методы см. В классе _BBox (GraphicsObject).

Я вижу, что у класса GraphWin есть любой ключ, где он захватывает ключи. Но как мне вернуть это в свою основную программу, особенно как событие, которое сработает, как только пользователь его наберет?

Нужно ли мне писать собственный слушатель - см. Также python графическую функцию win.getKey ()? …. В этом посте я oop жду ключей. Я не уверен, где бы я поместил такое время l oop, и как это сработает в классе «Game», чтобы запустить событие. Мне нужно написать свой собственный слушатель?

Ответы [ 2 ]

5 голосов
/ 21 июня 2020

Причина, по которой команда on_press() не запускается, связана с тем, что вызов .bind() привязан к экземпляру Canvas. Это означает, что виджет холста должен иметь фокус для регистрации нажатия клавиши.

Используйте bind_all вместо bind.

Альтернативы исправлению:

  1. Использование mainWindow.bind_all("h", hit) - для привязки буквы h непосредственно к функции обработчика кнопки "нажатия" (просто убедитесь, что функция нажатия имеет следующую подпись: def hit(self, event='')

  2. Использование mainWindow.bind_all("h", game.on_press) - привязывает нажатие клавиши ко всему приложению

  3. Использование root.bind("h", game.on_press) - связывает нажатие клавиши с окном root (возможно, toplevel здесь более точен в зависимости от on, если их несколько windows)

Что касается перехвата любого ключа, здесь есть несколько примеров выполнения этого с использованием спецификатора события "<Key>": https://tkinterexamples.com/events/keyboard/keyboard.html

0 голосов
/ 21 июня 2020

всякий раз, когда вы хотите объединить ввод с помощью мыши и клавиатуры с виджетами, я настоятельно рекомендую вам использовать встроенный метод .bind(). .bind() может иметь два значения:

  1. Тип ввода
  2. Имя функции обратного вызова

Пример:

entry_name.bind("<Return>", function_name_here)

Это вызовет функцию function_name_here() всякий раз, когда будет нажата клавиша Return или Enter на клавиатуре. Этот метод доступен практически для всех виджетов tk. Вы также можете указать комбинации клавиш, а также элементы управления мышью (Ctrl-K, щелчок левой кнопкой мыши и т. Д. c.). Если вы хотите привязать несколько нажатий клавиш к определенной функции обратного вызова, просто свяжите их с помощью одной и той же функции обратного вызова, например:

entry_name.bind("<Return>", function_name_here)
entry_name.bind("<MouseButton-1>", function_name_here)

Это позволит вам вызывать функцию при любом из Return или Нажата левая кнопка мыши.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...