windows程序设计(12):绘制自己的控件

上一小节中虽然也提到了绘制自己控件,但是由于时间紧迫,只是光有一个空壳子,里面并没有实际的内容,这次专门设计一个自己的控件。在这个程序中,设计了两个控件,一个显示4个指向朝外的三角形,点击它会使客户区变大;另一个里面有4个指向朝里的三角形,点击它是得客户区缩小。程序的关键点有两个:1是如何响应自己画的按钮的消息,2是如何画自己的按钮。

先看程序:

/*---------------------------------------------
   OWNDRAW.C -- Owner-Draw Button Demo Program
                (c) Charles Petzold, 1998
  ---------------------------------------------*/

#include <windows.h>

#define ID_SMALLER      1
#define ID_LARGER       2

#define BTN_WIDTH        (8 * cxChar)
#define BTN_HEIGHT       (4 * cyChar)

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;

HINSTANCE hInst ;

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    PSTR szCmdLine, int iCmdShow)
{
     static TCHAR szAppName[] = TEXT ("OwnDraw") ;
     MSG          msg ;
     HWND         hwnd ;
     WNDCLASS     wndclass ;
     
     hInst = hInstance ;
     
     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  = szAppName ;
     wndclass.lpszClassName = szAppName ;
     
     if (!RegisterClass (&wndclass))
     {
          MessageBox (NULL, TEXT ("This program requires Windows NT!"),
                      szAppName, MB_ICONERROR) ;
          return 0 ;
     }
     
     hwnd = CreateWindow (szAppName, TEXT ("Owner-Draw Button 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 ;
}

//画三角形函数,参数为:设备内容句柄,POINT类型的数组
void Triangle (HDC hdc, POINT pt[])
{
    //选择黑刷子
	SelectObject (hdc, GetStockObject (BLACK_BRUSH)) ;
	//画三角形
    Polygon (hdc, pt, 3) ;
	//
    SelectObject (hdc, GetStockObject (WHITE_BRUSH)) ;
}

LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
     static HWND      hwndSmaller, hwndLarger ;
     static int       cxClient, cyClient, cxChar, cyChar ;
     int              cx, cy ;
     LPDRAWITEMSTRUCT pdis ;
     POINT            pt[3] ;
     RECT             rc ;
     
     switch (message)
     {
     case WM_CREATE :
		 //获得系统字体的宽度和高度
          cxChar = LOWORD (GetDialogBaseUnits ()) ;
          cyChar = HIWORD (GetDialogBaseUnits ()) ;
          
               // Create the owner-draw pushbuttons
          
          hwndSmaller = CreateWindow (TEXT ("button"), TEXT (""),
                                      WS_CHILD | WS_VISIBLE | BS_OWNERDRAW,
                                      0, 0, BTN_WIDTH, BTN_HEIGHT,
                                      hwnd, (HMENU) ID_SMALLER, hInst, NULL) ;
          
          hwndLarger  = CreateWindow (TEXT ("button"), TEXT (""),
                                      WS_CHILD | WS_VISIBLE | BS_OWNERDRAW,
                                      0, 0, BTN_WIDTH, BTN_HEIGHT,
                                      hwnd, (HMENU) ID_LARGER, hInst, NULL) ;
          return 0 ;
          
     case WM_SIZE :
          cxClient = LOWORD (lParam) ;
          cyClient = HIWORD (lParam) ;
          
          //将两个窗口移动到客户区的中心
          MoveWindow (hwndSmaller, cxClient / 2 - 3 * BTN_WIDTH  / 2,
                                   cyClient / 2 -     BTN_HEIGHT / 2,
                      BTN_WIDTH, BTN_HEIGHT, TRUE) ;
          
          MoveWindow (hwndLarger,  cxClient / 2 +     BTN_WIDTH  / 2,
                                   cyClient / 2 -     BTN_HEIGHT / 2,
                      BTN_WIDTH, BTN_HEIGHT, TRUE) ;
          return 0 ;
          
     case WM_COMMAND :
          GetWindowRect (hwnd, &rc) ;
          
          // 是得客户区放大或者缩小10%
          
          switch (wParam)
          {
          case ID_SMALLER :
               rc.left   += cxClient / 20 ;
               rc.right  -= cxClient / 20 ;
               rc.top    += cyClient / 20 ;
               rc.bottom -= cyClient / 20 ;
               break ;
               
          case ID_LARGER :
               rc.left   -= cxClient / 20 ;
               rc.right  += cxClient / 20 ;
               rc.top    -= cyClient / 20 ;
               rc.bottom += cyClient / 20 ;
               break ;
          }
         
          MoveWindow (hwnd, rc.left, rc.top, rc.right  - rc.left,
                            rc.bottom - rc.top, TRUE) ;
          return 0 ;
     //WM_DRAWITEM消息的lParam参数指向 DRAWITEMSTRUCT结构
	 //这个结构包含了自己绘制的控件的信息
     case WM_DRAWITEM :
		 //DRAWITEMSTRUCT中的hDC表明设备内容句柄
        // pdis = (LPDRAWITEMSTRUCT) lParam ;
		  pdis = (LPDRAWITEMSTRUCT) lParam ;
          
          
          //给白色矩形加上黑色边框
          FillRect (pdis->hDC, &pdis->rcItem,
                    (HBRUSH) GetStockObject (WHITE_BRUSH)) ;
          
          FrameRect (pdis->hDC, &pdis->rcItem,
                     (HBRUSH) GetStockObject (BLACK_BRUSH)) ;
          
          //画向内和向外的黑三角
          cx = pdis->rcItem.right  - pdis->rcItem.left ;
          cy = pdis->rcItem.bottom - pdis->rcItem.top  ;
         // DRAWITEMSTRUCT结构中CtlID表明窗口控件ID
          switch (pdis->CtlID)
          {
          case ID_SMALLER :
			  //需要定义3个点,然后调用画三角形函数画图
               pt[0].x = 3 * cx / 8 ;  pt[0].y = 1 * cy / 8 ;
               pt[1].x = 5 * cx / 8 ;  pt[1].y = 1 * cy / 8 ;
               pt[2].x = 4 * cx / 8 ;  pt[2].y = 3 * cy / 8 ;
               
               Triangle (pdis->hDC, pt) ;
               
               pt[0].x = 7 * cx / 8 ;  pt[0].y = 3 * cy / 8 ;
               pt[1].x = 7 * cx / 8 ;  pt[1].y = 5 * cy / 8 ;
               pt[2].x = 5 * cx / 8 ;  pt[2].y = 4 * cy / 8 ;
               
               Triangle (pdis->hDC, pt) ;
               
               pt[0].x = 5 * cx / 8 ;  pt[0].y = 7 * cy / 8 ;
               pt[1].x = 3 * cx / 8 ;  pt[1].y = 7 * cy / 8 ;
               pt[2].x = 4 * cx / 8 ;  pt[2].y = 5 * cy / 8 ;
               
               Triangle (pdis->hDC, pt) ;
               
               pt[0].x = 1 * cx / 8 ;  pt[0].y = 5 * cy / 8 ;
               pt[1].x = 1 * cx / 8 ;  pt[1].y = 3 * cy / 8 ;
               pt[2].x = 3 * cx / 8 ;  pt[2].y = 4 * cy / 8 ;
               
               Triangle (pdis->hDC, pt) ;
               break ;
               
          case ID_LARGER :
               pt[0].x = 5 * cx / 8 ;  pt[0].y = 3 * cy / 8 ;
               pt[1].x = 3 * cx / 8 ;  pt[1].y = 3 * cy / 8 ;
               pt[2].x = 4 * cx / 8 ;  pt[2].y = 1 * cy / 8 ;
               
               Triangle (pdis->hDC, pt) ;
               
               pt[0].x = 5 * cx / 8 ;  pt[0].y = 5 * cy / 8 ;
               pt[1].x = 5 * cx / 8 ;  pt[1].y = 3 * cy / 8 ;
               pt[2].x = 7 * cx / 8 ;  pt[2].y = 4 * cy / 8 ;
               
               Triangle (pdis->hDC, pt) ;
               
               pt[0].x = 3 * cx / 8 ;  pt[0].y = 5 * cy / 8 ;
               pt[1].x = 5 * cx / 8 ;  pt[1].y = 5 * cy / 8 ;
               pt[2].x = 4 * cx / 8 ;  pt[2].y = 7 * cy / 8 ;
               
               Triangle (pdis->hDC, pt) ;
               
               pt[0].x = 3 * cx / 8 ;  pt[0].y = 3 * cy / 8 ;
               pt[1].x = 3 * cx / 8 ;  pt[1].y = 5 * cy / 8 ;
               pt[2].x = 1 * cx / 8 ;  pt[2].y = 4 * cy / 8 ;
               
               Triangle (pdis->hDC, pt) ;
               break ;
          }
          
          
          //当按下按钮时itemState字段中的某位将被设为1,用ODS_SELECTED常数来测试这些位
          if (pdis->itemState & ODS_SELECTED)
			  //翻转矩形区域
               InvertRect (pdis->hDC, &pdis->rcItem) ;
          
          
          //绘制焦点矩形框
		  //判断是否处于焦点
          if (pdis->itemState & ODS_FOCUS)
          {
              //焦点矩形框大小
			  pdis->rcItem.left   += cx / 16 ;
              pdis->rcItem.top    += cy / 16 ;
              pdis->rcItem.right  -= cx / 16 ;
              pdis->rcItem.bottom -= cy / 16 ;
              //绘制焦点矩形框
              DrawFocusRect (pdis->hDC, &pdis->rcItem) ;
          }
		  
          return 0 ;
          
     case WM_DESTROY :
          PostQuitMessage (0) ;
          return 0 ;
     }
     return DefWindowProc (hwnd, message, wParam, lParam) ;
}


 

在WM_CREATE消息下,创建了两个子窗口;在WM_SIZE消息下,获得客户区的大小并把窗口移动到客户区的中间;每当点击控件时,父窗口将会接收到子窗口的WM_COMMAND消息,通过对消息的wParam参数判断,来确定点击的是哪个子窗口并进行放大或者缩小客户区的操作。

到此,子窗口的功能已经实现了,剩下的问题就是画出这个子窗口。这个工作在WM_DRAWITEM消息下完成。

这个消息的参数lParam非常关键,对它进行类型转换以后,就能得到指向DRAWITEMSTRUCT结构的指针。这个结构存储了你自己画的按钮的信息:比如CtlID表明窗口控件ID;当按下按钮时itemState字段中的某位将被设为1,用ODS_SELECTED常数来测试这些位;使用ODS_FOCUS来测试itemState判断窗口是否处于焦点等等。

在程序中,先画一个白色矩形,给他添上黑框;然后判断是哪个窗口,对应的窗口画出三角形;按下窗口时,翻转该窗口;判断其是否属于焦点,如果是,添加一个焦点框。

这就是程序的基本流程。

 

你可能感兴趣的:(windows程序设计(12):绘制自己的控件)