3

I am looking to make the background of the tkinter canvas transparent, but still have Mouse events of the canvas, here is my code, I am on Windows 10, Python 3.6:

from tkinter import *
import time

WIDTH = 500
HEIGHT = 500
LINEWIDTH = 1
TRANSCOLOUR = 'gray'
global old
old = ()

tk = Tk()
tk.title('Virtual whiteboard')
tk.wm_attributes('-transparentcolor', TRANSCOLOUR)
canvas = Canvas(tk, width=WIDTH, height=HEIGHT)
canvas.pack()
canvas.config(cursor='tcross')
canvas.create_rectangle(0, 0, WIDTH, HEIGHT, fill=TRANSCOLOUR, outline=TRANSCOLOUR)
def buttonmotion(evt):
    global old
    if old == ():
        old = (evt.x, evt.y)
        return
    else:
       canvas.create_line(old[0], old[1], evt.x, evt.y, width=LINEWIDTH)
        old = (evt.x, evt.y)
def buttonclick(evt):
    global old
    canvas.create_line(evt.x-1, evt.y-1, evt.x, evt.y, width=LINEWIDTH)
    old = (evt.x, evt.y)
canvas.bind('<Button-1>', buttonmotion)
canvas.bind('<B1-Motion>', buttonclick)
while True:
    tk.update()
    time.sleep(0.01)

When run the code, it makes a transparent background, but I select the things under, instead of the canvas.

Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685
Benjy
  • 115
  • 3
  • 12

2 Answers2

4

I build a little workaround with the help of the win api, here is my suggestion:

from tkinter import *
import time
import win32gui
import win32api

WIDTH = 500
HEIGHT = 500
LINEWIDTH = 1
TRANSCOLOUR = 'gray'
title = 'Virtual whiteboard'
global old
old = ()
global HWND_t
HWND_t = 0

tk = Tk()
tk.title(title)
tk.lift()
tk.wm_attributes("-topmost", True)
tk.wm_attributes("-transparentcolor", TRANSCOLOUR)

state_left = win32api.GetKeyState(0x01)  # Left button down = 0 or 1. Button up = -127 or -128

canvas = Canvas(tk, width=WIDTH, height=HEIGHT)
canvas.pack()
canvas.config(cursor='tcross')
canvas.create_rectangle(0, 0, WIDTH, HEIGHT, fill=TRANSCOLOUR, outline=TRANSCOLOUR)

def putOnTop(event):
    event.widget.unbind('<Visibility>')
    event.widget.update()
    event.widget.lift()
    event.widget.bind('<Visibility>', putOnTop)
def drawline(data):
    global old
    if old !=():
        canvas.create_line(old[0], old[1], data[0], data[1], width=LINEWIDTH)
    old = (data[0], data[1])

def enumHandler(hwnd, lParam):
    global HWND_t
    if win32gui.IsWindowVisible(hwnd):
        if title in win32gui.GetWindowText(hwnd):
            HWND_t = hwnd

win32gui.EnumWindows(enumHandler, None)

tk.bind('<Visibility>', putOnTop)
tk.focus()

running = 1
while running == 1:
    try:
        tk.update()
        time.sleep(0.01)
        if HWND_t != 0:
            windowborder = win32gui.GetWindowRect(HWND_t)
            cur_pos = win32api.GetCursorPos()
            state_left_new = win32api.GetKeyState(0x01)
            if state_left_new != state_left:
                if windowborder[0] < cur_pos[0] and windowborder[2] > cur_pos[0] and windowborder[1] < cur_pos[1] and windowborder[3] > cur_pos[1]:
                drawline((cur_pos[0] - windowborder[0] - 5, cur_pos[1] - windowborder[1] - 30))
            else:
                old = ()
    except Exception as e:
        running = 0
        print("error %r" % (e))

Shot explanation of the new code bits:

tk.lift()
tk.wm_attributes("-topmost", True)

...

def putOnTop(event):
event.widget.unbind('<Visibility>')
event.widget.update()
event.widget.lift()
event.widget.bind('<Visibility>', putOnTop)

...

tk.bind('<Visibility>', putOnTop)
tk.focus()

These lines ensure, that the window will be always be on top of all other windows.

global HWND_t
HWND_t = 0

...

def enumHandler(hwnd, lParam):
    global HWND_t
    if win32gui.IsWindowVisible(hwnd):
        if title in win32gui.GetWindowText(hwnd):
            HWND_t = hwnd

win32gui.EnumWindows(enumHandler, None)

This code bit will go through all the windows currently displayed and catches the handle of the whiteboard window (make sure the title is unique, or this could capture the wrong handle).

state_left = win32api.GetKeyState(0x01)

...

if HWND_t != 0:
    windowborder = win32gui.GetWindowRect(HWND_t)
    cur_pos = win32api.GetCursorPos()
    state_left_new = win32api.GetKeyState(0x01)
    if state_left_new != state_left:
        if windowborder[0] < cur_pos[0] and windowborder[2] > cur_pos[0] and windowborder[1] < cur_pos[1] and windowborder[3] > cur_pos[1]:
                drawline((cur_pos[0] - windowborder[0] - 5, cur_pos[1] - windowborder[1] - 30))
    else:
        old = ()

This

  1. Checks, if the handle is found
  2. Checks, if mouse button 1 is clicked or not
  3. Checks, if mouse is inside the window

if all is true, it takes the mouse data and draws the line

the current mode is, that it doesn't draw anything till the button is clicked and then draws until the button is clicked again.

Alex
  • 779
  • 7
  • 15
-1

I'm sure you've thought of this, but have you tried setting the hexadecimal colour as ""?

i.e

canvas = tk.Canvas(width, height, bg = "")

That works for my version of python.

John Liu
  • 162
  • 1
  • 9
  • 4
    What version of Python are you on? Because I get `_tkinter.TclError: unknown color name ""` – Benjy Feb 11 '19 at 05:27
  • 1
    What version of Python are you on? – Gautam Jain May 27 '20 at 12:12
  • What platform are you on? – WinEunuuchs2Unix Jun 15 '21 at 19:46
  • This is only possible with Frames and LabelFrames, as we can see in the [tk docs](https://www.tcl.tk/man/tcl8.7/TkCmd/frame.html). The background option is a widget-specific option for Frames and LabelFrames, thus allowing the empty string, while the other widgets use the [standard option](https://www.tcl.tk/man/tcl8.7/TkCmd/options.html#M-background) – ProblemsLoop May 31 '23 at 12:24