import tkinter as tk import multiprocessing global_balloon = None class balloon_class: process_creation_count = 0 root_creation_count = 0 def __init__(self, queue, message, x, y, font_name, font_size, fore_color, back_color, transparency, show_border): # プロセスを2重生成しないためのフラグ type(self).process_creation_count += 1 if 1 >= type(self).process_creation_count: self.process = multiprocessing.Process(target=self.create_balloon, daemon=True, args=(queue,message,x,y,font_name,font_size,fore_color,back_color,transparency, show_border)) self.process.start() else: print("Error: プロセス生成メソッドが2回以上参照されています") def create_balloon(self, queue, message, x, y, font_name, font_size, fore_color, back_color, transparency, show_border): # rootを2重生成しないためのフラグ type(self).root_creation_count += 1 if 1 >= type(self).root_creation_count: self.root = tk.Tk() self.root.withdraw() self.root.wm_overrideredirect(True) self.sub_window = tk.Toplevel(self.root) self.sub_window.deiconify() self.previous_should_hide_balloon = False if show_border: border = 'solid' else: border = 'flat' #サイズ計算用のラベル self.dummy_label = tk.Label(self.sub_window, text=message, font=(font_name, font_size)) # ラベルサイズを計算 width = self.dummy_label.winfo_reqwidth() height = self.dummy_label.winfo_reqheight() self.sub_window.attributes("-topmost", True) # 全体を半透明にする(ウィンドウ全体が透明度を持つ) self.sub_window.wm_attributes("-alpha", transparency) self.sub_window.geometry(f"{width+20}x{height+20}+{x}+{y}") self.sub_window.configure(bg=back_color) self.frame = tk.Frame(self.sub_window, bg=back_color, bd=1, relief=border) self.frame.place(x=0, y=0, width=width+20, height=height+20) self.label = tk.Label(self.frame, text=message, fg=fore_color, bg=back_color, font=(font_name, font_size), justify="left") self.label.place(x=8, y=10, width=width, height=height) self.sub_window.wm_overrideredirect(True) self.update_balloon(queue, message, x, y, font_name, font_size, fore_color, back_color, transparency, show_border) self.root.mainloop() else: print("Error: 初期ウィンドウ生成メソッドが2回以上参照されています") def update_balloon(self, queue, message, x, y, font_name, font_size, fore_color, back_color, transparency, show_border): # キューから変数を取得 try: balloon_args, should_hide_balloon = queue.get_nowait() except Exception: balloon_args = None should_hide_balloon = False # 比較用に使う前回使用の引数とqueueで読み取った表示更新用の引数のリスト previous_args = [message, x, y, font_name, font_size, fore_color, back_color, transparency, show_border] new_args = balloon_args previous_should_hide_balloon = self.previous_should_hide_balloon new_should_hide_balloon = should_hide_balloon # ウィンドウを消しても引数が前と同じ値だとGUI表示を更新しない仕組みなので、ウィンドウ再生成用のフラグを作成、要再生成なら再生成と更新処理を実行 try: should_redefine_sub_window = not self.sub_window.winfo_exists() #sub_windowが存在しない場合True except Exception: should_redefine_sub_window = False if new_args is not None and (previous_args != new_args or previous_should_hide_balloon != new_should_hide_balloon or should_redefine_sub_window == True): #値が更新された場合かウィンドウを消した場合に更新処理を実行 message, x, y, font_name, font_size, fore_color, back_color, transparency, show_border = new_args self.previous_should_hide_balloon = new_should_hide_balloon #次回表示更新時の値比較用に代入 try: if new_should_hide_balloon: self.sub_window.withdraw() else: self.sub_window.deiconify() except Exception: # ウィンドウをAlt+F4で消した場合も再生成 self.sub_window = tk.Toplevel(self.root) self.sub_window.wm_overrideredirect(True) self.sub_window.deiconify() self.dummy_label = tk.Label(self.sub_window) self.frame = tk.Frame(self.sub_window) self.label = tk.Label(self.frame) if show_border: border = 'solid' else: border = 'flat' self.dummy_label.config(text=message, font=(font_name, font_size)) width = self.dummy_label.winfo_reqwidth() height = self.dummy_label.winfo_reqheight() self.sub_window.wm_attributes("-alpha", transparency) self.sub_window.geometry(f"{width+20}x{height+20}+{x}+{y}") self.sub_window.configure(bg=back_color) self.frame.config(bg=back_color, bd=1, relief=border) self.frame.place(x=0, y=0, width=width+20, height=height+20) self.label.config(text=message, fg=fore_color, bg=back_color, font=(font_name, font_size), justify="left") self.label.place(x=8, y=10, width=width, height=height) self.root.after(30, self.update_balloon, queue, message, x, y, font_name, font_size, fore_color, back_color, transparency, show_border) def balloon(message=" ", x=0, y=0, font_name="Arial", font_size=14, fore_color="#000000", back_color="#FFFF00", transparency=1, show_border=True): global global_balloon, balloon_queue if not isinstance(global_balloon, balloon_class): balloon_queue = multiprocessing.Queue() global_balloon=balloon_class(balloon_queue, message, x, y, font_name, font_size, fore_color, back_color, transparency, show_border) elif isinstance(global_balloon, balloon_class): try: if balloon_queue.empty(): balloon_args = [message, x, y, font_name, font_size, fore_color, back_color, transparency, show_border] should_hide_balloon = False balloon_queue.put((balloon_args, should_hide_balloon)) except Exception: pass def hide_balloon(): try: if balloon_queue.empty(): dummy_args = [" ", 0, 0, "Arial", 14, "#000000", "#FFFF00", 1, True] should_hide_balloon = True balloon_queue.put((dummy_args, should_hide_balloon)) except Exception: pass if __name__ == "__main__": import time balloon(message="Hello World!\nThis is a test message.\nこんにちは", x=300, y=500, font_name="Arial", font_size=14, fore_color="#000000", back_color="#00FFFF", transparency=0.7, show_border=False) time.sleep(3) hide_balloon() time.sleep(1) hide_balloon() time.sleep(1) balloon(message="Updated Content!", x=300, y=500, font_size=14, back_color="#00FFFF", transparency=0.7, show_border=False) time.sleep(3) balloon(message="Final Update!", x=300, y=500, font_size=14, back_color="#00FFFF", transparency=0.7, show_border=True) time.sleep(3)