打开VC集成开发环境,建立一个基于对话框的工程。我们把这个工程取名为SpyXX。在窗体中画上一个图片框控件(Picture)、一个静态文本控件(Static)、两个复选框控件(Check Box)和一个选项卡控件(Tab Control)。界面设计如下图。
探测器的制作需要两个图标文件(.ico)和一个鼠标光标文件(.cur),分别用于正常状态下的显示、鼠标拖出时的显示以及拖出时的鼠标指针;这些资源哪里来啊?Spy++中就有啊,用eXeScope挖一下吧。(我是从其他软件中挖出来的,名字好像叫超级什么霸,记不太清了,呵呵。)选项卡控件定义5个标签页,分别为"常规"、"样式"、"类"、"窗口"和"消息"。每个标签页的内容用一个属性页(Property Page)对话框来制作。下面,我们按照顺序描述一下开发过程。
一、探测器的制作
探测器用一个图片框控件来显示,正常状态下显示一幅有靶的图标。当鼠标在上面按下时,显示内容立刻换为另一幅无靶的图标,同时鼠标指针变为靶状。这样,就给人一种靶心被拖出去的感觉了。通过上面的叙述,我们了解到图片框需要响应WM_LBUTTONDOWN消息和WM_LBUTTONUP消息。而图片框在正常状态下只响应鼠标单击消息BN_CLICK。所以,我们要通过子类化来响应上述两个消息。
把图片框的ID设为IDC_PIC,并选中其Notify属性(否则不响应消息)。依次点击菜单Insert->New Class,Class type选择MFC Class,类名取为CMyPic,基类为CStatic。添加CSpyXXDlg类的私有成员变量CMyPic m_pic,在对话框的初始化过程中将其与图片框关联。代码如下:
1
2
3
4
5
6
7
|
BOOL
CSpyXXDlg::OnInitDialog()
{
CDialog::OnInitDialog();
m_pic.SubclassDlgItem(IDC_PIC,
this
);
……
return
TRUE;
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
void
CMyPic::OnLButtonDown(
UINT
nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
SetCapture();
//鼠标捕获
HCURSOR
hc = LoadCursor(AfxGetApp()->m_hInstance, MAKEINTRESOURCE (IDC_CURSOR1));
//IDC_CURSOR1是靶形光标资源号
::SetCursor(hc);
HICON
hicon2 = LoadIcon(AfxGetApp()->m_hInstance, MAKEINTRESOURCE (IDI_ICON2));
//IDI_ICON2为无靶图标资源号
this
->SetIcon(hicon2);
CStatic::OnLButtonDown(nFlags, point);
}
void
CMyPic::OnLButtonUp(
UINT
nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
ReleaseCapture();
//释放鼠标捕获
HICON
hicon1 = LoadIcon(AfxGetApp()->m_hInstance, MAKEINTRESOURCE (IDI_ICON1));
//IDI_ICON1是有靶图标资源号
this
->SetIcon(hicon1);
CStatic::OnLButtonUp(nFlags, point);
}
|
1
|
FromHandle(g_hMe)->SetTimer(1,600,NULL);
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
POINT pnt;
RECT rc;
HWND
DeskHwnd = ::GetDesktopWindow();
//取得桌面句柄
HDC
DeskDC = ::GetWindowDC(DeskHwnd);
//取得桌面设备场景
int
oldRop2 = SetROP2(DeskDC, R2_NOTXORPEN);
::GetCursorPos(&pnt);
//取得鼠标坐标
HWND
UnHwnd = ::WindowFromPoint(pnt) ;
//取得鼠标指针处窗口句柄
g_hWnd=UnHwnd;
::GetWindowRect(g_hWnd, &rc);
//获得窗口矩形
if
( rc.left < 0 ) rc.left = 0;
if
(rc.top < 0 ) rc.top = 0;
HPEN
newPen = ::CreatePen(0, 3, 0);
//建立新画笔,载入DeskDC
HGDIOBJ
oldPen = ::SelectObject(DeskDC, newPen);
::Rectangle(DeskDC, rc.left, rc.top, rc.right, rc.bottom);
//在窗口周围显示闪烁矩形
Sleep(400);
//设置闪烁时间间隔
::Rectangle( DeskDC, rc.left, rc.top, rc.right, rc.bottom);
::SetROP2(DeskDC, oldRop2);
::SelectObject( DeskDC, oldPen);
::DeleteObject(newPen);
::ReleaseDC( DeskHwnd, DeskDC);
DeskDC = NULL;
|
二、两个复选框
第一个复选框是"总在最上面",代码如下:
1
2
3
4
5
6
7
8
|
void
CSpyXXDlg::OnChktop()
{
int
nTop=((CButton*)GetDlgItem(IDC_CHKTOP))->GetCheck();
if
(nTop==1)
:: SetWindowPos(m_hWnd,HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE);
else
::SetWindowPos(m_hWnd,HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE);
}
|
1
2
3
4
|
void
CSpyXXDlg::OnChkhex()
{
g_nHex=((CButton*)GetDlgItem(IDC_CHKHEX))->GetCheck();
}
|
1
2
3
4
5
6
7
8
9
10
11
12
|
CString Display(
int
nVal)
{
CString str;
if
(g_nHex==1)
{
str.Format(
"%x"
,nVal);
str.MakeUpper();
}
else
str.Format(
"%d"
,nVal);
return
str;
}
|
选项卡控件中,5个标签页对应5个属性页对话框,与它们关联的类分别取名为CPage0、CPage1、CPage2、CPage3、CPage4。在CSpyXXDlg中建立私有成员变量m_page0、m_page1、m_page2、m_page3、m_page4。在其初始化过程中建立这5个属性页对话框:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
m_page0.Create(IDD_OLE_PROPPAGE_LARGE,GetDlgItem(IDC_TAB1));
m_page1.Create(IDD_OLE_PROPPAGE_LARGE1,GetDlgItem(IDC_TAB1));
m_page2.Create(IDD_OLE_PROPPAGE_LARGE2,GetDlgItem(IDC_TAB1));
m_page3.Create(IDD_OLE_PROPPAGE_LARGE3,GetDlgItem(IDC_TAB1));
m_page4.Create(IDD_OLE_PROPPAGE_LARGE4,GetDlgItem(IDC_TAB1));
CRect rs;
m_tab.GetClientRect(rs);
rs.top+=20;
rs.bottom-=3;
rs.left+=3;
rs.right-=3;
m_page0.MoveWindow(rs);
m_page1.MoveWindow(rs);
m_page2.MoveWindow(rs);
m_page3.MoveWindow(rs);
m_page4.MoveWindow(rs);
m_page0.ShowWindow(SW_SHOW);
m_tab.SetCurSel(0);
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
void
CSpyXXDlg::OnSelchangeTab1(NMHDR* pNMHDR,
LRESULT
* pResult)
{
// TODO: Add your control notification handler code here
int
i=m_tab.GetCurSel();
switch
(i)
{
case
0:
m_page0.ShowWindow(SW_SHOW);
m_page1.ShowWindow(SW_HIDE);
m_page2.ShowWindow(SW_HIDE);
m_page3.ShowWindow(SW_HIDE);
m_page4.ShowWindow(SW_HIDE);
break
;
case
1:
m_page0.ShowWindow(SW_HIDE);
m_page1.ShowWindow(SW_SHOW);
m_page2.ShowWindow(SW_HIDE);
m_page3.ShowWindow(SW_HIDE);
m_page4.ShowWindow(SW_HIDE);
break
;
case
2:
……
default
:
;
}
*pResult = 0;
}
|
常规标签页负责显示窗口句柄、窗口类名、标题文本、窗口矩形、窗口ID、进程ID和程序路径。控制其显示或改变应在CMyPic的WM_LBUTTONUP响应函函数中进行。代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
((CPage0*)FromHandle(g_hPage0))->m_editHWND.SetWindowText(Display((
int
)g_hWnd));
char
strClass[200]=
""
;
::GetClassName(g_hWnd,strClass,200);
((CPage0*)FromHandle(g_hPage0))->m_editCLASS.SetWindowText(strClass);
((CPage2*)FromHandle(g_hPage2))->SetDlgItemText(IDC_EDITCLASSNAME,strClass);<
char
strTitle[200]=
""
;
::GetWindowText(g_hWnd,strTitle,200);
((CPage0*)FromHandle(g_hPage0))->m_editTITLE.SetWindowText (strTitle);
long
iWNDID=GetWindowLong(g_hWnd,GWL_ID);
((CPage0*)FromHandle(g_hPage0))->m_editWNDID.SetWindowText(Display((
int
)iWNDID));
unsigned
long
iPID=0;
GetWindowThreadProcessId(g_hWnd,&iPID);
((CPage0*)FromHandle(g_hPage0))->m_editPID.SetWindowText(Display((
int
)iPID));
CString strPath;
strPath=getProcPath(iPID);
((CPage0*)FromHandle(g_hPage0))->m_editPATH.SetWindowText(strPath);
RECT rc;
::GetWindowRect(g_hWnd, &rc);
//获得窗口矩形
CString strRect;
strRect.Format(
"(%d,%d),(%d,%d) %dx%d"
,rc.left,rc.top,rc.right,rc.bottom,
rc.right-rc.left,rc.bottom-rc.top);
((CPage0*)FromHandle(g_hPage0))->m_editRECT.SetWindowText(strRect);
|
1
2
3
4
5
6
7
8
9
10
11
12
|
CString getProcPath(
int
PID)
{
HANDLE
hModule;
MODULEENTRY32* minfo=
new
MODULEENTRY32;
minfo->dwSize=
sizeof
(MODULEENTRY32);
hModule=CreateToolhelp32Snapshot(TH32CS_SNAPMODULE,PID); Module32First(hModule,minfo);
CString str;
str.Format(
"%s"
,minfo->szExePath);
CloseHandle(hModule);
if
(minfo)
delete
minfo;
return
str;
}
|
五、样式标签页
样式标签页设计如下图:
API函数GetWindowLong可以获取窗口样式或扩展样式的值。然后我们罗列出以WS_开头的所有窗口样式与上述样式值做"位与"操作,如果被包含,则返回其窗口样式,否则返回0。这样,就可以得到窗口样式的列表了。扩展样式列表与样式列表类似。相关代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
CListBox* pListStyle=(CListBox*)(((CPage1*)FromHandle(g_hPage1))->GetDlgItem(IDC_LIST_STYLE));
CListBox* pListExStyle=(CListBox*)(((CPage1*)FromHandle(g_hPage1))->GetDlgItem(IDC_LIST_EX_STYLE));
CEdit* pEditStyle=(CEdit*)(((CPage1*)FromHandle(g_hPage1))->GetDlgItem(IDC_EDIT_STYLE));
CEdit* pEditExStyle=(CEdit*)(((CPage1*)FromHandle(g_hPage1))->GetDlgItem(IDC_EDIT_EX_STYLE));
long
style = GetWindowLong(g_hWnd, GWL_STYLE);
long
styleEx= GetWindowLong(g_hWnd, GWL_EXSTYLE);
pEditStyle->SetWindowText(Display((
int
)style));
pEditExStyle->SetWindowText(Display((
int
)styleEx));
pListStyle->ResetContent();
//清空样式列表框
pListExStyle->ResetContent();
//清空扩展样式列表框
if
(style & WS_BORDER)
pListStyle->AddString(
"WS_BORDER"
);
if
( style & WS_CAPTION)
pListStyle->AddString(
"WS_CAPTION"
);
if
( style & WS_CHILD)
pListStyle->AddString(
"WS_CHILD"
);
……
|
六、类标签页
类标签页的设计如下图:
类名在常规标签页已获取。API函数GetClassLong可以获取类样式值。样式列表的实现与窗口样式类似,不再赘述。
七、窗口标签页
窗口标签页的设计如下图:
在该页中,主要用到了下面几个API函数:GetNextWindow、GetWindow和SendMessage。这三个API函数搭配以不同的参数值可以实现不同的功能。这里没有用GetWIndowText函数,是因为它不能取出部分系统窗口和隐藏窗口的标题。我们用SendMessage函数加WM_GETTEXT参数取代之。代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
CPage3* pPage3=(CPage3*)FromHandle(g_hPage3);
HWND
tempHandle;
char
tempstr[255]=
""
;
tempHandle = g_hWnd;
//本窗口句柄
pPage3->SetDlgItemText(IDC_MYHWND, Display((
int
)tempHandle));
//获取本窗口标题
::SendMessage(tempHandle, WM_GETTEXT, 255, (
LPARAM
)tempstr);
pPage3->SetDlgItemText(IDC_MYTITLE, tempstr);
//上一窗口
tempHandle = ::GetNextWindow(g_hWnd, GW_HWNDPREV);
pPage3->SetDlgItemText(IDC_PREHWND, Display((
int
)tempHandle));
//获取上一窗口标题
memset
(tempstr,0,255);
::SendMessage(tempHandle, WM_GETTEXT, 255, (
LPARAM
)tempstr);
pPage3->SetDlgItemText(IDC_PRETITLE, tempstr);
//下一窗口
tempHandle = ::GetNextWindow(g_hWnd, GW_HWNDNEXT);
pPage3->SetDlgItemText(IDC_NEXTHWND,Display((
int
)tempHandle));
memset
(tempstr,0,255);
//获取下一窗口标题
::SendMessage(tempHandle, WM_GETTEXT, 255, (
LPARAM
)tempstr);
pPage3->SetDlgItemText(IDC_NEXTTITLE, tempstr);
tempHandle = ::GetParent(g_hWnd);
//父窗口
pPage3->SetDlgItemText(IDC_PARENTHWND, Display((
int
)tempHandle));
memset
(tempstr,0,255);
::SendMessage(tempHandle, WM_GETTEXT, 255, (
LPARAM
)tempstr);
pPage3->SetDlgItemText(IDC_PARENTTITLE,tempstr);
//第一子窗口
tempHandle = ::GetWindow(g_hWnd, GW_CHILD);
pPage3->SetDlgItemText(IDC_CHILDHWND,Display((
int
)tempHandle));
memset
(tempstr,-0,255);
::SendMessage(tempHandle, WM_GETTEXT, 255, (
LPARAM
)tempstr);
pPage3->SetDlgItemText(IDC_CHILDTITLE,tempstr);
//所有者窗口
tempHandle = ::GetWindow(g_hWnd, GW_OWNER);
Page3->SetDlgItemText(IDC_OWNERHWND,Display((
int
)tempHandle));
memset
(tempstr,0,255);
::SendMessage(tempHandle, WM_GETTEXT, 255, (
LPARAM
)tempstr);
pPage3->SetDlgItemText(IDC_OWNERTITLE, tempstr);
|
八、消息标签页
消息标签页的设计如下图:
该页中的列表框与样式列表框不同,它的每个列表项前都有一个复选框。这要用到类CCheckListBox。这里要再次用到子类化的知识。从本文第一段制作CMyPric过程中,我们体会到了子类化的作用,也感到了它的不便之处。这里,我们采取另外一种方法,借鸡生蛋:即用Class Wizard生成相关代码,然后再修改它。首先在该属性页对话框上画一个列表控件,打开Class Wizard关联一个CListBox类变量m_listStatus。设置列表框的Owner Draw属性为Fixed,并选中其Has Strings选项。如下图:
然后,在Page4.h中查找到m_listStatus的定义 CListBox m_listStatus并将其改为CCheckListBox m_listStatus。这样,我们就可以使用CCheckListBox的全部函数了。
在对话框初始化过程中添加下列语句以加入各列表项:
1
2
3
4
5
6
7
8
9
10
|
CCheckListBox* plistStatus=((CCheckListBox*)FromHandle(g_hPage4)->GetDlgItem(IDC_LISTSTATUS));
plistStatus->AddString(
"窗口可见"
);
plistStatus->AddString(
"窗口可用"
);
plistStatus->AddString(
"总在最前"
);
plistStatus->AddString(
"窗口只读"
);
plistStatus->AddString(
"最大化"
);
plistStatus->AddString(
"最小化"
);
plistStatus->AddString(
"窗口还原"
);
plistStatus->AddString(
"关闭窗口"
);
plistStatus->AddString(
"激活窗口"
);
|
1
2
3
4
5
|
long
style = GetWindowLong(g_hWnd, GWL_STYLE);
if
( style & WS_VISIBLE )
{
pListStatus->SetCheck(0,1);
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
|
void
CPage4::OnSelchangeListstatus()
{
// TODO: Add your control notification handler code here
int
n=m_listStatus.GetCurSel();
switch
(n)
{
case
0:
if
(m_listStatus.GetCheck(0)== 1 )
::ShowWindow(g_hWnd, SW_SHOW);
else
::ShowWindow(g_hWnd, SW_HIDE);
break
;
case
1:
if
(m_listStatus.GetCheck(1) == 1)
::EnableWindow(g_hWnd, TRUE);
else
::EnableWindow(g_hWnd,FALSE);
break
;
case
2:
if
(m_listStatus.GetCheck(2) == 1)
::SetWindowPos(g_hWnd,HWND_TOPMOST,0,0,0,0,SWP_NOMOVE | SWP_NOSIZE);
else
::SetWindowPos(g_hWnd,HWND_NOTOPMOST,0,0,0,0,SWP_NOMOVE|SWP_NOSIZE);
break
;
case
3:
if
(m_listStatus.GetCheck(3) == 1)
::SendMessage(g_hWnd, EM_SETREADONLY, TRUE, 0);
else
::SendMessage(g_hWnd, EM_SETREADONLY, FALSE, 0);
break
;
case
4:
if
(m_listStatus.GetCheck(4) ==1)
{
::ShowWindow(g_hWnd, SW_MAXIMIZE);
m_listStatus.SetCheck(5,0);
}
else
::ShowWindow (g_hWnd, SW_RESTORE);
break
;
case
5:
if
(m_listStatus.GetCheck(5) == 1)
{
::ShowWindow(g_hWnd, SW_MINIMIZE);
m_listStatus.SetCheck(4,0);
}
else
::ShowWindow(g_hWnd, SW_RESTORE);
break
;
case
6:
if
(m_listStatus.GetCheck(6) ==1)
{
::ShowWindow (g_hWnd, SW_RESTORE);
m_listStatus.SetCheck(6,0);
m_listStatus.SetCheck(5,0);
m_listStatus.SetCheck(4,0);
}
break
;
case
7:
if
(m_listStatus.GetCheck(7) ==1)
{
::SendMessage (g_hWnd, WM_CLOSE, 0, 0);
m_listStatus.SetCheck(7,0);
}
break
;
case
8:
if
(m_listStatus.GetCheck(8) ==1)
{
::BringWindowToTop(g_hWnd);
m_listStatus.SetCheck(8,0);
}
break
;
default
:
;
}
}
|