Issue
I would either like to capture all key strokes, or associate a key stroke to a button. At this time, there is no user input in this game other than the user clicking buttons. I would like to assign a single keyboard letter to each button. I was also playing with pynput, but since the program is already using TKinter, seems like I should be able to accomplish it with its features.
I can either have an on_press method in the main Game class that then calls the appropriate function for each key (same as user click the key), or perhaps there is a better way.
Most of the examples I've seen deal with the object created from tkinter class, but in this case, it's removed from my main program several levels.
This is a game I got from GitHub, and adapting to my preferences. So I'm trying to change it as little as possibly structurally.
In Graphics.py, I see this code:
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))
The main program is basically something like this:
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()
I added a test function in the Game class, and it currently is not firing.
def on_press(self):
#print("key=" + str(key))
print( "on_press")
#if key == keyboard.KeyCode(char='h'):
# self.hit()
Buttons are setup like this:
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 )
then later...
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 )
And finally, the Button code is in 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
Extra code requested:
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
Update
Some of the notes I put in the comments: It is using this: http://mcsp.wartburg.edu/zelle/python/graphics.py John Zelle's graphic.py.
http://mcsp.wartburg.edu/zelle/python/graphics/graphics.pdf - See class _BBox(GraphicsObject): for common methods.
I see class GraphWin has an anykey - where it captures the keys. But how would I get that back in my main program, especially as an event that would fire as soon as the user typed it?
Do I need to write my own listener - see also python graphics win.getKey() function?…. That post has a while loop waiting on the keys. I'm not sure where I would put in such a while loop, and how that would trigger into the "Game" class to fire an event. Do I need to write my own listener?
Solution
The reason the on_press()
command not firing is due to the fact that the .bind()
call is bound to an instance of Canvas
. This means the canvas widget must have the focus for the keypress to register.
Use bind_all
instead of bind
.
Alternatives to fixing this:
Using
mainWindow.bind_all("h", hit)
- to bind the letter h directly to the "hit" button handler function (just make sure the hit function has the signature as follows:def hit(self, event='')
Using
mainWindow.bind_all("h", game.on_press)
- binds the keypress to the whole applicationUsing
root.bind("h", game.on_press)
- binds the keypress to the root window (maybe atoplevel
is more accurate here depending on if there are multiple windows)
Related to catching any key, there are some examples over here about doing that, using the "<Key>"
event specifier: https://tkinterexamples.com/events/keyboard
Answered By - Tresdon
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.