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好像有很多界面美化庫,有待學習學習怎么利用別人的界面庫,這里第一個實例,界面美化的實例就記錄到這里;接着步入下一個實例;