VC1.窗体设计-自绘对话框
VC的代码看起来总是那么的凌乱,总是不想下手去学习它,这些书本一直在暑假沉睡了很久,为了程序考试,拿出来学习了,因为至少现在不在是一无所知的少年了,知道了语言的语法,知道了一些API的应用,了解了一点面向对象的思想,决定直接拿着实例来啃了,昨天尝试了第一个实例,虽然只是简单的功能,昨天看了一天无从下手,一直不愿意下手,一拖再拖,今天终于下手了,虽然做了很多typewriter的工作,但是我相信这样还是会有进步的,只要把理解的记录下来。
这两天完成的一个实例是自绘对话框,虽然win7的对话框已经很美丽了,但是如果能够利用自己设计的位图作为对话框岂不是更完美,不是么?先上最终的效果图,这个效果可是花费了我很久的时间,因为res的位图文件别人设计的,比较丑,其实我们可以设计出更好的,接下来只是学习知识的记录。
主要思路:
如同上图设计出来的对话框,设计思路将对话框非为左边框,下边框,右边框,上部的标题栏拆分为左标题栏(固定),中间标题栏(伸缩),右边标题栏(固定且包含三个图标);所有的这些均是位图文件绘制上去的,利用下面提到的技术;
这部分其中主要难点可能是绘制的位置的计算;下面就是这些位图绘制的主要思路,下面判断绘制也防止了重新绘制;
1 int CDesignDlgDemoDlg::DrawDialog(int nFlag) 2 { 3 int nFrameCY=GetSystemMetrics(SM_CYFIXEDFRAME); 4 int nFrameCX=GetSystemMetrics(SM_CXFIXEDFRAME); 5 if (GetStyle()&WS_BORDER) 6 { 7 m_nBorderCY=nFrameCY+2*GetSystemMetrics(SM_CYBORDER)+2*GetSystemMetrics(SM_CYEDGE); 8 m_nBorderCX=nFrameCX+2*GetSystemMetrics(SM_CXBORDER)+2*GetSystemMetrics(SM_CXEDGE); 9 } 10 else 11 { 12 m_nBorderCX=nFrameCX; 13 m_nBorderCY=nFrameCY; 14 } 15 m_nTitleBarCY=GetSystemMetrics(SM_CYCAPTION)+m_nBorderCY; 16 CRect ClientRC; 17 GetClientRect(ClientRC); 18 CRect WinRC,FactRC; 19 GetWindowRect(WinRC); 20 FactRC.CopyRect(CRect(0,0,WinRC.Width(),WinRC.Height())); 21 CWindowDC WindowDC(this); 22 CBitmap Bmp; 23 BITMAPINFO bmpinfo; 24 CDC memDC; 25 memDC.CreateCompatibleDC(&WindowDC); 26 if (nFlag&LEFTBAR) 27 { 28 Bmp.LoadBitmap(IDB_BITMAP_LEFTFORM); 29 memDC.SelectObject(&Bmp); 30 Bmp.GetObject(sizeof(BITMAPINFO),&bmpinfo); 31 int nBmpCX=bmpinfo.bmiHeader.biWidth; 32 int nBmpCY=bmpinfo.bmiHeader.biHeight; 33 WindowDC.StretchBlt(0,m_nTitleBarCY,m_nBorderCX,FactRC.Height()-m_nTitleBarCY,&memDC,0,0,nBmpCX,nBmpCY,SRCCOPY); 34 Bmp.DeleteObject(); 35 } 36 // 37 int nLeftBmpCX=0; 38 int nRightBmpCX=0; 39 if (nFlag&LEFTTITLE) 40 { 41 Bmp.LoadBitmap(IDB_BITMAP_UPLEFT); 42 memDC.SelectObject(&Bmp); 43 Bmp.GetObject(sizeof(BITMAPINFO),&bmpinfo); 44 int nBmpCX=bmpinfo.bmiHeader.biWidth; 45 int nBmpCY=bmpinfo.bmiHeader.biHeight; 46 nLeftBmpCX=nBmpCX; 47 WindowDC.StretchBlt(0,0,nBmpCX,m_nTitleBarCY,&memDC,0,0,nBmpCX,nBmpCY,SRCCOPY); 48 Bmp.DeleteObject(); 49 } 50 if (nFlag&RIGHTTITLE) 51 { 52 Bmp.LoadBitmap(IDB_BITMAP_UPRIGHT); 53 memDC.SelectObject(&Bmp); 54 Bmp.GetObject(sizeof(BITMAPINFO),&bmpinfo); 55 int nBmpCX=bmpinfo.bmiHeader.biWidth; 56 int nBmpCY=bmpinfo.bmiHeader.biHeight; 57 nRightBmpCX=nBmpCX; 58 m_nRightTitleCX=nRightBmpCX; 59 int nOrgX=FactRC.Width()-nBmpCX; 60 m_TitleBarRC.CopyRect(CRect(FactRC.Width()-nBmpCX,0,FactRC.right,m_nTitleBarCY)); 61 WindowDC.StretchBlt(nOrgX,0,nBmpCX,m_nTitleBarCY,&memDC,0,0,nBmpCX,nBmpCY,SRCCOPY); 62 Bmp.DeleteObject(); 63 } 64 if (nFlag&MIDTITLE) 65 { 66 Bmp.LoadBitmap(IDB_BITMAP_UPMID); 67 memDC.SelectObject(&Bmp); 68 Bmp.GetObject(sizeof(BITMAPINFO),&bmpinfo); 69 int nBmpCX=bmpinfo.bmiHeader.biWidth; 70 int nBmpCY=bmpinfo.bmiHeader.biHeight; 71 int nMidStreatch=FactRC.Width()-nLeftBmpCX-nRightBmpCX; 72 WindowDC.StretchBlt(nLeftBmpCX,0,nMidStreatch,m_nTitleBarCY,&memDC,0,0,nBmpCX,nBmpCY,SRCCOPY); 73 Bmp.DeleteObject(); 74 } 75 if (nFlag&RIGHTBAR) 76 { 77 Bmp.LoadBitmap(IDB_BITMAP_RIGHTFORM); 78 memDC.SelectObject(&Bmp); 79 Bmp.GetObject(sizeof(BITMAPINFO),&bmpinfo); 80 int nBmpCX=bmpinfo.bmiHeader.biWidth; 81 int nBmpCY=bmpinfo.bmiHeader.biHeight; 82 WindowDC.StretchBlt(FactRC.Width()-m_nBorderCX,m_nTitleBarCY,m_nBorderCX,FactRC.Height()-m_nTitleBarCY,&memDC,0,0,nBmpCX,nBmpCY,SRCCOPY); 83 Bmp.DeleteObject(); 84 } 85 if (nFlag&BOTTOMBAR) 86 { 87 Bmp.LoadBitmap(IDB_BITMAP_BUTTOM); 88 memDC.SelectObject(&Bmp); 89 Bmp.GetObject(sizeof(BITMAPINFO),&bmpinfo); 90 int nBmpCX=bmpinfo.bmiHeader.biWidth; 91 int nBmpCY=bmpinfo.bmiHeader.biHeight; 92 WindowDC.StretchBlt(m_nBorderCX,FactRC.Height()-m_nBorderCY,FactRC.Width()-2*m_nBorderCX,m_nBorderCY,&memDC,0,0,nBmpCX,nBmpCY,SRCCOPY); 93 Bmp.DeleteObject(); 94 } 95 if (nFlag&MINBUTTON) 96 { 97 Bmp.LoadBitmap(IDB_BITMAP_MIN); 98 memDC.SelectObject(&Bmp); 99 Bmp.GetObject(sizeof(BITMAPINFO),&bmpinfo); 100 int nBmpCX=bmpinfo.bmiHeader.biWidth; 101 int nBmpCY=bmpinfo.bmiHeader.biHeight; 102 WindowDC.StretchBlt(m_MinRC.left,m_MinRC.top,m_MinRC.Width(),m_MinRC.Height(),&memDC,0,0,nBmpCX,nBmpCY,SRCCOPY); 103 Bmp.DeleteObject(); 104 } 105 if (nFlag&MAXBUTTON) 106 { 107 Bmp.LoadBitmap(IDB_BITMAP_MAX); 108 memDC.SelectObject(&Bmp); 109 Bmp.GetObject(sizeof(BITMAPINFO),&bmpinfo); 110 int nBmpCX=bmpinfo.bmiHeader.biWidth; 111 int nBmpCY=bmpinfo.bmiHeader.biHeight; 112 WindowDC.StretchBlt(m_MAXRC.left,m_MAXRC.top,m_MAXRC.Width(),m_MAXRC.Height(),&memDC,0,0,nBmpCX,nBmpCY,SRCCOPY); 113 Bmp.DeleteObject(); 114 } 115 if (nFlag&CLOSEBUTTON) 116 { 117 Bmp.LoadBitmap(IDB_BITMAP_CLOSE); 118 memDC.SelectObject(&Bmp); 119 Bmp.GetObject(sizeof(BITMAPINFO),&bmpinfo); 120 int nBmpCX=bmpinfo.bmiHeader.biWidth; 121 int nBmpCY=bmpinfo.bmiHeader.biHeight; 122 WindowDC.StretchBlt(m_CloseRC.left,m_CloseRC.top,m_CloseRC.Width(),m_CloseRC.Height(),&memDC,0,0,nBmpCX,nBmpCY,SRCCOPY); 123 Bmp.DeleteObject(); 124 } 125 ReleaseDC(&memDC); 126 return 0; 127 }
这里首先编译源码的时候发现windows自带的边框没有被去除,这里如果border属性选择none,则无法resize,这里就要响应另外一个消息;OnNcPaint(),绘制非客户区域的时候的消息,响应至为空,则不进行边框绘制,响应此函数必须响应另外两个对应的函数,不然焦点失去的时候会出现bug,这里失去获得焦点进行重新绘制:
1 //取消边框绘制 2 void CDesignDlgDemoDlg::OnNcPaint() 3 { 4 // TODO: 在此处添加消息处理程序代码 5 // 不为绘图消息调用 CDialog::OnNcPaint() 6 //DrawDialog(); 7 } 8 //切换焦点时重新绘制 9 void CDesignDlgDemoDlg::OnActivate(UINT nState, CWnd* pWndOther, BOOL bMinimized) 10 { 11 CDialog::OnActivate(nState, pWndOther, bMinimized); 12 Invalidate(); 13 // TODO: 在此处添加消息处理程序代码 14 } 15 //切换焦点时重新绘制 16 BOOL CDesignDlgDemoDlg::OnNcActivate(BOOL bActive) 17 { 18 // TODO: 在此添加消息处理程序代码和/或调用默认值 19 Invalidate(); 20 return CDialog::OnNcActivate(bActive); 21 }
窗口大小响应onsize消息进行变化的时候重新计算各个位图的位置,然后重新进行绘制,invalidate调用onPaint函数即可,所以onSize处理的重点是计算各种位图的位置;
1 void CDesignDlgDemoDlg::OnSize(UINT nType, int cx, int cy) 2 { 3 CDialog::OnSize(nType, cx, cy); 4 //获取窗口边框的高度 5 //获取窗口边框的宽度 6 //根据窗口的风格计算对话框的高度和宽度 7 //获取对话框是否有边框 8 //计算标题栏宽度 9 //获取客户区域 10 //获取窗口区域 11 //更新整个窗口 12 int nFrameCY = GetSystemMetrics(SM_CYFIXEDFRAME); 13 int nFrameCX = GetSystemMetrics(SM_CXFIXEDFRAME); 14 //希望能够调试输出信息 15 16 if (GetStyle()&WS_BORDER) 17 { 18 m_nBorderCY = nFrameCY+2*GetSystemMetrics(SM_CYBORDER)+2*GetSystemMetrics(SM_CYEDGE); 19 m_nBorderCX = nFrameCX+2*GetSystemMetrics(SM_CXBORDER)+2*GetSystemMetrics(SM_CXEDGE); 20 } 21 else 22 { 23 m_nBorderCY=nFrameCY; 24 m_nBorderCX=nFrameCX; 25 } 26 // 27 m_nTitleBarCY = GetSystemMetrics(SM_CYCAPTION)+m_nBorderCY; //标题栏高度 28 CRect ClientRC; 29 GetClientRect(ClientRC); 30 CRect WinRC; 31 GetWindowRect(WinRC); //为什么不是地址? 32 // 33 m_MinRC.left=m_MinPoint.x+WinRC.Width()-m_nRightTitleCX; //逻辑坐标? 34 m_MinRC.top=(m_nTitleBarCY-m_nTitleBtnCY)/2+m_MinPoint.y; 35 m_MinRC.right=m_nTitleBtnCX+m_MinRC.left; 36 m_MinRC.bottom=m_MinRC.top+m_nTitleBtnCY; 37 38 m_MAXRC.left=m_MAXPoint.x+WinRC.Width()-m_nRightTitleCX; //逻辑坐标? 39 m_MAXRC.top=(m_nTitleBarCY-m_nTitleBtnCY)/2+m_MAXPoint.y; 40 m_MAXRC.right=m_nTitleBtnCX+m_MAXRC.left; 41 m_MAXRC.bottom=m_MAXRC.top+m_nTitleBtnCY; 42 43 m_CloseRC.left=m_ClosePoint.x+WinRC.Width()-m_nRightTitleCX; //逻辑坐标? 44 m_CloseRC.top=(m_nTitleBarCY-m_nTitleBtnCY)/2+m_ClosePoint.y; 45 m_CloseRC.right=m_nTitleBtnCX+m_CloseRC.left; 46 m_CloseRC.bottom=m_CloseRC.top+m_nTitleBtnCY; 47 48 m_WinRCWidth=WinRC.Width(); 49 50 Invalidate(); //强制更新; 51 }
这里主要难点在于GetSystemMetrics获取的边框有些问题,上面的计算也是利用经验得出这样才不会在边框和客户区之间有裂缝;否则会出现裂缝,所以GetSystemMetrics的许多参数需要试验具体的意义;
鼠标在非客户区移动时候,响应的函数为OnNcMouseMove函数,这里处理进入热点按钮区域则进行相应绘制热点,这里注意鼠标的坐标是按照屏幕中的位置计算的,所以这里的区域重新计算一下:
1 void CDesignDlgDemoDlg::OnNcMouseMove(UINT nHitTest, CPoint point) 2 { 3 // TODO: 在此添加消息处理程序代码和/或调用默认值 4 CRect MinRC,MAXRC,CloseRC,WINRC; 5 CWindowDC WindowDC(this); 6 CDC memDC; 7 memDC.CreateCompatibleDC(&WindowDC); 8 BITMAPINFO bmpinfo; 9 CBitmap Bmp; 10 int nBmpCX,nBmpCY; 11 GetWindowRect(WINRC); 12 MinRC.CopyRect(CRect(WINRC.left+m_MinRC.left,WINRC.top+m_MinRC.top,WINRC.left+m_MinRC.right,WINRC.top+m_MinRC.bottom)); 13 MAXRC.CopyRect(CRect(WINRC.left+m_MAXRC.left,WINRC.top+m_MAXRC.top,WINRC.left+m_MAXRC.right,WINRC.top+m_MAXRC.bottom)); 14 CloseRC.CopyRect(CRect(WINRC.left+m_CloseRC.left,WINRC.top+m_CloseRC.top,WINRC.left+m_CloseRC.right,WINRC.top+m_CloseRC.bottom)); 15 if (MinRC.PtInRect(point)) 16 { 17 if (m_BtnState!=BS_MIN) 18 { 19 Bmp.LoadBitmap(IDB_BITMAP_MIN2); 20 Bmp.GetObject(sizeof(BITMAPINFO),&bmpinfo); 21 nBmpCX=bmpinfo.bmiHeader.biWidth; 22 nBmpCY=bmpinfo.bmiHeader.biHeight; 23 memDC.SelectObject(&Bmp); 24 WindowDC.StretchBlt(m_MinRC.left,m_MinRC.top,m_MinRC.Width(),m_MinRC.Height(),&memDC,0,0,nBmpCX,nBmpCY,SRCCOPY); 25 m_bFirstDraw=FALSE; 26 m_BtnState=BS_MIN; 27 Bmp.DeleteObject(); 28 } 29 } 30 else if (MAXRC.PtInRect(point)) 31 { 32 if (m_BtnState!=BS_MAX) 33 { 34 Bmp.LoadBitmap(IDB_BITMAP_MAX2); 35 Bmp.GetObject(sizeof(BITMAPINFO),&bmpinfo); 36 nBmpCX=bmpinfo.bmiHeader.biWidth; 37 nBmpCY=bmpinfo.bmiHeader.biHeight; 38 memDC.SelectObject(&Bmp); 39 WindowDC.StretchBlt(m_MAXRC.left,m_MAXRC.top,m_MAXRC.Width(),m_MAXRC.Height(),&memDC,0,0,nBmpCX,nBmpCY,SRCCOPY); 40 m_bFirstDraw=FALSE; 41 if (m_bMaxed) 42 { 43 m_BtnState=BS_RES; 44 } 45 else 46 { 47 m_BtnState=BS_MAX; 48 } 49 Bmp.DeleteObject(); 50 } 51 } 52 else if (CloseRC.PtInRect(point)) 53 { 54 if (m_BtnState!=BS_CLOSE) 55 { 56 Bmp.LoadBitmap(IDB_BITMAP_CLOSE2); 57 Bmp.GetObject(sizeof(BITMAPINFO),&bmpinfo); 58 nBmpCX=bmpinfo.bmiHeader.biWidth; 59 nBmpCY=bmpinfo.bmiHeader.biHeight; 60 memDC.SelectObject(&Bmp); 61 WindowDC.StretchBlt(m_CloseRC.left,m_CloseRC.top,m_CloseRC.Width(),m_CloseRC.Height(),&memDC,0,0,nBmpCX,nBmpCY,SRCCOPY); 62 m_bFirstDraw=FALSE; 63 m_BtnState=BS_CLOSE; 64 Bmp.DeleteObject(); 65 } 66 } 67 else 68 { 69 if (m_bFirstDraw==FALSE) 70 { 71 if (m_BtnState==BS_MIN) 72 { 73 DrawDialog(MINBUTTON); 74 } 75 else if (m_BtnState==BS_MAX) 76 { 77 DrawDialog(MAXBUTTON); 78 } 79 else if (m_BtnState==BS_CLOSE) 80 { 81 DrawDialog(CLOSEBUTTON); 82 } 83 } 84 m_BtnState=BS_NONE; 85 } 86 ReleaseDC(&memDC); 87 CDialog::OnNcMouseMove(nHitTest, point); 88 }
主要技术要点:
1、指定的位置输出位图,因为这里的位图很多均是贴在非客户区域,所以这里要注意一下。输出位图需要使用设置上下文CDC类的StretchBlt方法,这里非客户区域使用CWindowDC类的此方法,CWindowDC派生于CDC,提供了在窗口非客户区域绘制的功能。
1 CWindowDC WindowDC(this); 2 CBitmap Bmp; 3 BITMAPINFO bmpinfo; 4 CDC memDC; 5 memDC.CreateCompatibleDC(&WindowDC); 6 Bmp.LoadBitmap(IDB_BITMAP_LEFTFORM); 7 memDC.SelectObject(&Bmp); 8 Bmp.GetObject(sizeof(BITMAPINFO),&bmpinfo); 9 int nBmpCX=bmpinfo.bmiHeader.biWidth; 10 int nBmpCY=bmpinfo.bmiHeader.biHeight; 11 WindowDC.StretchBlt(0,m_nTitleBarCY,m_nBorderCX,FactRC.Height()-m_nTitleBarCY,&memDC,0,0,nBmpCX,nBmpCY,SRCCOPY); 12 Bmp.DeleteObject();
2、绘制对话框背景位图,可以处理对话框的WM_CTLCOLOR消息,用于设置控件的背景颜色,主要包括对话框的背景颜色。
1 HBRUSH CDesignDlgDemoDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) 2 { 3 // TODO: 在此更改 DC 的任何属性 4 HBRUSH hbr; 5 if (nCtlColor==CTLCOLOR_DLG) 6 { 7 CBrush m_Brush(m_crBK); 8 CRect rect; 9 GetClientRect(rect); 10 pDC->SelectObject(&m_Brush); 11 pDC->FillRect(rect,&m_Brush); 12 return m_Brush; 13 } 14 else 15 hbr=CDialog::OnCtlColor(pDC, pWnd, nCtlColor); 16 // TODO: 如果默认的不是所需画笔,则返回另一个画笔 17 return hbr; 18 }
其他还有一些设计的细节,比如相应NcMouseMove响应绘制移动至三种按钮重新绘制三种按钮,模拟热点按钮,点击NcButtonDown的时候响应然后进行相应的动作;主要是设计思路要理解,关键是保存一组位图绘制位置的信息,然后不断的重新绘制上去,界面美化只是这一点点的工作就需要这么多的代码,真是不能小觑,不过VC好像有很多界面美化库,有待学习学习怎么利用别人的界面库,这里第一个实例,界面美化的实例就记录到这里;接着步入下一个实例;