Member Avatar for Lomholdt

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.

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.