0

I have the following GUI as shown in the Figure below. Instead of the black box, I wanted to draw a transparent rectangle on top of all the scales covering values 1, 0, and -1 of the scale.

Is there a way to make the Tkinter canvas transparent? If it is not the correct way to do it, what are the alternatives that I could try? I shared the sample code I use. That can be used to reproduce this GUI.

enter image description here

from tkinter import *
import itertools

root = Tk()
root.geometry('840x420')
root.title('Test Window')

variables = {"var1", "var2", "var3", "var4"}
pair_list = list(itertools.combinations(list(variables), 2))
pair_result_dictionary = dict.fromkeys(pair_list)

my_canvas = Canvas()
my_canvas.pack(side=LEFT, fill=BOTH, expand=1)

my_scrollbar = Scrollbar(root, orient=tk.VERTICAL, command=my_canvas.yview)
my_scrollbar.pack(side=RIGHT, fill=Y)

my_canvas.configure(yscrollcommand=my_scrollbar.set)
my_canvas.bind('<Configure>', lambda e: my_canvas.configure(scrollregion=my_canvas.bbox("all")))

second_frame = Frame(my_canvas)

my_canvas.create_window((0, 0), window=second_frame, anchor="nw")

i = 0

heading_label = Label(second_frame, text="Test Window", font=('Arial',16))
heading_label.grid(column=0, row=0, sticky=tk.NW, columnspan=2, padx=(52, 0), pady=(20, 10))

for pair in pair_list:
    sample_scale = tk.Scale(second_frame, from_=9, to=-9, length=600, orient=tk.HORIZONTAL, font=('Arial', 15),
                            tickinterval=1,resolution=1)

    label_left = tk.Label(second_frame, text=pair[0], font=('Arial', 15))
    label_left.grid(column=0, row=2 + i, sticky=tk.W, padx=(52, 0), pady=5)

    sample_scale.set(((sample_scale['from'] - sample_scale['to']) / 2) + sample_scale['to'])
    sample_scale.grid(column=1, row=2 + i, sticky=tk.W, padx=(5, 0), pady=5)

    label_right = tk.Label(second_frame, text=pair[1], font=('Arial', 15))
    label_right.grid(column=2, row=2 + i, sticky=tk.W, padx=(5, 5), pady=5)

    i = i + 100

rectangle_holder_canvas = tk.Canvas(second_frame, width=100, height=70, bd=0, background='#000000')
rectangle_holder_canvas.grid(column=1, row=2, sticky=tk.S, padx=(0, 0), pady=0)
rec = rectangle_holder_canvas.create_rectangle(3, 3, 100, 70, outline='blue', fill='')
rectangle_holder_canvas.tag_raise(rec, 'all')

root.mainloop()

If I use the following code lines, it makes the entire square transparent and will see what's underneath the main window which is not what I want sadly.

root.wm_attributes("-transparentcolor", 'grey')

rectangle_holder_canvas = tk.Canvas(second_frame, width=100, height=70, bd=0, bg="grey")

Appreciate your thoughts and time on how to achieve this.

I read that, "tag_raise" method does not affect canvas window items. To change a window item's stacking order, use a lower or lift method on the window.. So I tried to draw a rectangle on my_canvas by setting second_frame.lower() as shown below code. Then I only see the rectangle but not the scales or labels on the second frame.

my_canvas.create_window((0, 0), window=second_frame.lower(), anchor=CENTER)
rec = my_canvas.create_rectangle(50, 50, 200, 200, outline='blue', fill='blue')
Mad
  • 435
  • 2
  • 17

1 Answers1

1

Your question has no easy answer.

The organisation of canvas objects is divided in two groups. Ordinary graphical objects like rectangle, polygon etc are stacked in the order of their creation, with the first object in the background and the last object in the foreground. The stacking order of such objects can be changed via canvas.lift, canvas.lower, canvas.tag_raise and canvas.tag_lower.

The second group are the canvas window objects, these are created using a fixed stacking order that ALLWAYS sit above graphical objects. Therefore it is not possible to place ordinary graphical objects above window objects.

This would seem to make your objective impossible, however there is (at least) one solution.

The following code achieves your objective by using a Toplevel window that is positioned above the canvas. This window uses wm_attributes("-transparentcolor", color) and overrideredirect(1), it also sets wm_attributes("-topmost", True).

The main window and top window are then bound together via the "Configure" binding so that moving main window will automatically move top window.

This creates the effect you are looking for, although it may not suit your needs.

I've used one of your Scale objects placed in a canvas window object to simulate a fragment of your code.

import tkinter as tk

color = "red"

class transParent(tk.Tk):

    def __init__(self):

        super().__init__()
        self.withdraw()
        self.columnconfigure(0, weight = 1)

        wide, high = 606, 106
        self.geometry(f"{wide}x{high}+20+20")
        
        self.canvas = tk.Canvas(
            self, background = color, highlightthickness = 0, borderwidth = 0)
        self.canvas.grid(sticky = tk.NSEW)

        self.s_scale = tk.Scale(
            self.canvas, from_ = 9, to = -9, length = 600,
            orient = tk.HORIZONTAL, font = "Arial 15",
            takefocus = 1, tickinterval = 1, resolution = 1)
        self.s_scale.set(0)
        self.s_scale.grid(
            column = 0, row = 0, sticky = tk.W, padx = 5, pady = 5)

        self.sample_window = self.canvas.create_window(
            0, 0, window = self.s_scale, anchor = tk.NW)

        self.deiconify()
        self.wait_visibility()

        # build transparency toplevel : width, height, borderwidth
        w, h, bw = 100, 100, 4
        self.top = tk.Toplevel(
            self, padx = 0, pady = 0, background = color,
            highlightthickness = 0, relief = "solid", borderwidth = bw)

        self.X, self.Y = self.winfo_x, self.winfo_y
        self.top.geometry(f"{w}x{h}+{self.X()}+{self.Y()}")

        self.top.wm_attributes("-transparentcolor", color)
        self.top.overrideredirect(1)
        self.top.wm_attributes("-topmost", True)
        # estimate top position > trial & error?
        self.xx, self.yy = int(wide / 2 - w / 2.4), int(high / 3.4)
        # primary bindings
        self.bind("<Configure>", self.moveit)
        self.top.event_add(
            "<<HOOD>>", "<Button-1>", "<Button-2>", "<Button-3>")
        self.top.bind("<<HOOD>>", self.focal)
        self.focus_force()


    def moveit(self, ev):
        self.top.geometry(f"+{self.X()+self.xx}+{self.Y()+self.yy}")
        self.top.lift()


    def focal(self, ev):
        self.s_scale.focus_set()


if __name__ == "__main__":

    app = transParent()
    app.mainloop()
Derek
  • 1,916
  • 2
  • 5
  • 15