windows编程之鼠标消息总结

1 确定鼠标是否存在:fMouse = GetSystemMetrics (SM_MOUSEPRESENT) ;
2 获取鼠标上按键数目:cButtons = GetSystemMetrics (SM_CMOUSEBUTTONS) ;
3 鼠标消息
可以分为显示区域消息,非显示区域消息,总共21个鼠标消息,其中11个消息和显示区域无关。
WM_MOUSEMOVE --鼠标移过窗口的显示区域时
WM_LBUTTONDOWN --
WM_LBUTTONUP --
WM_LBUTTONDBCLK --
WM_MBUTTONNDOWN --
WM_MBUTTONUP --
WM_MBUTTONDBCLK --
WM_RBUTTONDOWN --
WM_RBUTTONUP --
WM_RBUTTONDBCLK --


4 显示区域鼠标消息中的wParam和lParam的值
x = LOWORD(lParam); //获取鼠标x位置
y = HIWORD(lParam); //获取鼠标y位置
wParam指示Shift以及Ctrl键的状态,如wparam & MK_SHIFT 为TRUE表示在按下鼠标的同时按下了Shift键。


5 鼠标引起的活动窗口切换
在非活动窗口按下鼠标键,windows会把活动窗口改为在其中按下鼠标按键的窗口,同时该窗口收到WM_LBUTTONDWON,或者右键,中键。
在一个窗口按下鼠标键,然后在另一个窗口释放,此时在新的活动窗口只收到WM_LBUTTONUP此类的消息


6 双击消息
如果窗口要能接受双击消息,则在指定窗口类类别的时候必须包含CS_DBLCLKS标示符,如:
wndclass.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS ;
如果没有这样指定,则双击左键的时候,产生的鼠标消息依次是:WM_LBUTTONDOWN,WM_LBUTTONUP,WM_LBUTTONDOWN,WM_LBUTTONUP;
如果有这样的指定,则双击左键的时候,产生的鼠标消息依次是:WM_LBUTTONDOWN,WM_LBUTTONUP,WM_LBUTTONDBCLK,WM_LBUTTONUP。


7 非显示区域消息
如果鼠标在窗口的显示区域之外但是还在窗口中,windows就会给窗口消息处理程序发送一条非显示区域消息。非显示区域包括标题栏,菜单栏,滚动条。消息种类如下:
WM_MOUSEMOVE --鼠标移过窗口的显示区域时
WM_NCLBUTTONDOWN --
WM_NCLBUTTONUP --
WM_NCLBUTTONDBCLK --
WM_NCMBUTTONNDOWN --
WM_NCMBUTTONUP --
WM_NCMBUTTONDBCLK --
WM_NCRBUTTONDOWN --
WM_NCRBUTTONUP --
WM_NCRBUTTONDBCLK --

8 非显示区域鼠标消息的wParam和lParam
和显示区域鼠标消息不同,非显示区域的鼠标消息wParam表示的是移动或者按下鼠标的区域是非显示区域,lParam表示的坐标是相对于屏幕坐标系的。

9 命中测试消息
WM_NCHITTEST是优先级别最高的消息,wParam没有用,lParam包含鼠标坐标信息,windows通常把这个消息传递给DefWindowProc,然后由DefWindowProc产生新的鼠标信息,其中wParam参数可以是非显示区域wParam参数,以及HTCLIENT,HTNOWHERE,HTTRANSPARENT,HTERROR。如果wParam传回的是HTCLIENT,那么windows就会把屏幕坐标信息转换为显示区域坐标信息。所以一切的鼠标消息都是WM_NCHITTEST产生的,只要拦截它,其他鼠标信息就会作废。
case WM_NCHITTEST:   
  return (LRESULT) HTNOWHERE ;


10 光标位置函数
GetCursorPos(&pt);
SetCursorPos(&pt);


