実現したいこと
https://94.gigafile.nu/0325-c7f527087ad73dda5963f92a783e34fbb
上記ファイルのrun.batを押して動くプログラムをPythonで再現したいと思っています
バルーンウィンドウを表示し、マウスの現在座標を表示するというプログラムです
しかしPythonではバルーンを表示する機能がなく、一から作成する必要があります
Tkinterで似たようなものは作れますが、見た目をなるべく同じにしたいため、上記プログラムと同じようにwin32apiを利用したウィンドウ描写を試みています
そこで上記プログラム(Rust製)のballoon関数のソースコードをChatGPTにトランスパイルしてもらい修正を繰り返したのですがある問題が解決できずにいます
こちらのバルーンですが
指定した文字列をバルーン上に表示、バルーンの大きさは文字列の文字数と行数に応じて変化する
というプログラムになっています、別途でバルーン自体の色や透明度も変更可能です
内容に変更があれば、シームレスに変更が可能で、すぐに文字列が変えられます
ひとまずテスト用プログラムとして最初に"Hello World!"とバルーンに表示
3秒後に"Updated Content!"とバルーンに表示し、また3秒後に"Final Update!"と表示するプログラムを作成しましたが、問題が解決できずにいるので助言いただけると幸いです
発生している問題・分からないこと
こちらのコードですが
実行直後に別ウィンドウをクリックし、別のウィンドウがアクティブ状態になると
"Updated Content!"
までは正常にウィンドウが表示されるのですが
balloon.update(message="Final Update!")
を処理するときにウィンドウの表示が乱れてメッセージが正常に表示されません
別ウィンドウをアクティブにしないままなら正常に動きます
3回内容を更新させて実験してみたところ、バルーンが非アクティブかつupdateメソッドが2回目以降の場合に表示が乱れるようです
プログラムの用途上、ウィンドウのアクティブ状態にかかわらず内容が表示される必要があります
なぜ、1回目のupdateではウィンドウが非アクティブでも正常に更新されるのに2回目以降は表示が乱れるのですか?
どうやったら問題を解決できますか?
該当のソースコード ※12/21追記
Python
1import win32gui 2import win32api 3import win32con 4import win32ui 5from ctypes import windll 6 7class Balloon: 8 class_name = "BalloonWindow" 9 class_registered = False 10 11 def __init__(self, message, x=0, y=0, font_name="Arial", font_size=20, fore_color=0x000000, back_color=0xFFFFFF, transparency=255): 12 self.message = message 13 self.x = x 14 self.y = y 15 self.font_name = font_name 16 self.font_size = font_size 17 self.fore_color = fore_color 18 self.back_color = back_color 19 self.transparency = transparency 20 21 # フォント作成 22 self.font = win32ui.CreateFont({ 23 "name": self.font_name, 24 "height": self.font_size, 25 }) 26 27 # ウィンドウ作成 28 self.hwnd = self.create_window() 29 30 # 描画 31 self.draw() 32 33 @classmethod 34 def register_class(cls): 35 if not cls.class_registered: 36 wnd_class = win32gui.WNDCLASS() 37 wnd_class.hInstance = win32api.GetModuleHandle(None) 38 wnd_class.lpszClassName = cls.class_name 39 wnd_class.lpfnWndProc = cls.wnd_proc 40 win32gui.RegisterClass(wnd_class) 41 cls.class_registered = True 42 43 def create_window(self): 44 self.__class__.register_class() 45 46 hwnd = win32gui.CreateWindowEx( 47 win32con.WS_EX_LAYERED | win32con.WS_EX_TOPMOST | win32con.WS_EX_TOOLWINDOW, 48 self.__class__.class_name, 49 "Balloon", 50 win32con.WS_POPUP, 51 self.x, 52 self.y, 53 300, 150, # 仮の初期サイズ 54 0, 55 0, 56 win32api.GetModuleHandle(None), 57 None 58 ) 59 60 win32gui.SetLayeredWindowAttributes( 61 hwnd, 0, self.transparency, win32con.LWA_ALPHA 62 ) 63 win32gui.ShowWindow(hwnd, win32con.SW_SHOW) 64 return hwnd 65 66 def draw(self): 67 hdc = win32gui.GetDC(self.hwnd) 68 hdc_mem = win32gui.CreateCompatibleDC(hdc) 69 70 text_width, text_height = self.calculate_text_size(hdc_mem) 71 72 new_width = text_width + 20 73 new_height = text_height + 20 74 win32gui.MoveWindow(self.hwnd, self.x, self.y, new_width, new_height, True) 75 76 brush = win32gui.CreateSolidBrush(self.back_color) 77 bitmap = win32gui.CreateCompatibleBitmap(hdc, new_width, new_height) 78 win32gui.SelectObject(hdc_mem, bitmap) 79 win32gui.FillRect(hdc_mem, (0, 0, new_width, new_height), brush) 80 81 win32gui.SelectObject(hdc_mem, self.font.GetSafeHandle()) 82 win32gui.SetTextColor(hdc_mem, self.fore_color) 83 win32gui.SetBkMode(hdc_mem, win32con.TRANSPARENT) 84 win32gui.DrawText( 85 hdc_mem, 86 self.message, 87 -1, 88 (10, 10, new_width - 10, new_height - 10), 89 win32con.DT_LEFT | win32con.DT_WORDBREAK 90 ) 91 92 win32gui.BitBlt(hdc, 0, 0, new_width, new_height, hdc_mem, 0, 0, win32con.SRCCOPY) 93 94 win32gui.DeleteObject(bitmap) 95 win32gui.DeleteDC(hdc_mem) 96 97 def calculate_text_size(self, hdc): 98 win32gui.SelectObject(hdc, self.font.GetSafeHandle()) 99 100 lines = self.message.split("\n") 101 max_width = 0 102 line_height = abs(self.font_size) 103 total_height = len(lines) * line_height 104 105 for line in lines: 106 size = win32gui.GetTextExtentPoint32(hdc, line) 107 max_width = max(max_width, size[0]) 108 109 return max_width, total_height 110 111 def update(self, message=None, x=None, y=None, font_name=None, font_size=None, fore_color=None, back_color=None, transparency=None): 112 """ バルーンの内容や位置を更新 """ 113 if message is not None: 114 self.message = message 115 if x is not None: 116 self.x = x 117 if y is not None: 118 self.y = y 119 if font_name is not None: 120 self.font_name = font_name 121 if font_size is not None: 122 self.font_size = font_size 123 if fore_color is not None: 124 self.fore_color = fore_color 125 if back_color is not None: 126 self.back_color = back_color 127 if transparency is not None: 128 self.transparency = transparency 129 self.draw() 130 131 @staticmethod 132 def wnd_proc(hwnd, msg, wparam, lparam): 133 if msg == win32con.WM_DESTROY: 134 win32gui.PostQuitMessage(0) 135 return win32gui.DefWindowProc(hwnd, msg, wparam, lparam) 136 137 def destroy(self): 138 win32gui.DestroyWindow(self.hwnd) 139 140import time 141win32gui.PumpWaitingMessages() 142 143# 初期バルーン 144balloon = Balloon( 145 message="Hello World!", 146 x=500, 147 y=300, 148 font_name="Arial", 149 font_size=20, 150 fore_color=0x000000, 151 back_color=0xFFFF00, 152 transparency=200 153) 154win32gui.PumpWaitingMessages() 155time.sleep(3) 156 157# 内容と位置を更新 158balloon.update(message="Updated Content!", x=500, y=300) 159win32gui.PumpWaitingMessages() 160time.sleep(3) 161 162# さらに更新 163balloon.update(message="Final Update!") 164win32gui.PumpWaitingMessages() 165time.sleep(3) 166 167# 終了 168balloon.destroy() 169 170
試したこと・調べたこと
- teratailやGoogle等で検索した
- ソースコードを自分なりに変更した
- 知人に聞いた
- その他
上記の詳細・結果
ChatGPTに症状を伝え、修正を依頼することを何度も繰り返しましたが、修正してもらったコードでも正常な結果が得られません
win32apiの情報もネット上に少なく、検索してもいまいちです
補足
Python 3.10.4で実行しています
ライブラリはpywin32です
追記
rust製のインタプリタ言語「UWSCR」での挙動
UWSCRソースコード
UWSCR
1FUNCTION GETSTATE() 2 x=G_MOUSE_X 3 y=G_MOUSE_Y 4 s="マウス座標:" + x + "," + y 5 result=s 6FEND 7 8WHILE True 9 s = GETSTATE() 10 balloon(s, 10, 10) 11 Sleep(0.01) 12WEND
上記についてTkinterで再現できました、今のところ問題ありません
※balloon.pyのソースコードは省略
Python
1from balloon import * 2import time 3import pyautogui 4 5 6def getstate(): 7 x,y =pyautogui.position() 8 s=f"マウス座標:{x},{y}" 9 return s 10 11while True: 12 s=getstate() 13 balloon(s,10,10) 14 time.sleep(0.01)