在Windows操作系统中,当用户按下"PrintScreen"按钮后,Windows自动将当前屏幕的图像拷贝到系统剪贴板中,这时可以通过"画笔"这个小程序将剪贴板中的内容保存成图像文件,可以看出,如果需要将当前屏幕保存下来还是比较麻烦的,能否可以自己编写一个应用程序,自动将当前屏幕的内容保存到一个图像文件中去呢?这个答案是肯定的,本实例应用程序就是利用通用的热键管理DLL库实现的热键功能,在收到热键通知后截取屏幕的内容并保存到bmp文件中。例如我们设置图片保存路径为c:\,热键为F9 + Control,然后按Change按钮设置好热键,那么当我们按下CTRL+F9后,当前界面将以BMP图像文件的格式被保存在C:\目录下。程序编译运行后的界面效果如图一所示:
!9iVe7V
RW>Z~Nj 一、实现方法
5
+(YcV(" v-G(bw3 热键管理DLL实际上是一个键盘钩子,由它来监视系统的键盘事件。如果有和程序登记符合的按键组合就通知该程序的窗口。为了应用方便,本实例把它做成了一个标准的管理库来为其它的程序通过热键服务,它有两个输出函数:AddHotkey()和DeleteHotkey(),程序只需要调用这两个函数就可以了,如果编译之后不用改变热键,则只需要AddHotkey就可以了。DLL中的所有的全局变量都放在一个共享段中,定义如下:
X+iA"B f$V']dOj1q #pragma data_seg("shareddata")
g;]2'Rj HHOOK hHook =NULL; //钩子句柄
aDza"Ln UINT nHookCount =0; //挂接的程序数目
94nvh:n static UCHAR HotKey[MAX_KEY] = {0}; //热键虚拟键码
m!;mEBL{ static UCHAR HotKeyMask[MAX_KEY] = {0}; //组合掩码, control=4,alt=2,shift=1
@ n;WVG static HWND hCallWnd[MAX_KEY] = {0}; //window handle associated with hotkey
~n"V0!:'4 static int KeyCount =0;
a3Es7R+S static UCHAR MaskBits =0; //00000 Ctrl=4 & Alt=2 & Shift=1
$
Qg81mu #pragma data_seg()
mq'q@@:c 6t]oSxN 关于共享段,有几点重要的说明:一是必须在链接选项里指定该段为共享:一种方法是在project->settings->link->object/library中加上/section:shareddata,rws;第二种方法是在def文件的sections里加上一句shareddata read write shared;第三种指定共享段的方法在程序里加上一句#pragma comment(linker,"section:shareddata,rws")。二是所有的变量必须初始化,否则链接程序会把它放到普通数据段。三是如果不初始化变量,需要在段外用"__declspec(allocate("shareddata")) 变量类型 变量名"的方式定义。
P'ZWAxd aKCCFHq t! DLL中的两个输出函数分别用来添加/删除热键,函数代码如下:
WlZ[9,:p1 ^r;}6 BOOL __declspec(dllexport) __stdcall AddHotkey(HWND hWnd,UCHAR
o}WbW }& cKey,UCHAR cMask)
3L>V-RPi M {
aeUm,'Y$ BOOL bAdded=FALSE;
uX}M0W for(int index=0;index<MAX_KEY;index++){
by6E
"7% if(hCallWnd[index]==0){
`5 e#9@/e hCallWnd[index]=hWnd;
NqqLRgMOR' HotKey[index]=cKey;
z8z U3? HotKeyMask[index]=cMask;
|k
4+I bAdded=TRUE;
>>^c_ 0"O KeyCount++;
oF,8j1 break;
(:T~*7/" }
%_tL}m{? }
, PN?_N return bAdded;
5-u=ZB%p }
,st4K;- //删除热键
$#Ji=JX BOOL __declspec(dllexport) __stdcall DeleteHotkey(HWND hWnd,UCHAR cKey,UCHAR cMask)
u> >t"w {
0HxF#SlKM BOOL bRemoved=FALSE;
-JwH^*Ad for(int index=0;index<MAX_KEY;index++){
sOJ"~p if(hCallWnd[index]==hWnd){
-QS_bQG% if(HotKey[index]==cKey&&HotKeyMask[index]==cMask){
,rX!V=Z5 hCallWnd[index]=NULL;
<Bu*: O HotKey[index]=0;
$$qhX]^~ HotKeyMask[index]=0;
J)g(Nw,O bRemoved=TRUE;
_5y)m5I KeyCount--;
3'&]v6| break;
5@ZD' }
X#eVw| }
p3^7Hr }
>{GC@Cw return bRemoved;
lBh {8a|2W }
eW >k'ez O Zt 'ovY t]vX9vv+D DLL中的钩子函数如下:
I/^Lr_\ ?'_iqg3 LRESULT CALLBACK KeyboardProc(int nCode,WPARAM wParam,LPARAM lParam)
NpRC3^ {
L7Skn-*tnA BOOL bProcessed=FALSE;
mbS
&> if(HC_ACTION==nCode)
UhEJznfi {
&x=<>~Ag3 if((lParam&0xc0000000)==0xc0000000){// 有键松开
,hOJe=u46 switch(wParam)
7?hCt {
?on3z case VK_MENU:
b$gDFNa MaskBits&=~ALTBIT;
Uc9Uj break;
6K<vyr40 case VK_CONTROL:
j@9nX4Z MaskBits&=~CTRLBIT;
l_f"}l break;
H
uE*jQ case VK_SHIFT:
>/'WU79TYE MaskBits&=~SHIFTBIT;
`C!Pe84( break;
@69q// #B default: //judge the key and send message
*Li;:b"t break;
QCtG #/ }
T\cdtjk for(int index=0;index<MAX_KEY;index++){
, H[o.r= if(hCallWnd[index]==NULL)
VJ1`& continue;
u8[X\f if(IsWindow(hCallWnd[index])&&(HotKey[index]==wParam)&&(HotKeyMask[index]==MaskBits))
has5"Bb {
msoE8YK&tg SendMessage(hCallWnd[index],WM_HOTKEY,wParam,WM_KEYUP);
uNx3us- bProcessed=TRUE;
^Y'>3o21f }
((?^B
}
;wvVhQ }
O9>/WmLe else if((lParam&0xc000ffff)==1){ //有键按下
CF>NyY:_ switch(wParam)
iWtWT1n8n {
kF\QO
[ case VK_MENU:
%gf8'Q MaskBits|=ALTBIT;
f'%}{l: ss break;
`,7BU??+u case VK_CONTROL:
cCj}{=U MaskBits|=CTRLBIT;
8H{@0_M break;
*YDx6\><
case VK_SHIFT:
}D|"$* MaskBits|=SHIFTBIT;
u(REEc~nj break;
^rxXAc[ default: //judge the key and send message
LL,~&5{ break;
=n#xnZ3 }
mY%PG for(int index=0;index<MAX_KEY;index++){
@P@t/ if(hCallWnd[index]==NULL)
FNm8j#c~Q continue;
g\jdR_/ if(IsWindow(hCallWnd[index])&&(HotKey[index]==wParam)&&(HotKeyMask[index]==MaskBits))
>eU;lru2Q {
Crey}A/N SendMessage(hCallWnd[index],WM_HOTKEY,wParam,WM_KEYDOWN);
'vCFT(C- bProcessed=TRUE;
p6ZKyi }
lR-4"/1|y }
8`*`4m }
~i(*.Z)
\ if(!bProcessed){ //一般按键事件,为监视键盘的程序留出余地
isDr|g$S for(int index=0;index<MAX_KEY;index++){
sjzZl*GSy if(hCallWnd[index]==NULL)
nq$^}L3&~ continue;
L:%h]- if(IsWindow(hCallWnd[index])&&(HotKey[index]==0)&&(HotKeyMask[index]==0))
0,VbB7 z SendMessage(hCallWnd[index],WM_HOTKEY,WM_HOTKEY,lParam);
q&P" //lParam的意义可看MSDN中WM_KEYDOWN部分
:z^c<KFX }
"2Ye\#BU6 }
m$$U%=r>@ }
Li7/pUq>}! return CallNextHookEx( hHook, nCode, wParam, lParam );
A).wjd(_, }
jdoI)J@9H t(s']r 抓图程序是一个基于对话框的程序,它在建立对话框的时候调用前面的DLL,登记热键,因此需要将hook.lib添加到工程里,在程序里给出两个DLL函数的定义,也可以写个头文件,再包含进来以下代码:
5$9j&&R rgOB0[ BOOL __declspec(dllexport)__stdcall AddHotkey(HWND,UCHAR key,UCHAR mask);
2p'qp/ BOOL __declspec(dllexport)__stdcall DeleteHotkey(HWND,UCHAR key,UCHAR mask);
aFl(K\ EnfSVG8kB8 为了添加应用程序对热键通知事件的响应,程序中用的办法是重载WindowProc()函数,该函数代码如下:
&{7%VsTB W}T$ Z LRESULT CCaptureDlg::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
[z Y9"B<3 {
=k &'ft if(message==WM_HOTKEY&&lParam==WM_KEYDOWN)
$)PNf'5Zg {
h],_1!0 //lParam表示是按下还是松开,如果有多个热键,由wParam来区分
= [:ruE SaveBmp();
t/nu/yz5E return FALSE;
iXXgPapz }
PY) 74sa …… //其它处理及默认处理
9v/1>rziE }
ON!1lS eLl;M4d RX#:27: 将屏幕图像保存到BMP文件中的任务是由函数SaveBmp()来完成的,具体实现参见代码部分。另外为了顺利实现屏幕抓图,程序中还实现了"托盘"功能,由于这一部分本书在实例中已经专门介绍过了,所以不再赘述,读者朋友可以参考相关实例。
8vchLl# (Kx3:gs 最后需要提醒读者朋友们注意的是,源程序的编译与使用时要先编译hook.dll并将其放在系统目录(win2000/NT是system32,98/ME是system),然后编译对话框程序运行即可。
5)mn "|&SC0* 二、编程步骤
5 kQC sx|=*j,_ 1、 启动Visual C++,生成一个DLL项目和一个基于对话框的应用程序项目,并将两个项目分别命名为"Hook"和"Capture";
ZfIQ Fh> g9
g
&] 2、 在"Hook"项目中导出AddHotkey()、DeleteHotkey()函数;
j1>1vD-`T T}U`?s`) 3、 在"Capture"项目中按照图一所示设置对话框的界面,具体设置参见代码部分;
?HU(0Vgn' ?n[+0a:8E 4、 使用Class Wizard在"Capture"项目中添加按钮的鼠标单击消息响应函数,并重载对话框的WindowProc()函数;
Y2Y/laD :5p`H 5、 添加代码,编译运行程序。
q?JP\_o: hXZk$a' 三、程序代码
Xo$(zGb ^F_c' ///////////////////////////////////// Hook.h : main header file for the HOOK DLL
7eZ,;
x #if !defined(AFX_HOOK_H__AEBFF705_C93A_11D5_B7D6_0080C82BE86B__INCLUDED_)
6J-tcL*4"% #define AFX_HOOK_H__AEBFF705_C93A_11D5_B7D6_0080C82BE86B__INCLUDED_
~|+ #if _MSC_VER > 1000
[_CIN #pragma once
w 8T#~Dc #endif // _MSC_VER > 1000
91[(K'=& #ifndef __AFXWIN_H__
[9*+s #error include 'stdafx.h' before including this file for PCH
@_0XK)pW #endif
[?_^Cy #include "resource.h" // main symbols
PYW~x@]k%, class CHookApp : public CWinApp
0(HUy`]> {
td{$c6 public:
'V } -0 CHookApp();
Z+FJ cvYx // Overrides
[N.4i"
Cd // ClassWizard generated virtual function overrides
FzW7MW>\x //{{AFX_VIRTUAL(CHookApp)
b$%W<D public:
l2z@t3{ virtual BOOL InitInstance();
ig jr=e virtual int ExitInstance();
G4]T //}}AFX_VIRTUAL
Qp]V~s( //{{AFX_MSG(CHookApp)
arRbq!mO // NOTE - the ClassWizard will add and remove member functions here.
51l : // DO NOT EDIT what you see in these blocks of generated code !
kwWDGA?zFB //}}AFX_MSG
AvH^9zEE( DECLARE_MESSAGE_MAP()
qy/xJ>: };
f D2.Zh LRESULT CALLBACK KeyboardProc(int nCode,WPARAM wParam, LPARAM lParam);
UJ
n3sZ<} BOOL __declspec(dllexport)__stdcall AddHotkey(HWND,UCHAR key,UCHAR mask);
PkMN@JS BOOL __declspec(dllexport)__stdcall DeleteHotkey(HWND,UCHAR key,UCHAR mask);
`Z0FQ( r_ BOOL InitHotkey();
K#tT \ BOOL UnInit();
c7,p5[ #endif
Qne@Vf kA bRfac/:} //////////////////////////////////////// Hook.cpp : Defines the initialization routines for the DLL.
={B%qq #include "stdafx.h"
9J$N5 #include "hook.h"
4Zo.c*
BZ #include <windowsx.h>
Wv8?G~> #ifdef _DEBUG
KZ>cfv-&a #define new DEBUG_NEW
=g
UOHH #undef THIS_FILE
RGf&KV/ static char THIS_FILE[] = __FILE__;
Z<@0~t_:?p #endif
J>TNyVaoQ #define MAX_KEY 100
l|j #define CTRLBIT 0x04
/R!:l l2 #define ALTBIT 0x02
Jx!#y A; #define SHIFTBIT 0x01
YZMSiDv[e #pragma data_seg("shareddata")
xG/B$DLn HHOOK hHook =NULL;
:Ugf3%sQ UINT nHookCount =0;
kZ>_m&g static UCHAR HotKey[MAX_KEY] = {0}; //hotkey
))66_bech static UCHAR HotKeyMask[MAX_KEY] = {0}; //flag for hotkey, value is VK_CONTRL or VK_NEMU or VK_SHIFT
kc-=5l static HWND hCallWnd[MAX_KEY] = {0}; //window associated with hotkey
,` 6O{Z~ static int KeyCount =0;
2Jo|]>nl}u static UCHAR MaskBits =0; //00000 Ctrl Alt Shift
kNR -eG #pragma data_seg()
Qzt'ZK HINSTANCE hins;
~}pc&jz>q void VerifyWindow();
Y 3h`uLQ BEGIN_MESSAGE_MAP(CHookApp, CWinApp)
_(l?gj //{{AFX_MSG_MAP(CHookApp)
?(0=+o(` // NOTE - the ClassWizard will add and remove mapping macros here.
qILb># // DO NOT EDIT what you see in these blocks of generated code!
k{d] //}}AFX_MSG_MAP
JuQwZ]3ed END_MESSAGE_MAP()
g[';1}/B4 1-0tG+ CHookApp::CHookApp()
SMoJKr(:w# {
'
Dcj\=8 // TODO: add construction code here,
Swa0TiT( // Place all significant initialization in InitInstance
Ql"kJ_F!br }
)0+6^[Tqq 0Q?)?8_ CHookApp theApp;
$>Qq 7 LRESULT CALLBACK KeyboardProc(int nCode,WPARAM wParam,LPARAM lParam)
g&z8t;@ {
,4:=n$e 0 BOOL bProcessed=FALSE;
' Dp;fEU$ if(HC_ACTION==nCode)
'.EO+1{a {
%
bfe_k( if((lParam&0xc0000000)==0xc0000000){// Key up
d^MRu#] switch(wParam)
J?{@pA {
_Ne fzZWUJ case VK_MENU:
:aQ.:b(n MaskBits&=~ALTBIT;
mC2K &'[ break;
~(nc<M[ case VK_CONTROL:
=' uePM") MaskBits&=~CTRLBIT;
7-:R{&3Lm: break;
.V4- case VK_SHIFT:
(Zg']) MaskBits&=~SHIFTBIT;
0vX4v)-^u break;
xt_:R~/[ default: //judge the key and send message
V6Mt;e)C break;
@`$'sU }
6_,JW{#" for(int index=0;index<MAX_KEY;index++){
0civXZgj if(hCallWnd[index]==NULL)
Y<L35
? continue;
w=pr?jt1: if(IsWindow(hCallWnd[index])&&(HotKey[index]==wParam)&&(HotKeyMask[index]==MaskBits))
'X<4";$mU {
m8@&-,T SendMessage(hCallWnd[index],WM_HOTKEY,wParam,WM_KEYUP);
]
# VHx bProcessed=TRUE;
@A4$k
dJ2 }
@}=(4% }
hw$!LTB2 }
d~1uK-L]* else if((lParam&0xc000ffff)==1){ //Key down
b9-IrR4h switch(wParam)
nr2 Q[9~ {
_Jy7` 4B. case VK_MENU:
)fHr]#v MaskBits|=ALTBIT;
N=AHS break;
U%q-#^A case VK_CONTROL:
F+"_] MaskBits|=CTRLBIT;
}}"pQ!Z break;
h PL]B_< case VK_SHIFT:
}R`Rqg-W MaskBits|=SHIFTBIT;
|lt]9>| break;
],_+J* default: //judge the key and send message
)/?H]o$NU break;
d]poUN~x }
h5SJVa for(int index=0;index<MAX_KEY;index++)
dgL>7X=7 {
D/?Ec\t if(hCallWnd[index]==NULL)
OvAhp&k continue;
+$|fUn{ if(IsWindow(hCallWnd[index])&&(HotKey[index]==wParam)&&(HotKeyMask[index]==MaskBits))
W:,Wex^9n {
K>dB{w#gS SendMessage(hCallWnd[index],WM_HOTKEY,wParam,WM_KEYDOWN);
om`T/@_, bProcessed=TRUE;
N0H=;CIQ }
V"m S$MN }
&\1n=y }
#l ZK_N|1x if(!bProcessed){
N+'j on}U for(int index=0;index<MAX_KEY;index++){
fqhL"Ah
if(hCallWnd[index]==NULL)
P0e-v0 continue;
R*:>h8 if(IsWindow(hCallWnd[index])&&(HotKey[index]==0)&&(HotKeyMask[index]==0))
[% C,&h5 SendMessage(hCallWnd[index],WM_HOTKEY,WM_HOTKEY,lParam);
xNzGp5H }
I]^>>>p$ }
GL=}Vu`(* }
/M_$4O;*@ return CallNextHookEx( hHook, nCode, wParam, lParam );
$c9-Q+pZ }
XEgJ7h_ >~SS^I0 BOOL InitHotkey()
r/2=
nE {
ZUHRATT- if(hHook!=NULL){
7~SwNt, nHookCount++;
0?<#! return TRUE;
F}5d>nw }
6Q^~O*cw else
+{1.kb
Zq hHook=SetWindowsHookEx(WH_KEYBOARD,(HOOKPROC)KeyboardProc,hins,0);
I |U'@E if(hHook!=NULL)
.E<nQWz8 nHookCount++;
gxN>q4z return (hHook!=NULL);
51SmoFbMz }
X*QS/\ BOOL UnInit()
P.}d@qD{) {
J#zr50@@ if(nHookCount>1){
xSm;~')g nHookCount--;
]1|P|Jp return TRUE;
h q)1YO }
d'RvpoM BOOL unhooked = UnhookWindowsHookEx(hHook);
D7;9D*o\ if(unhooked==TRUE){
$@D a|d4 nHookCount=0;
64<;6* hHook=NULL;
8NWo)y49H }
Snx!^4+MF return unhooked;
aYWWln }
$VuXr=f} s
V_(9@b BOOL __declspec(dllexport) __stdcall AddHotkey(HWND hWnd,UCHAR cKey,UCHAR cMask)
"j@\a)a {
5&ku]l+ BOOL bAdded=FALSE;
)h8}{* for(int index=0;index<MAX_KEY;index++){
bC/":+s& p if(hCallWnd[index]==0){
)th[fUC( hCallWnd[index]=hWnd;
Q?#I{l)V( HotKey[index]=cKey;
2;8m0+tl HotKeyMask[index]=cMask;
uh)S;3| bAdded=TRUE;
1^!SuAA@ KeyCount++;
>Icr4?zq break;
TmsIyDcD~ }
/|IPBU 5 }
j*jUcD* return bAdded;
*.DC(2:o! }
*yu}e)(0 4J2^zx,H BOOL __declspec(dllexport) __stdcall DeleteHotkey(HWND hWnd,UCHAR cKey,UCHAR cMask)
cCe~OlXQ {
{KG 6#/%; BOOL bRemoved=FALSE;
>]\I:T for(int index=0;index<MAX_KEY;index++){
ieFl4hh[G if(hCallWnd[index]==hWnd){
o4);5~1l if(HotKey[index]==cKey&&HotKeyMask[index]==cMask){
1~5DIU^ hCallWnd[index]=NULL;
qN $t_ HotKey[index]=0;
0cd_l
2f#g HotKeyMask[index]=0;
;mkkaW,D* bRemoved=TRUE;
x HRSzYn$ KeyCount--;
bGPE0}b break;
l/&.H F }
LQ jbEYp }
d$zJLgkA }
eU[g@Pq:Y return bRemoved;
o*S_" }
\^x{NV@v42 xN 1P# void VerifyWindow()
O
G`8::S {
,/42^|=Z6O for(int i=0;i<MAX_KEY;i++){
/Mqhx_)>A if(hCallWnd
!=NULL){ 9iA rBL"
if(!IsWindow(hCallWnd)){ K^Awf6%
hCallWnd=NULL; 0l!#u`cCI
HotKey=0; Cn{Hk)6
HotKeyMask=0; '5e,@t%y
KeyCount--; c3$T3Lu1
} mj~:MCC
} LeKovt%
} H@Dpht>[
} "Ms;sdjg}&
W>K^55'
BOOL CHookApp::InitInstance() XKoY!Y\
{ "
kDiK`i
AFX_MANAGE_STATE(AfxGetStaticModuleState()); J2YQdCL
hins=AfxGetInstanceHandle(); z3oi(
InitHotkey(); 3k Ci5C
return CWinApp::InitInstance(); (l{vlFWd
} '![oLy
*g/klK
int CHookApp::ExitInstance() b;k+N`
{ YW7W6mWspS
VerifyWindow(); =>jp\A
UnInit(); J:xGEa t
return CWinApp::ExitInstance(); yvAO"43
} [q<'ty
kv+%
////////////////////////////////////////////////////////////////////// CaptureDlg.h : header file sV\_DP/l
#if !defined(AFX_CAPTUREDLG_H__97B51708_C928_11D5_B7D6_0080C82BE86B__INCLUDED_) C]`uC^6g
#define AFX_CAPTUREDLG_H__97B51708_C928_11D5_B7D6_0080C82BE86B__INCLUDED_ *l2`- gbE
#if _MSC_VER > 1000 l/eF
P
#pragma once @~3--
#endif // _MSC_VER > 1000 O$Rz/&
p"g|]@m
class CCaptureDlg : public CDialog ,eXtY}E
{ Sc)^k
// Construction 'xwCeZcg
public: bc)>h!'Y
BOOL bTray; 2hh8G5IaQ
BOOL bRegistered; iOE. .xA:
BOOL RegisterHotkey(); hXW` n*Zw
UCHAR cKey; /%wS5IZ^
UCHAR cMask; |Splbsk
void DeleteIcon(); %opBJ
void AddIcon(); xoaO=7\io
UINT nCount; +$2{u_m,
void SaveBmp(); S;|:ci<[=
CCaptureDlg(CWnd* pParent = NULL); // standard constructor /jbAf ]"F;
// Dialog Data ?t#wK}d.
//{{AFX_DATA(CCaptureDlg) Ey6R/M)?:y
enum { IDD = IDD_CAPTURE_DIALOG }; !l:GrT8J
CComboBox m_Key; ;nY#/%f
BOOL m_bControl; =2Y;)wrF
BOOL m_bAlt; l$42MRi/
BOOL m_bShift; m,l/=M
CString m_Path; O%bbyR2
CString m_Number; ajYe?z
//}}AFX_DATA 9T,/R1N8
// ClassWizard generated virtual function overrides .tBlGMcN
//{{AFX_VIRTUAL(CCaptureDlg) 0-.
d{P
public: r*X,]\V0x
virtual BOOL PreTranslateMessage(MSG* pMsg); Z>[7#;;
protected: 04a
^jjc
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support aSL`yuXu
virtual LRESULT WindowProc(UINT message, WPARAM wParam, LPARAM lParam); 1+l 8%G=hB
//}}AFX_VIRTUAL rIyH/=;
// Implementation ;b~ S/
protected: PwY/VGT
HICON m_hIcon; 'ofj1%c
// Generated message map functions v^|U?
//{{AFX_MSG(CCaptureDlg) ,:_c-d#
virtual BOOL OnInitDialog(); jHLs
5%
afx_msg void OnSysCommand(UINT nID, LPARAM lParam); D=tZ}_'{t
afx_msg void OnPaint(); &q