本文将介绍几个WindowsAPI函数的使用,WindowFromPoint获取鼠标位置窗口控件,GetWindowRect、GetClientRect获取控件区域,用屏幕画图标记出区域,通过RegisterHotKey注册全局类系统热键操作窗口控件的截图。
C#.NET代码如下:(点击下载完整源代码)
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace Hotkey_Screenshot
{
public partial class Form1 : Form
{
#region API引用区域
/// <summary>
/// 获得窗体大小,包括客户区和非客户区,非客户区包括标题及感知鼠标靠近的区域(鼠标在这区域时显示调节窗口大小的光标)
/// </summary>
/// <param name="hwnd">窗体句柄</param>
/// <param name="lpRect">存入矩形对象(相对屏幕的左上角坐标及右下角坐标)</param>
/// <returns></returns>
[DllImport("user32.dll", EntryPoint = "GetWindowRect")]
public static extern int GetWindowRect(IntPtr hwnd, ref RECT lpRect);
/// <summary>
/// 获得窗体客户区域大小,不包括非客户区
/// </summary>
/// <param name="hwnd">窗体句柄</param>
/// <param name="lpRect">存入矩形对象(相对窗体的左上角坐标及右下角坐标)</param>
/// <returns></returns>
[DllImport("user32.dll", EntryPoint = "GetClientRect")]
public static extern int GetClientRect(IntPtr hwnd, ref RECT lpRect);
/// <summary>
/// 将控件的相对坐标转换为屏幕坐标
/// </summary>
/// <param name="hWnd">控件句柄</param>
/// <param name="lpPoint">引用坐标参数:使用前用控件相对坐标赋值,转换成功则变成屏幕坐标</param>
/// <returns>是否成功</returns>
[DllImport("user32.dll")]
public static extern bool ClientToScreen(IntPtr hWnd, ref Point lpPoint);
/// <summary>
/// 通过全屏幕坐标获取控件句柄,不获取隐藏或禁止的窗口句柄
/// </summary>
/// <param name="p">屏幕坐标</param>
/// <returns>返回值为包含该点的窗口的句柄。如果包含指定点的窗口不存在,返回值为NULL。如果该点在静态文本控件之上,返回值是在该静态文本控件的下面的窗口的句柄。</returns>
[DllImport("user32.dll")]
public static extern IntPtr WindowFromPoint(Point p);
[DllImport("gdi32.dll")]
public static extern bool BitBlt(IntPtr hObject, int nXDest, int nYDest, int nWidth, int nHeight, IntPtr hObjectSource, int nXSrc, int nYSrc, int dwRop);
[DllImport("gdi32.dll")]
public static extern IntPtr CreateCompatibleBitmap(IntPtr hDC, int nWidth, int nHeight);
/// <summary>
/// 创建一个与指定设备兼容的内存设备上下文环境(DC)。通过GetDc()获取的HDC直接与相关设备沟通,而本函数创建的DC,则是与内存中的一个表面相关联。
/// </summary>
/// <param name="hDC"></param>
/// <returns></returns>
[DllImport("gdi32.dll")]
public static extern IntPtr CreateCompatibleDC(IntPtr hDC);
[DllImport("gdi32.dll")]
public static extern bool DeleteDC(IntPtr hDC);
[DllImport("gdi32.dll")]
public static extern bool DeleteObject(IntPtr hObject);
/// <summary>
/// 该函数选择一对象到指定的设备上下文环境中,该新对象替换先前的相同类型的对象
/// </summary>
/// <param name="hDC"></param>
/// <param name="hObject"></param>
/// <returns></returns>
[DllImport("gdi32.dll")]
public static extern IntPtr SelectObject(IntPtr hDC, IntPtr hObject);
[DllImport("user32.dll")]
public static extern IntPtr GetDesktopWindow();
[DllImport("user32.dll")]
public static extern IntPtr GetWindowDC(IntPtr hWnd);
[DllImport("user32.dll")]
public static extern IntPtr ReleaseDC(IntPtr hWnd, IntPtr hDC);
[DllImport("User32.dll")]
public extern static System.IntPtr GetDC(System.IntPtr hWnd);
public struct RECT
{
/// <summary>
/// 左上角横坐标
/// </summary>
public int X;
/// <summary>
/// 左上角纵坐标
/// </summary>
public int Y;
/// <summary>
/// 右下角横坐标
/// </summary>
public int X2;
/// <summary>
/// 右下角纵坐标
/// </summary>
public int Y2;
};
/// <summary>
/// BitBlt函数参数:将源矩形区域直接拷贝到目标矩形区域
/// </summary>
public const int SRCCOPY = 0x00CC0020;
/// <summary>
/// 注册全局热键(需要重写消息处理函数WndProc处理WM_HOTKEY = 0x0312消息)
/// </summary>
/// <param name="hWnd">处理热键消息的窗体句柄</param>
/// <param name="id">热键标识符:应用程序范围0X0000-0xBFFF,共享的动态链接库范围0xC000-0xFFFF,若在程序内注册同一ID值,则旧的热键被覆盖</param>
/// <param name="fsModifiers">组合键类型</param>
/// <param name="vk">热键按键的虚拟码</param>
/// <returns></returns>
[DllImportAttribute("user32.dll")]
public static extern bool RegisterHotKey(IntPtr hWnd, int id, KeyModifiers fsModifiers, Keys vk);
/// <summary>
/// 释放之前注册的全局热键
/// </summary>
/// <param name="hWnd">窗体句柄,同RegisterHotKey的hWnd</param>
/// <param name="id">热键标识符,同RegisterHotKey的id</param>
/// <returns></returns>
[DllImportAttribute("user32.dll")]
public static extern bool UnregisterHotKey(IntPtr hWnd, int id);
public enum KeyModifiers //组合键枚举
{
None = 0,
Alt = 1,
Control = 2,
Shift = 4,
Windows = 8
}
[DllImport("user32")]
public static extern bool InvalidateRect(IntPtr hWnd, ref RECT lpRect, bool bErase);
[DllImport("user32.dll")]
public static extern bool UpdateWindow(IntPtr hWnd);
[DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
public static extern bool RedrawWindow(IntPtr hwnd, ref RECT rcUpdate, IntPtr hrgnUpdate, int flags);
#endregion
/// <summary>
/// 是否标出控件边框
/// </summary>
bool showRect = false;
/// <summary>
/// 鼠标最后停留位置的控件句柄
/// </summary>
IntPtr lastControl = IntPtr.Zero;
public Form1()
{
InitializeComponent();
this.FormClosing += Form1_FormClosing;
}
private void Form1_Load(object sender, EventArgs e)
{
label1.Text = "\r\n\r\n";
label1.Text += " 热键使用说明:\r\n\r\n";
label1.Text += " (ALT+D) 全屏幕截图\r\n\r\n";
label1.Text += " (ALT+S) 显示鼠标停留位置的控件区域\r\n\r\n";
label1.Text += " (ALT+F) 鼠标停留位置的控件所在区域的屏幕截图\r\n\r\n";
label1.Text += " (ALT+C) 鼠标停留位置的控件客户区截图";
bool r=RegisterHotKey(this.Handle, 100, KeyModifiers.Alt, Keys.D);
r = RegisterHotKey(this.Handle, 101, KeyModifiers.Alt, Keys.S);
r = RegisterHotKey(this.Handle, 102, KeyModifiers.Alt, Keys.F);
r = RegisterHotKey(this.Handle, 103, KeyModifiers.Alt, Keys.C);
BackgroundWorker bw = new BackgroundWorker();
bw.DoWork += Bw_DoWork;
bw.ProgressChanged += Bw_ProgressChanged;
bw.WorkerReportsProgress = true;
bw.RunWorkerAsync();
}
private void Bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
}
private void Bw_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker me = sender as BackgroundWorker;
while (true)
{
IntPtr curControl = WindowFromPoint(Cursor.Position);//获取鼠标位置的控件句柄
if (curControl != lastControl)
{
if (showRect)
{
//RECT rt = new RECT();
//GetWindowRect(lastControl, ref rt);//获取上个控件在屏幕中的区域
//ClearRect(IntPtr.Zero,rt);//清除上个控件的矩形标记
//System.Threading.Thread.Sleep(100);//等待刷新完成再画框,否则框会被清除
//GetWindowRect(curControl, ref rt);//获取当前控件在屏幕中的区域
//DrawingRect(new Point(rt.X, rt.Y), rt.X2 - rt.X, rt.Y2 - rt.Y);//标记矩形框
ClearRect(lastControl);
System.Threading.Thread.Sleep(100);//等待刷新完成再画框,否则框会被清除
DrawingRect(curControl);
}
lastControl = curControl;
}
System.Threading.Thread.Sleep(200);//每隔200毫秒刷新一次鼠标位置的控件句柄
}
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
UnregisterHotKey(this.Handle, 100);
UnregisterHotKey(this.Handle, 101);
UnregisterHotKey(this.Handle, 102);
UnregisterHotKey(this.Handle, 103);
}
protected override void WndProc(ref Message m)
{
const int WM_HOTKEY = 0x0312;
if (m.Msg== WM_HOTKEY)
{
string fileName = System.IO.Path.GetTempPath() + "\\"+DateTime.Now.Ticks.ToString()+".jpg";
Bitmap bmp;
switch (m.WParam.ToInt32())
{
case 100:
bmp=ShotScreen();//全屏幕截图
bmp.Save(fileName);
bmp.Dispose();
System.Diagnostics.Process.Start(fileName);
break;
case 101:
if (showRect)
showRect = false;
else
showRect = true;
break;
case 102:
bmp = CaptureWindow(lastControl);//获取控件所在区域的屏幕图像,如果控件上方有其它窗体遮盖,则显示遮盖的图像
bmp.Save(fileName);
bmp.Dispose();
System.Diagnostics.Process.Start(fileName);
break;
case 103:
bmp = CaptureClientWindow(lastControl);//获取控件的显示图像,如果控件上方有其它窗体遮盖不受影响,但无法获取标题部分图像
bmp.Save(fileName);
bmp.Dispose();
System.Diagnostics.Process.Start(fileName);
break;
default:
break;
}
}
base.WndProc(ref m);
}
/// <summary>
/// 桌面截图,效果等同ShotScreen
/// </summary>
/// <returns></returns>
public static Bitmap CaptureDesktop()
{
return CaptureClientWindow(GetDesktopWindow());
}
/// <summary>
/// 屏幕截图,效果等同ShotScreen
/// </summary>
/// <returns></returns>
public static Bitmap CaptureScreen()
{
return CaptureClientScreen(GetDesktopWindow());
}
/// <summary>
/// 屏幕截图,效果等同CaptureScreen
/// </summary>
/// <returns></returns>
public static Bitmap ShotScreen()
{
Graphics g_Screen = Graphics.FromHwnd(IntPtr.Zero);//从屏幕创建Graphics对象
Bitmap bitmap = new Bitmap(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height, g_Screen);//创建位图
Graphics g_Bitmap = Graphics.FromImage(bitmap);//创建位图关联的Graphics对象
IntPtr sDC = g_Screen.GetHdc();//获取屏幕DC
IntPtr bmpDC = g_Bitmap.GetHdc();//获取位图DC
BitBlt(bmpDC, 0, 0, bitmap.Width, bitmap.Height, sDC, 0, 0, SRCCOPY);//拷贝图像
g_Bitmap.ReleaseHdc(bmpDC);
g_Screen.ReleaseHdc(sDC);
g_Bitmap.Dispose();
g_Screen.Dispose();
return bitmap;
}
/// <summary>
/// 通过窗体句柄获取此窗体区域的屏幕图像(如果窗体上方有其它窗体遮盖,获取的是遮盖后的图像)
/// </summary>
/// <param name="hwnd"></param>
/// <returns></returns>
public static Bitmap CaptureClientScreen(IntPtr hwnd)
{
IntPtr sDC = GetDC(IntPtr.Zero);//获取屏幕DC
RECT windowRect = new RECT();
GetWindowRect(hwnd, ref windowRect);//获取控件在屏幕中的矩形坐标(包含非客户区)
int width = windowRect.X2 - windowRect.X;
int height = windowRect.Y2 - windowRect.Y;
IntPtr dDC = CreateCompatibleDC(sDC);
IntPtr bmpHandle = CreateCompatibleBitmap(sDC, width, height);
IntPtr oldObj = SelectObject(dDC, bmpHandle);//替换成功后返回被替换的对象句柄
BitBlt(dDC, 0, 0, width, height, sDC, windowRect.X, windowRect.Y, SRCCOPY);//拷贝坐标位置图像
SelectObject(dDC, oldObj);//将被替换的对象句柄重新复位回去
DeleteDC(dDC);
ReleaseDC(IntPtr.Zero, sDC);
Image img = Image.FromHbitmap(bmpHandle);
DeleteObject(bmpHandle);
return (Bitmap)img;
}
/// <summary>
/// 通过窗体句柄获取此窗体的客户区图像(非客户区如标题部分无法获取)
/// </summary>
/// <param name="hwnd">窗体句柄</param>
/// <returns></returns>
public static Bitmap CaptureClientWindow(IntPtr hwnd)
{
IntPtr sDC = GetDC(hwnd);//获取控件DC
RECT clientRect = new RECT();
GetClientRect(hwnd, ref clientRect);//获取控件在客户区的矩形坐标,左上角一般都是Point(0,0)
int width = clientRect.X2 - clientRect.X;
int height = clientRect.Y2 - clientRect.Y;
IntPtr dDC = CreateCompatibleDC(sDC);//由控件DC创建内存DC
IntPtr bmpHandle = CreateCompatibleBitmap(sDC, width, height);//创建与指定的设备环境相关的设备兼容的位图
IntPtr oldObj = SelectObject(dDC, bmpHandle);//替换同类对象,替换成功后返回被替换的对象句柄
BitBlt(dDC, 0, 0, width, height, sDC, clientRect.X, clientRect.Y, SRCCOPY);//拷贝控件图像
SelectObject(dDC, oldObj);//将原来被替换的对象重新复位回去
DeleteDC(dDC);
ReleaseDC(hwnd, sDC);
Image img = Image.FromHbitmap(bmpHandle);
DeleteObject(bmpHandle);
return (Bitmap)img;
}
/// <summary>
/// 通过窗体句柄获取此窗体的图像(细节处理优化)
/// </summary>
/// <param name="hwnd">窗体句柄</param>
/// <returns></returns>
public static Bitmap CaptureWindow(IntPtr hwnd)
{
IntPtr sDC = GetDC(hwnd);//获取控件DC
RECT clientRect = new RECT();
GetClientRect(hwnd, ref clientRect);//获取控件在客户区的矩形坐标,左上角一般都是Point(0,0)
int width = clientRect.X2 - clientRect.X;
int height = clientRect.Y2 - clientRect.Y;
IntPtr dDC = CreateCompatibleDC(sDC);//由控件DC创建内存DC
IntPtr bmpHandle = CreateCompatibleBitmap(sDC, width, height);//创建与指定的设备环境相关的设备兼容的位图
IntPtr oldObj = SelectObject(dDC, bmpHandle);//替换同类对象,替换成功后返回被替换的对象句柄
BitBlt(dDC, 0, 0, width, height, sDC, clientRect.X, clientRect.Y, SRCCOPY);//拷贝控件图像
SelectObject(dDC, oldObj);//将原来被替换的对象重新复位回去
DeleteDC(dDC);
ReleaseDC(hwnd, sDC);
Image imgClient = Image.FromHbitmap(bmpHandle);
DeleteObject(bmpHandle);
Point p_client1 = new Point(clientRect.X, clientRect.Y);
Point p_client2 = new Point(clientRect.X2, clientRect.Y2);
ClientToScreen(hwnd, ref p_client1);
ClientToScreen(hwnd, ref p_client2);
RECT windowRect = new RECT();
GetWindowRect(hwnd, ref windowRect);//获取控件在屏幕中的矩形坐标(包含非客户区)
if (windowRect.X == p_client1.X && windowRect.Y == p_client1.Y)//如果客户区就是完整的区域返回客户区图像
return (Bitmap)imgClient;
sDC = GetDC(IntPtr.Zero);//获取屏幕DC
width = p_client2.X - p_client1.X;//标题部分的宽度
height = p_client1.Y - windowRect.Y;//标题部分的高度
dDC = CreateCompatibleDC(sDC);
bmpHandle = CreateCompatibleBitmap(sDC, width, height);
oldObj = SelectObject(dDC, bmpHandle);
BitBlt(dDC, 0, 0, width, height, sDC, p_client1.X, windowRect.Y, SRCCOPY);//拷贝标题区域的屏幕图像
SelectObject(dDC, oldObj);
DeleteDC(dDC);
ReleaseDC(IntPtr.Zero, sDC);
Image imgTitle = Image.FromHbitmap(bmpHandle);
DeleteObject(bmpHandle);
Bitmap bmp = new Bitmap(imgClient.Width,imgClient.Height+imgTitle.Height);//创建Bitmap对象,尺寸高度加了标题
Graphics g = Graphics.FromImage(bmp);
g.DrawImage(imgTitle, new Point(0, 0));//先将标题填充进图像
g.DrawImage(imgClient, new Point(0, imgTitle.Height));//然后往下将客户区部分填充进图像
g.Dispose();
return bmp;//返回拼接好的图像
}
/// <summary>
/// 在屏幕指定位置画个红色矩形框
/// </summary>
/// <param name="p">相对屏幕的左上角坐标</param>
/// <param name="width"></param>
/// <param name="height"></param>
public static void DrawingRect(Point p, int width, int height)
{
IntPtr dc = GetDC(IntPtr.Zero);
System.Drawing.Graphics g = System.Drawing.Graphics.FromHdc(dc);
g.DrawRectangle(new System.Drawing.Pen(System.Drawing.Brushes.Red, 2), new System.Drawing.Rectangle(p.X, p.Y, width, height));
ReleaseDC(IntPtr.Zero, dc);
}
/// <summary>
/// 在控件客户区的指定区域画红色矩形框
/// </summary>
/// <param name="hwnd">控件句柄</param>
/// <param name="p">相对控件的左上角坐标</param>
/// <param name="width"></param>
/// <param name="height"></param>
public static void DrawingRect(IntPtr hwnd, Point p, int width, int height)
{
IntPtr dc = GetDC(hwnd);
System.Drawing.Graphics g = System.Drawing.Graphics.FromHdc(dc);
g.DrawRectangle(new System.Drawing.Pen(System.Drawing.Brushes.Red, 2), new System.Drawing.Rectangle(p.X, p.Y, width, height));
ReleaseDC(hwnd, dc);
}
/// <summary>
/// 用红色矩形框标出控件区域(经过细节处理)
/// </summary>
/// <param name="hwnd"></param>
public static void DrawingRect(IntPtr hwnd)
{
RECT rt_window = new RECT();
GetWindowRect(hwnd, ref rt_window);//获取控件在屏幕中的矩形坐标(包含非客户区)
RECT rt_client = new RECT();
GetClientRect(hwnd, ref rt_client);//获取控件的客户区区域
Point p_client1 = new Point(rt_client.X, rt_client.Y);//客户区左上角坐标
Point p_client2 = new Point(rt_client.X2, rt_client.Y2);//客户区右下角坐标
ClientToScreen(hwnd, ref p_client1);//客户区左上角坐标转化为屏幕坐标
ClientToScreen(hwnd, ref p_client2);//客户区右下角坐标转化为屏幕坐标
int width = p_client2.X-p_client1.X;
int height = p_client2.Y - rt_window.Y;//这里的高度包含了标题部分
Point p = new Point(p_client1.X, rt_window.Y);//这里的坐标是包含标题的左上角位置
DrawingRect(p, width, height);
}
/// <summary>
/// 通知控件重绘,清除所画红色框
/// </summary>
/// <param name="hWnd">控件句柄</param>
public static void ClearRect(IntPtr hWnd,RECT rect)
{
InvalidateRect(hWnd, ref rect, true);
//UpdateWindow(hWnd);
//const int RDW_FRAME = 1024;
//const int RDW_INVALIDATE = 1;
//const int RDW_UPDATENOW = 256;
//const int RDW_ALLCHILDREN = 128;
//RedrawWindow(hWnd, ref rect, IntPtr.Zero, RDW_FRAME | RDW_INVALIDATE | RDW_UPDATENOW | RDW_ALLCHILDREN);
}
/// <summary>
/// 通知控件重绘,对应DrawingRect(IntPtr hwnd)使用
/// </summary>
/// <param name="hWnd">控件句柄</param>
public static void ClearRect(IntPtr hwnd)
{
RECT rt_window = new RECT();
GetWindowRect(hwnd, ref rt_window);//获取控件在屏幕中的矩形坐标(包含非客户区)
RECT rt_client = new RECT();
GetClientRect(hwnd, ref rt_client);//获取控件的客户区区域
Point p_client1 = new Point(rt_client.X, rt_client.Y);//客户区左上角坐标
Point p_client2 = new Point(rt_client.X2, rt_client.Y2);//客户区右下角坐标
ClientToScreen(hwnd, ref p_client1);//客户区左上角坐标转化为屏幕坐标
ClientToScreen(hwnd, ref p_client2);//客户区右下角坐标转化为屏幕坐标
RECT rect = new RECT();
rect.X= p_client1.X;
rect.Y= rt_window.Y;//这里的坐标包含了标题部分
rect.X2 = p_client2.X;
rect.Y2 = p_client2.Y;
InvalidateRect(IntPtr.Zero, ref rect, true);
//UpdateWindow(IntPtr.Zero);
//const int RDW_FRAME = 1024;
//const int RDW_INVALIDATE = 1;
//const int RDW_UPDATENOW = 256;
//const int RDW_ALLCHILDREN = 128;
//RedrawWindow(IntPtr.Zero, ref rect, IntPtr.Zero, RDW_FRAME | RDW_INVALIDATE | RDW_UPDATENOW | RDW_ALLCHILDREN);
}
}
}