2
votes

I want to fill rectangles just like in paint. When mouse button is pressed I want every rectangle I enter to be filled and otherwise I want no event to take place.

Here Is my code:

from tkinter import Canvas
import tkinter

_width = 50
_height = 50
_size = 8

root = tkinter.Tk()
root.title("draw me a lovely matrix")
canv = Canvas(root, width=_width * _size, height=_height * _size)


class Wrapper:
    btn1d = False


def set_btn1d(value):
    print(value)
    Wrapper.btn1d = value


def toggle_color(rect):
    print('called')
    if Wrapper.btn1d:
        color = canv.itemcget(rect, 'fill')
        canv.itemconfig(rect, fill=("#aaa" if color == '#fff' else '#fff'))


rects = []
canv.bind('<ButtonPress-1>', lambda e, value=True: set_btn1d(value))
canv.bind('<ButtonRelease-1>', lambda e, value=False: set_btn1d(value))
for i in range(_size):
    for j in range(_size):
        rect = canv.create_rectangle(_width * j, _height * i, _width * (j + 1), _height * (i + 1), fill="#fff", width=0)
        rects.append(rect)
        canv.tag_bind(rect, '<Enter>', lambda e, rect=rect: toggle_color(rect))

canv.pack()
root.mainloop()

The problem is that when I press the mouse button only the cell in which the mouse was pressed detects the entrance of mouse pointer(also the one in which mouse will be released at the end)

Any beneficial general advice about my code would be of course much appreciated.

1
Note that it doesn't register <Enter> at all times when left click is pressed.Nae
so what should I do now? is there any workaround?yukashima huksay
You may want to use find_closest as a workaround.Nae
This asks for a <B1-Enter> similar to <B1-Motion> but it doesn't seem to exist.Nae
This is not how you would really do that, you would make a function that would be called with enter that would get the rectangle it is touching and fill it, if that is what you are trying to do.Hippolippo

1 Answers

1
votes

There's no need to use <Enter> at all. Try:

from tkinter import Canvas
import tkinter

_width = 50
_height = 50
_size = 8

root = tkinter.Tk()
root.title("draw me a lovely matrix")
canv = Canvas(root, width=_width * _size, height=_height * _size)
rects = []

# B1-Motion activates every time the mouse is moved with button 1 held down
# See https://effbot.org/tkinterbook/tkinter-events-and-bindings.htm
# We fill every item that the mouse comes in contact with while button 1 is held
# The tag tied to the item the event was called on, "current", points to the
#   item under the pointer when the button was pressed, i.e. the first box.
# Only the x and y properties change, so we configure the item closest to those.
def motion(event):
  canv.itemconfig(canv.find_closest(event.x, event.y), fill="#aaa")
canv.bind("<B1-Motion>", motion)

for i in range(_size):
  for j in range(_size):
    rects.append(canv.create_rectangle(_width * j, _height * i, _width * (j + 1), _height * (i + 1), fill="#fff", width=0))
    # don't need to bind anything to <Enter>

canv.pack()
root.mainloop()

Unfortunately, there's no possible solution using <Enter>. The following non-working code would theoretically be correct if <Enter> worked the way you wanted it to.

import tkinter

_width = 50
_height = 50
_size = 8

root = tkinter.Tk()
root.title("draw me a lovely matrix")
canv = tkinter.Canvas(root, width=_width * _size, height=_height * _size)
rects = []

# This javascript-like approach doesn't work in tkinter because
# <Enter> and <Leave> events aren't called while any mouse buttons
# are held.
def fill(tag):
  canv.itemconfig(tag, fill="#aaa")
def enter(event):
  fill("current")
def mousedown(event):
  fill("current")
  canv.tag_bind("all", "<Enter>", enter, add="+")
def mouseup(event):
  canv.tag_unbind("all", "<Enter>")
canv.bind("<Button-1>", mousedown)
canv.bind("<ButtonRelease-1>", mouseup)

for i in range(_size):
  for j in range(_size):
    rects.append(canv.create_rectangle(_width * j, _height * i, _width * (j + 1), _height * (i + 1), fill="#fff", width=0))

canv.pack()
root.mainloop()

Tested in python 3.8.2, tkinter 8.6