Hello.
I recently finished my 1. semester in Computer Science, and got a good fundamental idea on programming in Python.
Our exam assignment whas to create a game where we had to smash robots by clicking around with the mouse.
The GUI was provided by our instructor.
Now we newer learned about binding, but now that the class is finished, i really want to know how this is possible to do.
I found this code on the internet:
from tkinter import *
root = Tk()
listen = []
def key(event):
print ("pressed", repr(event.char))
listen.append(event.char)
def callback(event):
frame.focus_set()
print ("clicked at", event.x, event.y)
frame = Frame(root, width=500, height=500)
frame.bind("<Key>", key)
frame.bind("<Button-1>", callback)
frame.pack()
root.mainloop()
Here is the GUI we got for the assignement
import threading
import time
try:
# For python 3
from tkinter import Frame,Canvas,Tk,Label,Entry,Button,END
except ImportError:
# Fall back for python 2
from Tkinter import Frame,Canvas,Tk,Label,Entry,Button,END
class GameGUI(Frame):
# The GUI is not alive to begin with
_alive = False
def _teleport(self):
"""Internal function to handle teleport clicks"""
# Sanity checks
if not self._alive:
print("GUI cannot be used, it is dead!")
return None
# Acquire the click-lock
self._click_condition.acquire()
# Inform the other threads that a click has happened
self._clicked = True
self._last_click = ("teleport", "teleport")
self._click_condition.notifyAll()
# Release the click-lock
self._click_condition.release()
def _button_click(self, event):
"""Internal function to handle button clicks"""
# Sanity checks
if not self._alive:
print("GUI cannot be used, it is dead!")
return None
# Acquire the click-lock
self._click_condition.acquire()
# Inform the other threads that a click has happened
self._clicked = True
self._last_click = (max(0,min(event.x//self.pixels,self.cols-1)),max(0,min(event.y//self.pixels, self.rows-1)))
self._click_condition.notifyAll()
# Release the click-lock
self._click_condition.release()
def _clear_command_queue(self):
"""Internal function to clear the command queue"""
while self._command_queue:
command, args = self._command_queue.pop()
if command == "text":
self._label.config(text=args)
elif command == "color":
c,r,color = args
self._canvas.itemconfig(self._squares[(c,r)], fill=color)
elif command == "ask":
self._text_label.config(text=args)
self._text_entry.delete(0, END)
self._text_frame.pack()
self._text_entry.focus()
if self._alive:
self._root.after(10, self._clear_command_queue)
def _start(self):
"""Internal function to setup the frame"""
# Create the window
self._root = Tk()
self._root.protocol("WM_DELETE_WINDOW", self._on_destroy)
Frame.__init__(self, self._root)
self.pack()
# Create the click-lock
self._click_condition = threading.Condition()
# Create the lock for the label
self._text_lock = threading.Lock()
# Create the canvas
self._canvas = Canvas(self, width=self.pixels*self.cols+1, height=self.pixels*self.rows+1)
self._canvas.bind("<ButtonRelease-1>", self._button_click)
self._canvas.pack()
# Create the teleport button
self._teleport = Button(self, text="Teleport", command=self._teleport)
self._teleport.pack()
# Create the colored squares
self._squares = {}
for c in range(self.cols):
for r in range(self.rows):
self._squares[(c,r)] = self._canvas.create_rectangle(
self.pixels*c+1, # x-left
self.pixels*r+1, # y-upper
self.pixels*(c+1)+1, # x-right
self.pixels*(r+1)+1, # y-lower
fill="white") # color
# Creates the label
self._label = Label(self, text="", font=("Comic Sans MS", 12))
self._label.pack()
# Text input widgets
self._text_frame = Frame(self)
self._text_label = Label(self._text_frame)
self._text_button = Button(self._text_frame, text="Gem", command=self._text_save)
self._text_entry = Entry(self._text_frame, text="")
self._text_entry.bind("<Key-Return>", self._text_save)
self._text_label.grid (row=0,column=0)
self._text_entry.grid (row=0,column=1)
self._text_button.grid(row=0,column=2)
# Sets up the command queue
self._command_queue = []
# The GUI is now alive
self._alive = True
# Clear the command queue and continue doing it
self._clear_command_queue()
# Run the main loop
self.mainloop()
def _wait_and_callback(self, callback=None):
# Wait for it to finish initializing
while not self._alive:
time.sleep(0.01)
if callback != None:
threading.Thread(target=callback, args=[self]).start()
def __init__(self, cols=10, rows=10, pixels=35, callback=None):
"""Constructor for the GameGUI. It takes the following arguments:
- cols/rows, which specify the desired size of the board
- pixels, which is the pixel-size of a square
- a callback function, which is called when initialization is done
- a bool, which specifies whether the mainloop should be run by __init__
"""
# Same values
self.cols = cols
self.rows = rows
self.pixels = pixels
if callback == None:
# If no callback is given, start another thread for the main-loop
threading.Thread(target=self._start).start()
self._wait_and_callback()
else:
# If a callback is given, run the main-loop in this thread
threading.Thread(target=self._wait_and_callback, args=[callback]).start()
self._start()
def _on_destroy(self):
"""Called when the GUI is destroyed."""
# The GUI is no longer alive
self._alive = False
# Wait a bit for things to exit cleanly
time.sleep(0.05)
# When the window is destroyed, make sure
# that all remaining calls to get_click
# returns with ("dead", "dead")
click_condition = self._click_condition
self._click_condition.acquire()
self._clicked = True
self._last_click = ("dead", "dead")
self._click_condition.notifyAll()
self._click_condition.release()
# Then pass on the destroy-event
self._root.destroy()
def get_click(self):
"""Waits for the user to click a square, then returns which square was clicked
as a tuple. If the teleport button is clicked, returns ("teleport", "teleport").
If the gui is destroyed before a click is gotten, return ("dead", "dead")."""
# Sanity checks
if not self._alive:
print("GUI cannot be used, it is dead!")
return None
# Acquire the condition-lock
self._click_condition.acquire()
# Set the condition to false, and wait for
# it to become True
self._clicked = False
while not self._clicked:
self._click_condition.wait()
# Return the last click-value and release the lock
retval = self._last_click
self._click_condition.release()
return retval
def set_label(self, text):
"""Sets the label-text."""
# Sanity checks
if not self._alive:
print("GUI cannot be used, it is dead!")
return None
if type(text) != type(""):
print("In set_label: text must be a string!")
return None
self._command_queue.append(("text", text))
def set_color(self, col, row, color="white"):
"""Sets the color of a square."""
# Sanity checks
if not self._alive:
print("GUI cannot be used, it is dead!")
return None
if type(col) != type(0):
print("In set_color: col must be a number!")
return None
if type(row) != type(0):
print("In set_color: row must be a number!")
return None
if type(color) != type(""):
print("In set_color: color must be a string!")
return None
self._command_queue.append(("color", (col, row, color)))
def is_alive(self):
"""Returns whether the GUI is alive."""
return self._alive
def _text_save(self, event=None):
"""Internal function to save the text-result and remove the text-frame."""
self._text_result = self._text_entry.get()
self._text_frame.pack_forget()
def ask_user(self, text):
"""Asks the user for some text-input. Returns None, if the GUI dies before a result is entered."""
# Sanity checks
if not self._alive:
print("GUI cannot be used, it is dead!")
return None
if type(text) != type(""):
print("In ask_user: text must be a string!")
return None
self._text_lock.acquire()
self._text_result = None
self._command_queue.append(("ask", text))
while self._alive and self._text_result == None:
time.sleep(0.01)
retval = self._text_result
self._text_lock.release()
return retval
def test_main():
GameGUI(6,10, callback=test_main_callback)
def test_main_callback(gui):
gui.set_label(gui.ask_user("test"))
teleport_count = 0
while gui.is_alive():
(r,c) = gui.get_click()
if r == "teleport":
teleport_count += 1
gui.set_label("Teleports: " + str(teleport_count))
else:
gui.set_color(r,c,"red")
if __name__ == "__main__":
test_main()
I really want to know how i can implement the binding feature in this GUI, so that it returns a key when the keyboard is pressed (just like the mouse in this case).
Since i don't have much understanding in how the GUI is made, i would really like if someone could come up with a step by step tutorial on how i can do it myself, or just do it for me, so i can go through the code and learn by looking.
Hope someone has the time to help. Thanks.