Home > Blockchain >  Making parts of canvas transparent while still detecting and blocking mouse clicks in transparent ar
Making parts of canvas transparent while still detecting and blocking mouse clicks in transparent ar

Time:09-17

I'm trying to make a program where the user can paint on the screen. So I want to make an invisible canvas window in fullscreen where only the user's pen marks on the canvas will be visible. The closest thing I found is this function: root.attributes("-transparentcolor","color code here"), which will make all the parts of the window that's in the color you give transparent. So if I give the second parameter the background color of the canvas, then only the pen strokes on the canvas will be visible. This is so close to what I want, except for one thing, the transparent areas can't detect or block mouse clicks! Any mouse clicks will just go through to whatever is behind the tkinter window. Is there a way to make it so the transparent areas will still block mouse clicks? I really need help on this!

CodePudding user response:

Here is a much better way to do this using only tkinter. Explanation is in code comments. Basically uses two windows, one for "blocking" the mouse and being transparent using the "-alpha" attribute and the other window for "hosting" canvas and having one completely transparent color while keeping others opaque using "-transparentcolor" attribute. That also means that this is cross-platform solution too (except I think the -transparentcolor attribute differs a little bit on other OS like Linux where I think it is -splash or sth and maybe something different on MacOS):

from tkinter import Tk, Toplevel, Canvas


# setting the starting coordinate of the line so that
# on motion it is possible to immediately draw it
def set_first(event):
    points.extend([event.x, event.y])


# on motion append new coordinates to the list and if there are
# 4 (the minimum), create a new line and save the id
# otherwise update the existing line
def append_and_draw(event):
    global line
    points.extend([event.x, event.y])
    if len(points) == 4:
        line = canvas.create_line(points, **line_options)
    else:
        canvas.coords(line, points)


# when released clear the list to not waste space
# and not necessarily but also set "id" to None
def clear_list(event=None):
    global line
    points.clear()
    line = None


line = None  # this is a reference to the current line (id)
points = []  # list to keep track of current line coordinates
line_options = {}  # dictionary to allow easier change of line options

# just a variable to more easily store the transparent color
transparent_color = 'grey15'

# creating the root window which will help with drawing the line
# because it will "block" mouse because `-alpha` (0.01 seems to be the lowest value)
# attribute is used, however it makes everything transparent on the window
# so need another window to "host" the canvas
root = Tk()
root.attributes('-alpha', 0.01)
root.attributes('-topmost', True)
root.attributes('-fullscreen', True)

# just press Esc key to close the whole thing, otherwise
# it is only doable by pressing Alt   F4 or turning off
# the computer
root.bind('<Escape>', lambda e: root.quit())

# create the host window, because it allows to have only
# one transparent color while keeping the other opaque and
# visible
top = Toplevel(root)
top.attributes('-transparentcolor', transparent_color)
top.attributes('-topmost', True)
top.attributes('-fullscreen', True)

# set the focus to root because that is where events are bound
root.focus_set()

# create the canvas to draw on
canvas = Canvas(top, bg=transparent_color, highlightthickness=0)
canvas.pack(fill='both', expand=True)

# bind all the events to `root` which "blocks" mouse
# but is also almost (because it has a very small alpha value
# it is not entirely invisible but human eye won't notice much)
# invisible
root.bind('<Button-1>', set_first)
root.bind('<B1-Motion>', append_and_draw)
root.bind('<ButtonRelease-1>', clear_list)

root.mainloop()

CodePudding user response:

Here is an improvable example (you may need to pip install pyautogui, ctypes is a built-in library), it is also Windows only as far as I know:

Note: The other answer using two windows, however, is a lot better but I will keep this too just for the information.

from tkinter import Tk, Canvas
import pyautogui as pag
import ctypes


data = {
    'draw': True,
    'cur_line_points': [],
    'cur_line_id': None
}


# function taken mainly from here: https://stackoverflow.com/a/46596592/14531062
def is_pressed(btn: str = 'left') -> bool:
    if btn == 'left':
        btn = 0x01
    elif btn == 'right':
        btn = 0x02
    else:
        raise Warning("incorrect argument, should be 'left' or 'right'")

    return ctypes.windll.user32.GetKeyState(btn) not in (0, 1)


def draw_line(canvas_):
    if not data['draw']:
        root.after(10, draw_line, canvas_)
        return

    pressed = is_pressed('left')
    cur_line_points = data['cur_line_points']
    cur_line_id = data['cur_line_id']
    if not pressed:
        if cur_line_id is not None:
            canvas_.coords(cur_line_id, cur_line_points)
        data['cur_line_id'] = None
        cur_line_points.clear()
    else:
        mouse_x, mouse_y = pag.position()
        cur_line_points.extend((mouse_x, mouse_y))
        len_points = len(cur_line_points)
        if len_points == 4:
            data['cur_line_id'] = canvas_.create_line(cur_line_points)
        elif len_points > 4:
            canvas_.coords(cur_line_id, cur_line_points)
    root.after(10, draw_line, canvas_)


transparent_color = 'grey15'

root = Tk()
root.config(bg=transparent_color)
root.attributes('-transparentcolor', transparent_color)
root.attributes('-topmost', True)
root.attributes('-fullscreen', True)

canvas = Canvas(root, bg=transparent_color, highlightthickness=0)
canvas.pack(fill='both', expand=True)

draw_line(canvas)

root.mainloop()

Basically detects if mouse button is pressed using the built-in library ctypes and if it is adds the current mouse coordinates (does that using pyautogui library which may need be installed) to a list and then draws a line based on that list (it also keeps the reference of the currently drawn line and simply changes its coordinates instead of drawing a new line each time it loops), the only slight issue is that while drawing the mouse is also interacting with the window below, highlighting text and stuff, couldn't really figure out how to remove that yet but at least you get to draw a line.

  • Related