C#绘图技术GDI
命名空间:System.Drawing;(基本绘图操作)//在这个命名空间下找相关的类
Graphics对象,可以理解为一只画笔,要在哪个控件上绘图,就要得到对应控件的Graphics对象(就是获得对应控件的绘图表面)。
获得Graphics对象一般有两种方式:
1.控件.CreateGraphics()//通过该方式创建后要调用g.Dispose()方法释放资源。
//得到了窗体的“画笔,其实就是窗体的表面”
Graphics g = this.CreateGraphics();
g.DrawString("qweqwedetwetrtr",this.Font,Brushes .Red ,200,240 );
g.DrawEllipse(Pens.Blue, 50, 50, 50, 50);
g.DrawRectangle(Pens.Red, 50, 50, 50, 50);
g.Dispose();
2.在重写OnPaint()方法中通过参数PaintEventArgs e.Graphics;来获得,一般使用为Paint事件中。//通过这种方式一般不需要手动调用Dispose();区别OnPaint()与窗体的Paint事件。(画一些简单图形)
Graphics g = e.Graphics;
Pen pen = new Pen(Color.Yellow ,10);
g.DrawLine(Pens.Yellow, 20, 30, 50, 80);
pen.Dispose();
//g.Dispose //不需要了
什么样类型的对象需要使用完成后Dispose()
答:Pen,Brush,Font,Image,Ico,FileStream(文件操作),网络操作等。
(在这些类中封装了一些操作系统的资源)//Pens.XXX或Brushes.XXX等不需要,手动创建的才需要。强行释放资源会出错.
GDI+中的坐标系。
原点在左上角。X水平向右为正,Y垂直向下为正。
(Demo,鼠标移动,显示坐标)//定义一个图形的坐标,即指定了该图形离顶部与左边的距离。//每个控件的左上角都是(0,0)
从使用的角度看,GDI+坐标系是一种转换规则,把你所制定的逻辑数据转换成最终设备驱动所能使用的数据。
Point、Size、Rectangle//封装,对”坐标”的封装
DrawXXX(),画各种图形,Pen
FillXXX(),填充,画实心的,Brush
Graphics g = this.panel1.CreateGraphics();
//三个常用的对坐标的封装
Point point =new Point (50,50);//对坐标点的封装
Point point2 =new Point (100,100);
Size size = new Size(100, 200);//对形状大小的封装
Rectangle rec = new Rectangle(point2,size );//对一个矩形区域进行封装
g.FillRectangle(Brushes .Blue ,rec );
//g.FillRectangle(Brushes.Yellow, point.X, point.Y, size.Width, size.Height);
//g.DrawLine(Pens.Red, point, point2);
//g.DrawEllipse(Pens.Red, point.X, point.Y, point2.X, point2.Y);
//g.FillEllipse(Brushes.Red, 50, 50, 50, 50);//画实心
g.Dispose();
private void Form1_MouseMove(object sender, MouseEventArgs e)//鼠标移动显示坐标
{
this.Text = string.Format ("x={0},y={1}",e.X ,e.Y );//在标题栏显示
}
例:绘制某工厂某产品的年度销售情况表
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
this.Text = string.Format("x={0},y={1}", e.X, e.Y);
}
double[] date = { 34.3,65.7,33.1,87,57.3,56,99,56.3,41.6,48.1,57.3,38.8};
PointF[] datePoint = new PointF[12];
private void Form1_Paint(object sender, PaintEventArgs e)
{
//首先确定x轴和y轴的交点,原点
Point centerPoint = new Point(150,300);
//得到当前窗体的 Graphics对象
Graphics g = e.Graphics;
//从新new了一只笔,可以自己设置一些更详细的属性,供我们自己使用
Pen pen = new Pen(Color.Black, 1);
pen.EndCap = System.Drawing.Drawing2D.LineCap.ArrowAnchor;//带箭头
//画x轴
g.DrawLine (pen ,centerPoint,new Point (centerPoint .X +400,centerPoint .Y));
//画x轴刻度
for (int i = 0; i < 12; i++)
{
g.DrawLine(Pens .Black ,new Point (centerPoint .X +(i+1)*30,centerPoint .Y ),new Point (centerPoint .X +(i+1)*30,centerPoint .Y-5 ));
//回值x轴显示的汉字“月份”
g.DrawString((i + 1).ToString() + "月", this.Font, Brushes.Black, new PointF(centerPoint.X + (i + 1) * 30-5 , centerPoint.Y + 3));
}
g.DrawString( "月份", this.Font, Brushes.Black, new PointF(centerPoint.X + 390, centerPoint.Y + 3));
//画y轴
g.DrawLine(pen, centerPoint, new Point(centerPoint.X, 10));
for (int i = 0; i < 10; i++)
{
g.DrawLine(Pens.Black, new Point(centerPoint.X, centerPoint.Y - (i + 1) * 25), new Point(centerPoint.X + 5, centerPoint.Y - (i + 1) * 25));
// 绘制y轴显示的数字10、20、、、
g.DrawString(String .Format ("{0}",(i+1)*10), this.Font, Brushes.Black, new PointF(centerPoint.X - 25, (centerPoint.Y - (i + 1) * 25)-5));
}
g.DrawString("销售额:单位(万元)", this.Font, Brushes.Black, new PointF(centerPoint.X -120, centerPoint.Y -290));
//计算12个月的销售额对应的坐标点
for (int i = 0; i < date.Length; i++)
{
float x= centerPoint .X +(i+1)*30;
float y= centerPoint .Y - (float )(date [i ]*2.5);
PointF point = new PointF(x,y);
datePoint[i] = point;
}
/绘制图表上的12个点
for (int i = 0; i < datePoint.Length; i++)
{
g.DrawRectangle(Pens.Black, datePoint[i].X, datePoint[i].Y, 2, 2);
}
g.DrawLines(Pens.Black, datePoint);//用数组画
//GraphicsPath path = new GraphicsPath();
//for (int i = 0; i < datePoint.Length; i++)
//{
// path.AddRectangle(new RectangleF(datePoint [i ],new SizeF(2,2)) );
//}
//path.AddLines(datePoint );
//g.DrawPath(Pens .Black ,path );
pen.Dispose();
例:如果想在其他地方也调用上面表格,用类的方法
private void button1_Click(object sender, EventArgs e)
{
DrawReport report = new DrawReport();
report.CenterPoint = new Point(170, 300);
report.g = this.panel1.CreateGraphics();
report.Draw();
report.g.Dispose();
}
//建一个类
class DrawReport
{
//原点坐标
public Point CenterPoint
{
get;
set;
}
public Graphics g
{
get;
set;
}
public double[] date={ 34.3,65.7,33.1,87,57.3,56,99,56.3,41.6,48.1,57.3,38.8};
public void Draw()
{
PointF[] datePoint = new PointF[12];
Font font = new Font("宋体",9);
//从新new了一只笔,可以自己设置一些更详细的属性,供我们自己使用
Pen pen = new Pen(Color.Black, 1);
pen.EndCap = System.Drawing.Drawing2D.LineCap.ArrowAnchor;//带箭头
//画x轴
g.DrawLine(pen, CenterPoint, new Point(CenterPoint.X + 400, CenterPoint.Y));
//画x轴刻度
for (int i = 0; i < 12; i++)
{
g.DrawLine(Pens.Black, new Point(CenterPoint.X + (i + 1) * 30, CenterPoint.Y), new Point(CenterPoint.X + (i + 1) * 30, CenterPoint.Y - 5));
//回值x轴显示的汉字“月份”
g.DrawString((i + 1).ToString() + "月", font, Brushes.Black, new PointF(CenterPoint.X + (i + 1) * 30 - 5, CenterPoint.Y + 3));
}
g.DrawString("月份", font, Brushes.Black, new PointF(CenterPoint.X + 390, CenterPoint.Y + 3));
//画y轴
g.DrawLine(pen, CenterPoint, new Point(CenterPoint.X, 10));
for (int i = 0; i < 10; i++)
{
g.DrawLine(Pens.Black, new Point(CenterPoint.X, CenterPoint.Y - (i + 1) * 25), new Point(CenterPoint.X + 5, CenterPoint.Y - (i + 1) * 25));
// 绘制y轴显示的数字10、20、、、
g.DrawString(String.Format("{0}", (i + 1) * 10), font, Brushes.Black, new PointF(CenterPoint.X - 25, (CenterPoint.Y - (i + 1) * 25) - 5));
}
g.DrawString("销售额:单位(万元)", font, Brushes.Black, new PointF(CenterPoint.X - 120, CenterPoint.Y - 290));
//计算12个月的销售额对应的坐标点
for (int i = 0; i < date.Length; i++)
{
float x = CenterPoint.X + (i + 1) * 30;
float y = CenterPoint.Y - (float)(date[i] * 2.5);
PointF point = new PointF(x, y);
datePoint[i] = point;
}
//绘制图表上的12个点
for (int i = 0; i < datePoint.Length; i++)
{
g.DrawRectangle(Pens.Black, datePoint[i].X, datePoint[i].Y, 2, 2);
}
g.DrawLines(Pens.Black, datePoint);//用数组画
font.Dispose();
pen.Dispose();
}
方法的重写
虚方法:使用virtual关键字修饰,使用virtual关键字修饰的方法在本类中必须有实现,哪怕是空的{}。虚方法存在的意义就是为了让子类重写。子类可以重写,也可以不重写。
方法重写:只有用virtual、abstract、override修饰的方法在继承后子类可以重写。//实现方法重写的方式3个关键字
什么是方法重写?
答:方法重写是指子类继承父类后在子类中有一个与父类中某个方法签名一致,但方法内容可以重新定义的方法,并用override关键字修饰。
例:普通方法
static void Main(string[] args)
{
Person[] per = new Person[4];
Chinese ch1 = new Chinese();
Chinese ch2 = new Chinese();
Japanese jp1 = new Japanese();
Japanese jp2 = new Japanese();
per[0] = ch1;
per[1] = ch2;
per[2] = jp1;
per[3] = jp2;
for (int i = 0; i < per.Length;i++ )
{
if (per[i] is Chinese)
{
Chinese c = per[i] as Chinese;
c.ShowNationality();
}
else if (per[i] is Japanese)
{
Japanese c = per[i] as Japanese;
c.ShowNationality();
}
}
Console.ReadKey();
class Person
{
public string Name
{
get;
set;
}
public int Age
{
get;
set;
}
}
class Chinese : Person
{
public string HuKou
{
get;
set;
}
//显示国籍
public void ShowNationality()
{
Console.WriteLine("中国");
}
}
class Japanese : Person
{
public void PoFu()
{
Console.WriteLine("剖腹");
}
public void ShowNationality()
{
Console.WriteLine("日本");
}
}
用方法重写
static void Main(string[] args)
{
Person[] per = new Person[5];
Chinese ch1 = new Chinese();
Chinese ch2 = new Chinese();
Japanese jp1 = new Japanese();
Japanese jp2 = new Japanese();
Beijing bj1 = new Beijing();
per[0] = ch1;
per[1] = ch2;
per[2] = jp1;
per[3] = jp2;
per[4] = bj1;
for (int i = 0; i < per.Length;i++ )
{
per[i].ShowNationality();
}
Console.ReadKey();
}
class Person
{
public string Name
{
get;
set;
}
public int Age
{
get;
set;
}
//virtual关键字写在返回值前面,public virtual或 virtual public
//使用virtual关键字修饰的方法必须有实现{}
//子类可以重写,也可以不重写
//子类重写时使用overrid关键字
//注:方法重写时,方法签名必须与父类中的虚方法完全一致,否者重写不成功,其中包括“返回值”
public virtual void ShowNationality()
{
Console.WriteLine("还没有国籍");
}
}
class Chinese : Person
{
public string HuKou
{
get;
set;
}
//显示国籍
public override void ShowNationality()
{
base.ShowNationality();
Console.WriteLine("中国");
}
}
class Japanese : Person
{
public void PoFu()
{
Console.WriteLine("剖腹");
}
public override void ShowNationality()
{
Console.WriteLine("日本");
}
抽象类:abstract关键字修饰;不能实例化;
可以有抽象成员也可以有非抽象成员
(1.有抽象成员的类必须标记为abstract,并且不能有任何实现;
2.类中的抽象成员在子类中必须实现(override),除非子类也是抽象类);
virtual方法和abstract方法的区别:
virtual方法子类可以override,也可以不override。
Abstract标记的成员在子类中必须override,除非子类也是抽象类。
ToString()方法、Equals()来自于Object,任何类都有,通过override有了不同的实现。
//以下是关于抽象方法与抽象类
//抽象方法必须重写
abstract class Person
{
public string Name
{
get;
set;
}
public int Age
{
get;
set;
}
public void sayHello()
{
Console.WriteLine("hello");//可以有抽象成员也可以有非抽象成员
}
//抽象方法
abstract public void ShowNationality();
virtual public void test()
{
}
public void go()
{
Console.WriteLine("go person");
}
}
class Chinese : Person
{
public string HuKou
{
get;
set;
}
//显示国籍
public override void ShowNationality()
{
//base.ShowNationality();//抽象类不能实例化,所以不能通过base来调用
Console.WriteLine("中国");
base.sayHello();
//this.sayHello();
}
public new void go()
{
Console.WriteLine("go china");
}
}
class Japanese : Person
{
public void PoFu()
{
Console.WriteLine("剖腹");
}
public override void ShowNationality()
{
Console.WriteLine("日本");
}
}
class Beijing : Chinese
{
public override void ShowNationality()
{
Console.WriteLine("北京人");
}
}
}
思考:父类到底要不要写成抽象类?
抽象类的意义:不能从抽象类创建对象,它的意义在于被扩展。代码重用、多态(简化了is object的判断)。普通类的继承能实现多态吗?
方法重写的意义:为所有子类定义了某种规范(某种必须有的规范),多态。