轉自:http://www.jizhuomi.com/software/246.html
上一節講了CDC類及其屏幕繪圖函數,本節的主要內容是GDI對象之畫筆CPen。
GDI對象
在MFC中,CGdiObject類是GDI對象的基類,通過查閱MSDN我們可以看到,CGdiObject類有六個直接的派生類,GDI對象主要也是這六個,分別是:CBitmap、CBrush、CFont、CPalette、CPen和CRgn。
在這六個GDI對象中,最常用的莫過於畫筆和畫刷了,即CPen類和CBrush類。本文就主要講解畫筆的使用。
畫筆的應用實例
這里直接通過一個波形圖的實例,來詳細講解畫筆的使用方法。
首先介紹此實例要實現的功能:在對話框上有一個Picture控件,將此控件的背景填充為黑色;啟動一個定時器,每次定時器到時,所有波形數據都前移一個單位,並獲取一個80以內的隨機數作為波形的最后一個數據,然后以綠色畫筆在繪圖控件上繪制波形。這樣就實現了波形的繪制及動態變化。
下面是具體實施步驟:
1、創建一個基於對話框的MFC工程,名字設為“Example50”。
2、在自動生成的對話框模板IDD_EXAMPLE50_DIALOG中,刪除“TODO: Place dialog controls here.”靜態文本框,添加一個Picture控件,ID設為IDC_WAVE_DRAW。
3、為Picture控件IDC_WAVE_DRAW添加CStatic變量,名稱設為m_picDraw。
4、在文件Example50Dlg.h文件中CExample50Dlg類聲明的上面添加宏定義:
此符號常量的意義是波形的點數,這里用define將其定義為符號常量是為了方便以后可能的修改,假如我們以后想將點數改為200,則只改此宏定義就可以了:#define POINT_COUNT 200,而如果沒有使用符號常量,在程序中直接使用了100,那么就需要將所有使用100的位置找出來,並替換為200,這樣不僅麻煩也很容易出錯,所以最好是將其定義為符號常量。
5、在CExample50Dlg.h文件中為CExample50Dlg類添加成員數組:
int m_nzValues[POINT_COUNT];
此數組用於存放波形數據。
6、在CExample50Dlg類的構造函數中為數組m_nzValues的元素賦初值:
CExample50Dlg::CExample50Dlg(CWnd* pParent /*=NULL*/) : CDialogEx(CExample50Dlg::IDD, pParent) { m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); // 將數組m_nzValues的元素都初始化為0 memset(m_nzValues, 0, sizeof(int) * POINT_COUNT); }
7、在CExample50Dlg對話框的初始化成員函數CExample50Dlg::OnInitDialog()中,構造隨機數生成器,並啟動定時器。CExample50Dlg::OnInitDialog()修改如下:
BOOL CExample50Dlg::OnInitDialog() { CDialogEx::OnInitDialog(); // Add "About..." menu item to system menu. // IDM_ABOUTBOX must be in the system command range.
ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX); ASSERT(IDM_ABOUTBOX < 0xF000); CMenu* pSysMenu = GetSystemMenu(FALSE); if (pSysMenu != NULL) { BOOL bNameValid; CString strAboutMenu; bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX); ASSERT(bNameValid); if (!strAboutMenu.IsEmpty()) { pSysMenu->AppendMenu(MF_SEPARATOR); pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu); } } // Set the icon for this dialog. The framework does this automatically // when the application's main window is not a dialog
SetIcon(m_hIcon, TRUE); // Set big icon
SetIcon(m_hIcon, FALSE); // Set small icon // TODO: Add extra initialization here // 以時間為種子來構造隨機數生成器
srand((unsigned)time(NULL)); // 啟動定時器,ID為1,定時時間為200ms
SetTimer(1, 200, NULL); return TRUE; // return TRUE unless you set the focus to a control
}
8、為CExample50Dlg類添加波形繪制的成員函數CExample50Dlg::DrawWave(CDC *pDC, CRect &rectPicture),參數分別為設備上下文指針和繪圖的矩形區域。
void CExample50Dlg::DrawWave(CDC *pDC, CRect &rectPicture) { float fDeltaX; // x軸相鄰兩個繪圖點的坐標距離 float fDeltaY; // y軸每個邏輯單位對應的坐標值 int nX; // 在連線時用於存儲繪圖點的橫坐標 int nY; // 在連線時用於存儲繪圖點的縱坐標 CPen newPen; // 用於創建新畫筆 CPen *pOldPen; // 用於存放舊畫筆 CBrush newBrush; // 用於創建新畫刷 CBrush *pOldBrush; // 用於存放舊畫刷 // 計算fDeltaX和fDeltaY fDeltaX = (float)rectPicture.Width() / (POINT_COUNT - 1); fDeltaY = (float)rectPicture.Height() / 80; // 創建黑色新畫刷 newBrush.CreateSolidBrush(RGB(0,0,0)); // 選擇新畫刷,並將舊畫刷的指針保存到pOldBrush pOldBrush = pDC->SelectObject(&newBrush); // 以黑色畫刷為繪圖控件填充黑色,形成黑色背景 pDC->Rectangle(rectPicture); // 恢復舊畫刷 pDC->SelectObject(pOldBrush); // 刪除新畫刷 newBrush.DeleteObject(); // 創建實心畫筆,粗度為1,顏色為綠色 newPen.CreatePen(PS_SOLID, 1, RGB(0,255,0)); // 選擇新畫筆,並將舊畫筆的指針保存到pOldPen pOldPen = pDC->SelectObject(&newPen); // 將當前點移動到繪圖控件窗口的左下角,以此為波形的起始點 pDC->MoveTo(rectPicture.left, rectPicture.bottom); // 計算m_nzValues數組中每個點對應的坐標位置,並依次連接,最終形成曲線 for (int i=0; i<POINT_COUNT; i++) { nX = rectPicture.left + (int)(i * fDeltaX); nY = rectPicture.bottom - (int)(m_nzValues[i] * fDeltaY); pDC->LineTo(nX, nY); } // 恢復舊畫筆 pDC->SelectObject(pOldPen); // 刪除新畫筆 newPen.DeleteObject(); }
9、有了定時器和繪圖成員函數,我們就可以在WM_TIMER消息的響應函數中添加對波形數據的定時處理和對波形的定時繪制了。定時器及WM_TIMER消息處理函數的添加方法如果忘記了,可以再到VS2010/MFC編程入門之四十四(MFC常用類:定時器Timer)溫習下。
WM_TIMER消息的處理函數修改如下:
void CExample50Dlg::OnTimer(UINT_PTR nIDEvent) { // TODO: Add your message handler code here and/or call default CRect rectPicture; // 將數組中的所有元素前移一個單位,第一個元素丟棄 for (int i=0; i<POINT_COUNT-1; i++) { m_nzValues[i] = m_nzValues[i+1]; } // 為最后一個元素賦一個80以內的隨機數值(整型) m_nzValues[POINT_COUNT-1] = rand() % 80; // 獲取繪圖控件的客戶區坐標 // (客戶區坐標以窗口的左上角為原點,這區別於以屏幕左上角為原點的屏幕坐標) m_picDraw.GetClientRect(&rectPicture); // 繪制波形圖 DrawWave(m_picDraw.GetDC(), rectPicture); CDialogEx::OnTimer(nIDEvent); }
10、在對話框銷毀時,定時器應關閉。所以為CExample50Dlg類添加WM_DESTROY消息的處理函數,並修改如下:
void CExample50Dlg::OnDestroy() { CDialogEx::OnDestroy(); // TODO: Add your message handler code here // 關閉定時器 KillTimer(1); }
11、一切准備就緒,編譯運行。最終的效果如下圖:
關於畫筆就講到這里了,下一節將為大家簡單講講畫刷的使用。謝謝大家的關注!