使用 .NET Framework 绘制图形入门指南






4.88/5 (29投票s)
一份帮助初学者入门图形绘制的指南。
引言
不可否认,Windows Presentation Foundation (WPF) 已经确立了其在图形等方面的强大技术地位。但这并不意味着初学者无法从 .NET Framework 2.0 提供的图形绘制功能中受益。在 .NET 中,图形绘制始于线条和形状的绘制,然后可以继续处理图像和格式化文本。绘制始于 System.Drawing.Graphics
类。要创建实例,通常需要调用控件的 CreateGraphics
方法。或者,如果您想将图片保存为文件,也可以创建一个基于 Image
对象的 Graphics
对象。一旦创建了 Graphics
对象,您就可以使用许多方法来执行绘制操作。
Clear
:清除整个表面并用颜色填充。DrawEllipse
:绘制由指定的坐标对、高度和宽度定义的边界矩形所定义的椭圆或圆形。DrawIcon
和DrawUnstretched
:在指定坐标处绘制由指定图标表示的图像,可以选择是否缩放图标。DrawImage
:在指定位置绘制指定的Image
对象。DrawLine
:绘制连接由坐标对定义的两个点的线段。DrawLines
:绘制连接一组Point
结构的连续线段。DrawPath
:绘制一系列连接的线条和曲线。DrawPie
:绘制由指定的坐标对、宽度、高度以及两条半径线定义的椭圆所确定的扇形。DrawPolygon
:绘制一个具有三个或更多边的形状,该形状由一组Point
结构定义。DrawRectangle
:绘制由指定的坐标对、宽度和高度定义的矩形或正方形。DrawString
:在指定位置使用指定的Brush
和Font
对象绘制指定的文本字符串。
要使用这些方法中的任何一种,您都必须提供 Pen
类的实例。通常,您需要指定 Pen
类的颜色和像素宽度。例如,以下代码从左上角 (1,1) 开始绘制一条 7 像素宽的红色直线到窗体中间附近的点 (100, 100),如下图所示。
要运行此代码,请创建一个 Windows Forms 应用程序,并将代码添加到在窗体的 Paint
事件期间运行的方法中。
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
public class MainForm : System.Windows.Forms.Form
{
private System.ComponentModel.Container components;
public MainForm()
{
InitializeComponent();
CenterToScreen();
SetStyle(ControlStyles.ResizeRedraw, true);
}
protected override void Dispose( bool disposing )
{
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
private void InitializeComponent()
{
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
this.ClientSize = new System.Drawing.Size(292, 273);
this.Text = "Fun with graphics";
this.Resize += new System.EventHandler(this.Form1_Resize);
this.Paint += new System.Windows.Forms.PaintEventHandler(this.MainForm_Paint);
}
[STAThread]
static void Main()
{
Application.Run(new MainForm());
}
private void MainForm_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{
Graphics g = this.CreateGraphics();
// Make a big red pen.
Pen p= new Pen(Color.Red, 7);
g.DrawLine(p, 1, 1, 100, 100);
}
private void Form1_Resize(object sender, System.EventArgs e)
{
}
}
同样,以下代码绘制一个带有 60 度角的蓝色扇形,如下所示。
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
public class MainForm : System.Windows.Forms.Form
{
private System.ComponentModel.Container components;
public MainForm()
{
InitializeComponent();
CenterToScreen();
SetStyle(ControlStyles.ResizeRedraw, true);
}
protected override void Dispose( bool disposing )
{
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
private void InitializeComponent()
{
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
this.ClientSize = new System.Drawing.Size(292, 273);
this.Text = "Fun with graphics";
this.Paint += new System.Windows.Forms.PaintEventHandler(this.MainForm_Paint);
}
[STAThread]
static void Main()
{
Application.Run(new MainForm());
}
private void MainForm_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{
Graphics g = this.CreateGraphics();
Pen p = new Pen(Color.Blue, 3);
g.DrawPie(p, 1, 1, 100, 100, -30, 60);
}
}
GDI+
GDI 代表图形设备接口 (Graphical Device Interface),GDI+ 库包含允许您完成各种渲染操作的类:渲染线条、曲线、渐变、显示图像…… GDI+ 还允许您绘制诸如贝塞尔曲线之类的几何形状。同样,我们通过调用 Control
或 Form
类的 CreateGraphics
方法来获取 System.Drawing.Graphics
类的实例。重要的是,一旦通过调用 CreateGraphic( )
获取了 Graphics
类实例,应尽快调用其 Dispose( )
方法。
System.Drawing.Pen 类
正如我们所见,正如艺术家需要一支笔来绘制曲线一样,Graphics
类的各种方法用于绘制线条、曲线或形状的轮廓;我们只需要一个 System.Drawing.Pen
的实例。下面是绘制贝塞尔曲线的代码。您可以使用 Pen
类的 Width
属性指定笔触的粗细。此外,您可以使用 Pen
类的 DashStyle
属性指示笔触是实线、虚线还是点线。您还可以使用 Pen
类的 StartCap
和 EndCap
属性指定笔触末端的绘制类型。
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
public class MainForm : System.Windows.Forms.Form
{
private System.ComponentModel.Container components;
public MainForm()
{
InitializeComponent();
CenterToScreen();
SetStyle(ControlStyles.ResizeRedraw, true);
}
protected override void Dispose( bool disposing )
{
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
private void InitializeComponent()
{
this.AutoScaleBaseSize = new System.Drawing.Size(8, 17);
this.ClientSize = new System.Drawing.Size(292, 273);
this.Text = "Fun with graphics";
this.Paint += new System.Windows.Forms.PaintEventHandler(this.MainForm_Paint);
}
[STAThread]
static void Main()
{
Application.Run(new MainForm());
}
private void MainForm_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{
using ( Graphics g = CreateGraphics() )
{
Pen pen = new Pen( Color.Black);
pen.Width = 5;
pen.DashStyle = DashStyle.Dash;
pen.StartCap = LineCap.RoundAnchor;
pen.EndCap = LineCap.ArrowAnchor;
g.DrawBezier( pen, new Point(10, 30), new Point(30, 200),
new Point(50, -100), new Point(70, 100));
}
}
}
使用图像
Image 和 Bitmap 类
System.Drawing.Image
抽象类使您可以创建、加载、修改和保存图像,例如 BMP 文件、JPG 文件和 TIFF 文件。Image
类是 abstract
的,但您可以使用 Image.FromFile
(接受图像文件的路径作为参数)和 Image.FromStream
(接受 System.IO.Stream
对象作为参数)来创建该类的实例。要在窗体中显示保存到磁盘的图像(或作为窗体或控件的背景),请使用 Graphics.DrawImage( )
方法。以下是一个保存到我的桌面的图像示例(在 Vista 终端上,因此路径为 c:\users\dave\desktop\image.jpg)。
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
public class MainForm : System.Windows.Forms.Form
{
private System.ComponentModel.Container components;
public MainForm()
{
InitializeComponent();
CenterToScreen();
SetStyle(ControlStyles.ResizeRedraw, true);
}
protected override void Dispose( bool disposing )
{
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
private void InitializeComponent()
{
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
this.ClientSize = new System.Drawing.Size(292, 273);
this.Text = "Fun with graphics";
this.Paint += new System.Windows.Forms.PaintEventHandler(this.MainForm_Paint);
}
[STAThread]
static void Main()
{
Application.Run(new MainForm());
}
private void MainForm_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{
Bitmap bm = new Bitmap(@"C:\users\dave\desktop\imageA.jpg");
Graphics g = this.CreateGraphics();
g.DrawImage(bm, 1, 1, this.Width, this.Height);
}
}
如何创建和保存图片
要创建一张新的空白图片,请使用不要求现有图像的构造函数之一来创建 Bitmap
类的实例。然后,您可以使用 Bitmap.SetPixel
方法对其进行编辑,或者可以调用 Graphics.FromImage
并使用 Graphics
绘制方法来编辑图像。要保存图片,请调用 Bitmap.Save
。下面的代码创建了一个 600x600 的空白 Bitmap
,创建了一个基于该 Bitmap
的 Graphics
对象,使用 Graphics.FillPolygon
和 Graphics.DrawPolygon
方法在 Bitmap
中绘制一个形状,然后将其保存到当前目录(.NET Framework 2.0 目录)中名为 bm.jpg 的文件中。与上面的代码类似,尽管许多部分是重复的,但它将在控制台命令行上运行。它需要 System.Drawing2D
和 System.Drawing.Imaging
命名空间。
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
public class MainForm : System.Windows.Forms.Form
{
private System.ComponentModel.Container components;
public MainForm()
{
InitializeComponent();
CenterToScreen();
SetStyle(ControlStyles.ResizeRedraw, true);
}
protected override void Dispose( bool disposing )
{
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
private void InitializeComponent()
{
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
this.ClientSize = new System.Drawing.Size(292, 273);
this.Text = "Fun with graphics";
this.Paint += new System.Windows.Forms.PaintEventHandler(this.MainForm_Paint);
}
[STAThread]
static void Main()
{
Application.Run(new MainForm());
}
private void MainForm_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{
Bitmap bm = new Bitmap(600, 600);
Graphics g = Graphics.FromImage(bm);
Brush b = new LinearGradientBrush(new Point(1,1),
new Point(600, 600), Color.White, Color.Red);
Point[] points = new Point[] { new Point(77, 500), new Point(590, 100),
new Point(250, 590), new Point(300, 410)};
g.FillPolygon(b, points);
bm.Save("bm.jpg", ImageFormat.Jpeg);
}
}
此代码的编写方式将使其编译并执行以输出一个简单的窗体。但是,如果您键入“bm.jpg”,您会发现您创建的文件实际上已保存为 JPEG 文件。下面显示的尺寸已被缩小。
如何使用图标
图标是特定大小的透明位图,Windows 使用它们来传达状态。.NET Framework 通过 SystemIcons
类的属性提供了标准的 40x40 系统图标。添加图标的最简单方法是调用 Graphics.DrawIcon
方法。
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
public class MainForm : System.Windows.Forms.Form
{
private System.ComponentModel.Container components;
public MainForm()
{
InitializeComponent();
CenterToScreen();
SetStyle(ControlStyles.ResizeRedraw, true);
}
protected override void Dispose( bool disposing )
{
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
private void InitializeComponent()
{
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
this.ClientSize = new System.Drawing.Size(292, 273);
this.Text = "Fun with graphics";
this.Paint += new System.Windows.Forms.PaintEventHandler(this.MainForm_Paint);
}
[STAThread]
static void Main()
{
Application.Run(new MainForm());
}
private void MainForm_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{
Graphics g = this.CreateGraphics();
g.DrawIcon(SystemIcons.Question, 40, 40);
}
}
当您厌倦了在图形领域有所作为时
图形应用程序可以具有功能性,但这需要我们关注 Control
类的一些核心事件。
Click
、DoubleClick
:Control
类定义了许多响应鼠标输入的事件。MouseEnter
、MouseLeave
、MouseDown
、MouseUp
、MouseMove
、MouseHover
、MouseWheel
。KeyPress
、KeyUp
、KeyDown
:Control
类定义了许多响应键盘输入的事件。
此应用程序旨在展示 Windows Forms 如何响应标准输入。在命令行上运行它。它会为按下的每个键弹出一个对话框,并给出每个鼠标位置的坐标。它还将为每种类型的鼠标点击或按键输出一个消息框。我们首先构建一个新的 Form
类(我们已经做了——MainForm
),将窗体的初始大小设置为任意尺寸,并覆盖 Dispose()
方法。为了在窗体顶部显示坐标,必须建立 Rectangle
的边界。
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
public class MainForm : System.Windows.Forms.Form
{
private System.ComponentModel.Container components;
public MainForm()
{
Top = 100;
Left = 75;
Height = 100;
Width = 500;
MessageBox.Show(Bounds.ToString(), "Current rect");
// Add delegates for mouse & keyboard events.
this.MouseUp += new MouseEventHandler(OnMouseUp);
this.MouseMove += new MouseEventHandler(OnMouseMove);
this.KeyUp += new KeyEventHandler(OnKeyUp);
InitializeComponent();
CenterToScreen();
}
protected override void Dispose( bool disposing )
{
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
MessageBox.Show("Disposing this Form");
}
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
this.Size = new System.Drawing.Size(300,300);
this.Text = "Form1";
}
[STAThread]
static void Main()
{
Application.Run(new MainForm());
}
protected void OnMouseUp(object sender, MouseEventArgs e)
{
// Which mouse button was clicked?
if(e.Button == MouseButtons.Left)
MessageBox.Show("Left click!");
else if(e.Button == MouseButtons.Right)
MessageBox.Show("Right click!");
else if(e.Button == MouseButtons.Middle)
MessageBox.Show("Middle click!");
}
protected void OnMouseMove(object sender, MouseEventArgs e)
{
this.Text = "Current Pos: (" + e.X + ", " + e.Y + ")";
}
public void OnKeyUp(object sender, KeyEventArgs e)
{
MessageBox.Show(e.KeyCode.ToString(), "Key Pressed!");
}
}
在命令行上运行此代码。键入“type con > ControlApp.cs”,然后将代码粘贴到控制台,然后按 Ctrl-Z。使用 /target:winexe 标志并提供 /reference:System.dll。代码运行后,我们就可以看到如何使用图形了。更重要的是,我们现在可以看到图形应用程序如何响应标准输入。例如,假设您必须在标准 Paint
事件处理程序的范围之外渲染某个图像——您想在鼠标单击的 (x, y) 位置绘制一个小圆。首先要做的就是获取一个有效的 Graphics
对象,该对象可以通过静态 Graphics.FromHwnd( )
方法获得。请注意,我们将当前句柄作为唯一参数传递,并注意 Handle
属性是从 Control
类继承的。这应该是合理的,但与 MouseUp 逻辑结合并添加一个新点到内部 Point
对象集合,然后调用 Invalidate( )
(关闭窗口并清除绘制的点)一起使用,效果会更好吗?
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
public class MainForm : System.Windows.Forms.Form
{
private ArrayList myPts = new ArrayList();
public MainForm()
{
InitializeComponent();
CenterToScreen();
this.Text = "Basic Paint Form (click on me)";
}
public void MyPaintHandler(object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
}
private void InitializeComponent()
{
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
this.ClientSize = new System.Drawing.Size(292, 273);
this.Text = "Form1";
this.MouseDown += new System.Windows.Forms.MouseEventHandler(this.MainForm_MouseDown);
this.Paint += new System.Windows.Forms.PaintEventHandler(this.MainForm_Paint);
}
[STAThread]
static void Main()
{
Application.Run(new MainForm());
}
private void MainForm_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e)
{
// Grab a new Graphics object.
Graphics g = Graphics.FromHwnd(this.Handle);
// Now draw a 10*10 circle at mouse click.
// g.DrawEllipse(new Pen(Color.Green), e.X, e.Y, 10, 10);
// Add to points collection.
myPts.Add(new Point(e.X, e.Y));
Invalidate(); // means close window
}
private void MainForm_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{
Graphics g = e.Graphics;
g.DrawString("Hello GDI+", new Font("Times New Roman", 20),
new SolidBrush(Color.Black), 0, 0);
foreach(Point p in myPts)
g.DrawEllipse(new Pen(Color.Green), p.X, p.Y, 10, 10);
}
}
显示的圆是鼠标在指定位置单击的结果。因此,假设我们有三个 JPEG 图像。我们想单击其中一个,并在窗体顶部识别它。考虑这段代码。
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
public class Form1 : System.Windows.Forms.Form
{
private System.ComponentModel.Container components;
// The images.
private Image bMapImageA;
private Image bMapImageB;
private Image bMapImageC;
// Rects for the images.
private Rectangle rectA = new Rectangle(10, 10, 90, 90);
private Rectangle rectB = new Rectangle(10, 110, 90, 90);
private Rectangle rectC = new Rectangle(10, 210, 90, 90);
// A polygon region.
GraphicsPath myPath = new GraphicsPath();
// Did they click on an image?
private bool isImageClicked = false;
private int imageClicked;
public Form1()
{
InitializeComponent();
// Fill the images with bitmaps.
bMapImageA = new Bitmap(@"c:\users\dave\desktop\imageA.jpg");
bMapImageB = new Bitmap(@"c:\users\dave\desktop\imageB.jpg");
bMapImageC = new Bitmap(@"c:\users\dave\desktop\imageC.jpg");
// Create an interesting region.
myPath.StartFigure();
myPath.AddLine(new Point(150, 10), new Point(120, 150));
myPath.AddArc(200, 200, 100, 100, 0, 90);
Point point1 = new Point(250, 250);
Point point2 = new Point(350, 275);
Point point3 = new Point(350, 325);
Point point4 = new Point(250, 350);
Point[] points = {point1, point2, point3, point4};
myPath.AddCurve(points);
myPath.CloseFigure();
CenterToScreen();
}
protected override void Dispose( bool disposing )
{
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
private void InitializeComponent()
{
this.AutoScaleBaseSize = new System.Drawing.Size(5, 11);
this.ClientSize = new System.Drawing.Size(292, 273);
this.Text = "Form1";
this.MouseUp += new System.Windows.Forms.MouseEventHandler(this.Form1_MouseUp);
this.Paint += new System.Windows.Forms.PaintEventHandler(this.Form1_Paint);
}
[STAThread]
static void Main()
{
Application.Run(new Form1());
}
private void Form1_MouseUp(object sender, System.Windows.Forms.MouseEventArgs e)
{
// Get (x, y) of mouse click.
Point mousePt = new Point(e.X, e.Y);
// See if the mouse is anywhere in the 3 regions...
if(rectA.Contains(mousePt))
{
isImageClicked = true;
imageClicked = 0;
this.Text = "You clicked image A";
}
else if(rectB.Contains(mousePt))
{
isImageClicked = true;
imageClicked = 1;
this.Text = "You clicked image B";
}
else if(rectC.Contains(mousePt))
{
isImageClicked = true;
imageClicked = 2;
this.Text = "You clicked image C";
}
else if(myPath.IsVisible(mousePt))
{
isImageClicked = true;
imageClicked = 3;
this.Text = "You clicked the strange shape...";
}
else // Not in any shape, set defaults.
{
isImageClicked = false;
this.Text = "Images";
}
// Redraw the client area.
Invalidate();
}
private void Form1_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{
Graphics g = e.Graphics;
// Render all three images.
g.DrawImage(bMapImageA, rectA);
g.DrawImage(bMapImageB, rectB);
g.DrawImage(bMapImageC, rectC);
// Draw the graphics path.
g.FillPath(Brushes.AliceBlue, myPath);
// Draw outline (if clicked...)
if(isImageClicked == true)
{
Pen outline = new Pen(Color.Red, 5);
switch(imageClicked)
{
case 0:
g.DrawRectangle(outline, rectA);
break;
case 1:
g.DrawRectangle(outline, rectB);
break;
case 2:
g.DrawRectangle(outline, rectC);
break;
case 3:
g.DrawPath(outline, myPath);
break;
default:
break;
}
}
}
}
那么,我们得到了什么?
单击图像,并在窗体顶部识别它。功能不强,但肯定可以扩展。回看代码,注意 Point
结构的数组以及图像的文件路径。
动画和双缓冲
要创建动画,通过每秒显示几十帧不同的帧,请使用 System.Windows.Forms.Timer
类的实例,该类将负责由窗口线程定期调用某个方法。以下代码演示了如何创建一个动画,该动画表示一个在窗口中央旋转的正方形。
using System;
using System.Drawing;
using System.Windows.Forms;
using System.Drawing.Drawing2D;
public partial class AnimForm : Form {
private float angle;
private Timer timer = new Timer();
private BufferedGraphics bufferedGraphics;
public AnimForm() {
BufferedGraphicsContext context = BufferedGraphicsManager.Current;
context.MaximumBuffer = new Size( this.Width + 1, this.Height + 1 );
bufferedGraphics = context.Allocate( this.CreateGraphics(),
new Rectangle( 0, 0, this.Width, this.Height) );
timer.Enabled = true;
timer.Tick += OnTimer;
timer.Interval = 20; // 50 images per second.
timer.Start();
}
private void OnTimer( object sender, System.EventArgs e ) {
angle ++;
if (angle > 359)
angle = 0;
Graphics g = bufferedGraphics.Graphics;
g.Clear( Color.Black );
Matrix matrix = new Matrix();
matrix.Rotate( angle, MatrixOrder.Append );
matrix.Translate( this.ClientSize.Width / 2,
this.ClientSize.Height/ 2, MatrixOrder.Append );
g.Transform = matrix;
g.FillRectangle( Brushes.Azure, -100, -100, 200, 200 );
bufferedGraphics.Render( Graphics.FromHwnd( this.Handle ) );
}
[System.STAThread]
public static void Main() {
Application.Run( new AnimForm() );
}
}