Chapter I:在WM_MOUSEMOVE中绘制.

有时候要求在一个窗口中绘制鼠标的拖曳框,就像是用户在桌面上拖曳图标显示的框一样.对于这样的矩形框windows的API提供了一个函数DrawFocusRect, 当然这个函数是可以的,不过这个函数有两个小问题,那就是它的第二个参数是一个RECT参数,而且这个矩形的必须有以下特点: left<right,top<bottom.否则,绘制必定失败,而且也没有办法设置矩形框的颜色.当然,计算这样的矩形并不是什么困难的事情,不过这并不是这篇文章的重点. 在这里,提供一种自己绘制矩形的办法,这样可以根据需要自己来改写.下面是这个函数的实现(两个):
 
 
  
  1. void DrawDragRect(HDC hDC, RECT& rc) 
  2.     for (int j=rc.top; j<rc.bottom; j++) 
  3.     { 
  4.         if (j%2==0) 
  5.             SetPixel(hDC, rc.left, j, RGB(255, 0, 0)); 
  6.     } 
  7.  
  8.     for (int i=rc.left; i<rc.right; i++) 
  9.     { 
  10.         if (i%2==0) 
  11.             SetPixel(hDC, i, rc.top, RGB(255, 0, 0)); 
  12.     } 
  13.  
  14.     for (int j=rc.top; j<rc.bottom; j++) 
  15.     { 
  16.         if (j%2==0) 
  17.             SetPixel(hDC, rc.right, j, RGB(255, 0, 0)); 
  18.     } 
  19.  
  20.     for (int i=rc.left; i<rc.right; i++) 
  21.     { 
  22.         if (i%2==0) 
  23.             SetPixel(hDC, i, rc.bottom, RGB(255, 0, 0)); 
  24.     } 
  25. void DrawDragRect(HDC hDC, POINT ptSource, POINT ptDest) 
  26.     int left = ptSource.x <= ptDest.x ? ptSource.x : ptDest.x; 
  27.     int right = left + abs(ptDest.x - ptSource.x); 
  28.     int top = ptSource.y <= ptDest.y ? ptSource.y : ptDest.y; 
  29.     int bottom = top + abs(ptDest.y - ptSource.y); 
  30.  
  31.     ASSERT(left<=right && bottom<=top); 
  32.  
  33.     for (int j=top; j<bottom; j++) 
  34.     { 
  35.         if (j%2==0) 
  36.             SetPixel(hDC, left, j, RGB(255, 0, 0)); 
  37.     } 
  38.  
  39.     for (int i=left; i<right; i++) 
  40.     { 
  41.         if (i%2==0) 
  42.             SetPixel(hDC, i, top, RGB(255, 0, 0)); 
  43.     } 
  44.  
  45.     for (int j=top; j<bottom; j++) 
  46.     { 
  47.         if (j%2==0) 
  48.             SetPixel(hDC, right, j, RGB(255, 0, 0)); 
  49.     } 
  50.  
  51.     for (int i=left; i<right; i++) 
  52.     { 
  53.         if (i%2==0) 
  54.             SetPixel(hDC, i, bottom, RGB(255, 0, 0)); 
  55.     } 