11 关于GetWindowLong 和SetWindowLong
LONG GetWindowLong(  HWND hWnd,int nIndex);
LONG SetWindowLong(  HWND hWnd, int nIndex,LONG dwNewLong);
理解其中一个便可以理解所有的,GetWindowLong(),这个函数意思就是根据窗口句柄获得与窗口相关的一些属性值,其中windows系统已经为每个窗口包含的属性包括:
#define GWL_WNDPROC         (-4)
#define GWL_HINSTANCE       (-6)
#define GWL_HWNDPARENT      (-8)
#define GWL_STYLE           (-16)
#define GWL_EXSTYLE         (-20)
#define GWL_USERDATA        (-21)
#define GWL_ID              (-12)
可以看到他们的值都是负值。
现在重新看看关于nIndex的解释:Specifies the zero-based offset to the value to be retrieved. Valid values are in the range zero through the number of bytes of extra window memory, minus four;
翻译过来:指定基于0的偏移量,有效值是0 -- 窗口额外字节-4的范围。(根据函数的字面意义,long表示的是4个字节,所以其存储类型字节应该是4的倍数,但是在实际测试中,即便超过指定字节的范围也没出现报错或者报警)。
总之这个函数当自己指定额外字节的时候,索引从0开始,利用系统的,索引就是那些宏了(负的)。

示例程序:

#include 
#define DIVISIONS 5
        
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM) ;       
LRESULT CALLBACK ChildWndProc(HWND, UINT, WPARAM, LPARAM) ;      
TCHAR szChildClass[] = TEXT ("Checker3_Child") ;
        
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,PSTR szCmdLine, int iCmdShow)       
{
        
    static TCHAR  szAppName[]   = TEXT ("Checker3") ;
    HWND          hwnd ;
    MSG           msg ;      
    WNDCLASS      wndclass ;
    wndclass.style           = CS_HREDRAW | CS_VREDRAW ;
    wndclass.lpfnWndProc     = WndProc ;
    wndclass.cbClsExtra      = 0 ;
    wndclass.cbWndExtra      = 0 ;
    wndclass.hInstance       = hInstance ;
    wndclass.hIcon           = LoadIcon (NULL, IDI_APPLICATION) ;
    wndclass.hCursor         = LoadCursor (NULL, IDC_ARROW) ;    
	wndclass.hbrBackground   = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
    wndclass.lpszMenuName    = NULL ;
    wndclass.lpszClassName   = szAppName ;// 通过ClassName来标志注册的窗口,创建的时候也用它
        
    if (!RegisterClass (&wndclass))
    {
		MessageBox (  NULL, TEXT ("Program requires Windows NT!"),szAppName, MB_ICONERROR) ;
		return 0 ;
    }
        
	wndclass.lpfnWndProc     = ChildWndProc ;
    wndclass.cbWndExtra      = sizeof (long) + 3 ;
    wndclass.hIcon           = NULL ;
    wndclass.lpszClassName   = szChildClass ;
	RegisterClass (&wndclass) ;


    hwnd = CreateWindow(szAppName, TEXT ("Checker3 Mouse Hit-Test Demo"),WS_OVERLAPPEDWINDOW,
                CW_USEDEFAULT, CW_USEDEFAULT,CW_USEDEFAULT, CW_USEDEFAULT,
                NULL, NULL, hInstance, NULL) ;


    ShowWindow(hwnd, iCmdShow) ;
    UpdateWindow(hwnd) ;


    while (GetMessage (&msg, NULL, 0, 0))
    {
        
		TranslateMessage (&msg) ;
        DispatchMessage (&msg) ;
        
    }
    return msg.wParam ;        
}


LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam)      
{
        
    static HWND   hwndChild[DIVISIONS][DIVISIONS] ;
    int    cxBlock, cyBlock, x, y ;
    switch (message)
	{
        
    case   WM_CREATE :
            for (x = 0 ; x < DIVISIONS ; x++)
                    for (y = 0 ; y < DIVISIONS ; y++)
						//GetWindowLong根据窗口句柄来获取窗口相关的信息
						//hMenu变量或者为menu句柄,或者为子窗口ID
						hwndChild[x][y] = CreateWindow (szChildClass, NULL,WS_CHILDWINDOW | WS_VISIBLE,
						0, 0, 0, 0,hwnd, (HMENU)(y << 8 | x),(HINSTANCE)GetWindowLong(hwnd, GWL_HINSTANCE),NULL);
            return 0 ;

    case   WM_SIZE :
            cxBlock = LOWORD (lParam) / DIVISIONS ;    
            cyBlock = HIWORD (lParam) / DIVISIONS ;
            for (x = 0 ; x < DIVISIONS ; x++)
				for (y = 0 ; y < DIVISIONS ; y++)
					MoveWindow(  hwndChild[x][y], x * cxBlock, y * cyBlock,cxBlock, cyBlock, TRUE);
            return 0 ;
        
    case   WM_LBUTTONDOWN :
            MessageBeep (0) ;
            return 0 ;

    case   WM_DESTROY :
            PostQuitMessage (0) ;
            return 0 ;      
    }
    return DefWindowProc (hwnd, message, wParam, lParam) ;        
}
        
LRESULT CALLBACK ChildWndProc(HWND hwnd, UINT message,WPARAM wParam, LPARAM lParam)       
{
        
    HDC    hdc ;
    PAINTSTRUCT  ps ;
    RECT         rect ;
	int nTest;
    switch (message)
	{
		case WM_CREATE:
			SetWindowLong(hwnd, 0, 0) ;       // on/off flag
			return 0 ;


		case WM_LBUTTONDOWN :
			SetWindowLong (hwnd, 0, 1 ^ GetWindowLong (hwnd, 0)) ;
			nTest = GetWindowLong(hwnd,100);
			SetWindowLong(hwnd,100,0);
			InvalidateRect (hwnd, NULL, FALSE) ;
			return 0 ;

		case WM_PAINT :
			hdc = BeginPaint (hwnd, &ps) ;
			GetClientRect (hwnd, &rect) ;
			Rectangle (hdc, 0, 0, rect.right, rect.bottom) ;
			if (GetWindowLong (hwnd, 0))
			{
				MoveToEx (hdc, 0,          0, NULL) ;
				LineTo   (hdc, rect.right, rect.bottom) ;
				MoveToEx (hdc, 0,          rect.bottom, NULL) ;
				LineTo   (hdc, rect.right, 0) ;
			}
			EndPaint (hwnd, &ps) ;
			return 0 ;
    }
    return DefWindowProc (hwnd, message, wParam, lParam) ;
        
}
12 关于父窗口,子窗口消息传递的一些总结:

一个程序有父窗口,也有子窗口的时候,如果没有特别设置,那么初始化的时候焦点是在父窗口,如果要改变窗口焦点到子窗口那么可以直接在子窗口上点击鼠标,或者在程序中通过SetFocus(hwnd,childId)来设置。

总之:鼠标可以直接改变焦点,键盘不能直接改变焦点。

实例程序如下:

#include 
        
#define DIVISIONS 5

