简单的Spy++实现
从Spy++运行的效果来看,我们不难推测出,它是根据鼠标移动的位置来进行窗口的查找。那么是什么API呢?那就需要靠万能的谷歌和百度了。
源码实现:
解决方案结构如下:
运行效果如下图:
主要代码如下:
// Spy++DemoDlg.cpp : 实现文件
//
#include "stdafx.h"
#include "Spy++Demo.h"
#include "Spy++DemoDlg.h"
#include "afxdialogex.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
// CSpyDemoDlg 对话框
CSpyDemoDlg::CSpyDemoDlg(CWnd* pParent /*=NULL*/)
: CDialogEx(IDD_SPYDEMO_DIALOG, pParent), m_hCursor(nullptr), m_bCapture(FALSE)
{
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}
void CSpyDemoDlg::DoDataExchange(CDataExchange* pDX)
{
CDialogEx::DoDataExchange(pDX);
DDX_Control(pDX, IDC_STATIC_PIC_TARGET, m_picTarget);
}
BEGIN_MESSAGE_MAP(CSpyDemoDlg, CDialogEx)
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_WM_LBUTTONDOWN()
ON_WM_LBUTTONUP()
ON_WM_MOUSEMOVE()
END_MESSAGE_MAP()
// CSpyDemoDlg 消息处理程序
BOOL CSpyDemoDlg::OnInitDialog()
{
CDialogEx::OnInitDialog();
// 设置此对话框的图标。 当应用程序主窗口不是对话框时,框架将自动
// 执行此操作
SetIcon(m_hIcon, TRUE); // 设置大图标
SetIcon(m_hIcon, FALSE); // 设置小图标
// TODO: 在此添加额外的初始化代码
m_picTarget.GetWindowRect(&m_rectPicTarget);
ScreenToClient(&m_rectPicTarget);
m_hCursor = AfxGetApp()->LoadCursorW(IDC_CURSOR_TARGET);
static_cast<CButton *>(GetDlgItem(IDC_RADIO_PROPERTY))->SetCheck(TRUE);
GetWindowRect(m_rectWindow);
m_iconTargetBlank = AfxGetApp()->LoadIcon(IDI_ICON_BLANK_TARGET);
m_iconTarget = AfxGetApp()->LoadIcon(IDI_ICON_TARGET);
return TRUE; // 除非将焦点设置到控件,否则返回 TRUE
}
// 如果向对话框添加最小化按钮,则需要下面的代码
// 来绘制该图标。 对于使用文档/视图模型的 MFC 应用程序,
// 这将由框架自动完成。
void CSpyDemoDlg::OnPaint()
{
if (IsIconic())
{
CPaintDC dc(this); // 用于绘制的设备上下文
SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);
// 使图标在工作区矩形中居中
int cxIcon = GetSystemMetrics(SM_CXICON);
int cyIcon = GetSystemMetrics(SM_CYICON);
CRect rect;
GetClientRect(&rect);
int x = (rect.Width() - cxIcon + 1) / 2;
int y = (rect.Height() - cyIcon + 1) / 2;
// 绘制图标
dc.DrawIcon(x, y, m_hIcon);
}
else
{
CDialogEx::OnPaint();
}
}
//当用户拖动最小化窗口时系统调用此函数取得光标
//显示。
HCURSOR CSpyDemoDlg::OnQueryDragIcon()
{
return static_cast<HCURSOR>(m_hIcon);
}
void CSpyDemoDlg::OnLButtonDown(UINT nFlags, CPoint point)
{
if (m_rectPicTarget.PtInRect(point))
{
SetCursor(m_hCursor);
::SetCapture(m_hWnd);
m_bCapture = TRUE;
m_picTarget.SetIcon(m_iconTargetBlank);
GetWindowRect(&m_rectWindow);
}
CDialogEx::OnLButtonDown(nFlags, point);
}
void CSpyDemoDlg::OnLButtonUp(UINT nFlags, CPoint point)
{
if (m_bCapture)
{
ReleaseCapture();
m_picTarget.SetIcon(m_iconTarget);
m_bCapture = FALSE;
}
CDialogEx::OnLButtonUp(nFlags, point);
}
void CSpyDemoDlg::OnMouseMove(UINT nFlags, CPoint point)
{
if (m_bCapture)
{
// 画框
// 使得我们的程序也支持dpi缩放
SetProcessDPIAware();
// 获取鼠标指向窗口的信息
// 窗口句柄
POINT ptMouse = { 0 };
if (GetCursorPos(&ptMouse))
{
HWND hwnd = ::WindowFromPoint(ptMouse);
if (hwnd)
{
if (!m_rectWindow.PtInRect(ptMouse))
{
RECT rectClient = { 0 };
if (::GetWindowRect(hwnd, &rectClient))
{
HDC hdc = ::GetDC(::GetDesktopWindow());
if (hdc)
{
::InvalidateRect(m_lastDrawHwnd, &m_lastRect, TRUE);
HPEN hpen = CreatePen(PS_SOLID, 5, RGB(255, 0, 0));
HPEN oldPen = (HPEN)SelectObject(hdc, hpen);
HBRUSH hOldBrush = (HBRUSH)SelectObject(hdc, GetStockObject(HOLLOW_BRUSH));
Rectangle(hdc, rectClient.left, rectClient.top, rectClient.right, rectClient.bottom);
SelectObject(hdc, hOldBrush);
SelectObject(hdc, oldPen);
DeleteObject(hpen);
::ReleaseDC(hwnd, hdc);
m_lastRect = rectClient;
m_lastDrawHwnd = hwnd;
}
CString strHwnd;
strHwnd.Format(TEXT("%08X"), reinterpret_cast<unsigned int>(hwnd));
SetDlgItemTextW(IDC_EDIT_HANDLE, strHwnd);
TCHAR pTitle[MAXBYTE] = { 0 };
::GetWindowTextW(hwnd, pTitle, MAXBYTE);
SetDlgItemTextW(IDC_STATIC_TITLE, pTitle);
TCHAR pClass[MAXBYTE] = { 0 };
::GetClassNameW(hwnd, pClass, MAXBYTE);
SetDlgItemTextW(IDC_STATIC_CLASS, pClass);
CString strStyle;
strStyle.Format(TEXT("%08X"), ::GetWindowLong(hwnd, GWL_STYLE));
SetDlgItemTextW(IDC_STATIC_STYLE, strStyle);
CString strRect;
RECT rect = { 0 };
::GetWindowRect(hwnd, &rect);
strRect.Format(TEXT("(%d,%d)-(%d,%d) %dx%d"), rect.left, rect.top, rect.right, rect.bottom, (rect.right - rect.left), (rect.bottom - rect.top));
SetDlgItemTextW(IDC_STATIC_RECT, strRect);
}
}
}
}
}
CDialogEx::OnMouseMove(nFlags, point);
}
// 实现
protected:
HICON m_hIcon;
// 生成的消息映射函数
virtual BOOL OnInitDialog();
afx_msg void OnPaint();
afx_msg HCURSOR OnQueryDragIcon();
DECLARE_MESSAGE_MAP()
public:
afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
CStatic m_picTarget;
CRect m_rectPicTarget;
CRect m_rectWindow;
RECT m_lastRect;
HCURSOR m_hCursor;
BOOL m_bCapture;
HWND m_lastDrawHwnd;
HICON m_iconTargetBlank;
HICON m_iconTarget;
afx_msg void OnLButtonUp(UINT nFlags, CPoint point);
afx_msg void OnMouseMove(UINT nFlags, CPoint point);
POINT m_pPrev;
至于用的图标,读者可以自行去网上搜索一下,有很多。
注意的点
- 只有桌面才会不停地刷新,它的刷新时机不受我们控制,而我们自己写的程序界面的刷新是受我们控制的。
- dpi缩放的支持,使用SetProcessDPIAware()函数就开启了dpi的缩放功能。但是这个函数只有在Vista之后才被支持。GetDeviceCaps()函数可以帮我们获取dpi缩放值。
- picture控件的使用,可以显示一个图片,本程序中显示的是一个target图标。鼠标在按下的时候,还可以更换成另一个图标。
- 消息路由,我们想在鼠标在picture控件上响应鼠标按键的消息时,但是picture控件没有鼠标按键响应的消息处理,那么只能将此操作传递给父类(对话框)来处理。此时的picture的Notify属性应该设置为False,表示它的消息全部由父类来处理,这样就把消息路由到父类对话框中了。我们知道对话框肯定有鼠标响应的函数,我们就可以添加处理逻辑了。还需要注意的是,在按下鼠标的时候,需要判断是否是picture所在的区域,是的话才响应鼠标按键消息,否则不要响应。在判断窗口位置的时候,还需要注意坐标系的转换,这里就涉及到了三套坐标系,桌面坐标系、对话框坐标系以及picture坐标系,需要使用ScreenToClient(&m_rectPicTarget)函数进行坐标转换。
- 鼠标图标资源的加载需要注意,SetCursor(m_hCursor);
- 鼠标消息的捕获需要使用::SetCapture(m_hWnd)函数设置,但是用完之后还需要用ReleaseCapture()函数释放。在我们程序中,鼠标按下时需要捕获,在鼠标弹起时需要释放。
- 矩形的擦除,如果我们使用的是桌面DC,那么就可以在我们程序窗口的外部画矩形,因为它画的是桌面,但是这个矩形的擦除(也就是刷新)是不受我们程序控制的,所以就会出现画的矩形久久不会消去。这样就不像一般的截图软件那样给窗口画矩形的效果了。