Tom
Tom

Reputation: 2661

how to design a board in tkinter canvas

I want to design a 9x9 board in tkinter canvas. Each rectangle should have a width and height of 30 (pixels?). Do I always have to use the pixel coordinates for drawing shapes onto the canvas or is there a more relative way possible? For example, my board looks like this:

class TkCanvas(tk.Canvas):
   RECT_WIDTH = 30

   def __init__(self, parent, width=600, height=600, columns=9, rows=9):
      super().__init__(parent, width=width, height=height)
      self.columns=columns
      self.rows=rows
      self.board = [[None for col in range(columns)] for row in range(rows)]

   def draw_board(self, x1=0, x2=0,y1=RECT_WIDTH,y2=RECT_WIDTH):
        for col in range(self.columns):
            for row in range(self.rows):
                x1 = col * self.RECT_WIDTH
                y1 = (self.rows-1-row) * self.RECT_WIDTH
                x2 = x1 + self.RECT_WIDTH
                y2 = y1 + self.RECT_WIDTH
                tag = f"tile{col}{row}"
                self.board[row][col] = self.create_rectangle(x1, y1, x2, y2, fill="white", tags=tag, outline="black")
                self.tag_bind(tag,"<Button-1>", lambda e, i=col, j=row: self.get_location(e,i,j))

   def get_location(self, event, i, j):
        print (i, j)

   def get_x_coord(self, x):
        return x * self.RECT_WIDTH
    
   def get_y_coord(self, y):
        return y * self.RECT_WIDTH

Now when I want to draw a shape I get the exact coordinates x0,y0 first with get_x_coord and get_y_coord and then calculate x1 and y1 by adding the RECT_WIDTH.

Is there a cleaner way to draw the shapes onto the canvas? Something where I would only have to pass in the coordinates, eg. (4,5) and it would automatically draw it in the right rectangle or do I always have to make these calculations?

Upvotes: 0

Views: 359

Answers (1)

Derek
Derek

Reputation: 2244

There are many ways to produce a grid board and yours works fine.

Using relative offsets to position squares and pieces is easy in tkinter Canvas, just use canvas.move(itemID, xoffset, yoffset). You can also move or scale the entire board by using canvas.addtag_all('somename') then canvas.scale('somename', 0, 0, s, s). Where s is a float s > 0

The following code creates class drawBoard that can be instantiated using just two values or by using many control values and demonstrates how to use relative coordinates to build a scalable board.

The board is created by drawing all rectangles at (0, 0, w, h) then moving them to location via relative values (x, y). Once all squares have been created the entire board is scaled to size.

Method get_location displays user input.

import tkinter as tk

back, fore, draw, wall, light = "white", "blue", "red", "black", "yellow"

class drawBoard(tk.Tk):

    def __init__(
        self, w, h, columns = 9, rows = 9, scale = 1, line = 1, border = 1):

        super().__init__()
        
        self.withdraw()
        self.configure(background = light, borderwidth = border)
        # pre calculate sizes and set geometry
        self.store = dict()
        # small change to w, h and wide, high
        x, y, w, h = 0, 0, w + line, h + line
        wide = w * columns * scale + (line==1)
        high = h * rows * scale + (line==1)
        self.geometry(f"{wide + border * 2 }x{high + border * 2}")
        # minimal Canvas
        self.canvas = tk.Canvas(
            self, width = wide, height = high, background = back,
            borderwidth = 0, highlightthickness = 0, takefocus = 1)
        self.canvas.grid(sticky = tk.NSEW)
        # draw the board
        for row in range(rows):
            for column in range(columns):
                item = self.canvas.create_rectangle(
                    0, 0, # removed line, line
                    w, h, width = line,
                    fill = back, outline = fore)

                self.canvas.move(item, x, y)
                self.store[item] = (row, column)
                x = x + w
            x, y = 0, y + h
        # tag all items and scale them
        self.canvas.addtag_all("A")
        self.canvas.scale("A", 0, 0, scale, scale)
        # bind user interaction
        self.bind("<Button-1>", self.get_location)
        self.after(500, self.complete)


    def complete(self):
        self.resizable(0, 0)
        self.deiconify()


    def get_location(self, ev):
        # find user selection
        item = self.canvas.find_closest(
            self.canvas.canvasx(ev.x), self.canvas.canvasy(ev.y))[0]
        # flip color for demo
        fill = self.canvas.itemcget(item, "fill")
        self.canvas.itemconfig(item, fill = [draw, back][fill == draw])
        # extract and display info
        row, column = self.store[item]
        x, y, w, h = self.canvas.coords(item)
        print(f"{row}, {column} >> {x}, {y}, {w-x}, {h-y}")

if True:
    # the easiest way
    app = drawBoard(30, 30)
else:
    # Or with lots of control
    app = drawBoard(
        30, 30, columns = 40, rows = 20, scale = 1, line = 1, border = 1)

Upvotes: 1

Related Questions