#!python # -*- mode: python; Encoding: utf-8; coding: utf-8 -*- # Last updated: <2025/12/26 21:35:52 +0900> """ 与えられたウインドウハンドル(HWND)を親として、 子ウインドウを作成して画像を描画。 Windows11 x64 25H2 + Python 3.10.10 64bit """ import sys import win32gui # Windowsのウィンドウ操作(作成、描画開始など)に使用 import win32con # Windows APIの定数(WM_PAINT, WS_CHILDなど)を定義 import win32api # Windowsの基本機能(インスタンス取得、サイズ取得など)に使用 import ctypes # WindowsのDLL(システム設定)を直接呼び出すために使用 from PIL import Image, ImageWin # 画像処理とWin32描画の橋渡しに使用 IMAGEFILE = "preview.png" # --- Windows 11等の高解像度ディスプレイ(DPI)への対応設定 --- try: # プロセスに対して「DPIを認識する」よう命令し、文字や画像のボケを防ぐ ctypes.windll.shcore.SetProcessDpiAwareness(1) except Exception: # 古いOS環境(Windows 7等)でエラーにならないための回避策 ctypes.windll.user32.SetProcessDPIAware() class PreviewWindow: """親ウィンドウの中に画像を高品質に描画する子ウィンドウを管理するクラス""" def __init__(self, parent_hwnd, image_path): """初期化""" self.parent_hwnd = parent_hwnd # 外部から渡された親ウィンドウのハンドルを保存 self.resized_dib = None # リサイズ後の描画データを保持 # 描画範囲(left, top, right, bottom)を保持 self.draw_rect = (0, 0, 0, 0) # 起動時の親ウィンドウのサイズに合わせて画像を高品質にリサイズする self._prepare_high_quality_image(image_path) # ウィンドウの見た目や振る舞いをOSに登録 # 実際に子ウィンドウを作成し、ハンドルを取得 self.class_name = "PythonStaticHighQualityChild" self.hwnd = self._create_window() def _prepare_high_quality_image(self, path): """Pillowで親ウィンドウのクライアント領域サイズに合わせた画像を作成""" try: # クライアント領域サイズを取得 rect = win32gui.GetClientRect(self.parent_hwnd) target_w = rect[2] - rect[0] target_h = rect[3] - rect[1] if target_w <= 0 or target_h <= 0: print("Error: 親ウインドウサイズが取得できません。") sys.exit(1) # 画像をPillowで開いてRGB形式に変換 img = Image.open(path).convert("RGB") # LANCZOSアルゴリズムでリサイズ resized_img = img.resize((target_w, target_h), Image.Resampling.LANCZOS) # リサイズ画像を Win32 API で直接描画できるDibオブジェクトに変換して保持 self.resized_dib = ImageWin.Dib(resized_img) # 描画時に使用する矩形情報を保存 self.draw_rect = (0, 0, target_w, target_h) except Exception as e: print(f"Error: {e}") sys.exit(1) def _create_window(self): """親ウィンドウの中に子ウィンドウ(コントロール)としてウィンドウを実体化する""" wc = win32gui.WNDCLASS() # ウィンドウクラス構造体 wc.lpfnWndProc = self.wnd_proc # メッセージ処理関数を指定 wc.lpszClassName = self.class_name # クラス識別名 # アプリ自体のインスタンスハンドル wc.hInstance = win32api.GetModuleHandle(None) # 標準の矢印カーソルを使用 wc.hCursor = win32gui.LoadCursor(0, win32con.IDC_ARROW) # 背景色を指定 wc.hbrBackground = win32gui.CreateSolidBrush(win32api.RGB(0, 0, 0)) # 水平・垂直方向のリサイズが起きたときに再描画を要求するスタイル wc.style = win32con.CS_HREDRAW | win32con.CS_VREDRAW try: # 定義したクラスをOSに登録 win32gui.RegisterClass(wc) except win32gui.error as e: # 登録済みなら Error code = 1410 が出るので、それ以外なら例外を出す if e.winerror != 1410: raise w = self.draw_rect[2] # 準備した画像の幅 h = self.draw_rect[3] # 準備した画像の高さ # ウィンドウを作成してハンドルを返す hwnd = win32gui.CreateWindowEx( 0, # 拡張スタイル(今回は無し) self.class_name, # 登録したクラス名 "Child Window", # ウインドウタイトル win32con.WS_CHILD | win32con.WS_VISIBLE, # 子ウィンドウとして作成、即表示 0, 0, w, h, # 親の左上(0,0)を起点に、計算したサイズで配置 self.parent_hwnd, # 親ウィンドウのハンドルを指定 0, # メニューなし win32api.GetModuleHandle(None), # インスタンス None, # 追加のパラメータなし ) if not hwnd: print("[Child] Error: ウインドウ生成に失敗") return None print(f"[Child] ウインドウ生成。HWND: {hwnd}") return hwnd def wnd_proc(self, hwnd, msg, wparam, lparam): """メッセージ処理関数""" if msg == win32con.WM_PAINT: # ウィンドウの再描画が必要になったとき(初回表示時や、隠れていた窓が出た時) # 描画の準備。hdc(デバイスコンテキスト)を取得 hdc, ps = win32gui.BeginPaint(hwnd) if self.resized_dib: # 事前にPillowで作成した高品質画像を画面(HDC)に転送 self.resized_dib.draw(hdc, self.draw_rect) # 描画終了をOSに告げる win32gui.EndPaint(hwnd, ps) print("[Child] WM_PAINT 受信。") return 0 # メッセージを処理した場合は0を返すのが基本 if msg == win32con.WM_DESTROY: # ウィンドウが破棄されるとき(親が閉じる時など) print("[Child] WM_DESTROY 受信。Quit を送信。") win32gui.PostQuitMessage(0) return 0 # 自分で行わないメッセージ処理はOS既定の処理に渡す return win32gui.DefWindowProc(hwnd, msg, wparam, lparam) def main(): """プログラムのメイン実行部分""" # 引数が正しく渡されているか確認 (/p HWND) if len(sys.argv) == 3 and sys.argv[1].lower() == "/p": parent_hwnd = int(sys.argv[2]) else: print("Usage: python script.py /p ") return # 指定されたハンドルが、実際に動作しているウィンドウか確認 if not win32gui.IsWindow(parent_hwnd): print("Error: 指定された親ウィンドウ(HWND)は存在しません。") return # プレビュー管理クラスを作成 preview = PreviewWindow(parent_hwnd, IMAGEFILE) # 初回描画を強制的に促す(メッセージキューにWM_PAINTを入れる) win32gui.InvalidateRect(preview.hwnd, None, True) # キューを待たずに、今すぐ描画を反映 win32gui.UpdateWindow(preview.hwnd) # メッセージループ: OSからの信号(WM_PAINT等)を待ち受け、wnd_procへ流し続ける # これがないと、スクリプトは一瞬で終了してしまいます。 win32gui.PumpMessages() print("[Child] プロセス終了。") if __name__ == "__main__": main()