Delphi如何使用基本的繪圖函數繪製統計圖
一個windows內建的畫圖工具是無論如何也不能滿足我們的畫圖需要的,很多效果都需要我們在另外的工具中來實現。這些進階的功能是如何?的呢,如何操縱一些基本的屬性和函數,讓它們最終能作出我們想要的效果呢?這裡我們以繪製統計圖來說明這些問題。
解決思路――
這裡,我們暫且先撇開具體的問題,綜合地一下討論畫圖的問題。
畫圖工具是基本元素的具體實現,對於我們初學者來說,還是有很好的參考價值的,在delphi 5中有一個內建的工程例子“……Borland\Delphi5\Demos\Doc\Graphex”,這個例子可以實現一些基本的繪圖功能。對這個例子多加修改,一定會有所收穫的。這裡就不列出它的詳細代碼了,有心的讀者可以自己找到這個例子。我這裡只是想綜合地討論這方面的問題。使用DELPHI編寫繪圖軟體的靈魂就在於操作畫布,畫筆和刷子,儘可能地挖掘它們的屬性和相關參數的設定。
(一)畫布
畫布,畫筆和刷子之間的關係很明了.其實,畫筆和刷子都是畫布的一個屬性.而畫布也只是TForm,TImage,TShape等組件對象的一個屬性,專門負責與圖象相關的資訊打交道.它的主要作用可以概括如下幾點:
1.指定使用畫筆,刷子和字型的使用類型;
2.繪製和填充指定形狀的線或圖形;
3.修飾和改變圖象;
畫布的主要屬性有:
Brush--指定填充圖形和背景的樣式
CanvasOrientation--指定畫布的定位類型,有coLeftToRight, coRightToLeft兩個屬性;
ClipRect--指定剪下矩形的邊界;
CopyMode--指定圖形圖象的複製模式;
Font--指定畫布上使用的字型;
Handle--為畫布指定視窗GDI對象的裝置描述表;
LockCount--指定畫布被別的線程鎖定的次數;
Pen--指定畫布上使用的畫筆,具體見下面描述;
PenPos--指定畫筆當前的位置;
Pixels--指定當前剪下矩形的象素顏色;
TextFlags--指定字型在畫布上的顯示方式,有ETO_CLIPPED,ETO_OPAQUE,ETO_RTLREADING, ETO_GLYPH_INDEX,ETO_IGNORELANGUAGE,ETO_NUMERICSLOCALETO_NUMERICSLATIN等值可選;
畫布相關的API函數及其注釋如下:
Arc--按指定方式畫一條弧;
BrushCopy--把位元影像複製到指定的畫布的矩形中,用畫布刷子顏色替換位元影像的顏色;
Chord--按指定方式畫弦;
CopyRect--從一個矩形地區複製部分圖象到另一個矩形地區;
Draw--用指定參數在指定位置畫圖;
DrawFocusRect--按指定焦點風格,通過異或操作來繪製一焦點矩形;
Ellipse--按指定參數畫一橢圓;
FillRect--按指定的刷子填充一矩形;
FloodFill--使用當前選定的刷子填充指定裝置描述表中的一塊地區;
FrameRect--使用指定的方式畫一矩形的邊框;
LineTo--使用當前畫筆從當前位置到指定點畫一條直線;
Lock--防止其它線程在畫布上繪圖;
MoveTo--指定一新的當前畫筆位置;
Pie--按指定方式畫餅狀圖;
PolyBezier--按指定方式畫多條貝塞爾線;
PolyBezierTo--按指定方式畫多條貝塞爾線並更新當前的畫筆位置值;
Polygon--繪製一個由多個頂點的任意序列組成 的多邊形;
Polyline--使用當前畫筆畫一系列的多邊形;
Rectangle--繪製矩形;
RoundRect--繪製圓角矩形;
StretchDraw--在指定的矩形地區通過指定的繪圖參數來繪製圖形;
TextExtent--返回使用當前字型設定的字元的象素寬度和高度等參數;
TextHeight--返回使用當前字型設定的字元的象素高度;
TextOut--在指定位置繪製文本,並更新畫筆的當前位置;
TextRect--在一剪下矩形地區中繪製文本;
TextWidth--返回使用當前字型設定的字元的象素寬度;
TryLock--對當前沒加鎖的畫布進行加鎖;
Unlock--對當前加鎖的畫布進行解鎖;
例如以下是兩個小例子:
procedure TForm1.Button2Click(Sender: TObject);
var
ARect: TRect;
begin //實現了剪下效果;
with Image1.Canvas do
begin
CopyMode := cmWhiteness; //設定複製模式;
ARect := Rect(0, 0, Image1.Width, Image1.Height);
CopyRect(ARect, Image1.Canvas, ARect);
CopyMode := cmSrcCopy; //恢複復制模式;
end;
end;
procedure TForm1.Button3Click(Sender: TObject);
var
W: Word;
begin //在視窗中畫一條綵線;
for W := 10 to 200 do
Canvas.Pixels[W, 10] :=RGB(random(255),random(255),random(255));;
end;
靈活使用這些函數及其內部參數會讓我們得到意想不到的效果;
(二) 畫筆
畫筆是一個GDI對象,定義了繪製直線或輪廓形狀的方法.
畫筆內部共有五種屬性:顏色,控制代碼,模式,風格和寬度.
Color--決定指定直線或輪廓形狀的RGB顏色。
Handle--指向了視窗畫筆物件控點。
Mode--指定了畫筆以何種方式在畫布(canvas)上畫線,在協助文檔中的該定義是(全部以pm_開頭):
type TPenMode =( pmBlack, //總是黑色;
pmWhite, //總是白色;
pmNop, //顏色不變;
pmNot, //畫布顏色取反;
pmCopy, //顏色屬性中指定的畫筆顏色;
pmNotCopy, //畫筆顏色取反;
pmMergePenNot, //畫筆顏色和畫布背景色取反後顏色的結合;
pmMaskPenNot, //畫筆顏色和畫筆背景色取反後顏色共同色的結合;
pmMergeNotPen, //畫筆顏色取反後和畫布背景色的結合;
pmMaskNotPen, //畫布顏色和畫筆顏色取反後顏色共同色的結合;
pmMerge, //畫筆和畫布背景色的結合;
pmNotMerge, //畫筆顏色和畫布背景色的結合;
pmMask, //畫筆和畫布背景色共同色的結合;
pmNotMask, //pmMask取反,畫筆和畫布背景色共同色的結合;
pmXor, //取畫筆或畫布背景中的任一種顏色;
pmNotXor //pmXor取反,取畫筆或畫布背景中的任一種顏色;
);
Style--則指定了畫筆操作的風格,線上文檔中的定義是(全部以ps_開頭):
type TPenStyle=( psSolid, //畫筆是───
psDash, //畫筆是------
psDot, //畫筆是......
psDashDot, //畫筆是_._._.
psDashDotDot, //畫筆是_.._..
psClear, //畫筆是透明色
psInsideFrame //畫筆是實線,但設定大於1時會抖動;
);
另外,在windows.pas中還有其他擴充的畫筆風格定義,只在特殊的支援裝置上
才有效,如PS_ENDCAP_ROUND, PS_JOIN_ROUND等;
Width--指定了待使用畫筆的寬度,單位是象素.
和畫筆相關的函數有:
CreatePen--用指定風格建立畫筆;
CreatePenIndirect--根據LOGPEN資料結構建立一畫筆;
ExtCreatePen-- 建立帶指定風格,寬度和刷子屬性的幾何畫筆;
(三)刷子
刷子定義了地區填充的GDI對象,刷子是一個8×8象素的地區,它可以被繪製在指定的設
備上.刷子不僅可以是純色的,也可以由不同的位元影像圖案組成.
刷子的屬性有位元影像,顏色,控制代碼和風格四種:
Bitmap--是指定一個外部位元影像檔案來填充指定的地區.如果指定的圖象比填充的地區大,
則只有左上方與填充地區等大的部分有效,其餘的被自動裁減了.
Color--指定了刷子的顏色.當刷子風格為bsClear時,該屬性無效.
Handle--指向指定裝置視窗.
Style--則指定了當前刷子的填充風格,線上文檔中的定義是(都以bs_開頭):
type TBrushStyle=( bsSolid, //填充格式為實體填充
bsClear, //填充格式為透明填充
bsHorizontal, //填充格式為------
bsVertical, // 填充格式為|||||
bsFDiagonal, // 填充格式為/////
bsBDiagonal, // 填充格式為\\\\\
bsCross, // 填充格式為+++++
bsDiagCross // 填充格式為xxxxx
);
和刷子有關的API函數有:
CreateBrushIndirect--根據LOGBRUSH建立一刷子;
CreateDIBPatternBrushPt--使用裝置無關位元影像來建立刷子,以便指定刷子的模式;
CreateHatchBrush--建立一帶有陰影模式的刷子,陰影模式為以HS_開頭的常數;
CreatePatternBrush--用位元影像來建立刷子,以便指定刷子的模式;
CreateSolidBrush--建立一實體顏色刷子;
GetBrushOrgEx--擷取指定裝置描述表中當前選擇刷子的原點;
GetSysColorBrush--擷取和指定色彩索引相關的邏輯刷子的控制代碼;
SetBrushOrgEx--設定指定裝置描述表中當前選擇刷子的原點;
(四)畫圖和填充相關的API函數;
BeginPaint--準備在指定視窗繪畫或對指定地區進行填充;
DrawAnimatedRects--NT支援函數,畫一環有遊動邊框的矩形;
DrawCaption--NT支援函數,為指定視窗的標題賦值;
DrawEdge--為指定矩形畫一道或多道邊框;
DrawFocusRect--畫焦點矩形;
DrawFrameControl--畫一指定類型和風格的邊框控制項;
DrawState--NT支援函數,為圖象畫一可視效果標明其狀態;
DrawStateProc--NT支援函數,調用為圖象畫一可視效果標明其狀態的函數;
DrawTextEx--NT支援函數,在指定地區輸出格式化文本;
EndPaint--結束繪畫;
ExcludeUpdateRgn--將視窗無效部分(更新地區)從裁剪區中排除掉;
GdiFlush--使當前GDI閃爍;
GdiGetBatchLimit--擷取緩衝GDI函數數量;
GdiSetBatchLimit--設定緩衝GDI函數數量;
GetBkColor--擷取背景顏色;
GetBkMode--擷取背景模式;
GetBoundsRect--擷取邊界矩形;
GetROP2--擷取當前繪圖模式;
GetUpdateRect--擷取指定視窗最小的矩形;
GetUpdateRgn--擷取描述視窗中無效區的地區;
GetWindowDC--擷取視窗DC;
GetWindowRgn--擷取視窗地區;
GrayString--在指定位置畫灰色文本;
InvalidateRect--使DC指定的矩形無效;
InvalidateRgn--使DC指定的矩形無效;
LockWindowUpdate--禁止或允許在指定視窗中繪畫;
OutputProc--調用輸出進程,向GrayString輸送文本;
PaintDesktop--NT支援函數,在指定的視窗地區用指定的案頭顏色或牆紙填充裁剪區;
RedrawWindow--更新客戶區的指定地區或矩形;
SetBkColor--設定背景顏色;
SetBkMode--設定背景模式;
SetBoundsRect--設定邊界矩形;
SetRectRgn--設定矩形地區;
SetROP2--設定當前繪圖模式;
SetWindowRgn--設定視窗地區;
UpdateWindow--更新視窗;
ValidateRect--使客戶區中指定矩形有效;
ValidateRgn--使客戶區中的指定地區有效;
WindowFromDC--擷取和指定視窗相關的控制代碼;
具體實現――
1.本例以常見的統計圖來說明問題。該例能實現對統計圖的動態繪製,並且可以自訂設定統計圖的形狀和顏色。在說明問題之前,來瞭解程式用到的一些比較複雜的函數或演算法:
函數――
1.Polygon(Points: array of TPoint)
用於繪出指定的多邊形。括弧內是預定點的集合,該集合可以在使用之前定義,也可以在使用時同時定義,本例屬於後者;
2.Pie(X1, Y1, X2, Y2, X3, Y3, X4, Y4: Longint)
用於繪製餅狀圖,餅狀圖其實就是橢圓的一部分。在這些參數中,其中(X1, Y1)和(X2, Y2)定義了框住餅狀圖的矩形,而從橢圓中心發出的射線經過(X3, Y3)和(X4, Y4)兩點,就把一個餅狀圖截出來了。
3.FormatFloat(const Format: string; Value: Extended)
函數的意義是按指定方式格式化字串,Format指定了格式化的方式,Value則指定了要格式化的文本或其他資料。下面列舉了一些範例,可供我們學習時參考:
格式化符號(Format) 1234 -1234 0.5 0
1234 -1234 0.5 0
0 1234 -1234 1 0
0.00 1234.00 -1234.00 0.50 0.00
#.## 1234 -1234 .5
#,##0.00 1,234.00 -1,234.00 0.50 0.00
#,##0.00;(#,##0.00) 1,234.00 (1,234.00) 0.50 0.00
#,##0.00;;Zero 1,234.00 -1,234.00 0.50 Zero
0.000E+00 1.234E+03 -1.234E+03 5.000E-01 0.000E+00
#.###E-0 1.234E3 -1.234E3 5E-1 0E0
該例是在小數點後保留兩位小數,因此用"##.##",具體見程式碼中。
演算法――
本例的實現依賴一定的演算法。這裡介紹主要的兩點:
1)在串連多邊形各點時,我們要注意那幾個點一定要構成一個閉合的圖形,這就要保證最後一個點要和第一個點重合。至於其他的點怎麼布局,則要有一定的空間感。
我們先畫一個矩形,然後再根據平行關係確定其他的點:
rectangle(50,x,70,220); //畫主視面;
Canvas.Polygon([Point(50, x), Point(70,x-10),Point(90,x-10), Point(70, x),Point(50, x)]); //畫頂面;
Canvas.Polygon([Point(90,x-10), Point(70, x),Point(70,220),Point(90,210),Point(90,x-10)]); //畫側面;
2)確定圓上指定角度的邊與圓的交點
在該例中畫餅狀圖時,我們要按照一定的比例畫扇形,這就要確定扇形的起始點和終止點。我們把起始點設為一個定點,而終止點則根據實際情況設定,
――餅狀圖的數學原理――
假設畫圖時已知一個比例數是K,則在餅狀圖中的角度是θ=(K*360),根據圖中的關係(Y軸向下符合螢幕座標系定義),可以用三角函數知識求得PX,PY:
PX=op*cosθ
PY=op*sinθ
上述式子的前提條件是O點是原點,OP是圓的半徑。如果O點不是原點,而是座標系中的一個點(X0,Y0),則此時的P點座標是
PX=X0+op*cosθ
PY=Y0+op*sinθ
3.本例的介面布局可以參考程式啟動並執行結果圖,其中代表“刷子類型”的combobox1的items的屬性設定。該例實現的主要代碼如下:
procedure TForm1.Button1Click(Sender: TObject);
var
x,i,j:integer;
k:real;
begin
refresh;
//標明寫上“Y”軸;
label4.left:=25;
label4.top:=2;
label4.caption:='Y';
label4.Transparent:=true;
//標明寫上“X”軸;
label5.left:=395;
label5.top:=227;
label5.caption:='X';
label5.Transparent:=true;
x:=220-round(strtofloat(edit1.text)/strtofloat(edit2.text)*200);
with form1.Canvas do
begin
pen.width:=strtoint(edit3.text); //設定畫筆寬度;
case combobox1.Items.IndexOf(combobox1.text) of //設定刷子的填充風格;
0: brush.style:=bsSolid;
1: brush.style:=bsClear;
2: brush.style:=bsHorizontal;
3: brush.style:=bsVertical;
4: brush.style:=bsFDiagonal;
5: brush.style:=bsBDiagonal;
6: brush.style:=bsCross;
7: brush.style:=bsDiagCross;
end;
//畫出X軸;
MoveTo(2,220);
LineTo(400,220);
//畫出Y軸;
MoveTo(20,5);
LineTo(20,230);
//畫出Y軸的箭頭方向"∧";
moveto(20,5);
lineto(15,12);
moveto(20,5);
lineto(25,12);
//畫出X軸的箭頭方向"∧";
moveto(400,220);
lineto(395,213);
moveto(400,220);
lineto(395,227);
if checkbox1.Checked then //繪製立體的直方柱圖;
begin
//畫正面的矩形圖;,可以根據實際情況動態定義它的高度;
rectangle(50,x,70,220);
//畫頂面,隨著正面矩形的高度變化而變化;
Canvas.Polygon([Point(50, x), Point(70,x-10),Point(90,x-10), Point(70, x),Point(50, x)]);
//畫側面,隨著正面矩形的高度變化而變化;
Canvas.Polygon([Point(90,x-10), Point(70, x),Point(70,220),Point(90,210),Point(90,x-10)]);
end
else
rectangle(50,x,70,220); //如果沒有選中要以立體形式繪製,則以平面形式繪製的直方柱圖;
//畫餅狀統計圖
k:=(strtofloat(edit1.text)/strtofloat(edit2.text))*360; //將資料按比例轉換成;
i:=round(250+100*cos(k*3.14159/180));
j:=round(120+100*sin(k*3.14159/180));
pie(150,20,350,220,i,j,350,120);
label3.caption:='比例是'+formatfloat('##.##',(k/360)*100)+'%'; //設定比例的函數;
end;
end;
procedure TForm1.Shape1MouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
if colordialog1.Execute then //設定視窗背景顏色;
shape1.Brush.color:=colordialog1.Color;
form1.color:=ColorDialog1.Color;
end;
procedure TForm1.Shape2MouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
if colordialog2.Execute then //設定刷子顏色;
shape2.Brush.color:=colordialog2.Color;
form1.Canvas.Brush.color:=colordialog2.Color;
end;
procedure TForm1.Shape3MouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
if colordialog3.Execute then //設定畫筆顏色;
shape3.Brush.color:=colordialog3.Color;
form1.Canvas.Pen.color:=colordialog3.Color;
end;