LRESULT CALLBACK WndProc   (HWND, UINT, WPARAM, LPARAM) ;       
LRESULT CALLBACK ChildWndProc (HWND, UINT, WPARAM, LPARAM) ;      
int   idFocus = 0 ;        
TCHAR szChildClass[] = TEXT ("Checker4_Child") ;

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,PSTR szCmdLine, int iCmdShow)        
{
          static TCHAR szAppName[] = TEXT ("Checker4") ;
           HWND                                 hwnd ;
           MSG                                  msg ;
           WNDCLASS                         wndclass ;

           wndclass.style                               = CS_HREDRAW | CS_VREDRAW ;
           wndclass.lpfnWndProc                         = WndProc ;
           wndclass.cbClsExtra                          = 0 ;
           wndclass.cbWndExtra                          = 0 ;
           wndclass.hInstance                           = hInstance ;
           wndclass.hIcon                               = LoadIcon (NULL, IDI_APPLICATION) ;
           wndclass.hCursor                             = LoadCursor (NULL, IDC_ARROW) ;
           wndclass.hbrBackground                       = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
           wndclass.lpszMenuName                        = NULL ;
           wndclass.lpszClassName                       = szAppName ;
        
           if (!RegisterClass (&wndclass))
           {
                  MessageBox (NULL, TEXT ("Program requires Windows NT!"),szAppName, MB_ICONERROR) ;
                  return 0 ;
           }
        
           wndclass.lpfnWndProc         = ChildWndProc ;
           wndclass.cbWndExtra          = sizeof (long) ;
           wndclass.hIcon               = NULL ;
           wndclass.lpszClassName       = szChildClass ;
           RegisterClass (&wndclass) ;
        
           hwnd = CreateWindow (szAppName, TEXT ("Checker4 Mouse Hit-Test Demo"),WS_OVERLAPPEDWINDOW,
                       CW_USEDEFAULT, CW_USEDEFAULT,CW_USEDEFAULT, CW_USEDEFAULT,
					   NULL, NULL, hInstance, NULL) ;
           ShowWindow (hwnd, iCmdShow) ;
           UpdateWindow (hwnd) ;

           while (GetMessage (&msg, NULL, 0, 0))
           {
                TranslateMessage (&msg) ;
                DispatchMessage (&msg) ;
           }
          return msg.wParam ;       
}
        
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam)        
{   
    static HWND   hwndChild[DIVISIONS][DIVISIONS] ;   
    int  cxBlock, cyBlock, x, y ;
           switch (message)
           {
           case WM_CREATE :
                  for (x = 0 ; x < DIVISIONS ; x++)
                      for (y = 0 ; y < DIVISIONS ; y++)
							hwndChild[x][y] = CreateWindow (szChildClass, NULL,WS_CHILDWINDOW | WS_VISIBLE,
							0, 0, 0, 0,hwnd, (HMENU) (y << 8 | x),(HINSTANCE) GetWindowLong(hwnd, GWL_HINSTANCE),NULL) ;   
                  return 0 ;
        
           case WM_SIZE :
                  cxBlock = LOWORD (lParam) / DIVISIONS ;
                  cyBlock = HIWORD (lParam) / DIVISIONS ;
                  for (x = 0 ; x < DIVISIONS ; x++)
					for (y = 0 ; y < DIVISIONS ; y++)
						MoveWindow (  hwndChild[x][y], x * cxBlock, y * cyBlock, cxBlock, cyBlock, TRUE) ;
                  return 0 ;

           case   WM_LBUTTONDOWN :
                 MessageBeep (0) ;
                 return 0 ;

           // On set-focus message, set focus to child window
           case   WM_SETFOCUS:
                  //SetFocus (GetDlgItem (hwnd, idFocus)) ;
                  return 0 ;

            // On key-down message, possibly change the focus window
           case   WM_KEYDOWN:
                  x = idFocus & 0xFF ;
                  y = idFocus >> 8 ;
                  switch (wParam)
                  {
						case VK_UP: 
							y-- ;  break ;
						case VK_DOWN: 
							y++ ;  break ;
						case VK_LEFT:
							x-- ;  break ;  
						case VK_RIGHT:
							x++ ;  break ;   
						case VK_HOME:
							x = y = 0 ;break ;
						case VK_END:
							x = y = DIVISIONS - 1 ; break ;
						default: return 0 ;
                  }
        
                  x = (x + DIVISIONS) % DIVISIONS ;
                  y = (y + DIVISIONS) % DIVISIONS ;
                  idFocus = y << 8 | x ;
                  SetFocus (GetDlgItem (hwnd, idFocus)) ;
                  return 0 ;

           case   WM_DESTROY :
                 PostQuitMessage (0) ;
                 return 0 ;
        
           }
           return DefWindowProc (hwnd, message, wParam, lParam) ;      
}
        

