這篇就真的是用Python寫Windows API的最後篇章了
整個Windows API非常的繁雜,再加上包一層Python上去
就算是只想要的簡單應用,也是遇到不少挫折
上幾篇就是很好的證明
搞的東西沒有很大,一連寫連四篇blog
目前弄好的東西已經足夠後續的使用
從解決舊問題,到開發新功能,使用新功能遇到問題,到最後綜合所學弄個小成果
這樣算是十分完整的起承轉合了
只是從程式寫出來到要打成文章說明又多花了比想像中還長的時間
這最終章要呈現的就是選取目錄,並顯示目錄中圖檔的
一個小程式
版面要求就很簡單
左邊操作介面與訊息
左上角,目錄選擇按鈕
目錄選擇按鈕下方,目前選擇的目錄
再下方目前檔案的名稱
最下方是前後圖檔切換的兩顆按鈕
右邊就單純顯示圖檔畫面,畫面會隨著子視窗大小調整
那麼先律定一下一些設定,用說的進行一下程式設計
先從子視窗(元件)的編號開始
目錄選擇按鈕是101
目前目錄的輸入編輯與顯示是102
檔案名稱顯示是103
上一頁是104
下一頁是105
顯示用子視窗是106
以往寫Windows用resource.h裡寫些IDI開頭的定義
這邊我偷懶省略
也不過6個元件,就這樣用吧
然後其它變數的部分
繼承之前的一些變數名稱,這裡會省略ctypes.windll
直接從user32、kernel32、shell32與gdiplus這些物件用起
然後常用的Windows類別也是會省略ctypes.wintypes
直接用BOOL,INT,HWND等視窗元件
Windows API常用常數,如WS_CHILD,WM_CREATE,SS_BITMAP等都會先宣告在最前面
程式流程的部分
除了主視窗外的所有元件,都從WM_CREATE這邊建立
包含子視窗的Window Class與指定子視窗視窗處理Window Procedure的部分
初始時先鎖定104與105不給操作
if msg == WM_CREATE: user32.CreateWindowExW(0,"BUTTON", "目錄選擇", WS_VISIBLE | WS_CHILD | WS_TABSTOP | BS_PUSHBUTTON, 10, 10, 140, 30, hwnd, 101, hInstance, 0) user32.CreateWindowExW(0,"EDIT", None, WS_VISIBLE | WS_CHILD | WS_TABSTOP |WS_BORDER | ES_LEFT | ES_AUTOVSCROLL, 10, 60, 250, 25, hwnd, 102, hInstance, 0) user32.CreateWindowExW(0,"STATIC", "預計檔案顯示", WS_VISIBLE | WS_CHILD | WS_TABSTOP | WS_BORDER, 10, 120, 250, 25, hwnd, 103, hInstance, 0) but1 = user32.CreateWindowExW(0,"BUTTON", "上一頁", WS_VISIBLE | WS_CHILD | WS_TABSTOP | BS_PUSHBUTTON, 10, 175, 70, 30, hwnd, 104, hInstance, 0) but2 = user32.CreateWindowExW(0,"BUTTON", "下一頁", WS_VISIBLE | WS_CHILD | WS_TABSTOP | BS_PUSHBUTTON, 100, 175, 70, 30, hwnd, 105, hInstance, 0) user32.EnableWindow(but1, False) user32.EnableWindow(but2, False) self.image_window_hwnd = user32.CreateWindowExW( 0, "ImageViewClass", None, WS_CHILD | WS_VISIBLE | SS_OWNERDRAW, 280, 10, 480, 480, # 位置和大小(下方區域) hwnd, 106, hInstance, None )
載入圖片的部分,就做在WM_COMMAND中,當接收到101的執行完成取得目錄後
先將路徑傳到102顯示,再從指定的目錄載入所有圖檔,或是沒圖檔就不動作
並104與105都鎖起來不給操作
有圖檔就先將第一張圖資訊放在103,然後解鎖105按鈕的操作
elif msg == WM_COMMAND: if wparam == 101: #目錄選擇按鈕 result = self.getDirectoryname() if (result): user32.SetDlgItemTextW(hwnd, 102, result) self.getImageFiles(result.value) print(self.img_files) if self.ttl_num > 1: self.tgt_num = 0 self.load_image(hwnd) user32.EnableWindow(user32.GetDlgItem(hwnd, 104), False) user32.EnableWindow(user32.GetDlgItem(hwnd, 105), True) else: self.image_ptr = c_void_p() user32.InvalidateRect(self.image_window_hwnd, None, True) user32.SetDlgItemTextW(hwnd, 103, "沒有圖檔") user32.EnableWindow(user32.GetDlgItem(self.mhwnd, 104), False) user32.EnableWindow(user32.GetDlgItem(self.mhwnd, 105), False)
在WM_COMMAND中接收105的處理時,檔案清單移到下一個檔名,然後讀圖檔
如果檔案索引超過1,那就解鎖104操作
如果確認到檔案已經是清單的最後,那就鎖105操作
def Nextpage(self, hwnd): user32.EnableWindow(user32.GetDlgItem(hwnd, 104), True) self.tgt_num += 1 if self.tgt_num >= self.ttl_num - 1: user32.EnableWindow(user32.GetDlgItem(hwnd, 105), False)
104就跟上面相反運作而已
def Backpage(self, hwnd): user32.EnableWindow(user32.GetDlgItem(hwnd, 105), True) self.tgt_num -= 1 if self.tgt_num < 1: user32.EnableWindow(user32.GetDlgItem(hwnd, 104), False)
差不多是這樣,然後細部控制上就遇到了一點小狀況
主因是我除了設計按鈕控制外,還加上了鍵盤控制
沒辦法,從DOS指令列時代過來的人,還是無法完全依賴滑鼠控制
同時在102的視窗功能上,除了顯示外還有輸入與編輯的功能,所以我是採用EDIT的類別處理
也就是我希望目錄除了用選的之外,還能用輸入(KEY)的
但是在輸入目錄上發現,輸入完成後按下Enter並不能直接傳遞WM_KEYDOWN給主視窗
這部分是這個子視窗在處理的,畢竟它要接收WM_KEYDOWN的訊息才能產生文字
稍微想想,好像也只能用那個方法了
雖然心裡大概有個底了,還是再問問AI有沒有其它辦法處理這個事件
AI提供的幾個方案當中,其它新方案的就不贅述了,不然寫不完
最佳方案,還是跟心底的答案一樣用SetWindowLongPtr這個上篇的大魔王是最直觀且方便的
那不就好險上一篇有把這問題解掉,不然寫到這裡來還是卡關啊
所以就再建兩個物件內建變數old_edit_wndproc存舊的,然後edit_proc存新的處理
並改寫一下之前在WM_CREATE裡,Edit子視窗的創建,取得它的HWND
改寫程式如下:
ed_hwnd = user32.CreateWindowExW(0,"EDIT", None, WS_VISIBLE | WS_CHILD | WS_TABSTOP |WS_BORDER | ES_LEFT | ES_AUTOVSCROLL, 10, 60, 250, 25, hwnd, 102, hInstance, 0) self.edit_proc = WNDPROC(self.edit_wnd_proc) self.old_edit_wndproc = user32.SetWindowLongPtrW(ed_hwnd, -4, self.edit_proc)
而新的處理事件函數中,有兩個按鍵事件要處理
一個就是按下ESC時,可以跳出輸入子視窗,將焦點指向主視窗
再來就是按下Enter時會載入102輸入視窗中的目錄
然後當然也是要將焦點指向主視窗
這樣才能驅使在主視窗下用左右鍵切換圖片的事件
這部分處理的程式碼如下
def edit_wnd_proc(self, hwnd, msg, wparam, lparam): if msg == WM_KEYDOWN: if wparam == 0x1B: #VK_ESCAPE user32.SetFocus(self.mhwnd) elif wparam == 0x00D: #VK_RETURN print("Enter") tg_path = os.curdir buf = LPCWSTR("\0" * (255+1)) #建立取得文字的變數buf user32.GetDlgItemTextW(self.mhwnd, 102, buf, 255) print(buf.value) user32.SetFocus(self.mhwnd) if(buf.value != None): self.getImageFiles(buf.value) if self.ttl_num > 1: self.tgt_num = 0 self.load_image(self.mhwnd) user32.EnableWindow(user32.GetDlgItem(self.mhwnd, 104), False) user32.EnableWindow(user32.GetDlgItem(self.mhwnd, 105), True) else: self.image_ptr = c_void_p() user32.InvalidateRect(self.image_window_hwnd, None, True) user32.SetDlgItemTextW(self.mhwnd, 103, "沒有圖檔") user32.EnableWindow(user32.GetDlgItem(self.mhwnd, 104), False) user32.EnableWindow(user32.GetDlgItem(self.mhwnd, 105), False) return 0 elif msg == WM_CLOSE: return 0 # 防止子視窗關閉 return user32.CallWindowProcW(self.old_edit_wndproc, hwnd, msg, wparam, lparam)
弄到這裡差不多了
雖然是個小小的程式,一路完備它並寫出完整的說明也花了不少時間
這樣一個只要安裝基本python
甚至不用安裝,改用Embedded版就可以執行的GUI看圖程式就完成了
加上因為從Windows XP時代就有GDI+函式庫了
https://learn.microsoft.com/zh-tw/windows/win32/gdiplus/-gdiplus-about-gdi--about
我想,要有不支援的Windows應該很難了
這程式缺點除了不能讀HIEC格式外,應該就很完整了
好啦,啦
完整程式碼就放在下面了
from ctypes import WINFUNCTYPE, WinDLL, Structure, c_long, c_void_p, c_ulong from ctypes import POINTER, byref, c_float, addressof, create_string_buffer from ctypes.wintypes import HINSTANCE, HWND, UINT, WPARAM, LPARAM, MSG, RECT from ctypes.wintypes import BOOL, INT, BYTE, LPCWSTR, HICON, HANDLE, HBRUSH, HDC from ctypes.wintypes import LPWSTR import os from pathlib import Path # 定義必要的Windows API結構和常數 WNDPROC = WINFUNCTYPE(c_long, HWND, UINT, WPARAM, LPARAM) user32 = WinDLL('user32') kernel32 = WinDLL('kernel32') gdiplus = WinDLL('gdiplus') gdi32 = WinDLL('gdi32') user32.DefWindowProcW.argtypes = [HWND, UINT, WPARAM, LPARAM] WS_OVERLAPPED = 0x00000000 WS_POPUP = 0x80000000 WS_CHILD = 0x40000000 WS_VISIBLE = 0x10000000 WS_CAPTION = 0x00C00000 # WS_BORDER | WS_DLGFRAME */ WS_BORDER = 0x00800000 WS_SYSMENU = 0x00080000 WS_THICKFRAME = 0x00040000 WS_TABSTOP = 0x00010000 WS_MINIMIZEBOX = 0x00020000 WS_MAXIMIZEBOX = 0x00010000 WS_OVERLAPPEDWINDOW = (WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX) WM_CREATE = 0x0001 WM_PAINT = 0x000F WM_CLOSE = 0x0010 WM_KEYDOWN = 0x0100 WM_COMMAND = 0x0111 CW_USEDEFAULT = 0x8000 SS_OWNERDRAW = 0x0000000E BS_PUSHBUTTON = 0x00000000 ES_LEFT = 0x0000 ES_AUTOVSCROLL = 0x0040 ES_AUTOHSCROLL = 0x0080 # GDI+ 啟動結構 class GdiplusStartupInput(Structure): _fields_ = [ ("GdiplusVersion", UINT), ("DebugEventCallback", c_void_p), #註1 ("SuppressBackgroundThread", BOOL), ("SuppressExternalCodecs", BOOL), ] #註1:因為實際通常指定為空,所以就不使用實際結構節省程式碼(就是偷懶了) # 視窗結構 class WNDCLASS(Structure): _fields_ = [ ("style", UINT), ("lpfnWndProc", WNDPROC), ("cbClsExtra", INT), ("cbWndExtra", INT), ("hInstance", HINSTANCE), ("hIcon", HICON), ("hCursor", HANDLE), ("hbrBackground", HBRUSH), ("lpszMenuName", LPCWSTR), ("lpszClassName", LPCWSTR), ] # 繪畫結構 class PAINTSTRUCT(Structure): _fields_ = [ ("hdc", HDC), ("fErase", BOOL), ("rcPaint", RECT), ("fRestore", BOOL), ("fIncUpdate", BOOL), ("rgbReserved", BYTE * 32), ] # 定義GDI+函數的引數與回傳值 gdiplus.GdiplusStartup.argtypes = [POINTER(c_ulong), POINTER(GdiplusStartupInput), c_void_p] gdiplus.GdiplusStartup.restype = UINT gdiplus.GdipCreateFromHDC.argtypes = [HDC, POINTER(c_void_p)] gdiplus.GdipCreateFromHDC.restype = UINT gdiplus.GdipLoadImageFromFile.argtypes = [LPCWSTR, POINTER(c_void_p)] gdiplus.GdipLoadImageFromFile.restype = UINT gdiplus.GdipGetImageWidth.argtypes = [c_void_p, POINTER(UINT)] gdiplus.GdipGetImageWidth.restype = UINT gdiplus.GdipGetImageHeight.argtypes = [c_void_p, POINTER(UINT)] gdiplus.GdipGetImageHeight.restype = UINT gdiplus.GdipDrawImageRect.argtypes = [c_void_p, c_void_p, c_float, c_float, c_float, c_float] gdiplus.GdipDrawImageRect.restype = UINT gdiplus.GdipDisposeImage.argtypes = [c_void_p] gdiplus.GdipDisposeImage.restype = UINT gdiplus.GdipDeleteGraphics.argtypes = [c_void_p] gdiplus.GdipDeleteGraphics.restype = UINT gdiplus.GdiplusShutdown.argtypes = [c_ulong] gdiplus.GdiplusShutdown.restype = None # 目錄取得用變數設定 shell32 = WinDLL('shell32') ole32 = WinDLL('ole32') BFFCALLBACK = WINFUNCTYPE(c_long, HWND, UINT, WPARAM, LPARAM) user32.SendMessageW.argtypes = [HWND, UINT, WPARAM, LPARAM] class BROWSEINFOW(Structure): _fields_ = [('hwndOwner',HWND), ('pidlRoot', c_void_p), ('pszDisplayName', LPWSTR), # Return display name of item selected. ('lpszTitle', LPWSTR), # text to go in the banner over the tree. ('ulFlags', UINT), # Flags that control the return stuff ('lpfn', BFFCALLBACK), ('lParam', LPARAM), #extra info that's passed back in callbacks ('iImage', INT)] # output var: where to return the Image index. #SHBrowseForFolder的指定型別 shell32.SHBrowseForFolderW.restype = c_void_p shell32.SHBrowseForFolderW.argtypes = [c_void_p] shell32.SHGetPathFromIDListW.restype = INT shell32.SHGetPathFromIDListW.argtypes = [c_void_p, LPWSTR] ole32.CoTaskMemFree.argtypes = [c_void_p] # 明確指定 CallWindowProcW 的引數與返回型態 user32.CallWindowProcW.argtypes = [WNDPROC, HWND, UINT, WPARAM, LPARAM] user32.CallWindowProcW.restype = c_long # 明確指定 SetWindowLongPtrW 的引數與返回型態 user32.SetWindowLongPtrW.argtypes = [c_void_p, INT, WNDPROC] user32.SetWindowLongPtrW.restype = WNDPROC class PyWin(): image_ptr = c_void_p() img_files = [] tgt_num = 0 ttl_num = 0 old_edit_wndproc = None # 子視窗(圖片)的窗口過程函數 def image_wnd_proc(self, hwnd, msg, wparam, lparam): if msg == WM_PAINT: ps = PAINTSTRUCT() hdc = user32.BeginPaint(hwnd, byref(ps)) # 繪製 5 像素外框 rect = RECT() user32.GetClientRect(hwnd, byref(rect)) h_brush = gdi32.CreateSolidBrush(0x000000) # 黑色外框 frame_rect = RECT(left=rect.left, top=rect.top, right=rect.right, bottom=rect.bottom) user32.FrameRect(hdc, byref(frame_rect), h_brush) gdi32.DeleteObject(h_brush) # 縮小繪製區域以留出 5 像素外框 window_width = rect.right - rect.left - 10 # 左右各 5 像素 window_height = rect.bottom - rect.top - 10 # 上下各 5 像素 if self.image_ptr: #確認有圖像執行縮放顯示 graphics = c_void_p() status = gdiplus.GdipCreateFromHDC(hdc, byref(graphics)) if status == 0: img_width = UINT() img_height = UINT() gdiplus.GdipGetImageWidth(self.image_ptr, byref(img_width)) gdiplus.GdipGetImageHeight(self.image_ptr, byref(img_height)) if img_width.value > 0 and img_height.value > 0: scale_x = window_width / img_width.value scale_y = window_height / img_height.value scale = min(scale_x, scale_y) scaled_width = img_width.value * scale scaled_height = img_height.value * scale # 居中顯示,考慮 5 像素外框 x = (window_width - scaled_width) / 2 + 5 y = (window_height - scaled_height) / 2 + 5 gdiplus.GdipDrawImageRect(graphics, self.image_ptr, x, y, scaled_width, scaled_height) gdiplus.GdipDeleteGraphics(graphics) else: #確認無圖像執行釋放資源 graphics = c_void_p() status = gdiplus.GdipCreateFromHDC(hdc, byref(graphics)) if status == 0: gdiplus.GdipDeleteGraphics(graphics) print("log:drawing image") user32.EndPaint(hwnd, byref(ps)) return 0 elif msg == WM_CLOSE: return 0 # 防止子視窗關閉 return user32.DefWindowProcW(hwnd, msg, wparam, lparam) def edit_wnd_proc(self, hwnd, msg, wparam, lparam): if msg == WM_KEYDOWN: if wparam == 0x1B: #VK_ESCAPE #按下ESC鍵離開輸入編輯子視窗 user32.SetFocus(self.mhwnd) elif wparam == 0x00D: #VK_RETURN buf = LPCWSTR("\0" * (255+1)) #建立取得文字的變數buf user32.GetDlgItemTextW(self.mhwnd, 102, buf, 255) user32.SetFocus(self.mhwnd) if(buf.value != None): #從路徑中取得圖檔 self.getImageFiles(buf.value) if self.ttl_num > 1: self.tgt_num = 0 self.load_image(self.mhwnd) user32.EnableWindow(user32.GetDlgItem(self.mhwnd, 104), False) user32.EnableWindow(user32.GetDlgItem(self.mhwnd, 105), True) else: self.image_ptr = c_void_p() user32.InvalidateRect(self.image_window_hwnd, None, True) user32.SetDlgItemTextW(self.mhwnd, 103, "沒有圖檔") user32.EnableWindow(user32.GetDlgItem(self.mhwnd, 104), False) user32.EnableWindow(user32.GetDlgItem(self.mhwnd, 105), False) return 0 elif msg == WM_CLOSE: return 0 # 防止子視窗關閉 return user32.CallWindowProcW(self.old_edit_wndproc, hwnd, msg, wparam, lparam) def load_image(self, hwnd): #先清空之前的資料 self.image_ptr = c_void_p() #先讀取目標的圖檔 status = gdiplus.GdipLoadImageFromFile(self.img_files[self.tgt_num], byref(self.image_ptr)) #更新圖檔名稱在視窗上 user32.SetDlgItemTextW(hwnd, 103, Path(self.img_files[self.tgt_num]).name) #更新圖檔顯示在子視窗上 user32.InvalidateRect(self.image_window_hwnd, None, True) def Backpage(self, hwnd): user32.EnableWindow(user32.GetDlgItem(hwnd, 105), True) self.tgt_num -= 1 if self.tgt_num < 1: user32.EnableWindow(user32.GetDlgItem(hwnd, 104), False) self.load_image(hwnd) def Nextpage(self, hwnd): user32.EnableWindow(user32.GetDlgItem(hwnd, 104), True) self.tgt_num += 1 if self.tgt_num >= self.ttl_num - 1: user32.EnableWindow(user32.GetDlgItem(hwnd, 105), False) self.load_image(hwnd) # 視窗過程函數 def wnd_proc(self, hwnd, msg, wparam, lparam): hInstance = kernel32.GetModuleHandleW(None) if msg == WM_CREATE: user32.CreateWindowExW(0,"BUTTON", "目錄選擇", WS_VISIBLE | WS_CHILD | WS_TABSTOP | BS_PUSHBUTTON, 10, 10, 140, 30, hwnd, 101, hInstance, 0) ed_hwnd = user32.CreateWindowExW(0,"EDIT", None, WS_VISIBLE | WS_CHILD | WS_TABSTOP |WS_BORDER | ES_LEFT | ES_AUTOVSCROLL, 10, 60, 250, 25, hwnd, 102, hInstance, 0) user32.CreateWindowExW(0,"STATIC", "預計檔案顯示", WS_VISIBLE | WS_CHILD | WS_TABSTOP | WS_BORDER, 10, 120, 250, 25, hwnd, 103, hInstance, 0) but1 = user32.CreateWindowExW(0,"BUTTON", "上一頁", WS_VISIBLE | WS_CHILD | WS_TABSTOP | BS_PUSHBUTTON, 10, 175, 70, 30, hwnd, 104, hInstance, 0) but2 = user32.CreateWindowExW(0,"BUTTON", "下一頁", WS_VISIBLE | WS_CHILD | WS_TABSTOP | BS_PUSHBUTTON, 100, 175, 70, 30, hwnd, 105, hInstance, 0) user32.EnableWindow(but1, False) user32.EnableWindow(but2, False) self.edit_proc = WNDPROC(self.edit_wnd_proc) self.old_edit_wndproc = user32.SetWindowLongPtrW(ed_hwnd, -4, self.edit_proc) self.image_window_hwnd = user32.CreateWindowExW( 0, "ImageViewClass", None, WS_CHILD | WS_VISIBLE | SS_OWNERDRAW, 280, 10, 480, 480, # 位置和大小(下方區域) hwnd, 106, hInstance, None ) elif msg == WM_KEYDOWN: if wparam == 0x1B: #VK_ESCAPE if(user32.MessageBoxW(0,'您確定要離開嗎?','離開確認視窗', 1) == 1): #MB_OKCANCELL user32.PostQuitMessage(0) elif wparam == 0x00D: #VK_RETURN buf = LPCWSTR("\0" * (255+1)) #建立取得文字的變數buf user32.GetDlgItemTextW(hwnd, 102, buf, 255) if(buf.value != None): self.getImageFiles(buf.value) if self.ttl_num > 1: self.tgt_num = 0 self.load_image(hwnd) user32.EnableWindow(user32.GetDlgItem(hwnd, 104), False) user32.EnableWindow(user32.GetDlgItem(hwnd, 105), True) else: self.image_ptr = c_void_p() user32.InvalidateRect(self.image_window_hwnd, None, True) user32.SetDlgItemTextW(hwnd, 103, "沒有圖檔") user32.EnableWindow(user32.GetDlgItem(self.mhwnd, 104), False) user32.EnableWindow(user32.GetDlgItem(self.mhwnd, 105), False) elif wparam == 0x025: #VK_LEFT if self.tgt_num > 0: self.Backpage(hwnd) elif wparam == 0x027: #VK_RIGHT if self.tgt_num < (self.ttl_num - 1): self.Nextpage(hwnd) elif msg == WM_CLOSE: user32.PostQuitMessage(0) elif msg == WM_COMMAND: if wparam == 101: #目錄選擇按鈕 result = self.getDirectoryname() if (result): user32.SetDlgItemTextW(hwnd, 102, result) self.getImageFiles(result.value) print(self.img_files) if self.ttl_num > 1: self.tgt_num = 0 self.load_image(hwnd) user32.EnableWindow(user32.GetDlgItem(hwnd, 104), False) user32.EnableWindow(user32.GetDlgItem(hwnd, 105), True) else: self.image_ptr = c_void_p() user32.InvalidateRect(self.image_window_hwnd, None, True) user32.SetDlgItemTextW(hwnd, 103, "沒有圖檔") user32.EnableWindow(user32.GetDlgItem(self.mhwnd, 104), False) user32.EnableWindow(user32.GetDlgItem(self.mhwnd, 105), False) elif wparam == 104: #上一頁按鈕 self.Backpage(hwnd) elif wparam == 105: #下一頁按鈕 self.Nextpage(hwnd) return user32.DefWindowProcW(hwnd, msg, wparam, lparam) def getImageFiles(self, directory_path): gdi_extensions = { '.bmp', # Bitmap '.gif', # Graphics Interchange Format '.jpg', # JPEG '.jpeg', # JPEG '.png', # Portable Network Graphics '.tiff', # Tagged Image File Format '.tif', # Tagged Image File Format (簡寫) '.ico', # Icon '.wmf', # Windows Metafile '.emf' # Enhanced Metafile } self.img_files = [] try: # 確認路徑存在 if not os.path.exists(directory_path): print(f"錯誤:路徑 '{directory_path}' 不存在") #return [] # 確認是資料夾 if not os.path.isdir(directory_path): print(f"錯誤:'{directory_path}' 不是一個資料夾") #return [] # 遍歷資料夾中的所有檔案 for filename in os.listdir(directory_path): file_path = os.path.join(directory_path, filename) # 只處理檔案(不包含子資料夾) if os.path.isfile(file_path): # 取得副檔名並轉為小寫 file_extension = Path(filename).suffix.lower() # 檢查是否為GDI+支援的格式 if file_extension in gdi_extensions: self.img_files.append(file_path) self.ttl_num = len(self.img_files) except PermissionError: print(f"錯誤:沒有權限存取資料夾 '{directory_path}'") except Exception as e: print(f"發生錯誤:{e}") def BffCallbackProc(self, hwnd, msg, lp, data): """ 指定目錄選擇的處理 傳入預設的目錄位置 """ if msg == 1: #BFFM_INITIALIZED user32.SendMessageW(hwnd, 0x0465, 1, data) # BFFM_SETSELECTIONW return 1 return 0 def getDirectoryname( self, setpath = os.curdir): """選擇目錄名稱範例,如下""" titlename = "請選擇檔案所在目錄" #設定目錄選擇視窗的標題(說明) browseInfo = BROWSEINFOW() #建立目錄取得資料 browseInfo.pszDisplayName = "" #先設定取得名字為空 browseInfo.lpszTitle = titlename #指定視窗標題 #browseInfo.ulFlags = BIF_RETURNONLYFSDIRS | BIF_RETURNFSANCESTORS | BIF_USENEWUI #關掉FLAG減少當機 browseInfo.lParam = LPARAM.from_buffer(LPWSTR(os.path.abspath(setpath))) #指定LPARAM的參數,會傳給BFFCALLBACK的lpData browseInfo.lpfn = BFFCALLBACK(self.BffCallbackProc) #為了指定原始目錄,指定自建的函數 pidl = shell32.SHBrowseForFolderW(byref(browseInfo)) #指定取得目錄資料 if not pidl: result = None else: path = LPCWSTR("\0" * (1024+1)) #建立空字串 shell32.SHGetPathFromIDListW(pidl, path) ole32.CoTaskMemFree(pidl) result = path return result def __init__(self): # 初始化GDI+ self.gdiplus_token = c_ulong() #簡易寫法 self.startup_input = GdiplusStartupInput(1, None, False, False) #正式啟動GDI+函式庫 gdiplus.GdiplusStartup(byref(self.gdiplus_token), byref(self.startup_input), None) #使用取得目錄功能 ole32.CoInitialize(None) # 設置視窗類別 h_instance = kernel32.GetModuleHandleW(None) class_name = "SampleWindowClass" self.wc = WNDCLASS() self.wc.lpfnWndProc = WNDPROC(self.wnd_proc) self.wc.hInstance = h_instance self.wc.lpszClassName = class_name self.wc.hCursor = user32.LoadCursorW(None, LPCWSTR(32512)) # IDC_ARROW self.wc.hbrBackground = c_void_p(5 + 1) # COLOR_WINDOW + 1,直接使用COLOR_WINDOW的值(5)加1 user32.RegisterClassW(byref(self.wc)) # 註冊子視窗類(用於圖片) self.image_wc = WNDCLASS() self.image_wc.lpfnWndProc = WNDPROC(self.image_wnd_proc) self.image_wc.hInstance = h_instance self.image_wc.lpszClassName = "ImageViewClass" self.image_wc.hCursor = user32.LoadCursorW(None, LPCWSTR(32512)) # IDC_ARROW self.image_wc.hbrBackground = c_void_p(5 + 1) # COLOR_WINDOW + 1 user32.RegisterClassW(byref(self.image_wc)) # 創建主視窗 self.mhwnd = user32.CreateWindowExW( 0, class_name, "用GDI+顯示圖片範例", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 800, 600, None, None, h_instance, None ) if not self.mhwnd: return user32.ShowWindow(self.mhwnd, 1) # SW_SHOWNORMAL user32.UpdateWindow(self.mhwnd) print("Updating") def run(self): # 消息循環 msg = MSG() while user32.GetMessageW(byref(msg), None, 0, 0): user32.TranslateMessage(byref(msg)) user32.DispatchMessageW(byref(msg)) def __del__(self): # 清理GDI+ gdiplus.GdiplusShutdown(self.gdiplus_token) print("done") if __name__ == "__main__": view_win = PyWin() view_win.run()
呼,終於寫完了
沒有留言:
張貼留言