/* 第十五章 在窗口中繪圖 主要內容: 1 Windows為窗口繪圖提供的坐標系統 2 設置環境及其必要性 3 程序如何以及在窗口中繪圖 4 如何定義鼠標消息的處理程序 5 如何定義自己的形狀類 6 在窗口中繪制形狀時如何對鼠標進行編程 7 如何讓程序捕獲鼠標 15.1 窗口繪圖的基礎知識 15.1.1 窗口客戶區 由於可以使用鼠標來回拖動窗口,並且可以通過拖動其邊框調整大小,因此窗口在屏幕上沒有一個固定的位置,甚至沒有一個固定的可視區,那么如何知道應當在屏幕上的什么地方繪圖呢 15.1.2 Windows圖形設備界面 從直接的意義上來說,windows的最后一個限制是實際上沒有把數據寫入屏幕,所有到顯示屏的輸出都是圖形,而不管它是直線,圓還是文本 1 設置環境 2 映射模式 設備環境中的每種映射模式都有一個ID標識,其方式與標識windows消息時類似, 映射模式 MM_TEXT 邏輯單位是一個設備像素,在窗口客戶區中,x軸的正方向從左到右,y軸的正方向從上到下 MM_LOENGLISH 邏輯單位是0.0.1英寸,x軸的正方向從左到右,y軸的正方向從客戶區的頂部向上延伸 MM_HIENGLISH 邏輯單位是0.001英寸,x軸和y軸的方向與MM_LOENGLISH相同 MM_LOMETRIC 邏輯單位是0.1毫米,x軸和y軸的方向與MM_LOENGLISH相同 MM_HIMETRIC 邏輯單位是0.01毫米,x軸和y軸的方向與MM_LOENGLISH相同 MM_ISOTROPIC 邏輯單位是任意長度,但是在x軸和y軸上是相同的,x軸和y軸的方向與MM_LOENGLISH相同 MM_ANISOTROPIC 這種模式似乎MM_ISOTROPIC,但是它允許x軸上邏輯單位的長度不同於y軸上邏輯單位的長度 MM_TWIPS 邏輯單位是TWIP,其中WTIP是一個點的0.05,而一個點是1/72英寸,所以WTIP相當於1/1440英寸,即6.9*10(-4)英寸,(點是衡量字字體的單位),x軸和y軸的方向與MM_LOENGLISH相同 15.2 Visual C++中的繪圖機制 15.2.1 應用程序中的視圖類 類定義了包括幾個虛函數和理寫,不過我們在此處着重介紹一個函數是OnDraw(),每當需要重新繪制文檔窗口的客戶區時,都將調用這個函數,當程序接收到WM_PAINT消息時,應用程序架構調用的正是這個函數 OnDraw()成員函數 由MFC Application Wizard創建的OnDraw()成員函數實現如下所示 //pDC void CSketcherView::OnDraw(CDC* ) { SCketcherDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); //只是確保指針pDoc包含有效的地址 if(!pDoc){ return; } } 15.2.2 CDC類 應當使用CDC類的成員在程序中完成所有繪圖,這個類的所有對像和它派生的類都包含把圖形和文本發送到顯示器和打印機時需要的設備環境和成員函數 1 顯示圖形 //在設備環境中,您將繪制與當前位置相關的實體,如直接,圓和文本,當前位置是客戶區中的一個點,它或者由以前繪制的實體設置,或者是通過調用函數對它進行設置, void CSketcherView::OnDraw(CDC* pDC) { SCketcherDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); //只是確保指針pDoc包含有效的地址 if(!pDoc){ return; } pDC->MoveTo(50,50); //這個成員函數只針對被指定為參數的x和y坐標設置當前位置, CPoint MoveTo(int x, int y); CPoint MoveTo(POINT aPoint); POINT類型的參數,它是一個具有如下定義的結構 typedef struct tagPOINT { LONG x; LONG y; } POINT; LONG這種類型在windows API中定義,對應於32位符號整數 } 繪制直接 在對OnDraw()函數中的MoveTo()調用以后,調用函數LineTo(),這將在客戶區中繪制一條直線 CDC類還定義兩個牒一的LineTo函數 BOOL LineTo(int x, int y); BOOL LineTo(POINT aPoint); void CSketcherView::OnDraw(CDC* pDC) { SCketcherDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); //只是確保指針pDoc包含有效的地址 if(!pDoc){ return; } pDC->MoveTo(50,50); pDC->LineTo(50,200); pDC->LineTo(150,200); pDC->LineTo(150, 50); pDC->LineTo(50,50); } 繪制圓 在繪制圓時,CDC類中有幾種函數成員可供選擇,不過它們全都是設計用於繪制橢圓的, 由高中幾何可知,圓是橢圓的一種特例,是長軸等於短軸的隨圓,因此可以使用成員函數Ellipse()繪制圓 和CDC類支持的其他閉合形狀一樣,Ellipse()函數將利用設置的顏色填充形狀的內部,內部顏色由選入設備環境的畫刷確定,設備環境中的當前畫刷確定如何填充閉合形狀 繪制不進行填充的圓的另一種方法是使用Arc()函數,它不涉及畫刷,其優點是可以繪制橢圓的任意一段弧,而不是完整曲線,這個函數在CDC類中有兩個版本 BOOL Arc(int x1, int y1, int x2, int y2, int x3, int y3, int x4, int y4); BOOL Arc(LPCRECT lpRect, POINT StartPt, POINT EndPt); 2 利用顏色繪圖 繪圖意味着要使用有顏色的寬度的畫筆對像,並且我們一直在使用設備環境中提供的默認畫筆對像,當然,並不強迫您這么做 創建具有給定寬度的顏色的畫筆,MFC定義的類CPen可以提供幫助 創建畫筆 創建畫筆的最簡單方法是首先聲明一個CPen類 CPen aPen; BOOL CreatePen (int aPenStyle, int aWidth, COLORREF aColor); 第一個參數是定義繪圖時需要使用的線型 PS_SOLID 繪制實線 PS_DASH 繪制虛線,只有在把畫筆寬度指定為1時,這種線型才有效 PS_DOT 繪制點線,只有在把畫筆寬度指定為1時,這種線型才有效 PS_DASHDOT 繪制一划一點相間的直接,只有在把畫筆寬度指定為1時,這種線型才有效 PS_DASHDOTDOT 繪制一划雙點相間的直線,只有把畫筆寬度指定為1時,這種線型才有效 PS_NULL 不進行任何繪制 PS_INSIDEFRAME 繪制實線,但是和PS_SOLID不同,指定實線的點出現在畫筆的邊緣而不是中心,所以繪罎的對像永遠不會超出封閉矩形 CreatePen()函數第二個參實定義線寬 最后一個參數指定利用畫筆繪圖時使用的顏色 aPen.CreatePen(PS_SOLID, 2, RGB(255, 0, 0)); 使用畫筆 要使用畫筆,必須把它選入正在繪圖的設備環境中,為此需要使用CDC類成員函數SelectObject() CPen* pOldPen = pDC->SelectObject(&aPen); 要恢復以前的畫筆,只需要再次調用這個函數,以傳遞從最初調用返回的指針 pDC->SelectObject(pOldPen); 創建畫刷 CBrush類的對像封裝了Windows畫刷,可以把畫刷定義成純色,影線或者有圖案的畫刷 畫刷實際上是一個8*8的像素塊,它在要填充的區域上重復應用 要定義純色的畫刷,可以在創建畫刷對像的時指定顏色 CBrush aBrush(RGB(255,0,0)); 可以使用另一種構造函數定義影線畫刷,這需要指定兩個參數,和以前一樣,第一個參數定義影線的類型,第二個參數指定顏色,影線參數可以是下列符號常量之一 HS_HORIZONTAL 水平影線 HS_VERTICAL 垂直影線 HS_BDIAGONAL 從左到右的45下行影線 HS_FDIAGONAL 從左到右的45上行影線 HS_CROSS 水平和垂直交叉影線 HS_DIAGCROSS 45交叉影線 CBrush aBrush(HS_DIAGCROSS, RGB(255,0,0)); 在初始化CBrush對像時,也可以使用類似於初始化CPen對像時的方式,對純色畫刷使用CBrush類的CreateSolidBrush()成員函數,對影線畫刷使用這個類的CreateHatchBrush()成員函數 它們需要在參數和對應的構造數相同 CBrush aBrush; aBrush.CreateHatchBrush(HS_DIAGCROSS, RGB(255,0,0)); 使用畫刷 第種標准畫刷都由預定義的符號常量標識,可以使用的畫刷有7種,它們分別是 CRAY_BRUSH BLACK_BRUSH HOLLOW_BRUSH LTGRAY_BRUSH WHITE_BRUSH NULL_BRUSH DKGRAY_BRUSH pDC->SelectStockObject(NULL_BRUSH); 在SelectStockObject()函數中還可以使用標准畫筆 SelectStockObject()函數返回的指針指向設備環境中被替換的對像,這樣就可以把這個對像保存起來,並在完成繪圖以后進行恢復 在於使用庫存畫刷,然后在完成繪圖后恢復以前的畫刷這樣一種情況 CBrush* pOldBrush = (CBrush*)pDC->SelectStockObject(NULL_BRUSH); pDC->SelectObject(pOldBrush); 15.3 實際繪制圖形 15.4 對鼠標進行編程 1 按下鼠標表示繪圖操作開始 2 按住鼠標時鼠標指針的位置定義形狀態的參考點 3 在檢測到鼠標器鍵按下后,鼠標的移動表示要繪制一個形狀態,鼠標指針位置為這個形狀提供一個定義點 4 釋放鼠標鍵時鼠標指針的位置表示應當繪制的最終形狀 可以猜測到,所有這些信息都由Windows以發送到程序的消息的形式提供,直接和圓的繪制過程的實現幾乎完全由編寫消息處理程序組成 15.4.1 鼠標發出的消息 了解如下所示的三種鼠標消息 WM_LBUTTONDOWN 按下鼠標左鍵時出現消息 WM_LBUTTONUP 釋放鼠標左鍵時出現的消息 WM_MOUSEMOVE 移動鼠標時出現的消息 1 WM_LBUTTONDOWN 這種消息將啟動繪制元素的過程,所需要: (1) 注意元素繪制過程已經開始 (2) 把鼠標指針的當前位置作為定義元素的第一個點記錄下來 2 WM_MOUSEMOVE 這是一個中間階段,其中將創建和繪制當前元素的臨時版本,但是鼠標左鍵必須處理按下狀態,所以需要 (1) 檢查左鍵是否已經按下 (2) 如果已經按下,則刪除已經繪制的當前元素的前一個版本 (3) 如果沒有按下,則退出 (4) 把鼠標指針的當前位置記錄為元素的第二個定義點 (5) 使用這兩個定點繪制當前元素 3 WM_LBUTTONUP (1) 存儲由記錄的第一個點定義的元素的最終版本,同時存儲鼠標鍵在第二個點釋放時指針的位置 (2) 記錄元素繪制過程的結束 下面將生成這三種鼠標消息的處理程序 15.4.2 鼠標消息處理程序 //類中傳遞到處理程序的參數有兩個,即nFlags和point,nFlags是UNIT類型號,它包含很多表明是否按下各種鍵的狀態標志等,point是CPoint對像,它定義按下鼠標左鍵時鼠標指針的位置,UNIT類型號在windows API中定義,它對應於32位無符號整數 //傳遞到函數的nFlags的值可以是下列符號值的任意組合 MK_CONTROL 對應於按下Ctrl鍵 MK_LBUTTON 對應於按下鼠標左鍵 MK_MBUTTON 對就於按下鼠標中間鍵 MK_RBUTTON 對應於按下鼠標右鍵 MK_SHIFT 對應於按下Shift鍵 在消息處理程序中,如果能夠檢測是否按下一個鍵,那么根據發現的其他情況,就可以對消息進行不同的處理,nFlags的值可以包含一個以上這樣的指示符,每個指針符都對應於這個字中的一個特定位,所以可以使用按位與運算符測試特定的鍵,例如,要測試是否按下了Ctrl鍵 if(nFlags & MK_CONTROL){ } 如果nFolags變量具有由MK_CONTROL集合定義的位,那么表達式nFlags&MK_CONTROL的值只能是TRUE 這樣,在按下鼠標左健時,根據是否也按下了Ctrl鍵,可以采取不同的動作,由於此處使用的是按位與運算符,所以對應的位將一起進行與運算,不要把這個運算符同邏輯與運算符&&相混淆,它無法完成這里的運算 傳遞到其他兩種消息處理程序的參數和OnLButtonDown()函數相同,針對它們生成的代碼是 15.4.3 使用鼠標繪圖 1 重新繪制客戶區 繪制或刪除元素牽涉到重新繪制窗口的整個或部分客戶區 通過調用視圖類的繼承成員InvalidateRect()函數,可以告知Windows應當繪制的特定區域 這個函數接受兩個參數,第一個參是指RECT或CRect對像的指針,這些對像將在需要重新繪制的客戶區中定義矩形, 如果這個參數的值是空值,那么整個客戶區將被重繪制,第二個參數是bool值,如果准備去掉矩形的背景 那么這個BOOL的值是TRUE,否則為FALSH 因此在繪新創建的形狀態時,必須完成以下工作: (1) 確保視圖類中的OnDraw()函數在重新繪制的重新繪制窗口時包括新創建的項 (2) 調用InvalidateRect()函數,這時將向待重新繪制形狀的邊界矩形的指針作為第一個參數傳遞 刪除一個形狀,需要完成下列工作: (1) 從OnDraw()函數將要繪制的項中刪除這個形狀 (2) 調用InvalidateRect()函數,這時第一個參數指向待刪除形狀的邊界矩形 2 定義元素的類 我們需要以某種方式把元素存儲到一個文檔中,如果要使用草圖具有永久性,還必須能夠把這個文檔存儲到一個文件中,以便今后檢索,后面將詳細地討論文件操作,就目前而言,知道MFC類CObject包括大氣層需要的工具就足夠了 另外一個問題,就是無法提前知道用戶創建的元素類型的順序,Sketcher程序必須能夠處理任何順序的元素 這意味着在選擇特定元素類函數時,使用基類指針可能會使事情簡單一些 CObject CElement CLine CRectangle CCircie CCurve 在視圖中存儲臨時元素 在前面討論如何繪制形狀時已經介紹過,按下鼠標左鍵以后,拖動鼠標將創建和繪制一系列臨時元素對像, 既然現在已經知道所有形狀態的基類都是CElement,那么就可以添加指向這個視圖類的指針 用以存儲臨時元素的地址 3 CElement類 有些數據項(如顏色)顯然對於所有類型的元素都是通用的,所以可以把它們放在CElement類中,以便可以在每個派生類中繼承它們,但是在定義特定元素屬性的類中 有些數據成中卻極其不同,所以需要在它們所屬的特定派生類中聲明這些成員 注意: 串行化是將對像寫入到文件的通用術語,C++/CLI應用程序中串行化的工作方式與MFC中串行化的工作方式存在區別 4 CLine類 5 實現CLine類 CLine類構造函數 繪制直接 創建邊界矩形 用於繪形狀的矩形稱作為"封閉矩形",而把考慮到畫筆寬度的矩形稱作為"邊界矩形"以此區分它們 在計算不同映射模式中邊界矩形的坐標時,計算方法之間的區雖只與y坐標有關,x坐標的計算對於所有映射模式都一樣,在MM_TEXT映射模式中,計算邊界矩形的各個角時,要從定義矩形的左上角的y坐標中減去線條寬度,在右下角的y坐標中加上線條寬度,但是在MM_LOENGLISH映射模式()以及其他所有映射模式中,由於y軸在相反的方向上增大,因此需要的在定義矩形的左上角的y坐標中加上線條寬度,在右下角的y坐標中減去線條寬度,對於所有映射模式來說,都要從定義矩形的左上角的x坐標中減去線條寬度,在右下角的x坐標中加上線條寬度 規范化的矩形 InflateRect()函數從矩形的top和left成員中減去給定的值,給bottom和right成員加上這些值,這意味着,如果矩形不是規范化的,那么矩形實際上有可能縮小 規范化矩形的left值小於或等於right值,top值小於或等於bottom值,通過調用CRect對的NormalizeRect()成員 可以確保這個對像是規范化的,大部分CRect成員函數為了正常工作,都要求這個對像要規范化,所以在m_EnclosingRect中存儲封閉矩形時,必須確保它是規范化的 6 計算直接的封閉矩形 現在需要做的就是在直線的構造函數內編寫計算封閉矩形的代碼 7 CRectangle類 雖然在定義矩形對像時使用的數據和定義直接時相同--矩形對角線上起點和終點,但是現在不需要存儲定義點,由基類繼承而來的數據成員中的封閉矩形將完整地定義矩形這個形狀,所以不需要任何數據成員,因此可以把CRectangle類定義為; 繪制矩形 有一個稱作Rectangle()的CDC類成員可以繪制矩形,這個函數將繪制閉合圖形,然后利用當前畫刷進行填充 您也許認為這完全不符合您的要求,這時只要選擇NULL_BRUSH就完全可以繪制出您需要的形狀,另外還有一個函數PolyLine(),它根據一組點繪制由多條線段組成的形狀,當然也可以再次使用LineTo(),但是最容易的方法是使用Rectangle()函數 8 CCircle類 CCircle類的接口和CRectangle類完全相同,只根據圓的封閉矩形就可以繪制圓 實現CCircle類 如前所述,在創建圓時,按下鼠標左鍵時的點將成為圓心,移動鼠標指針以后,釋放鼠標左鍵時的點將是最終圓的圓周上的一個點,構造函數的工作是把這些點轉換成可以在定義圓的類中使用的形式 CCircle類構造函數 我們可以計算封閉矩形的左上角和右下角的點相對於圓心(x1, y1)的坐標,圓心是在按下鼠標左鍵時記錄的點,假設映射模式是MM_TEXT,計算左上角點的坐標時,只需要從圓心的坐標中減去半徑 類似地,把圓心的x和y坐標分別加上半徑,就可以得到右下角點的坐標,因此,可以把CCircle類構造函數的代碼編寫為 繪制圓 由於已經了解了如何使用CDC類中的Arc()函數繪制圓,因此現在將使用Ellipse()函數繪制圓, 9 CCurve類 CCurve類不同於其他類,因為它必須能夠處理數量可變的定義點,這就需要維護某種列表,盡管如此您可以使用TSL模板定列表,但是這種列表的缺點在於不能串行化,由於第16章將討論如何創建可串行化的列表,所以現在先不討論這個類的細節,就目前而言,可以包括一個提供啞成員函數的類定義,這樣就可以對包含調用這些函數的代碼進行編譯和鏈接 10 完成鼠標消息處理程序 鼠標進行移動時,這個處理程序只繪制元素的一系列臨時版本,因為最終的元素是在釋放鼠標左鍵時創建時,因此,可以把像拉橡皮筋那樣繪制臨時元素的過程看作是這個函數的局部操作 而由視圖的OnDraw()函數成員繪制元素的最終版本,這種方法使橡皮筋元素的繪制變得非常有效率,因為它不涉及到最后負責繪制完整文檔的OnDraw()函數 利用CDC類的一個成員SetROP2(),可以做得更好,這個成員函數特別適合於橡皮筋操作 設置繪圖模式 在與CDC對像相關聯的設備環境中,SetROP2()函數為所有后續輸出操作設置繪圖模式 這個函數名中的"ROP"代表光柵操作(Raster OPeration),因為繪圖模式的設置將應用於尖柵顯示器 R2_BLACK 所有繪圖顏色都是黑色 R2_WHITE 所人繪圖顏色都是白色 R2_NOP 不進行任何繪圖操作 R2_NOT 繪圖顏色是屏幕顏色豐富的的反色,這將確保輸出清晰可見,困烴它防止繪圖顏色與背景顏色相同 R2_COPYPEN 繪圖顏色是畫筆顏色,如果不行設置,這就是默認的繪圖模式 R2_NOTCOPYPEN 繪圖顏色是畫筆顏色的反色 R2_MERGEPENNOT 繪圖顏色是畫筆顏色和背景顏色的反色“相或”以后產生的顏色 R2_MASKPENNOT 繪圖顏色是畫筆顏色和背景顏色的反色"相與"以后產生的顏色 R2_MASKPENNOT 繪圖顏色是背景顏色和畫筆顏色的反色"相或"以后顏色的顏色 R2_MASKNOTPEN 繪圖顏色是背景顏色和畫筆顏色的反色"相與"以后產生的顏色 R2_MERGEPEN 繪圖顏色是背景顏色和畫筆顏色"相或"以后產生和顏色 R2_NOTMERGEPEN 繪圖顏色是R2_MERGEPEN顏色的反色 R2_XORPEN 繪圖顏色是畫筆顏色和背景顏色"異或"以后產生和顏色 R2_NOTXORPEN 繪圖顏色是R2_XORPEN顏色反色 白色是由相同比例,最大數量的約色,藍色和綠色構成的,為了簡化問題,可以把白色表示為(1,1,1,) 這三個值代表顏色的RGB成分,在相同的方案中,紅色被定義為(1,0,0) 計算過程 背景顏色--白色 1 1 1 畫筆顏色--紅色 1 0 0 XOR 0 1 1 NOT XOR--產生紅色 1 0 0 因此,第一次在白色背景上繪制紅色直接時,如上圖的最后一行所示,直線顯示和顏色是紅色,如果第二次繪制的相同直接覆蓋在現有的直線上,那么重寫的背景像素將是紅色,產生的繪圖顏色將作如 背景顏色-紅色 1-0-0 畫筆--白色 1-0-0 XOR 0-0-0 NOT XOR--產生白色 1-1-1 此處需要注意使用正確的背景顏色,應當看到,使用白色畫筆在紅色背景上繪圖的效果不太好,如第一次繪制的形狀是紅色,其結果是看不見的,它第二次出現的是白色,如果在黑色背景上進行繪制,如同在白色背景上那樣,形狀將出現,然后消失,但是它們不是以選擇的畫筆顏色繪制的 編寫OnMouseMove()處理程序 創建元素 處理WM_LBUTTONUP消息 WM_LBUTTONUP消息完成創建元素的過程,這種消息的處理程序的工作是把元素的最終版本傳遞到文檔對像,然后清理視圖對像數據成員,在創建和編輯這種處理程序的代碼時,可以采用和前面代友相同的方法 15.5 練習使用Sketchar程序 15.5.1 運行示例 15.5.2 捕獲鼠標消息 15.6.1 在窗體上繪圖 在CLR程序中,當用戶在窗體上繪圖時沒有任何復雜的映射模式,繪圖目標的初始位置位於窗體的左上角,x,x軸的正方向為從左到右,y軸的正方向為從上到下,用於在窗體上繪圖的類位於System::Drawing命名空間中,並且由Form1類中處理Paint事件的函數執行在窗體上繪圖的操作,在CLRSketcher的Design窗口中, 右擊窗體並從彈出式菜單中選擇Properties 選擇Events按鈕以顯示窗體的事件,然后雙擊Paint事件;在Properties窗口中,Paint事件是Appearance組中的唯一事件,雙擊該事件生成Form1_Paint()函數,為了響應必須重繪窗體時產生的Paint事件調用該函數 15.6.2 添加鼠標事件處理程序 操作事件是MouseDown,MouseMove, MouseUp事件, MouseDown事件在按下鼠標時發生,根據傳遞給相應事件處理程序的信息確定接下哪里個鼠標鍵 MouseUp事件在釋放鼠標鍵時發生,通過傳遞給該事件處理程序的參數了解釋放了哪里個鼠標鍵 //第一參數標識事件源 //第二參數提供關於該事件的信息,傳遞給該處理程序函數的MouseEventArgs對像 //實際上,所有的鼠標事件處理程序都有該類型的參數,包含一些屬性,這些屬性提供了可以在處理事件時使用的信息 Button: 枚舉類System::Windows::Forms::MouseButtons的屬性,該屬性標識按下哪里個鼠標鍵 MouseButtons 枚舉為該屬性定義了如下可能的值: MouseButtons::Left 鼠標左鍵 MouseButtons::Right 鼠標右鍵 MouseButtons::None 沒有按下任何鼠標鍵 MouseButtons::Middle 鼠標中間鍵 MouseButtons::XButton1 第一個XButton MouseButtons::XButton2 第二個XButton Location 類型為System::Drawing::Point的屬性,該屬性標識鼠標指針的位置,Point對像的X和Y屬性是x和y坐標的整數值 X 作為int類型值的鼠標旨針的x坐標 Y 作為int類型值的鼠標指針的y坐標 Clicks 按下或釋放鼠標鍵次數的int類型計數值 Delta 鼠標滾輪旋轉次數的有正負之分的int類型計數值 在Form1類中添加兩個私有的數據成員,我為drawing的bool變量,用於記錄當前是否在繪制元素 如為firstPoint的Point類型變量,用於記錄初始的鼠標指針位置,可以手工執行該操作 15.6.3 定義C++/CLI元素類 應用程序的代碼在CLRSketcher命名空間中定義,因此將元素類的定義放入相同的命名空間中, Element類和它的子類必須是引用類,因為值類不可能是派生類, Element類也必須被定義為abstract,因為沒有Draw()函數的實現,在每個派生類中將重寫Draw()函數以繪制特定類型的元素,您將以多態的方式調用該函數 Position成員的類型為System::Drawing::Point,這是定義具有兩個成員X和Y的點對象的值類型,派生類中將繼承position,color和boundRect成員,這些成員將存儲元素的位置,顏色和邊界矩形,相對於點position繪制所有元素,此刻不需要關心邊界矩形和封閉矩形之間的距離, 順帶提及的的,boundRect變量的類型名System::Drawing::Rectangle在此處理完全限定的名稱,因為將添加名為Rectangle的派生類,如果此處沒有使用完全限定的名稱,編輯器就會假定在這個頭文件中使用Rectangle類 1 定義直接 2 定義顏色 System::Drawing::Color是封裝ARGB顏色值的值類,ARGB顏色是32位的值 繪制直線 Draw()函數將使用作為參數傳遞的System::Drawing::Graphics對像,以適當的顏色繪制直線,Graphics類定義了大量用於繪制形狀的函數 DrawLine(Pen pen, Point p1, Point p2) 使用pen繪制從p1到p2的直線,Pen對像以特定的顏色和線寬繪制直線 DrawLine(Pen pen, int x1, int y1, int x2, int y2) 使用pen繪制從(x1,y1)到(x2,y2)的直線 DrawLines(Pen pen, Point[] pts) 使用pen繪制連接pts數組中的點的一系列直接 DrawRectangle(Pen pen, Rectangle rect) 使用pen繪制矩形rect DrawRectangle(Pen pen, int X, int Y, int width, int height) 使用pen在(x,y)位置繪制矩形,該矩形的尺寸由width和height指定 DrawEllipse(Pen pen, Rectangle rect) 使用pen繪制由邊界矩形rect指定的橢圓 DrawEllipse(Pen pen, int x, int y, int width, int height) 使用pen繪制橢圓,該橢圓由位於(x,y)位置,尺寸為width為height的邊界矩形指定 定義用於繪圖的畫筆 System::Drawing::Pen類代表繪制直接和曲線的畫筆 Pen^ pen = gcnew Pen(Color::CornflowerBlue); 定義了可以用於淺藍色進行繪圖的Pen對像 Pen^ pen = gcnew Pen(Color::Green, 2.0f); 定義了繪制線寬為2.0f的綠色直線的Pen對像,注意,畫筆寬度是公共屬性, pen->Width = 3.0f; Line類中的Draw()函數的定義 virtual void Draw(Graphics^ g) override { g->DrawLine(gcnew Pen(color), position, end); } 默認的Pen對像可以繪制1.0f默認寬度的連接直接 Pen的其它屬性 Color 獲取或設置畫筆的顏色,這里類型為System::Drawing::Color的值,例如,為了設置Pen對像的繪圖顏色為紅色,設置該屬性如下 Pen->Color = Color::Red; Width 獲得或設置畫筆繪制的直線的寬度,這是類型為float的值 DashPattern 獲得或設置float值的數組,該float值指定為了直接的點樣式,這些數組值指定了直接中交替的點和空格的長度 array<float>^ pattern = {5.0f, 2.0f, 4.0f, 3.0f}; pen->DashPattern = pattern; 該語句定了如下的樣式,首先長度為5的點,后跟長度為2的空格,再跟上長度為4的點,最后跟上長度為3的空格,根據需要在直接中多次重復使用該樣式 DashOffset 指定從直接起點到點樣式的開始位置之間的距離,這是float類型的值 StartCap 指定直接直點處理的帽型(cap style),帽型由System::Drawing::Drawing 2D::LineCap枚舉定義,該枚舉定義了如下可能的值 Flat, Square, Round, Triangle, NoAnchor, SquareAnchor, RoundAnchor, DiamondAnchor, ArrowAnchor, Custom, AnchorMask 例如, 為了繪制起點處為回旋帽(round line cap)的直線,設置該屬性如下 pen->StartCap = System::Drawing::Drawing2D::LineCap::Round; EndCap 指定直接終點的帽型,直線終點的帽型的可能值與StartCap相同事 標准畫筆 有時您可能並不需要靈活地改變所使用的Pen對像的屬性,System::Drawing::Pens定義了 大量標准畫筆,這些畫筆以給定顏色繪制度為1的直線,例如,Pens::Black和Pens::Beige分別是以黑色和米莧公繪圖的標准的畫筆,注意,不可以修改標准畫筆--所見即所得 2 定義矩形 3 定義圓 15.6.4 實現MouseMove事件處理程序 15.6.5 實現MouseUp事件處理程序 MouseUp事件處理程序的任務是存儲草圖中的新元素,將tempElement還原為nullptr,將drawing指示器還原為flash,並且重繪草圖 15.6.6 實現窗體的Paint事件處理程序 前面生成的Paint事件委托有兩個參數, 第一個類型為Object^ 的參數標識事件源, 第二個類型為System::Windows::Forms::PaintEventArgs的參數提供有關該事件的信息,特別需要指出的是,該參數Graphics屬性提供了用於在窗體上繪圖的Graphics對象 15.7 小結 1 認情況下,windows使用原點在客戶區左上角的客戶坐標系統處理窗口的客戶區 x軸的正方向從左到右,Y軸的正方向從上到下 2 只能使用設備環境在窗口的客戶區中繪圖 3 為了處理窗口的客戶區,設備環境提供了大量稱為映射模式的邏輯坐標系統 4 映射模式的默認原點位置在客戶區的左上角,默認的映射模式是MM_TEXT,它提供以像素為單位的坐標,在這種模式中,X軸的正方向從左到右,y軸的正方向從上到下。 5 盡管平常可以繪制臨時實體,但是在響應WM_PAINT消息時,程序始終應當在窗口的客戶區中繪制永久性內容,對應用程序文檔的所有繪制都應當從視圖類的OnDraw()成員函數進行控制,在應用程序接收到WM_PAINT消息時,將調用這個函數 6 通過調用視圖類的InvalidateRect()函數成員,可以標識希望重新繪制的那部分客戶區。當下一個WM_PAINT消息發送到應用程序時,Windows將把這個區域作為參數傳遞給要重新繪制的整個區域 7 windows向應用程序發送有關鼠標事件的標准消息,利用Class Wizard可以創建處理這些消息的處理程序 8 通過在視圖類中調用SetCapture()函數,可以將所有鼠標消息發送到應用程序,在完成這一操作時,必須通過調用ReleaseCapture()函數釋放鼠標鍵,否則,其他應用程序將不能接收鼠標消息 9 在創建向何實體時,通過在處理鼠標移動的消息處理程序中繪制它們,可以實現橡皮筋操作 10 利用CDC類的SetROP2()成員可以設置繪圖模式,選擇正確的繪圖模式將大大簡化橡皮筋操作 11 使用Form Design功能,可以交互式的創建CLR程序和CUI元素,可以將Toolbox窗口上的控件直接拖動到窗體上,當然,通過手工添加合適的代碼,也可以通過編程方式創建GUI元素 12 通過設置GUI控件的屬性,可以在窗體上定制這些GUI控件 13 通過GUI組件的Properties窗口也可以自動添加事件處理程序函數 14 Graphics類定義了用於在窗體上繪圖的函數 15 Pen類定義了以給定顏色的線型繪圖的對像 練習題沒有做好,失敗一次 */