这里为了在窗口被遮挡的时候也能够取得正确图像,我们不能采用屏幕图像传输的方法,而.NET仅提供了一个快捷方法传输屏幕图像,这个方法不适合我们。最终还是决定使用传统的WIN32 API来实现这个功能,涉及到的API和使用方法都是很久以前大家就非常熟悉的,只是把它们用在.NET中时需要更改一些内容。
其API声明如下:
Public Class GDI32
Public Const SRCCOPY As Integer = 13369376
<DllImport("gdi32.dll")> _
Public Shared Function BitBlt(ByVal hObject As IntPtr, ByVal nXDest As Integer, ByVal nYDest As Integer, ByVal nWidth As Integer, ByVal nHeight As Integer, ByVal hObjectSource As IntPtr, _
ByVal nXSrc As Integer, ByVal nYSrc As Integer, ByVal dwRop As Integer) As Boolean
End Function
<DllImport("gdi32.dll")> _
Public Shared Function CreateCompatibleBitmap(ByVal hDC As IntPtr, ByVal nWidth As Integer, ByVal nHeight As Integer) As IntPtr
End Function
<DllImport("gdi32.dll")> _
Public Shared Function CreateCompatibleDC(ByVal hDC As IntPtr) As IntPtr
End Function
<DllImport("gdi32.dll")> _
Public Shared Function DeleteDC(ByVal hDC As IntPtr) As Boolean
End Function
<DllImport("gdi32.dll")> _
Public Shared Function DeleteObject(ByVal hObject As IntPtr) As Boolean
End Function
<DllImport("gdi32.dll")> _
Public Shared Function SelectObject(ByVal hDC As IntPtr, ByVal hObject As IntPtr) As IntPtr
End Function
End Class
Public Class User32
<StructLayout(LayoutKind.Sequential)> Public Structure RECT
Public left As Integer
Public top As Integer
Public right As Integer
Public bottom As Integer
End Structure
<StructLayout(LayoutKind.Sequential)> Public Structure POINT
Public X As Integer, Y As Integer
End Structure
<DllImport("user32.dll")> Public Shared Function GetDesktopWindow() As IntPtr
End Function
<DllImport("user32.dll")> Public Shared Function GetWindowDC(ByVal hWnd As IntPtr) As IntPtr
End Function
<DllImport("user32.dll")> Public Shared Function ReleaseDC(ByVal hWnd As IntPtr, ByVal hDC As IntPtr) As IntPtr
End Function
<DllImport("user32.dll")> Public Shared Function GetWindowRect(ByVal hWnd As IntPtr, ByRef rect As RECT) As IntPtr
End Function
<DllImport("user32.dll")> Public Shared Function WindowFromPoint(ByVal pt As POINT) As IntPtr
End Function
End Class
接下来我们实现一个方法,这个方法只需要传入一个Handle,返回值为一个Image对象,这个返回值可以直接转化为BitMap对象供我们分析图像使用。
一下在同一个文件中,该文件开头需要导入名空间:
Imports System.Runtime.InteropServices
Imports System.Drawing.Imaging
Public Class ScreenCapture
''' <summary>
''' 取得窗口图像
''' </summary>
''' <param name="handle">句柄</param>
''' <returns>图像</returns>
''' <remarks></remarks>
Public Shared Function CaptureWindow(ByVal handle As IntPtr) As Image
Dim hdcSrc As IntPtr = User32.GetWindowDC(handle)
Dim windowRect As New User32.RECT()
User32.GetWindowRect(handle, windowRect)
Dim width As Integer = windowRect.right - windowRect.left
Dim height As Integer = windowRect.bottom - windowRect.top
Dim hdcDest As IntPtr = GDI32.CreateCompatibleDC(hdcSrc)
Dim hBitmap As IntPtr = GDI32.CreateCompatibleBitmap(hdcSrc, width, height)
Dim hOld As IntPtr = GDI32.SelectObject(hdcDest, hBitmap)
GDI32.BitBlt(hdcDest, 0, 0, width, height, hdcSrc, 0, 0, GDI32.SRCCOPY)
GDI32.SelectObject(hdcDest, hOld)
GDI32.DeleteDC(hdcDest)
User32.ReleaseDC(handle, hdcSrc)
Dim img As Image = Image.FromHbitmap(hBitmap)
GDI32.DeleteObject(hBitmap)
Return img
End Function
End Class
接下来只需要取得台球窗口句柄就可以了,这里还是使用传统的API来实现,使用SPY++即可得到这个窗口的类名信息,我们取最小的子窗口。这个方法同样需要一个传入参数,我们的瞄准器采取事件驱动模式,所以传入参数定位为鼠标所在坐标,那么这个函数实际上就是已知鼠标坐标,返回窗口Handle,但在SPY++中观察,发现有一个窗口和本窗口很相似,所以为了区分,我们遍历其所有父及父的父(也就是他爷爷,最后到了他爷爷的爹或者他爹的爷爷)。。。。。。。
Public Class GameFrame
'api
Private Declare Function WindowFromPoint Lib "user32" Alias "WindowFromPoint" (ByVal xPoint As Integer, ByVal yPoint As Integer) As Integer
Private Declare Function GetClassName Lib "user32" Alias "GetClassNameA" (ByVal hwnd As IntPtr, ByVal lpClassName As String, ByVal nMaxCount As Integer) As Integer
Private Declare Function GetParent Lib "user32" Alias "GetParent" (ByVal hwnd As IntPtr) As IntPtr
Private Shared _hwndback As IntPtr '上次取得的台面句柄
Public Shared Function GetBitMap(ByVal point As Point) As Bitmap
Dim hwnd As IntPtr = WindowFromPoint(point.X, point.Y) '鼠标所在窗口
If hwnd <> _hwndback Then
Dim Length As Integer = 100
Dim ClassName As String = Space(100)
GetClassName(hwnd, ClassName, Length) '窗口类名
ClassName = ClassName.Replace(Chr(0), "").Trim
If ClassName = Setting.CName Then
Dim phwnd As IntPtr
If GetpClassname(hwnd, phwnd) = Setting.pCName Then
Dim pphwnd As IntPtr
If GetpClassname(phwnd, pphwnd) = Setting.ppCName Then
Dim ppphwnd As IntPtr
If GetpClassname(pphwnd, ppphwnd) = Setting.pppCName Then
_hwndback = hwnd '保存刚取得的句柄
Line.Graphics = Graphics.FromHwnd(hwnd) '【创建画布】
Return ScreenCapture.CaptureWindow(hwnd) '【图像】
End If
End If
End If
End If
Else
Line.Graphics = Graphics.FromHwnd(hwnd) '【创建画布】
Return ScreenCapture.CaptureWindow(hwnd) '【图像】
End If
Return Nothing
End Function
'获取父窗口类名
Private Shared Function GetpClassname(ByVal hwnd As IntPtr, ByRef phwnd As IntPtr) As String
phwnd = GetParent(hwnd)
Dim pClassName As String = Space(100)
GetClassName(phwnd, pClassName, 100)
pClassName = pClassName.Replace(Chr(0), "").Trim
Return pClassName
End Function
End Class
这里的类名放在了Setting类当中,其具体值分别为:
Public Class Setting
Public Shared CName As String = "AfxWnd42"
Public Shared pCName As String = "#32770"
Public Shared ppCName As String = "AfxMDIFrame42"
Public Shared pppCName As String = "Afx:400000:0"
End Class
好了,可以测试了,截取一个台球图像到程序吧!
在窗体上绘制一个PICTUREBOX,命名为PIC,添加一个TIMER控件,修改其为有效。
在TICK事件中添加以下代码:
GameFrame.GetBitMap(500,400)
然后打开联众台球。。。。美式或斯诺克都可以。如果取不到图像,可以调节500,400这个数值。