今日も見に来てくださって、ありがとうございます。石川さんです。
先日、透明なウィンドウをつくったところで、ひらめきました。透明なキャンバスのウィンドウをつくって、それを別のウィンドウに重ねるのは、どうでしょうか。と、いうことで試しにやってみました。ちょっと実用には遠いですが、アイディアとして記録しておきます。
出来上がりイメージ

ソースコード
import tkinter as tk
class Application(tk.Tk):
def __init__(self):
super().__init__()
self.title("Transparent Canvas Challenge!")
self.geometry("500x300")
self.top = tk.Toplevel()
self.top.wm_attributes("-topmost", True)
self.top.overrideredirect(True)
self.top.geometry("500x300")
self.top.forward = tk.Canvas(self.top, background="white")
self.top.forward.pack(fill=tk.BOTH, expand=True)
self.top.forward.create_oval(50,50,450,250,fill="lightblue")
self.top.wm_attributes("-transparentcolor", "white")
self.back = tk.Canvas(self, background="white")
self.back.pack(fill=tk.BOTH, expand=True)
self.back.create_rectangle(50,50,450,250)
self.back.create_rectangle(100,100,400,200)
self.bind('<Configure>', self.change)
self.back.bind("<Unmap>", self.unmap)
self.back.bind("<Map>", self.map)
self.bind("<1>", self.draw1)
self.bind("<3>", self.draw3)
self.top.bind("<1>", self.draw1)
self.top.bind("<3>", self.draw3)
def draw1(self, event):
self.top.forward.create_oval(event.x, event.y, event.x + 30, event.y + 30)
def draw3(self, event):
self.back.create_rectangle(event.x, event.y, event.x + 30, event.y + 30)
def unmap(self, event):
self.top.withdraw()
def map(self, event):
self.lift()
self.top.wm_deiconify()
self.top.attributes("-topmost", True)
def change(self, event):
x, y = self.back.winfo_rootx(), self.back.winfo_rooty()
w, h = self.winfo_width(), self.winfo_height()
self.top.geometry(f"{w}x{h}+{x}+{y}")
if __name__ == "__main__":
application = Application()
application.mainloop()
説明
まずは、背景になるキャンバスをつくります。そして、前面に来る透明なキャンバスをつくるのですが、そのときに、Toplevelのウィンドウをつくって、このウィンドウ用のキャンバスとします。そして、Toplvelの方に透明となる設定を施す、というのがおおまかな流れです。
9行目でToplevelのウィンドウをつくっています。10行目「self.top.wm_attributes(“-topmost”, True)」は、割とポイントになりますが、常に一番上のウィンドウとなるようにする設定です。これで、透明なキャンバスが常に上に表示されるようになります。
11行目、「self.top.overrideredirect(True)」では、ウィンドウのタイトル部分を消去しています。これでアイコン化や閉じるボタンなども使えなくなります。
13行目、14行目でキャンバスをつくって、配置しています。キャンバスの背景色は、「”white”(白)」にセットしていますが、これが16行目の「self.top.wm_attributes(“-transparentcolor”, “white”)」のところで効いてきます。ここで-transparentcolorは透明にする色で、「”white”(白)」を指定しています。こうすることでキャンバスの背景色の白が透明になります。
23行目から、いくつかイベントをバインドしています。最初のイベントは<Configure>です。ウィンドウのサイズなどが変更されたときに発生します。ウィンドウサイズが変更されたときに、透明なキャンバスのサイズも同時に変更するために指定しました。<Unmap>と<Map>はウィンドウがアイコン化から戻されたときとアイコン化されたときに発生します。こちらもウィンドウを閉じたときに透明なキャンバスのウィンドウも閉じるために指定してあります。
25~28行目は左クリックされたときと右クリックされたときにキャンバス上に丸と四角を描くためにセットしました。ちゃんと透明なキャンバスが上になっていることを確認するためにおまけで付けてみました。
37行目の「self.top.withdraw()」は、ウィンドウを非表示にします。40行目の「self.lift()」は、ウィンドウを上に移動します。41行目「self.top.wm_deiconify()」でアイコン化から復帰し、42行目「self.top.attributes(“-topmost”, True)」で常に一番上のウィンドウとなるように再設定しています。
44~47行目のchangeメソッドでは、透明なキャンバスのウィンドウのサイズを合わせています。
まとめ
ウィンドウをふたつつくって、下のウィンドウにあわせて上のウィンドウが移動したりサイズ変更したり、ということで、透明なキャンバスを実現してみました。思ったとおりに動作させるのは、けっこう難しいですね。

