19.1 如何畫一條帶顏色的直線
換一支新畫筆
在畫點或畫線時,系統使用當前DC中的畫筆,所以想畫出不同顏色、線型和粗細的直線,需要使用新畫筆。
在創建畫筆後必須將其選入DC纔會在繪圖時產生效果。
繪圖工具類
MFC 中定義了一些 Windows 的圖形設備界面(GDI)對象類,即繪圖工具類,它們是 作圖的筆、給圖形塗色的畫刷,以及字體、位圖、區域和調色板等影響繪圖的工具。
繪圖工具類主要有:CGdiObject、CPen、CBrush、CFont、CRgn 和 CBitmap 類等。 其中 CGdiObject 類是 CObject 類的派生類,它是繪圖工具類的基類。
畫筆 Cpen與畫刷 CBrush
畫筆 CPen 用於繪製直線、矩形、圓等幾何圖形,其屬性主要有:線寬、線型和色 彩等。
畫刷 CBrush 用於填充繪圖區域,其屬性有填充圖案和色彩。
在生成設備描述表類對象時,如果沒有指定筆和畫刷,那麼默認筆爲線寬 1 個像素的黑色實線;默認的畫刷爲單一白色圖案,即圖形內部填充白色。
CPen繪圖工具
CPen 畫筆主要用於繪製直線、矩形、圓等幾何圖形。
CPen 的構造函數如下:
CPen( int nPenStyle, int nWidth, COLORREF crColor );
其中參數 nPenStyle 爲線型、nWidth 爲線寬、crColor 爲色彩。
其創建畫筆的成員函數 CreatePen 的參數與構造函數相同。
建立 CPen 畫筆的方法
1)定義一個 CPen 對象,並用其成員函數 CreatePen 構造畫筆。
CPen p;
p.CreatePen(PS_SOLID, 1, RGB(255,0,0));
2)使用 CPen 構造函數建立一個畫筆對象,並定義其參數 。
CPen p(PS_SOLID, 1, RGB(255,0,0));
3) 用 CPen 指針對象的形式動態創建一個畫筆
CPen *pPen;
pPen=new CPen(PS_SOLID, 1, RGB(255,0,0));
使用完畢後必須釋放給畫筆分配的內存空間,即刪除畫筆:
delete pPen;
畫筆的線型 nPenStyle
線型 PS_DASH、PS_DOT、PS_DASHDOT 和 PS_DASHDOTDOT 只有在畫筆寬度爲 1 個像素時才能使用。如果線寬 nWidth 值大於 1 個像素,則畫出給定寬度的實 線(PS_SOLID)。
畫筆屬性——顏色
釋放資源
pen.DeleteObject();
線型參數的設置
在前面程序的基礎上,爲繪圖程序MyDrawSystem增加”設置|線型參數”命令,點擊該命令後調用“線型參數”對話框完成“線型”、“線寬”、“線顏色”的設置。
運行程序可以完成不同顏色、線型和粗細的直線的繪製。
- 爲主菜單IDR_MAINFRAME添加“設置”菜單資源。菜單項相應的ID及Caption見表。
2. 創建“線型參數”對話框。
(1)添加新的對話框資源,ID爲IDD_DLG_LINETYPE,Caption爲 “設置線型參數”。添加靜態控件、列表框控件IDC_LIST_LINETYPE(注意:不勾選列表框控件的Sort屬性)、編輯框控件IDC_EDIT_LINEWIDTH和按鈕控件IDC_BUTTON_LINECOLOR。
(2)爲該對話框添加新類CLineTypeDlg。在CLineTypeDlg類中添加公有成員變量如下。
COLORREF m_LineColor;
//用於保存用戶在顏色對話框中選中的顏色
int m_nLineType;
//用於保存用戶在線型列表框中選中的線型
(3)添加各控件的成員變量,如圖所示。
(4)下面對“線型”列表框控件進行初始化。先添加對話框類的初始化成員函數OnInitDialog()。
在OnInitDialog()函數中添加初始化代碼如下。
BOOL CLineTypeDlg::OnInitDialog()
{
CDialogEx::OnInitDialog();
// TODO: 在此添加額外的初始化
m_List_LineType.AddString("實線"); //PS_SOLID
m_List_LineType.AddString("虛線"); //PS_DASH
m_List_LineType.AddString("點線"); //PS_DOT
m_List_LineType.AddString("點劃線"); //PS_DASHDOT
m_List_LineType.AddString("雙點劃線"); //PS_DASHDOTDOT
m_List_LineType.SetCurSel(0);
m_nLineType = 0;
m_nLineWidth = 1;
m_LineColor = RGB(0, 0, 0);
UpdateData(FALSE);
return TRUE; // return TRUE unless you set the focus to a control
// 異常: OCX 屬性頁應返回 FALSE
}
(5)添加”LBN_SELCHANGE”消息的響應函數。該響應函數的代碼如下。
該響應函數的代碼如下。
void CLineTypeDlg::OnLbnSelchangeListLinetype()
{
// TODO: Add your control notification handler code here
int nIndex = m_List_LineType.GetCurSel();
switch(nIndex)
{
case 0: m_nLineType = PS_SOLID; break;
case 1: m_nLineType = PS_DASH; break;
case 2: m_nLineType = PS_DOT; break;
case 3: m_nLineType = PS_DASHDOT; break;
case 4: m_nLineType = PS_DASHDOTDOT; break;
default: m_nLineType = PS_SOLID;
}
}
函數GetCurSel()用來返回當前被選擇項的索引。
(6) 添加“顏色對話框”按鈕IDC_BUTTON_LINECOLOR的消息響應函數。添加代碼如下。
void CLineTypeDlg::OnButtonLinecolor()
{
// TODO: Add your control notification handler code here
CColorDialog ColorDlg;
if(ColorDlg.DoModal() == IDOK)
m_LineColor = ColorDlg.GetColor();
}
- 在視圖類中添加線型參數變量如下。
int m_nPenType; //線型
int m_nPenWidth; //線寬
COLORREF m_PenColor; //線顏色
並在視圖類中初始化上述線型參數變量。代碼如下。
CMyDrawSystemView::CMyDrawSystemView()
{ // TODO: add construction code here
m_nMouseStep = 0; //鼠標單擊次數初始爲0
m_nFigureType= 0; //無圖元類型
m_nPenType = 0; //線型
m_nPenWidth = 1; //線寬
m_PenColor = RGB(0,0,0); //線顏色
}
- 添加“線型參數”菜單項的菜單響應函數OnLineType(),將該響應函數映射到視圖類。在OnLineType()函數中添加對線型對話框的調用和對視圖類線型參數變量的賦值。代碼如下。
#include "LineTypeDlg.h"
void CMyDrawSystemView::OnLineType()
{ // TODO: Add your command handler code here
CLineTypeDlg LineTypeDlg;
if(LineTypeDlg.DoModal() == IDOK)
{
m_nPenType = LineTypeDlg.m_nLineType;
m_nPenWidth = LineTypeDlg.m_nLineWidth;
m_PenColor = LineTypeDlg.m_LineColor;
}
}
- 在繪製直線之前,使用視圖類線型參數變量創建新畫筆,載入設備環境DC。代碼如下。
switch(m_nMouseStep) //鼠標單擊次數
{ case 0: //第一次單擊鼠標左鍵
m_StartPos = m_EndPos = point; //獲取線段起點
m_nMouseStep++;
break;
case 1: //第二次單擊鼠標左鍵
CPen *pPen,*pOldPen; //定義兩個畫筆指針變量
pPen=new CPen(m_nPenType,m_nPenWidth,m_PenColor);
pOldPen=(CPen *)pDC->SelectObject(pPen);
m_EndPos = point; //獲得線段終點
…………
pDoc->m_FigureList.AddTail(pLine); //將新生成的直線存入圖元鏈表
pDC->SelectObject(pOldPen); //恢復舊畫筆
delete pPen; //刪除動態創建的畫筆
} //switch(m_nMouseStep)
break;
解決問題
6. 修改Cline類,加入線型參數。
class CLine :public CObject
{ CPoint m_Start;
CPointm_End; //直線的起點和終點
int m_nLineType;
int m_nLineWidth;
COLORREF m_LineColor;
public:
CLine(void);
CLine(CPoint start, CPoint end,int linetype=PS_SOLID,int linewidth=1,COLORREF linecolor=RGB(0,0,0));
~CLine(void);
void Draw (CDC *pDC);
};
CLine::~CLine()
{}
void CLine::Draw (CDC *pDC) //繪製直線段的成員函數
{
CPen *pPen,*pOldPen; //定義兩個畫筆指針變量
pPen=new CPen(m_nLineType,m_nLineWidth,m_LineColor);
pOldPen=(CPen *)pDC->SelectObject(pPen);
pDC->MoveTo(m_Start);
pDC->LineTo(m_End);
pDC->SelectObject(pOldPen); //恢復舊畫筆
delete pPen; //刪除動態創建的畫筆
}
- 視圖類中保存的線型參數在建立新的CLine對象時要傳遞給其構造函數。
直線類對象的生成是在視圖類的OnLButtonDown()函數中進行的。現在修改OnLButtonDown()函數中的代碼。原來的代碼是:
CLine * pLine = new CLine(m_StartPos, m_EndPos);
pLine->Draw(pDC);
pDoc->m_FigureList.AddTail(pLine);
//將新繪製的直線存入圖元鏈表
修改之後代碼是:
CLine *pLine = new CLine(m_StartPos, m_EndPos, m_nPenType, m_nPenWidth,m_PenColor);
pLine->Draw(pDC);
pDoc->m_FigureList.AddTail(pLine);
//將新繪製的直線存入圖元鏈表
19.2 永久存儲一條直線
直線類CLine的序列化
參考Text的序列化過程自己完成。
19.3 自由曲線(不考)
自由曲線的繪製
對於自由曲線、多段線這樣由數量不確定的直線段構成的圖形,在編寫程序的過程中主要解決的問題是:
在繪製過程中如何收集這種不定數量的圖形數據,因爲不能像直線那樣“畫完”了再生成其對象來保存數據。
而是需要使用合適的動態數據結構,做到一邊“移動鼠標”一邊“記錄和保存座標”;
並且還要考慮如何在自由曲線類、文檔類、視圖類這幾個類當中使用這個動態的數據結構。
【編程步驟】
(1)增加自由曲線類的定義。自由曲線實際上是由許多條短的直線段連接構成,由於構成自由曲線的線段的數量不確定,因此要想將整條曲線保存起來,需要選用合適的動態數據結構,在這裏使用MFC集合類中的數組較爲合適。
其實,自由曲線可以看做是由許多個距離很近的點連線而成的,可以將這些點保存到動態數組中。在這裏使用一般模板類CArray類來定義這個動態數組。
使用CArray類對象可以創建數組,並提供了很多對數組元素進行操作的成員函數。由於這是一個類模板,因此可以創建任何數據類型的數組。CArray類的聲明如下:
template <class TYPE, class ARG_TYPE>
class CArray : public CObject
CArray類派生自CObject類,有兩個模板參數,第一個模板參數TYPE指定了存儲在數組中的對象的類型。後一個模板參數ARG_TYPE指定了用於訪問存儲在數組中對象的參數類型。
定義CLineType
(1)爲了能夠使類層次更加清晰,定義基類CLineType
在LineType.h中添加代碼如下。
class CLineType : public CObject
{
protected:
COLORREF m_LineColor; //圖元顏色
int m_nLineType; //圖元的線型
int m_nLineWidth; //圖元的線寬
public:
CLineType();
CLineType(int linewidth, int linetype, COLORREF linecolor);
virtual ~CLineType();
};
在LineType.cpp中添加代碼如下。
CLineType::CLineType()
{
m_LineColor = RGB(0,0,0); //圖元顏色爲黑
m_nLineType = PS_SOLID; //圖元的線型爲實線
m_nLineWidth =1 ; //圖元的線寬爲1
}
CLineType:: CLineType(int linewidth, int linetype, COLORREF linecolor)
{
m_nLineWidth = linewidth;
m_nLineType = linetype;
m_LineColor = linecolor;
}
(2)在LineType.h中添加代碼如下。
class CLineType : public CObject
{ ……
public:
……
virtual void Draw(CDC* pDC)=0;
//純虛函數——畫圖函數
};
(3) 修改Cline類在Line.h中添加代碼如下。
#include "LineType.h"
class CLine : public CLineType
{
protected:
CPoint m_Start,m_End; //直線的起點和終點
public:
CLine();
CLine(CPoint start, CPoint end, int linewidth=1,
int linetype=PS_SOLID,
COLORREF linecolor=RGB(0,0,0));
virtual ~CLine();
void Draw (CDC *pDC);
};
在Line.cpp中添加代碼如下。
CLine::CLine()
{
m_Start.x=m_Start.y=0;
m_End.x=m_End.y=0;
}
CLine::CLine(CPoint start, CPoint end, int linewidth, int linetype, COLORREF linecolor):CLineType(linewidth,linetype,linecolor)
{
m_Start = begin;
m_End = end;
}
定義CCurve
(4)Curve.h文件中的代碼如下。
#include "LineType.h"
#include "Line.h"
#include <AFXTEMPL.H> //使用MFC類模板需要包含該頭文件
class CCurve : public CLineType
{
protected:
CArray<CPoint,CPoint&> m_CurveArray;
//存放曲線中每個點的動態數組
public:
CCurve(int linewidth, int linetype, COLORREF linecolor,CPoint point);
virtual ~CCurve();
void AddPoint(CPoint point); //向動態數組中添加新的點
void Draw(CDC* pDC);
};
自由曲線的繪製
Curve…cpp文件中的代碼如下。
CCurve::CCurve(int linewidth, int linetype, COLORREF linecolor,CPoint point):CLineType(linewidth, linetype,linecolor)
{
m_CurveArray.SetAtGrow(0,point);
}
CCurve::~CCurve()
{
m_CurveArray.RemoveAll();
}
在構造函數中使用了SetAtGrow()函數第一次爲數組增添元素,同時爲下標爲0的元素指定了值。該函數功能是在指定的索引上設置數組元素。如果必要,數組自動增長(調整上界以接納新元素)。
void CCurve::AddPoint(CPoint point) //添加新的點
{
m_CurveArray.Add(point); //將該點加到動態數組中
}
void CCurve::Draw(CDC *pDC) //繪製自由曲線的成員函數
{
CPen *pPenOld,PenNew;
SetCurrentPen(PenNew); //生成新畫筆
pPenOld = pDC->SelectObject(&PenNew); //將新畫筆選入DC
int nIndex = m_CurveArray.GetSize();
for(int i=0;i<nIndex-1;i++)
{
pDC->MoveTo(m_CurveArray.GetAt(i));
pDC->LineTo(m_CurveArray.GetAt(i+1));
}
pDC->SelectObject(pPenOld); //恢復DC中原來的畫筆
PenNew.DeleteObject(); //刪除用完的畫筆
}
(2)在視圖類中添加成員變量表示自由曲線的起始點和當前鼠標的繪圖狀態,以及一個自由曲線對象的指針變量。
BOOL m_BDrawing ; //繪圖標記
CCurve *pCurve; //自由曲線對象指針
在MyDrawSystemView.h文件中添加包含命令如下:
#include "Curve.h"
在視圖類CMyDrawSystemView的構造函數中初始化m_BDrawing爲FALSE。
(3)爲菜單項“繪圖”|“自由曲線”添加相應的消息響應函數。將該命令映射到視圖類。代碼如下。
void CMyDrawSystemView::OnCurveMenu() //菜單命令“繪圖”|“自由曲線”
{
// TODO: Add your command handler code here
m_nFigureType=3; //3代表圖元爲自由曲線
//單擊菜單命令後獲得十字光標句柄
m_hCursor=AfxGetApp()->
LoadStandardCursor(IDC_CROSS);
}
(4)當按下鼠標左鍵進入拖曳狀態,鼠標光標變成十字光標,開始繪製自由曲線。
考慮到單擊了“自由曲線”命令之後,用戶可能繪製多條自由曲線,而一條自由曲線對應一個自由曲線對象,因此:
每次按下鼠標左鍵,都生成一個自由曲線對象,並且該自由曲線的起點即按下鼠標左鍵時的鼠標位置;
移動鼠標時,繪製線段,並且保存當前線段的起點和終點座標;
擡起鼠標左鍵則拖曳結束,釋放鼠標,還原鼠標形狀,繪製結束,將生成的自由曲線對象添加到圖元鏈表中。
添加如下代碼到三個鼠標響應函數中。
void CMyDrawSystemView::OnLButtonDown(UINT nFlags, CPoint point)
{ CMyDrawSystemDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
switch(m_nFigureType) //根據圖元類型進行分支處理
{…
case 3: //繪製自由曲線
SetCapture(); //捕獲鼠標
::SetCursor(m_hCursor); //設置十字光標
m_BDrawing = TRUE; //設置拖曳標記
//生成自由曲線對象
pCurve=new
CCurve(m_nPenWidth,m_nPenType,m_PenColor,point);
break;
…
}// switch(m_nFigureType)
CView::OnLButtonDown(nFlags, point);
}
void CMyDrawSystemView::OnMouseMove(UINT nFlags, CPoint point)
{
CDC* pDC = GetDC(); //獲取指定窗口的客戶區設備環境
switch(m_nFigureType) //根據圖元類型進行分支處理
{ case 1: //繪製直線
…
case 3: //繪製自由曲線
if(m_BDrawing)
{
pCurve->AddPoint(point); //加入點到數組
pCurve->Draw(pDC); //繪製曲線
}
break;
…
}// switch(m_nFigureType)
…
CView::OnMouseMove(nFlags, point);
}
void CMyDrawSystemView::OnLButtonUp(UINT nFlags, CPoint point)
{
CMyDrawSystemDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
CDC* pDC = GetDC(); //獲取指定窗口的客戶區的設備環境
switch(m_nFigureType) //根據圖元類型進行分支處理
{
case 1: //繪製直線
…
case 3: //繪製自由曲線
if(m_BDrawing)
{
m_BDrawing = FALSE ; //清除繪圖標記
ReleaseCapture(); //釋放鼠標,還原鼠標形狀
pDoc->m_FigureList.AddTail(pCurve);
//將新繪製的自由曲線存入圖元鏈表
}
break;
…
}// switch(m_nFigureType)
ReleaseDC(pDC); //釋放設備環境
CView::OnLButtonUp(nFlags, point);
}
(6)OnDraw函數修改爲:
POSITION pos = pDoc->m_FigureList.GetHeadPosition();
while(pos != NULL)
{
CLineType* pFigure = (CLineType*) pDoc->
m_FigureList.GetNext(pos);
pFigure->Draw(pDC);
}
(7)在關閉窗口或點擊“File|New”命令時要刪除自由曲線圖元。MyDrawSystemDoc.cpp文件中的函數RemoveList()代碼不變。
編譯、連接、運行程序,可以繪製各種線型、顏色和線寬的曲線和直線。