#!python # -*- mode: python; Encoding: utf-8; coding: utf-8 -*- # Last updated: <2025/12/26 22:14:22 +0900> """ 与えられたウインドウハンドル(HWND)を親として、 子ウインドウを作成して文字を描画 Windows11 x64 25H2 + Python 3.10.10 64bit """ import sys import time import win32gui # WindowsのGUI操作用 import win32con # Windowsの定数(WM_xxxなど)用 import win32api # Windowsの基本API(RGB, GetModuleHandleなど)用 class ChildOverlay: def __init__(self, parent_hwnd): """初期化""" self.parent_hwnd = parent_hwnd # 描画対象となる親のウィンドウハンドル self.hwnd = None # 自身(子)のウィンドウハンドル self.class_name = "PersistentOverlayClass" # ウィンドウクラス名 self.timer_id = 1 # 親ウィンドウ監視用タイマーの識別ID self.font_name = "Verdana" # フォント名 self.font_height = 32 # フォントサイズ self.font_weight = win32con.FW_NORMAL # 表示文字列 lines = [ "Overlay Activate", f"Font name: {self.font_name}", f"Font size: {self.font_height}", ] self.text_content = "\n".join(lines) def create_window(self): """ウインドウ生成""" # 親ウィンドウが現在も存在するか確認 if not win32gui.IsWindow(self.parent_hwnd): print(f"Error: Parent HWND {self.parent_hwnd} is not valid.") return False # 親ウィンドウのクライアント領域(枠の内側)のサイズを取得 left, top, right, bottom = win32gui.GetClientRect(self.parent_hwnd) width = right - left height = bottom - top # ウインドウクラスを設定 wc = win32gui.WNDCLASS() # ウィンドウクラス構造体 wc.lpfnWndProc = self.wnd_proc # イベント(メッセージ)処理関数を指定 wc.lpszClassName = self.class_name # クラス名 # 実行ファイルモジュールハンドル取得 wc.hInstance = win32api.GetModuleHandle(None) wc.hbrBackground = win32gui.GetStockObject(win32con.BLACK_BRUSH) # 背景色指定 wc.hCursor = win32gui.LoadCursor(0, win32con.IDC_ARROW) # マウスカーソル指定 try: win32gui.RegisterClass(wc) # システムに登録 except win32gui.error as e: # 既に同じ名前のクラスが登録されている場合はエラー(1410)が出るが、その場合は続行 if e.winerror != 1410: raise # ウィンドウの作成 self.hwnd = win32gui.CreateWindow( self.class_name, # 登録したクラス名 "Child Window", # ウインドウタイトル win32con.WS_CHILD | win32con.WS_VISIBLE, # 子ウィンドウとして作成、即表示 0, # x 0, # y width, # width height, # height self.parent_hwnd, # 親ウィンドウのハンドルを指定 0, # メニューハンドル(なし) win32api.GetModuleHandle(None), # インスタンスハンドル None, # 追加パラメータ(なし) ) if self.hwnd: print(f"[Child] 子ウインドウ生成。 HWND: {self.hwnd}") win32gui.InvalidateRect(self.hwnd, None, True) # 初回描画を強制 win32gui.UpdateWindow(self.hwnd) # 強制的に再描画を発生 return True print("[Child] 子ウインドウ生成に失敗。") return False def wnd_proc(self, hwnd, msg, wparam, lparam): """イベントメッセージ処理関数""" if msg == win32con.WM_CREATE: # ウィンドウが作られた直後にタイマー(500ミリ秒間隔)をセット win32gui.SetTimer(hwnd, self.timer_id, 500, None) return 0 if msg == win32con.WM_TIMER: # 指定したタイマーIDの通知が来た場合 if wparam == self.timer_id: # 親ウィンドウが閉じられていないかチェック if not win32gui.IsWindow(self.parent_hwnd): print("[Child] 親ウインドウ消失。終了します。") win32gui.KillTimer(hwnd, self.timer_id) # タイマー停止 win32gui.DestroyWindow(hwnd) # 自分を破棄 return 0 if msg == win32con.WM_PAINT: # 描画が必要なタイミング(ウィンドウ表示時など)で呼ばれる self._on_paint(hwnd) return 0 if msg == win32con.WM_DESTROY: # ウィンドウが破棄されたら、メッセージループを抜けるよう指示 print("[Child] WM_DESTROY 受信。Quit 送信。") win32gui.PostQuitMessage(0) return 0 if msg == win32con.WM_NCDESTROY: # ウィンドウが破棄されたら、メッセージループを抜けるよう指示 print("[Child] WM_NCDESTROY 受信。Quit 送信。") win32gui.PostQuitMessage(0) return 0 # 自分が処理しなかったメッセージはWindowsの標準処理に任せる return win32gui.DefWindowProc(hwnd, msg, wparam, lparam) def _on_paint(self, hwnd): """描画。GDIを使用""" hdc, ps = win32gui.BeginPaint(hwnd) # 描画開始。デバイスコンテキスト(HDC)を取得 rect = win32gui.GetClientRect(hwnd) # 描画範囲(自分自身のサイズ)を取得 # 背景塗りつぶし brush = win32gui.CreateSolidBrush(win32api.RGB(0, 0, 0)) # ブラシ作成 win32gui.FillRect(hdc, rect, brush) # 範囲内を塗りつぶす win32gui.DeleteObject(brush) # ブラシを捨ててメモリ解放 # フォントオブジェクトの作成 lf = win32gui.LOGFONT() lf.lfFaceName = self.font_name # 書体を指定 lf.lfHeight = self.font_height # 高さを指定 lf.lfWeight = self.font_weight # ウェイトを指定 lf.lfQuality = win32con.ANTIALIASED_QUALITY # アンチエイリアス hfont = win32gui.CreateFontIndirect(lf) # フォントを作成 # HDCにフォントを適用(古いのは保管) old_font = win32gui.SelectObject(hdc, hfont) # 文字色の設定と背景モード(文字の隙間を塗りつぶさない設定) win32gui.SetTextColor(hdc, win32api.RGB(255, 255, 255)) # 白色 win32gui.SetBkMode(hdc, win32con.TRANSPARENT) # 背景透過 # テキストの描画実行 (中央揃え設定) draw_style = win32con.DT_CENTER | win32con.DT_VCENTER | win32con.DT_WORDBREAK win32gui.DrawText( hdc, self.text_content, -1, rect, draw_style, ) # 後片付け (作成したリソースの解放) win32gui.SelectObject(hdc, old_font) # フォントを元に戻す win32gui.DeleteObject(hfont) # 作成したフォントを削除 win32gui.EndPaint(hwnd, ps) # 描画終了を報告 def run(self): """メインループを実行""" if self.create_window(): print("[Child] メッセージループ開始...") # Windowsからのメッセージを待ち受け、wnd_procに振り分ける無限ループ win32gui.PumpMessages() print("[Child] プロセス終了。") def main(): # 引数チェック: python script.py /p が前提 if len(sys.argv) == 3 and sys.argv[1].lower() == "/p": try: parent_hwnd = int(sys.argv[2]) # HWNDを数値に変換 except ValueError: print("Error: Invalid HWND. 10進数を指定してください。") return else: print("Usage: python child_drawtext2.py /p ") return # 親ウィンドウが準備できるまで少し待機 time.sleep(1.0) # オーバーレイオブジェクトを作成して実行 overlay = ChildOverlay(parent_hwnd) overlay.run() if __name__ == "__main__": main()