LRESULT CALLBACK ChildWndProc (HWND hwnd, UINT message,WPARAM wParam, LPARAM lParam)       
{

	HDC              hdc ;
	PAINTSTRUCT      ps ;
	RECT             rect ;    
	switch (message)   
	{  
		case   WM_CREATE :
			SetWindowLong (hwnd, 0, 0) ;       // on/off flag
			return 0 ;

		case   WM_KEYDOWN:
			// Send most key presses to the parent window
			if (wParam != VK_RETURN && wParam != VK_SPACE)
			{
				SendMessage (GetParent (hwnd), message, wParam, lParam) ;
				return 0 ;
			}

			// For Return and Space, fall through to toggle the square
		case   WM_LBUTTONDOWN :
			SetWindowLong (hwnd, 0, 1 ^ GetWindowLong (hwnd, 0)) ;
			SetFocus (hwnd) ;
			InvalidateRect (hwnd, NULL, FALSE) ;
			return 0 ;

			// For focus messages, invalidate the window for repaint        
		case   WM_SETFOCUS:
			//鼠标点击,按键移动都会触发此消息,但是当鼠标单击时候idFocus没有变化,所以这里要对其赋值。
			idFocus = GetWindowLong (hwnd, GWL_ID) ;
			InvalidateRect (hwnd, NULL, TRUE) ;
			break;

			// Fall through
		case   WM_KILLFOCUS:
			InvalidateRect (hwnd, NULL, TRUE) ;
			return 0 ;

		case   WM_PAINT :
			hdc = BeginPaint (hwnd, &ps) ;
			GetClientRect (hwnd, &rect) ;
			Rectangle (hdc, 0, 0, rect.right, rect.bottom) ;
			// Draw the "x" mark
			if (GetWindowLong (hwnd, 0))
			{
				MoveToEx (hdc, 0,          0, NULL) ;
				LineTo   (hdc, rect.right, rect.bottom) ;
				MoveToEx (hdc, 0,         rect.bottom, NULL) ;     
				LineTo   (hdc, rect.right, 0) ;
			}
			// Draw the "focus" rectangle
			if (hwnd == GetFocus ())
			{
				rect.left   += rect.right / 10 ;
				rect.right  -= rect.left ;
				rect.top    += rect.bottom / 10 ;
				rect.bottom -= rect.top ;
				SelectObject (hdc, GetStockObject (NULL_BRUSH)) ;
				SelectObject (hdc, CreatePen (PS_DASH, 0, 0)) ;
				Rectangle (hdc, rect.left, rect.top, rect.right, rect.bottom) ;
				DeleteObject (SelectObject (hdc, GetStockObject (BLACK_PEN))) ;
			}
			EndPaint (hwnd, &ps) ;
			return 0 ;
	}
	return DefWindowProc (hwnd, message, wParam, lParam) ;

}

程序中需要注意的几点:

(1)

子窗口过程函数中有对WM_SETFOCUS消息的处理--

idFocus = GetWindowLong (hwnd, GWL_ID) ;
这个消息处理对通过按键移动来改变窗口焦点的子窗口来说是多余的,但是对于通过鼠标单击改变焦点的子窗口说是必须的,所以就加了这么一句

(2)

子窗口过程函数中针对WM_SETFOCUS的消息处理

			InvalidateRect (hwnd, NULL, TRUE) ;
			break;
这两句在源程序中是没有的,一般针对case语句要加上break表示结束当前case,但是源程序没有加break,这样做是有道理的,原因:当通过按键改变窗口焦点的时候,需要重绘,但是源程序没有InvalidateRect的语句,所以基本焦点改变了,但是在焦点窗口中并没有重绘,这里没有break所以会把case WM_KILLFOCUS中的InvalidateRect执行,从而实现了对焦点窗口的重绘。

如果非要加break,那么就得在break前面再加一个InvalidateRect()函数了,如现在的程序所示。


你可能感兴趣的:(windows编程)