文章目錄
- Spy++原理
- PyWin32對應的函數
- 代碼實現
- 示範
Spy++是微軟出品的用來擷取Window視窗資訊的一個小工具。實現的原理其實不難,通過調用某些特定的Windows API即可。於是,我打算用Python也實現一個功能簡化版本的小工具,取名叫PySpy++。Python中調用Windows API一般使用pywin32這套庫,介面庫我使用PyQT4。
Spy++原理
Spy++中,最常用的一個功能,就是識別視窗。其中主要需要用到的Windows API有:
擷取當前滑鼠位置:
BOOL GetCursorPos( LPPOINT lpPoint );
擷取位於指定位置的視窗控制代碼:
HWND WindowFromPoint( POINT Point );
擷取視窗類別別:
int GetClassName( HWND hWnd, LPTSTR lpClassName, int nMaxCount );
擷取視窗內容或標題:
方法一:
int GetWindowText( HWND hWnd, LPTSTR lpString, int nMaxCount );
這個API有時候不能取到某些控制項的值,因此,使用方法二。
方法二:
給視窗發送WM_GETTEXT訊息:
LRESULT SendMessage( HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam );
高亮選中的視窗:
先擷取當前視窗的大小,然後畫一個矩形框。
BOOL GetWindowRect( HWND hWnd, LPRECT lpRect );
BOOL Rectangle(
HDC hdc, // handle to DC
int nLeftRect, // x-coord of upper-left corner of rectangle
int nTopRect, // y-coord of upper-left corner of rectangle
int nRightRect, // x-coord of lower-right corner of rectangle
int nBottomRect // y-coord of lower-right corner of rectangle
);
滑鼠移開視窗後,視窗需要恢複原狀,需要重新重新整理:
BOOL InvalidateRect(
HWND hWnd, // handle to window
CONST RECT* lpRect, // rectangle coordinates
BOOL bErase // erase state
);
BOOL UpdateWindow(
HWND hWnd // handle to window
);
BOOL RedrawWindow(
HWND hWnd, // handle to window
CONST RECT *lprcUpdate, // update rectangle
HRGN hrgnUpdate, // handle to update region
UINT flags // array of redraw flags
);
PyWin32對應的函數
在Python中調用Windows API,首先下載PyWin32,地址:http://pywin32.sourceforge.net/
安裝完成後,開啟協助文檔Python for Windows Documentation,裡面有所有需要的東西,隨時用來查看。
常用的API在win32api模組裡,介面相關的API在win32gui模組裡,API參數中定義的一些常量在win32con模組中。上面的Windows API對應PyWin32中的函數為:
(int, int) = win32gui.
GetCursorPos()
int = win32gui.
WindowFromPoint(point)
string = win32gui.
GetClassName(hwnd)
string = win32gui.
GetWindowText(hwnd)
int = win32gui.
SendMessage(hwnd, message , wparam , lparam )
(left, top, right, bottom) = win32gui.
GetWindowRect(hwnd)
win32gui.
Rectangle(hdc, LeftRect, TopRect, RightRect, BottomRect)
win32gui.
InvalidateRect(hWnd, Rect, Erase)
win32gui.
UpdateWindow(hwnd)
win32gui.
RedrawWindow(hWnd, rcUpdate, hrgnUpdate, flags)
代碼實現
介面庫使用PyQT4,參考資料可以從我之前的一篇部落格裡瞭解:PyQt4 學習資料匯總
工具對話方塊視窗有兩個控制項,一個是QLabel控制項,一個是QTextEdit控制項。QLabel控制項就是那個用來滑鼠按下去後去捕捉視窗,QTextEdit控制項用來顯示視窗的資訊。為了讓QTextEdit響應自訂的滑鼠事件,我建立了一個自訂QLabel控制項SpyLabel,繼承自QLabel。
class SpyLabel(QtGui.QLabel):
def __init__(self, parent = None):
QtGui.QLabel.__init__(self, parent)
self.parent = parent
self.spying = False
self.rectanglePen = win32gui.CreatePen(win32con.PS_SOLID, 3, win32api.RGB(255, 0, 0))
self.prevWindow = None
self.
setCursor(QtCore.Qt.SizeAllCursor)
SpyLabel中處理滑鼠移動事件:
def mouseMoveEvent(self, event):
if self.spying:
curX, curY = win32gui.GetCursorPos()
hwnd = win32gui.
WindowFromPoint((curX, curY))
if self.checkWindowValidity(hwnd):
if self.prevWindow:
self.refreshWindow(self.prevWindow)
self.prevWindow = hwnd
self.highlightWindow(hwnd)
self.displayWindowInformation(hwnd)
滑鼠鬆開事件:
def mouseReleaseEvent(self, event):
if self.spying:
if self.prevWindow:
self.refreshWindow(self.prevWindow)
win32gui.ReleaseCapture()
self.spying = False
高亮視窗的函數:
def highlightWindow(self, hwnd):
left, top, right, bottom = win32gui.GetWindowRect(hwnd)
windowDc = win32gui.GetWindowDC(hwnd)
if windowDc:
prevPen = win32gui.SelectObject(windowDc, self.rectanglePen)
prevBrush = win32gui.SelectObject(windowDc, win32gui.GetStockObject(win32con.HOLLOW_BRUSH))
win32gui.Rectangle(windowDc, 0, 0, right - left, bottom - top)
win32gui.SelectObject(windowDc, prevPen)
win32gui.SelectObject(windowDc, prevBrush)
win32gui.ReleaseDC(hwnd, windowDc)
重新整理視窗的函數:
def refreshWindow(self, hwnd):
win32gui.
InvalidateRect(hwnd, None, True)
win32gui.
UpdateWindow(hwnd)
win32gui.
RedrawWindow(hwnd,
None,
None,
win32con.RDW_FRAME|
win32con.RDW_INVALIDATE|
win32con.RDW_UPDATENOW|
win32con.RDW_ALLCHILDREN)
顯示視窗資訊:
def displayWindowInformation(self, hwnd):
className = win32gui.GetClassName(hwnd)
buf_size = 1 + win32gui.
SendMessage(hwnd, win32con.WM_GETTEXTLENGTH, 0, 0)
buffer = win32gui.PyMakeBuffer(buf_size)
win32gui.
SendMessage(hwnd, win32con.WM_GETTEXT, buf_size, buffer)
windowText = buffer[:buf_size]
try:
windowText = unicode(windowText, 'gbk')
except:
pass
message = ['Handle:\t' + str(hwnd),
'Class Name:\t' + className,
'Window Text:\t' + windowText]
self.output('\r\n'.join(message))
注意到上面SendMessage函數,需要傳入一個分配的緩衝區,用於擷取返回的內容。這裡使用了:
buffer = win32gui.PyMakeBuffer(buf_size)
由於返回的內容中可能有中文,因此使用unicode(windowText, 'gbk')進行一下轉換。
示範
二進位下載:
http://pyspyplusplus.googlecode.com/files/pyspy++.exe
原始碼:
http://code.google.com/p/pyspyplusplus/
Python 天天美味系列(總)
Python 天天美味(31) - python資料結構與演算法之插入排序
Python 天天美味(32) - python資料結構與演算法之堆排序
Python 天天美味(33) - 五分鐘理解元類(Metaclasses)[轉]
Python 天天美味(34) - Decorators詳解
Python 天天美味(35) - 細品lambda