可以发现,上面两个函数都是绘制矩形时都是通过在DC上设置像素的颜色实现的,GDI/GDI+没有提供绘制鼠标拖曳框的办法(如果有哪位网友发现了请 告诉笔者,不胜感激). 绘制矩形框最关键的部分好绘制的时机,主要是:
1)捕捉起点,这个主要是在鼠标在窗口客户区按下时刻作为矩形的起点.
2)捕捉过程点,用户按下鼠标左键,在窗口中拖曳,此时鼠标滑过的轨迹就会绘制矩形框的终点.
3)结束点,用户释放鼠标左键.
另外就是绘制过程中对背景擦除问题,只要用户释放鼠标,就要重绘背景,将矩形框擦除.
下面是代码.
 
 
  
  1. RECT g_rcDrag = {0};//待绘制的矩形框. 
  2. LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) 
  3.     ... 
  4.     case WM_LBUTTONDOWN: 
  5.         { 
  6.             // 在客户区按下左键,作为绘制的起点. 
  7.             g_rcDrag.right = g_rcDrag.left = LOWORD(lParam); 
  8.             g_rcDrag.bottom = g_rcDrag.top = HIWORD(lParam); 
  9.  
  10.             ::SetCapture(hWnd);//必须调用,否则收不到WM_LBUTTONUP. 
  11.         } 
  12.         break
  13.     case WM_MOUSEMOVE: 
  14.         { 
  15.             if (wParam == MK_LBUTTON) 
  16.             { 
  17.                 POINT pt; 
  18.                 pt.x = LOWORD(lParam); 
  19.                 pt.y = HIWORD(lParam); 
  20.  
  21.                 // 待绘制的拖曳矩形的4个点. 
  22.                 if (pt.x <= g_rcDrag.left)      g_rcDrag.left = pt.x; 
  23.                 if (g_rcDrag.left != pt.x)      g_rcDrag.right = pt.x; 
  24.                 if (pt.y <= g_rcDrag.top)       g_rcDrag.top = pt.y; 
  25.                 if (g_rcDrag.top != pt.y)       g_rcDrag.bottom = pt.y; 
  26.  
  27.                 // 发出WM_PAINT消息,以绘制该矩形. 
  28.                 InvalidateRect(hWnd, NULL, TRUE);// 注意,最后一个参数必须为TRUE,如果为FALSE,上次绘制的矩形不消失. 
  29.                 UpdateWindow(hWnd); 
  30.             } 
  31.         } 
  32.         break
  33.     case WM_LBUTTONUP: 
  34.         { 
  35.             ::ReleaseCapture(); 
  36.              
  37.             g_rcDrag.left = g_rcDrag.right = 0; 
  38.  
  39.             InvalidateRect(hWnd, NULL, TRUE);// 注意,最后一个参数必须为TRUE,如果为FALSE,上次绘制的矩形不消失. 
  40.             UpdateWindow(hWnd); 
  41.         } 
  42.         break
  43.     case WM_PAINT: 
  44.         { 
  45.             hdc = BeginPaint(hWnd, &ps); 
  46.              
  47.             if (0 < g_rcDrag.right-g_rcDrag.left) 
  48.                 DrawDragRect(hdc, g_rcDrag); //::DrawFocusRect(hdc, &g_rcDrag);也可以 
  49.  
  50.  
  51.             ::DrawDragRect(hdc, ) 
  52.         break
  53. ... 
按住鼠标左键在窗口客户区拖动吧,可以发现一个红色的矩形框.
Chapter II:更好的绘制办法
Chapter I的代码能够正常工作,不过这是一种理想的情况,但有时候会碰到这样一种情况:某些时候程序会对窗口背景或前景进行绘制,例如贴一个图片,画一些 复杂的图形等待,这些情况会导致一种情况:那就是当在窗口中拖曳鼠标时,WM_MOUSEMOVE不是那么灵光,具体表现在当在客户区拖曳鼠标后,矩形的4个边绘制的 不完整.总而言之,在WM_MOUSEMOVE绘制矩形不正常,在这种情况下,为了达到绘制的平滑效果,可以通过计时器来绘制.
 
 
   
  1. RECT g_rcDrag = {-1}; 
  2. #define ID_TIMER    1 
  3. LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) 
  4.     ... 
  5.     switch (message) 
  6.     { 
  7.     case WM_CREATE: 
  8.         ::SetTimer (hWnd, ID_TIMER, 100, NULL) ; //创建定时器,每10微妙产生一个WM_TIMER消息 
  9.         break
  10.     case WM_TIMER: 
  11.         if (-1 != g_rcDrag.left) 
  12.         { 
  13.             POINT pt = {0}; 
  14.             GetCursorPos(&pt); 
  15.             ScreenToClient(hWnd, &pt); 
  16.  
  17.             // 待绘制的拖曳矩形的4个点. 
  18.             if (pt.x <= g_rcDrag.left)      g_rcDrag.left = pt.x; 
  19.             if (g_rcDrag.left != pt.x)      g_rcDrag.right = pt.x; 
  20.             if (pt.y <= g_rcDrag.top)       g_rcDrag.top = pt.y; 
  21.             if (g_rcDrag.top != pt.y)       g_rcDrag.bottom = pt.y; 
  22.  
  23.             InvalidateRect(hWnd, NULL, TRUE); 
  24.             UpdateWindow(hWnd); 
  25.         } 
  26.         break
  27.     case WM_LBUTTONDOWN: 
  28.         { 
  29.             g_rcDrag.right = g_rcDrag.left = LOWORD(lParam); 
  30.             g_rcDrag.bottom = g_rcDrag.top = HIWORD(lParam); 
  31.      
  32.             ::SetCapture(hWnd); 
  33.         } 
  34.         break
  35.     case WM_PAINT: 
  36.         { 
  37.             hdc = BeginPaint(hWnd, &ps); 
  38.              
  39.             if (0 < g_rcDrag.right-g_rcDrag.left) 
  40.                 DrawDragRect(hdc, g_rcDrag); //::DrawFocusRect(hdc, &g_rcDrag);也可以 
  41.         } 
  42.         break
  43.     case WM_DESTROY: 
  44.         KillTimer (hWnd, ID_TIMER) ;          //销毁定时器 
  45.         break
  46.     ... 
  47.     } 
  48.     ... 
SetTimer的间隔时间越短,绘制效果越好.