1. 属性
1. 1.程序集属性(AssemblyInfo.cs)
2. 2.反射
3. 3. 内置属性
4. 4.定制属性
2. XML文档说明
1. 5.添加XML文档说明
2. 6. XML文档说明的注释元素
3. 7.生成XML文档说明文件
4. 8.使用XML文档说明
3. 网络
1. 9.联网概述
2. 10.名称的解析
3. 11. 统一资源表示符
4. 12.TCP 和UDP
5. 13.应用协议
6. 14.网络编程选项
7. 15.WebClient
8. 16. WebRequest 和 WebResponse
9. 17.TcpListener 和 TcpClient
4. GDI+简介
1. 18.图形绘制概述
1. Graphics类
2. 对象的删除
3. 坐标系统
4. GraphicsPaths
5. Regions
6. 使用Pen类绘制线条
2. 19.使用Brush类绘制图形
3. 20.使用Font类绘制文本
4. 21.使用图像进行绘制
1. 使用纹理画笔绘图
2. 使用钢笔绘制图像
3. 双倍缓冲
5. 22. GDI+高级功能介绍
________________
属性
1.程序集属性(AssemblyInfo.cs)
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// 有关程序集的常规信息通过下列属性集
// 控制。更改这些属性值可修改
// 与程序集关联的信息。
[assembly: AssemblyTitle("AresT")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("ARES LAB")]
[assembly: AssemblyProduct("AresT")]
[assembly: AssemblyCopyright("Copyright ARES LAB 2009")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly:...]属性定义;在程序集中存储属性的过程称为"pickling".
Microsoft把一些最常见的属性映射到Detail属性表上,但其他属性就必须用编写代码了,可以使用 Ildasm 或 Reflector工具。
添加Ildasm的方法,在VS2008 -» Tools -» External Tools -» 命令设置为C:/Program Files/Microsoft SDKs/Windows/V6.0/bin/ildasm.exe.
在AssemblelyInfo.cs代码中,使用了术语AssemblyTitle,但在IL中使用AssemblyTitleAttribute。C#编译器会分别查找这两个术语。
属性是一个包含程序集中其他数据的类,这些数据涉及到程序集或程序集中的任何类型。
AssemblyTitle属性有一个构造函数,该构造函数只有一个参数--字符串值。要访问这个值,可以用标准的Windows Explorer属性表,或者用Ildasm or Reflector查看程序集。
2.反射
反射可以用编程的方式检查程序集,获得该程序集的信息,包括其中包含的所有对象类型。这些信息还包含用户添加到这些类型中的属性。反射类位于System.Reflection名称空间中。
除了读取给定程序集中定义的类型外,还可以使用System.Reflector.Emit 或 System.CodeDom的服务程序生成自己的程序集 和 类型。
例:检查一个程序集,并显示定义的属性,生成一个列表
using System;
using System.Reflector;
namespace FindAttributes
{
class Program
{
static void Main(string[] args)
{
.......
try
{
Assembly a = Assembly.LoadForm(assemblyName);
object[] attributes = a.GetCustomerAttributes(true);
if(attributes.Length »0)
{
console.WriteLine("Assembly attributes fo '{0}'...", assemblyName);
foreach(object o in attributes)
{
Console.WriteLine(" {0} ", o.ToString());
}
}
else
....................
}
}
}
}
3. 内置属性
* System.Diagostics.ConditaionalAttribute
* System.ObsoleteAttribute
* System.SerializableAttribute
* System.Reflection.AssemblyDelaySignAttribute
System.Diagostics.ConditaionalAttribute
这是最常用的属性之一,它允许在编译时,根据符号的定义包含或不包含代码块。这个属性包含在System.Diagnostics名称空间中,该名称空间包含调试 和 跟踪输出的类、事件日志、性能计数器 和 继承信息。
例:
using System;
using System.Diagnostics;
namespace TestConditional
{
class Program
{
static void Main (string[] args)
{
Program.DebugOnly();
}
[conditional("DEBUG")] //只有在编译期间定义了DEBUG符号,才会调用该方法;在VS2008中,编译一个调试版程序,会自动设置DEBUG符号。
public static void DebugOnly()
{
console.WriteLine("this string only displays in debug");
}
}
}
Debug块的默认设置是定义DEBUG 和 TRACE符号。
在命令行上定义一个符号的方法:
»csc /d:DEBUG conditional.cs
conditional属性只能用于无返回值的方法,否则删除调用就以为着没有返回值,但可吧方法设置为带out 或 reg参数,便令仍保留为原始值。
System.Diagnostics名称空间中,Debug类有许多用[Conditional("DEBUG")]标记的静态方法,用于运行应用程序输出调试信息。如果,改为成承发布版本,对这些方法的所有调用就会在最终的程序中被忽略。
这非常适合于再添加调试信息,并在构建项目的最终发布版本时自动删除它们。
System.ObsoleteAttribute
Obsolete属性可以把类,方法 或 程序集中的其他项标记为不再使用。
用来提示,有新的功能可以使用,可以替代当前功能。比如:"***以过时,可以使用****方法"
public class Coder
{
public Coder()
{}
[Obsolete("CodeInCSharp instead. ")]
public void CodeInCPlusPlus()
{}
}
然后加入新方法:
public class CodeInCSharp()
{}
当使用CodeInCPlusPlus方法时,会提示有新的方法CodeInCSharp可以使用。
System.SerializableAttribute
序列化是指存储和获取磁盘文件、内存或其他位置上的对象。
在序列化时,所有的实例数据都保存到存储介质上;
在取消序列化时,对象会重新构建,且与其原实例完全相同。
ISerializable是一种改变序列化的数据的高级方式。
例:
[Serializable]
public class Person
{
public Person()
{}
public int Age(get ; set;);
public int WeightInPounds{get;set;};
}
//下面用binaryFormatter存储Person对象:
using System;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
public static void Serialize()
{
Person me = new Person();
me.Age = 38;
me.WeightInPounds =200;
Stream s File.Open ("Me.dat", FileMode.Create);
BinaryFormatter bf = new BinaryFormatter();
bf.Serialize(s,me);
s.Close();
}
[NonSerialized]属性,定义一个或多个不要序列化的字段。
[Serializable]
public class Person
{
public Person()
{}
public int Age {get;set;};
[NonSerialized]
private int _weightInPounds;
public int WeightInPounds
{
get {return _weightInPounds;};
set {_weightInPounds = value;};
}
}
序列话这个类到时候,只储存了Age成员,没有存储WeghtInPounds成员。
解序:
public static void DeSerialize()
{
Stream s = File.Open("Me.dat", FileMode.Open);
BinaryFormatter bf = new BinaryFormatter();
object o = bf.Deserialize();
Person p = o as Person;
if(p!= null)
Console.WriteLine(.........);
s.Close();
}
System.Reflection.AssemblyDelaySignAttribute
AssemblyDelaySign,它允许用户给程序集加上“延迟标记”,即可以在GAC中注册它,以便在没有私钥时测试它们。
延迟标记 的 一种是用场合时 开发商用软件。
在室内开发的每个程序集在销售给客户之前,都需要用公司的私钥标记,向客户保证,程序集的确是用公司创建的。
所以,当编译程序的时,应在GAC中注册它之前引用密钥文件。密钥文件保护两个密钥:公钥 和 私钥。
但,许多组织并不想它们的私钥出现在每个开发人员的机器上。因此,运行库允许部分的标记程序集,变换几个设置,使程序集可以在GAC中注册。
在全部测试完毕后,就可以由拥有私钥文件的人来标记它。
例:延迟标记
1)用程序 sn.exe创建一个密钥。
这个密钥包含:公钥 和 私钥。一般用公司命名它,扩展名是.snk.
»sn -k Company.key
2) 提取Public key。
接着,使用-p选项获取开发人员使用的公钥部分:
»sn -p Company.key Company.public
这个命令会生成一个密钥文件Company.public,其中只有包含密钥的公共部分。
公钥不需要保密,私钥需要保密。company.key需要存储在安全的地方,在最终标记程序集时应用。
3)获取公钥记号。
为了给程序集加上延迟标记,并在GAC中注册它,还需要得到公钥记号。
记号基本上是公钥的一个缩写版本,在注册程序集时使用它。可以用下面两种方式获取该记号:
a.从公钥本身获取:
»sn -t Company.Public
b.从使用该密钥标记的任何程序集中获取:
»sn -t «assembly»
这两个命令都会显示公钥的散列版本,且是区分大小写的。
4)延迟标记程序集
using System;
using System.Reflection;
[assembly:AssemblyKeyFile("Company.Public")] //定义了在哪里寻找密钥。可以是公钥文件,也可以是保护公钥和私钥的文件。
[assembly:AssemblyDelaySign(true)] //false时,完整标记了程序集;true时,延迟标记程序集
public class DelayedSigning
{
public DelayedSigning()
{}
}
在编译时,程序集将在清单中包含用于公钥的一项。还包括用于私钥的足够空间,所以重新标记程序集肯定不会改变它(而是把额外的字节写到程序集清单中)。
调试该程序集,会得到错误提示。
这是因为在默认情况下,.net运行库只加载未标记的程序集(没有定义密钥文件)或 运行完全标记的、包含公钥 和 私钥对的程序集。
.net不允许加载部分标记的程序集。所以,会显示错误提示。
5)在GAC中注册
如果试图使用Gacutil工具,在GAC中注册一个延迟标记的程序集,就会产生如下错误:
microsoft (R) .net global assembly cache utility. version 3.5.20706.1
copy right (c) microsoft corporation. all rights reserved.
Failure adding assembly to the cache:strong name signature could not be verified.
Was the assembly built delay-signed?
此程序集只进行了部分标记,在默认情况下,GAC和VS2008只接受有完整强名的程序集。
但对于延迟标记的程序集,可以使用sn实用程序指示.NET跳过强名验证:
»sn -Vr *,a1aad1...... //a1aad1...... 是前面的公钥记号
这将指示.NET允许在GAC中注册公钥记号为a1aad1......的程序集。
更重要的是,对于这个示例,它允许在VS中运行DelaySign .exe.在命令提示行上输入这个名利,会得到如下提示:
microsoft (R) .NET Framework strong name utility version 3.5.20706.1
copyright (C) microsoft corpration. all rights reserved
Verification entry added for assembly '*, a1aad1......
现在就可以成功的用Gacutil将程序集安装到GAC上了。
在为程序集添加验证项时,指定注册的所有程序集的命令:
»sn -Vr *
也可以:
通过程序集的完整名称来定义:
»sn -Vr DelaySign.exe
这些数据会永远保存在Verification Skip Table中。要得到该表,应输入下述命令:
»sn -Vl
下面是输出:
microsoft..................
copyright.................
assembly/strong Name users
=============================
*,a1ad1a5619382d41 All user
可以定义某个给定的程序集能由部分用户加载到GAC中,参阅sn.exe文档说明。
5)完整强名
这个过程的最后一项是把公钥 和 私钥 编译到程序集中。
待有这两项的程序集称为完整的强名,可以在GAC中注册,且不必跳过验证项。
再次使用sn.exe使用程序,是用-R选项,重新标记程序集,并添加私钥部分:
»sn -R delaysign.dll Company.Key
comany.key为包含公钥 和 私钥的密钥文件。
4.定制属性
定制属性必须遵循以下两个规范:
a.属性必须派生于System.Attribute
b. 属性的构造函数之能包含可再编译时解析的类型,例如,字符串和整数
属性构造函数的参数 的类型限制 源于属性存储在程序集元数据中的方式。
[assembly:AssemblyKeyFile("CompanyPublic.snk")]
1)自己定制 BugFixAttribute
在编写产品代码时,最好能自动生成在应用程序的各个版本之间已经完成的修改操作。一般需要查看错误数据库,检查在两个日期之间添加的错误和改进,该从这些数据中生成报告。
另一种方式,把修噶操作的细节嵌入代码中,这样可以根据代码生成报告。
创建一个定制属性类:
* 创建一个派生于 System.Attribute的类;
* 按照需要创建构造函数 和 公共属性;
* 给类添加属性,以定义可以在什么地方使用定制属性。
a.创建定制属性类:
只需创建一个派生自System.Attribute的类即可。
public class BugFixAttribute:Attribute
{ }
b.创建构造函数 和 属性:
当用户使用属性时,都会调用该属性的构造函数(对于BugFixAttribute,要记录错误号和注释)。
using System;
public class BugFixAttribute:Attribute
{
public BugFixAttribute(string bugNumber, string comments) //构造函数
{
BugNumber = bugNumber;
Comments = comments;
}
public readonly string BugNumber; //属性
public readonly string Comments;
}
c.标记类的用途
最后需要标记属性类,指定在什么地方可以使用这个属性类。
对于,BugFixAttribute,指定这个属性只在类、属性、方法和构造函数上有效。
[AttributeUsage(AttributeTargets.Class|AttributeTargets.Property|
AttributeTargets.Method | AttributeTargets.Constructor, AllowMultiple=false, Inherited=true)]
public class BugFixAttribute:Attribute
.....
定义好属性BugFixAttribute后,就可以开始用它编码了。
[BugFix("101","Created some methods")]
pbulic class MyBuggyCode
{
[BugFix("90125","Removed call to base")]
public MyBuggyCode()
{}
[BugFix("2112","Returned a non null string")]
[BugFix("38382","Returned OK")]
public string DoSomething()
{
return "OK";
}
}
2)System.AttributeUsageAttribute
在定义定制属性类时,需要添加AttributeUsage定义可以在什么地方使用。
最简单的格式如下:
[AttributeUsage(AttributeTargets.Class|AttributeTargets.Property|
AttributeTargets.Method | AttributeTargets.Constructor, AllowMultiple=false, Inherited=true)]
AttributeTargets枚举定义了表 30-2中所示的成员:
All :属性在程序集的任何地方都有效
Assembly:属性在程序集上有效
Class:属性在类定义上有效。TestCase属性就使用这个值,另一个示例是Serializable属性
Constructor:属性近在类的构造函数中有效
Delegate:属性仅在委托中有效
Enum:属性可以添加到枚举值中。
Event:属性在事件定义上有效
Field:属性可以放在字段上
Interface:属性在接口上有效。例:在System.Runtime.InteropServices中定义的GuidAttribute,它允许显示地定义接口的GUID
Method:属性方法上有效。System.Runtime.Remoting.Message中的OneWay属性就是用这个值
Module:属性在模块中有效。程序集可以从许多代码模块中创建,所以可以使用这个值把属性房子一个单独的模块上,而不是房子整个程序集上
Parameter:属性可应用于方法定义中的一个参数上
Property:属性可以用于一个特性
Return Value:属性与函数的返回值相关
Struct:属性在结构上有效
a.属性的作用域
[assembly:AssemblyTitle("AssemblyPeek")] 字符串指定了属性的作用域
[return:MyAttribute()] 属性作用于返回值
public long DoSomething()
{
...
}
b.AttributeUsage.AllowMutiple
[AttributeUsage(AttributeTargets.Class|AttributeTargets.Property|
AttributeTargets.Method | AttributeTargets.Constructor
, AllowMultiple=false, Inherited=true)]
AttributeUsage 的构造函数只带一个参数:可以使用属性的标志列表。AllowMultiple是AttributeUsage上的一个特性。
使用一个类似的方法设置Inherited属性(如果,定制属性有特性,就可以用相同的方法设置它们)。
添加错误修改者的姓名:
public readonly string BugNumber;
public readonly string Comments;
public string Author = null;
public override string ToString()
{
if(null== Author)
return string.Format("BugFix {0} : {1}", BugNumber, Comments);
else
return string.Format("BugFix {0} by {1} : {2}", BugNumber,Author,Comments);
}
报告类的错误更正的方法时将类的类型(也在System.Type中)传递给下面所示的DisplayFixes函数,
这也使用反射来查找应用到类上的错误更正,再迭代这个类上的所有方法,查找BugFix属性:
public static void DisplayFixes(System.Type T)
{
object[] fixes = t.GetCustomerAttributes (typeof (BugFixAttribute), false);
Console.WrtieLine("Displaying fixes for {0}",t);
foreach(BugFixAttribute bugFix in fixes)
{
Console.WriteLine(" {0}",bugFix);
}
//下面的参数,限制了返回的成员列表;BindingFlags枚举在System.Reflector中定义
foreach(MemberInfo member in t.GetMembers (BindingFlags.Instance|BindingFlags.Public|BindingFlags.NonPublic|BindingFlags.Static))
{
object[] memberFixes = member.GetCustomerAttributes(typeof(BugFixAttribute),false);
if(memberFixes.Length»0)
{
Console.WriteLine(" {0}", member.Name );
foreach(BugFixAttribute memberFix in memberFixes)
Console.WriteLine(" {0}", memberFix);
}
}
}
对于前面给出的MyBuggyCode类,将得到如下结果:
Displaying fixes for MyBuggyCode
BugFix 101 : Created some methods
DoSomething
BugFix 2112 : Returned a non-null string
BugFix 38382 : Returned OK
.ctor
BugFix 90125: Removed call to base()
如果要显示给定程序集中所有类的错误更正,可以使用反射类获取程序集中的所有类型,把每个类型传递给静态方法BugFixAttribute.DisplayFixes。
c.AttributeUsage.Inherited
通过设置这个标志可以把属性定义为可继承:
[AttributeUsage(AttributeTargets.Class ,Inherited=true)]
public class BugFixAttribute{...}
这表示BugFix属性可以由使用该属性的任何子类继承。即错误更正会应用到某个类上,并且还会应用到一些派生类上。
------------------
了解了定制属性知识后,就会考虑可以把额外的信息添加到程序集的什么地方。
我们可以提取这里定义的BugFix属性,并扩展它——例如,要定义更正了错误的软件版本号,可以使用反射查找BufFix属性;
再比较该更正的版本号与终端用户要求查看的版本号,向终端用户显示应用程序的给定版本中所有已更正的错误。
这也是和与在产品循环的最后生成文档说明,列出最终版本中的所有错误更正。
显然,必须依赖开发人员添加这些熟悉,但可以建立代码检查循环,确保所有的错误更正都在代码中做了说明。
________________
XML文档说明
XML文档说明,它允许在源代码中位所创建的类包含语法、帮助和示例。XML文档说明可以用做项目的MSDN连接的起点,或者使用XSLT格式化XML文档说明,毫不费力地获得即时HTML文档说明。
把这个创建文档说明的功能内置于用于开发代码的工具中是非常有用的。尽管给代码添加XML文档说明很费时间,但对于最终结果是值得的。
5.添加XML文档说明
添加XML文档说明时,使用三个斜杠,而不是两个进行注释。
///(XML Documentation goes here.)
class MyClass
{
///(And here)
public void MyMethod()
{
......
}
}
注意:XML文档说明不能应用于名称空间声明
XML文档说明一般占用多行,使用XML文档说明符号来标记:
///«XMLDocElement»
///Content.
///More Content
///«/XMLDocElement»
class MyClass
{
......
}
XML文档说明中最基本的元素是«summary»,它提供了类型或成员的简短描述。
///«summary»
/// this is a summary description for the DocumentedClass class
///«/summary»
public class DocumentClass
{
.....
}
在IntelliSense中会显示;
打开Object Browser窗口,展开FirstXMLDocumentation属性的项,单击DocumentedClass,在右下角的汇总信息中显示。
6. XML文档说明的注释元素
XML文档说明中基本元素的简短描述:
«c»:格式化代码字体中的文本。它用于潜在其他文本中的单个代码字
«code»:格式化代码字体中的文本。它用于未嵌在其他文本中的多行代码
«description»:把文本标记为项的描述,用做列表中«item»或«listheader»的子元素
«example»:把文本标记为目标的示例用法
«exception»:指定由目标抛出的异常
«include»:从外部文件中获得XML文档说明
«item»:表示列表中的一项,用做«list»的子元素,可以有«description»和«term»子元素
«list»:定义一个列表,可以有«listheader»和«term»子元素
«listheader»:表示表格的标题行,用作«list»的子元素,可以有«description»和«term»子元素
«para»:用于把文本分解为各个段落
«param»:描述模板的一个参数
«paramref»:引用一个方法参数
«permission»:指定目标需要的许可
«remarks»:与目标的相关的其他信息
«return»:描述目标的返回值,与方法一起使用
«see»:引用另一个目标,在元素体中使用,如«summary»
«seealso»:应用国另一个目标,通常在其他元素的外部或最后使用,如«summary»
«summary»:与目标相关的汇总信息
«term»:把文本标记为项的定义
«typeparam»:描述用于一般目标的类型参数
«typeparamref»:引用一个类型参数
«value»:描述模板的返回值,与属性一起使用
1)文本格式化元素:
«c»,«code»,«list»及相关元素«para»,«paramref»。
«see»、«seealso»比较特殊,也可以包含在这个表中,因为它们可以包含在文本体中,但通常放在最后。
«see»,«seealso»,«paramref»和«typeparamref»
这四个元素都用于表示项目的XML文档说明中的 其他项 或 外部的MSDN项。
它们都显示为一个超链接,允许文档说明浏览器调到其他项上。
«see»和«seealso»通过cref属性指定其目标,目标可以是任意类型 或 任意类的成员,在项目中或其他地方。
«paramref»和«typeparamref»使用name属性引用当前目标的一个参数。
///«summary»
///«para»
///This method uses «paramref name="museName" /» to choose a muse
/// for more details, see «see cref="MyPoet" /».
///«/para»
///«seealso cref="MyParchment" /»
///«seealse cref="MyTheme" /»
///«/summary»
«see»在使用属性langword引用C#关键字时特别有用。
///«summary»
///For more information, see «see langword="null" »
///«/summary»
优点是,如果指定语言专用的关键字,它就可以为其他语言修改文档说明。
注意,这些元素不包括要显示的文本,要显示的文本通常从name、ref 或 langword 属性中构造。
例:
/// this method uses «paramref name="museName" /» museName to choose a muse.
其中,museName会重复。
«list»和相关元素
«list»元素是最复杂的文本格式化元素,因为可以以不同的方法使用它。
它的type属性可以是:
* bullet——格式化带项目符号的列表
* number——格式化带编号的列表
* table——格式化表格
«list»元素一般包含一个«listheader»子元素 和 几个«item»子元素。这些子元素都有许多«term»和«description»子元素。
选择哪种子元素取决于列表的类型 和 工具格式化列表的方式。例如:
«term»在格式化的表格中使用,而«listheader»仅在表格中有意义。
对于带项目符号的列表,可以使用:
///«summary»
///«para»
///This method uses «paramref name="museName" /» to choose a muse
///«/para»
///«para»
///try the following muses:
///«list type="bullet"»
///«listheader»
///«term» Muse name «/term»
///«description» Muse's favorite pizza «/description»
///«/listheader»
///«item»
///«term»Calliope«/term»
///«description» Ham & Mushroom «/description»
///«/item»
///«item»
///«term»Clio«/term»
///«description» Four Seasons«/description»
///«/list»
///«/para»
///«/summary»
2)主要的结构元素
«summary» 该元素从不会包含在其他元素中,且总是用于给出目标的汇总信息。
«example»、«exception»、«param»、«permission»、«remarks»、«returns» 和 «value»。
«seealso»比较特殊,它可以是顶级元素,也可以是另一个元素的子元素。
«include»是另一个比较特殊的元素,它可以从外部文件中获取XML,有效的重写其他元素。
«summary»、«example»和«remarks»
这三个元素都提供目标的一般信息。这些信息显示在工具提示中,所以最好使该信息较短,而把其他信息放在«example»和«remarks»中
介绍一个类时,给出该类的用法示例 常常比较有效。这也适用于方法、属性等。这些信息放在«example»中。
///«summary»
///«para»
///this summary is about a «c» class «/c» that does interesting things.
///«/para»
///«example»
///«para»
///try this:
///«/para»
///«code»
/// MyPoet poet = new MyPoet("Homer");
///poet.AddMuse("Thalia");
///poet.WriteMeAnEpic();
///«/code»
///«/example»
同样,«remark»常常用于提供目标的较长描述,可以包含«see»和«seealso»元素,进行交叉引用。
«param»和«typeparam»
这些元素描述一般目标的参数,可以是标准方法的参数或类型参数。
要引用的参数用name属性来设置。如果使用了多个参数,元素可以出现多次:
///«summary»
///Method to add a muse
///«/summary»
///«param name="museName"»
///A «see langword="string" /» parameter specifying the nameof a muse.
///«/param»
///«param name="museModd"»
///A «see cref="MuseMood" /» parameter specifying the mood of a muse.
///«/param»
«returns» 和 «value»
它们都表示返回值,其中«return»用于方法的返回值,«value»用于属性值,这两个元素都不适用任何属性。
对于方法:
///«summary»
///Method to add a muse
........
///«returns»
///the return value of this method is «see langword="void"»
///«/returns»
对于属性:
///«summay»
///Property for getting / setting a muse
///«/summary»
///«value»
///the type of this property is «see langword="string" /»
///«/value»
«permission»
这个元素用于描述与目标相关的许可。可实际上设置许可应采用其他方式;
例如,把属性PermissionSetAttribute应用于方法,«permission»元素仅允许通知其他人有这些许可。
«permission»元素包含一个cref属性,以便引用包含附加信息的类,如,System.Security.PermissionSet。
例:
///«permission cref="System.Security.PermissionSet"»
///Only administrators can use this method
///«/permission»
«exception»
这个元素用于描述使用目标的过程中可能抛出的异常。它的cref属性可用于交叉应用异常类型。
例如:
试商用这个元素可以说明属性的取值范围,还可以说明如果试图把属性设置为不允许的值,会发生什么情况
///«exception cref="System.ArgumentException"»
///This exception will be thrown if you set «paramref name="Width" /» to a
/// nagative value
///«/exception»
«seealso»
该元素可以用作顶级元素。可以使用几个«seealso»元素,根据所使用的工具,该元素在目标的最后格式化为一个引用列表:
///«summary»
///this is a summary for the MyPoet class
///«/summary»
///«seealso cref="MyParchment" /»
///«seealso cref="MyTheme" /»
///«seealso cref="MyToenails" /»
☆«include»
可以把说明文档放在完全独立的文件中。
«include»元素通过两个属性file和path允许这么做。
例:
///«include file="ExternalDoc.xml" path="documentation/classes/MyClass/*" /»
public class MyClass
上面所引用的ExternalDoc.xml文件,包含如下代码:
«?xml version="1.0" encoding="utf-8"»
«documentation»
«classes»
«MyClass»
«summary»
summary in an external file
«/summary»
«remarks»
Nice,eh?
«/remarks»
«/MyClass»
«/classes»
«/documentation»
这些代码等价于:
///«summary»
///Summary in an external file
///«/summary»
///«remarks»
///Nice.eh?
///«/remarks»
public class MyClass
7.生成XML文档说明文件
如果要对 用与开发项目环境的XML说明文档 进行操作,就必须把项目的文档说明输出为XML文件。
为此,需要修改项目的构建设置(winform项目上右击,选择属性,选择“build”or “生成”标签)
唯一改变是选中了XML documentation file复选框,提供了一个输出文件名,最好在程序集所在的目录下保存XML文档说明文件,
因为IDE会在该目录下搜索XML文档说明。
比如:bin/debug/DiagramaticDocumentation.xml
如果,客户应用程序在另一个解决方案中解释程序集,则只有IDE找到XML文档说明文件,才能利用IntelliSense 和 Object Browser的帮助。
打开XML文档说明的设置后,编译器就配置为主动搜索类中的XML文档说明。忽略给定类 或 方法的XML文档说明不一定会出错,但IDE会在Error List窗口中发出警告。
生成的文档包含以下内容:
* 跟级«doc»元素,它包含其他所有信息
* «assembly»元素,它包含一个«name»元素,而«name»元素包含应用XML文档说明的程序集名
* «members»元素,它包含程序集中每个成员的XML文档说明
* 几个«member»元素,每个元素都带有一个name属性,说明«member»元素包含的XML文档说明应用于什么类型 或 成员
* «member»元素的name属性遵循同一个命名模式。它们都属于下述名称空间之一,即,它们都是以下面的一个字母开头:
* T:指定成员是一个类型(类、接口、结构、枚举或委托)
* M:指定成员是一个方法
* P:指定成员是一个属性 或 索引符
* F:指定类型是一个字段或枚举成员
* E:指定成员是一个事件
在类型XML文档说明中,错误会在生产的文件中显示为一个加了注释的«member»元素:
«!-- Badly formed XML comment ignored for member "T:DiagramticDocumentation.DocumentedClass" --»
8.使用XML文档说明
1)编程处理XML文档说明
XML文档说明最明显的用法是充分利用.NET名称空间中丰富的XML工具。
可以XML文档说明加载到XmlDocument对象中。
例:
using System;
....
using System.xml;
static void Main(string[] args)
{
//load XML documentation file
XmlDocument doucumentation = new XmlDocument();
documentation.Load(@"../../../DocumentedClassses"+@"/DocumentedClasses/bin/debug/DocumentedClasses.xml");
//Get «member» elements.
XmlNodeList memberNodes = documentation.SelectNodes("//member"); //使用XPath表达式//member,获得«member»所有元素列表
//Extract «member» elements.
List«XmlNode» typedNodes = new List«XmlNode»();
foreach(XmlNode node in memberNodes)
{
if(node.Attributes["name"].Value.StartsWith("T"))
{
typeNodes.Add(node);
}
}
//Write types to the console
Console.WriteLine("Types:");
foreach (XmlNode node in typedNodes)
Console.WriteLine("-{0}", node.Attributes["name"].Value.Substring(2));
Console.ReadKey();
}
2)用XSLT格式化XML文档说明
可以使用XSLT把XML转换成HTML,甚至是用XSLT格式化对象,创建可以打印的文档说明。
这里采用C#创始人Anders Heejlsberg的建议。他发布一个XSLT文档和相关CSS样式表,可以把XML文档说明转换为HTML。
说明文件中添加一个预处理指令,如下:
«?xml version="1.0" ?»
«?xml:stylesheet href="doc.xsl" type="text/xsl" ?» //doc.xsl 和 doc.css
«doc»
......
«/doc»
3)文档说明工具
另一种方式是使用工具。
XML文档说明样式化的首选工具是NDoc,这个第三方工具可以把文档说明转换为多种样式,包括MSDN样式的帮助文件。
http://ndoc.sourceforge.net/ 可以得到免费最新版本。
NDoc可能会被转让,所有应该看看另一个工具Sandcastle,Microsoft使用这个工具生成API文档说明,所有其功能也很全面。
但是,Sandcastle是一个内部工具,使用起来不太容易。
这个工具包含几个GUI,大多是用户创建的。
☆web站点:
http://blogs.msdn.com/sandcastle上包含更多信息
http://www.codeplex.com/shfb 可以下载Sandcastl的流行GUI
http://www.microsoft.com/Downloads/details.aspx?FamilyID=e82ea71d-da89-42ee-a715-696e3a4873b2&displaylang=en 程序下载
________________
网络
9.联网概述
联网就是与其他系统上的应用程序通信。通信是通过发送消息来实现的。消息可以发给单个系统,但在发送消息之前,要先建立连接。
消息也可以通过广播(broadcast)发送给多个系统,使用广播时,不需要建立连接,而是把消息发送给网络。
OSI 7层模型
应用层,显示层,会话层,传输层,网络层,数据链路层,物理层
数据链路层要进行错误更正,流控制和硬件寻址。
网络层使用逻辑地址查找WAN中的系统。
Internet Protocol(IP)是第3层上的协议,在第3层上,使用IP地址寻址其他系统。 IPV4上,地址包含4个字节。
传输层用于表示通信的应用程序。应用搞程序可以用端点来标识。等待客户机连接的服务器应用程序有一个已知的端点用于连接。
传输控制协议(TCP)和用户数据报协议(UDP)都是第4层上的协议,使用端口号(端点)来标识应用程序。
TCP协议用于可靠通信,连接时在数据发送之前建立的。
会话层定义了应用程序的服务,会话层支持应用程序之间的虚拟连接。
显示层考虑的是数据的格式,在这个层中,可以对数据进行加密,解密和压缩。
应用层为应用程序提供联网功能,例如,文件传输、电子邮件、web浏览等。
在TCP/IP协议组中,应用级协议覆盖了OSI层模型的第4~7层。该协议组中有HTTP(超文本传输协议),FTP(文件传输协议)和 SMTP(简单邮件传输协议)。
在传输层,端点用于到达其他应用程序,这些应用协议定义了发送到其他系统的数据的外观。
10.名称的解析
IPv6中,IP地址有128位组成,而不是32位(IPv4)。
为了把主机名映射为IP地址,需要使用Domain Name System(DNS)服务器。
☆Windows有一个命令行工具 nslookup,可以查找名称(从主机名种解析出IP地址,或者从IP地址中解析出主机名)。
通过System.Net名称空间中的DNS类来解析名称。利用DNS类可以把主机名解析为IP地址,或把IP地址解析为主机名。
例:
using System.Net;
.....
static void Main(string[] args)
{
if(args.Length !=1)
{
Console.WriteLine("Usage:DnsLookup hostname/IP Address");
return;
}
IPHostEntry ipHostEntry = Dns.GetHostEntry(args[0]);
Console.WriteLine("Host:{0} ", ipHostEntry.HostName);
if(ipHostEntry.Aliases.Length»0)
{
Console.WriteLine("/nAliases:");
foreach(string alias in ipHostEntry.Aliases)
{
Console.WrtieLine(alias);
}
}
Console.WriteLine("/nAddress(es):");
foreach(IPAddress address in ipHostEntry.AddressList)
Console.WriteLine("Address:{0}", address.ToString());
}
GetHostEntry()方法,给它传主机名,该方法就会返回一个IPHostEntry。
GetHostByAddress()方法,给它传一个IP地址,该方法返回一个IPHostEntry。
Dns类总是返回一个IPHostEntry。这个类包含主机的信息。
IPHostEntry包含3个属性:
HostName返回主机名,
Aliases返回所有别名的列表,
AddressList返回IPAddress元素数组。
IPAddress类的ToString()方法以标准记号法返回Internet地址。
11. 统一资源表示符
URI(Uniform Resource Identifier,统一资源标识符)
类Uri封装了URI,它的属性和方法用于解析、比较和合并URI。
把一个URI字符串传递给构造函数,就可以创建一个Uri对象:
Uri uri = new Uri("http://www.areslab.com/dotnet");;
如果一个站点上 有许多不同的页面,就可以使用一个基本URI,根据它构造包含目录的URI:
Uri baseUri = new Uri("http://msdn.microsoft.com");
Uri uri = new Uri(baseUri, "downloads");
使用Uri类的属性可以访问URI的各个部分。
例:
设 URI 为 http://www.areslab.com/marketbasket.cgi?isbn=0470124725
Uri属性 结果
Scheme http
Host www.areslab.com
Port 80
LocalPath /marketbasket.cgi
Query ?isbn=0470124725
12.TCP 和UDP
这两个协议都是用端口号标识要接收数据的应用程序。
TCP协议时面向连接的,而UDP协议时无连接的
TCP协议需要服务器应用程序用一只的端口号创建一个套接字,这样客户应用程序才能连接服务器。
客户程序用任意端口号创建一个套接字。
1)在客户机连接服务器时,客户机会把他的端口号发送给服务器,这样服务器才能知道到达客户机的路径;
2)在建立好连接后,客户机就可以发送由服务器接收的数据,接着服务器再发送一些客户机接收的数据。
发送和接收也可以使用其他方式,通过QOTD(qutoe of the day)协议(QOTD服务器是Windows组件Simple TCP/IP Service的一部分)
服务器将只在客户机打开连接后才发送数据,然后关闭连接。
在UDP协议中,服务器也必须使用已知的端口号创建一个套接字,客户机也要使用任意的端口号。
UDP和TCP的区别是,客户机不必启动连接,而可以在未建立连接的情况下发送数据。
没有连接,就不能保证服务器能接收到数据,但整体效果是传输比较快。
UDP协议有一大优点:可以进行广播——使用一个广播地址把信息发送给LAN中的所有系统。
广播地址是一个IP地址,但IP地址中主机部分的所有位置都设置为1.
13.应用协议
使用TCP 和 UDP的应用协议。
HTTP就是一个以TCP为基础的应用协议。
使用HTTP协议请求Web服务器上的数据时,要打开一个TCP连接,再发送HTTP请求。
例:
浏览器发出的HTTP请求如下所示:
GET /default.aspx HTTP/1.1 //GET命令, 定义HTTP版本
Accept:image/gig, image/x-xbitmap,image/jpeg,image/pjpeg, //以下是Http标题信息
application/x-ms-application,application/vnd.ms-xpsdocument, //支持的程序类型
application/xaml+xml,application/x-xbap,application/x-shockwave-flash,
*/*
Accept-Language:da-AT,en-us;q=0.7 //Brower使用的语言
Accept-Encoding:gzip,deflate
User-Agent:Mozilla/4.0 (compatible; MSIE 7.0;Windwos NT 6.0;.NET CLR
2.0.50727;Media Center PC 5.0; Tablet PC 2.0; .NET CLR 1.1.14322; .NET CLR
3.5.520706; .NET CLR 3.0.590)
Host:localhost:80
Connection:Keep-Alive //在Http1.1中可使用同一个连接
最常用的HTTP命令有GET,POST 和 HEAD。
GET用于从服务器上请求文件,POST命令也用于从服务器上请求文件,但与GET命令不同的是,POST命令还在HTTP标题之后发送给其他数据。
使用HEAD命令,服务器只返回文件的标题信息,让客户机确定该文件是否不同于高速缓存中已有的数据。
服务器接收到GET请求后,就返回一个响应消息:
HTTP/1.1 200 OK
Server:Microsoft-IIS/7.0
Date:Sun,29 Jul 2007 20:14:59 GMT
X-Powered-By:ASP.NET
X-AspNet-Version:2.0.50727
Cache-Control:private
Content-Type:text/html; charset=utf-8
Content-Length:991
«!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"»
«HTML»
«HEAD»
«title»Demo«/title»
.............
14.网络编程选项
System.Net 和 System.Net.Sockets名称空间提供了一个网络编程的几个选项。
进行网络编程最简单的方式是使用WebClient类。
使用这个类的方法,可以从Web服务器上获取文件,或者把文件传输给FTP服务器。
在.NET3.5中,只能通过HTTP和FTP协议使用它访问文件。
当然,也可以插入定制类,来支持其他协议。
Webclient类利用了WebRequest 和 WebResponse类。
要创建服务器,就不能用WebClient 或 WebRequest,必须使用System.Net.Socket名称空间中的TcpListener类。
TcpListener类可以用于为TCP协议创建一个服务器,而TcpClient类用于编写客户应用程序。
有了这些类,就不会被限制在HTTP和TCP协议上,而可以使用任意基于TCP的协议。
如果要使用UDP协议,就应使用UdpListener和UdpClient类(它们类似于TcpListener和TcpClient类)来编写UDP服务程序和客户程序。
如果要独立于协议,或需要TCP和UDP协议进行更多的控制,就可以用.NET进行套接字编程。用于套接字编程的类位于System.Net.Socket名称空间中。
15.WebClient
WebClient类的主要属性和方法。
WebClient类的方法用于上载 和 下载文件。
WebClient属性:
BaseAddress:属性BaseAddress定义了WebClient发出的请求的基地址
Credentials:使用Credentials属性可以传送身份验证信息,例:使用NetWorkCredential类来传送
UseDefaultCredentials:如果应使用当前登录的用户凭证,就把UseDefaultCredentials属性设置true
Encoding:Encoding属性用于把字符串的编码设置为上载 和 下载
Headers:Headers属性用于定义WebHeaderCollection,其中包含所用协议的特定标题信息
Proxy:默认使用通过Internet Exploer配置的代理程序访问Internet。如果需要其他代理程序,就可以用Proxy属性定义一个WebProxy对象
QueryString:如果需要将查询字符串发送给Web服务器,就可以用QueryString属性设置NameValueCollection
ResponseHeaders:发送请求之后,就可以在与ResponseHeaders类关联的WebHeaderCollection类中阅读响应的标题信息
WebClient方法:
DownloadData():通过DownloadData()方法,可以给Web地址传送一个放在BaseAddress后面的字符串和一个包含从服务器返回的数据字节数组
DownloadString():DownloadString()类似于DownloadData(),唯一的区别是返回的数据存储在字符串
DownloadFile():如果服务器返回数据应存储在文件夹中,就应使用DownloadFile()方法。这个方法的参数是Web地址和文件名
OpenRead():使用OpenRead()方法,除了下载文件之外,还可以从服务器上下载数据,下载的数据可以从这个方法返回的流中读取
UploadFile():要给FTP或Web服务器上传文件,可以使用UploadFile()方法。这个方法也允许传送用于上传的HTTP方法
UploadData():UploadFile()允许从本地文件系统中上传文件,而UploadDatat()把字节数组发送给服务器
UploadString():使用UploadString()可以把字符串上传给服务器
UploadValues():使用UploadValues()可以把Name ValueCollection上传给服务器
OpenWrite():该方法使用流把内容发送给服务器。
例:
使用WebClient类
static void Main()
{
① WebClient client = new WebClient();
② client.BaseAddress = "http://www.areslab.com";
③ string data = client.DownloadString("Office");
Console.WriteLine(data);
Console.ReadLine();
}
16. WebRequest 和 WebResponse
除了WebClient类之外,还可以使用WebRequest类与Web或FTP服务器通信,读取文件。
WebClient类在后台也使用WebRequest。
WebRequest类总是和WebResponse类都是抽象类,因此不能直接使用。
这些类都是基类,名称空间System.Net为HTTP,FTP和文件协议提供了具体的实现方式。
这些基类的具体实现方式是:
HttpWebRequest、HttpWebResponse、FtpWebResponse、FtpWebRequest、FileWebRequest和FileWebResponse。
HTTP协议由HttpWebXXX类使用,
FtpWebXXX类使用FTP协议,
FileWebXXX类允许用户访问文件系统。
例:
从FTP服务器上下载文件
首先,创建一个新的WPF应用程序FtpClient;
然后,在后台代码中,导入System.Net, System.IO 和 Microsoft.Win32
① 建立与服务器的初始化连接
private void buttonOpen_Click(object sender, RouterEventArgs e)
{
Cursor currentCursor = this.Cursor;
FtpWebResponse response = null;
Stream stream = null;
try
{
this.Cursor = Cursors.Wait;
//Create the FtpWebRequest object
① FtpWebRequest request = (FtpWebRequest)WebRequest.Create(textServer.Text);
request.Credentials = new NetWorkCredential(textUsername.Text, passwordBox.password);
request.Method = WebRequestMethods.Ftp.ListDirectory;
//send the request and fill the list box
② response = (FtpWebResponse)request.GetResponse();
//Read the response and fill the list box
③ stream = response.GetResponseStream();
FillDirectoryList(stream); //填充listbox的方法
....
}
catch(..)
....
}
②打开服务器上的特定目录
private void buttonOpenDirectory_Click(object sender, RouteEventArgs e)
{
Cursor currentCursor = this.Cursor;
FtpWebResponse response = null;
Steam stream = null;
try
{
this.Cursor = Cursor.Wait; //鼠标形状
string subDirectory = ...........;
① Uri baseUri = new Uri(textServer.Text);
Uri uri = new Uri(baseUri, serverDirectory);
② FtpWebRequest request = (FtpWebRequest)WebRequest.Create(uri);
request.Credentials = new NetworkCredential(textUsername.Text, passwordBox.password);
request.Method = WebRequestMethods.Ftp.ListDirectory;
response = (FtpWebResponse)request.GetResponse();
③ stream = response.GetResponseStream();
FillDirectoryList(stream);
}
catch(Exceptiron ex)
{
MessageBox......;
}
finally
{
if(response != null)
response.Close();
if(stream != null)
stream.Close();
this.Cursor = currentCursor; //鼠标形状
}
}
③ 从服务器上下载文件
private void ButtonGetFile_click(object sender , RoutedEventArgs e)
{
Cursor currentCursor = this.Cursor;
FtpWebResponse response = null;
Steam inStream = null;
Steam outStream = null;
try
{
this.Cursor = Cursor.Wait;
① Uri baseUri = new Uri(textServer.Text);
string filename = list......;
string fullFilname = .......;
Uri uri = new Uri(baseUri, fullFilename);
② FtpWebRequest request = (FtpWebRequest)WebRequest.Create(uri);
request.Credentials = new NetworkCredential(textUsername.Text, txtPasswordBox.Text);
request.Method = WebRequestMethods.Ftp.DownloadFile;
request.UseBinary = checkBoxBinary.Checked??false;
response = (FtpWebResponse)request.GetResponse();
③ inStream = response.GetResponseStream();
SaveFileDialog saveFileDialog = new SaveFileDialog();
saveFileDialog.FileName = filename;
if(saveFileDialog1.ShowDialog()==true)
{
④ outStream = File.OpenWrite(saveFileDialog.FileName);
byte[] buffer = new byte[8192];
int size = 0;
while ((size = inStream.Read(buffer, 0, 8192))»0 ) //从FTP上返回的流在While中读取,并写入本地文件流
{
outStream.Write(buffer,0,size);
}
}
}
catch.....
......
}
示例说明:
在给FTP服务器发送请求时,
①必须创建一个FtpWebRequest对象。
FtpWebRequest对象由构造方法WebRequest.Create()创建,
这个方法根据传送给它的URL字符串,创建FtpWebRequest、HttpWebRequest 或 FileWebRequest对象。
FtpWebRequest request = (FtpWebReuqest)WebRequest.Create(textSever.Text);
②通过设置其他属性来配置它。
Credentials属性允许设置用于访问服务器的用户名和密码。
③Method属性定义了要发送给服务器的FTP请求。
FTP协议,FTP协议允许使用的命令非常类似于HTTP协议命令。HTTP协议使用GET、HEAD和POST命令。
FTP协议命令在RFC959(www.ierf.org/rfc/rfc0959.txt)中定义,RETR用于文件下载,STOR用于上传文件,MKD用于创建目录,LIST用于从目录中获取文件。
在.NET中,不需要了解所有命令,因为WebRequestMethods类定义了这些命令。
WebRequestMethods.Ftp.ListDirectory创建一个要发送给服务器的LIST请求,
WebRequestMethods.Ftp.DownloadFiles用于从服务器上下载文件。
例:
request.Credentials = new NetWorkCredential(textUsername.Text,passwordBox.password);
request.Method = WebRequestMethods.Ftp.ListDirectory;
在定义请求时,可以把请求发给服务器。GetResponse()方法会把请求发给服务器,并等待直到接受响应。
response = (FtpWebResponse)request.GetResponse();
为了访问响应中的数据,GetResponseStream()方法返回一个流。
为了响应WebRequestMethods.Ftp.ListDirectory请求,该响应流包含了所请求服务器目录中的所有文件名。
在WebRequestMethods.Ftp.DownloadFile请求之后,响应会包含所请求文件的内容。
stream = response.GetResponseStream();
________________
17.TcpListener 和 TcpClient
System.Net.Sockets名称空间中的类为网络编程提供了更多控制。
TcpListener类可以在服务器上使用。
可以用该类的构造函数定义服务器监听的端口号,Start()方法会启动监听。
但,为了与客户机通信,服务器必须调用AcceptTcpClient()方法,这个方法在建立与客户机的连接之前是禁用的。
其返回值是一个TcpClient对象,其中包含与客户机的连接信息。
使用这个TcpClient对象,服务器可以接收客户机发送的数据,并把数据发送回客户机。
客户机也使用TcpClient类。
客户机可以用Connect()方法启动与服务器的连接,之后使用与TcpClient对象相关的流发送给接收数据。
例:
服务器给客户机发送一组图片文件,客户机从其中选择一个文件,向服务器请求这个文件。
在这个应用程序中,客户机可以发出两个请求:LIST请求返回文件列表,PICTURE:filename请求以字节流的形式返回图片。
创建TCP服务器:
①创建一个控制台程序。
②创建默认设置文件。
选择项目属性。打开Setting选项卡。单击This Project Does Not Contain A Default Setting File. Click Here To Create One.连接,创建一个默认设置文件。
③在Settings.settings文件中,添加一个字符串类型的PictureDirectory属性,和一个int类型的Port属性。
PictureDirectory的value设置为一个目录,比如:C:/PICTURES
Port设置为8888,定义服务器的端口号。
④添加一个帮助类PictureHelper。
添加几个静态方法:
GetFileList()返回所有文件的列表;
GetFileListBytes()方法在字节数组中返回文件列表;
GetPictureBytes()方法返回图片文件的字节数组
这个类使用文件I/O获取文件名并读取文件。
例:
using System;
.....
using System.IO;
using System.Text;
namespace PictureServer
{
internal static class PictureHelper
{
internal static IEnumerable«string» GetFileList()
{
//Remove the directory path from the filename
return form file in Directory.GetFiles(Properties.Settings.Default.PictureDirectory)
select Path.GetFileName(file);
}
}
internal static byte[] GetFileListBytes()
{
try
{
//LIST request - return list
IEnumerable«string» files = PictureHelper.GetFileList();
StringBuilder responseMessage = new StringBuilder();
foreach(string s in files)
{
responseMessage.Append(s);
responseMessage.Append(":");
}
return Encoding.ASCII.GetBytes(responseMessage.ToString());
}
catch(DirectoryNotFoundException ex)
{
Console.WriteLine(ex.Message);
throw;
}
}
}
⑤在Program.cs文件中导入System.Net、System.Net.Sockets和System.IO名称空间
⑥在服务器应用程序的Main()方法中添加如下代码:
class Program
{
static void Mian()
{
// 要创建一个服务器应用程序,以使用TCP协议等待客户机的连接,可以使用TcpListener类。
//要创建TcpListener对象,必须为服务器指定端口号,
//这里使用Properties.Settings.Default.Port从配置文件中读取端口号,
① TcpListener listener = new TcpListener(IPAddress.Any, Properties.Settings.Default.Port);
//再调用Start()方法监听传入的请求。
② listener.Start();
Console.WriteLine("Server running...");
while(true)
{
const int bufferSize = 8912;
//启动监听器后,服务器就在AcceptTcpClient()方法中等待客户机与它连接。
//与客户机的连接在AcceptTcpClient()方法返回的TcpClient对象中定义。
③ TcpClient client = listener.AcceptTcpClient();
//启动连接后,客户机就给服务器发送一个请求。
//该请求在流中读取,client.GetStream()返回一个NetworkStream对象,这个网络流中的数据被读入到字节数组缓存中,
//再转换为一个字符串,存储在变量request中
④ NetworkStream clientStream = client.GetStream();
byte[] buffer = new byte[bufferSize];
int readBytes = 0;
⑤ readBytes = clientStream.Read(buffer,0,bufferSize);
string request = Encoding.ASCII.GetString(buffer).Substring(0,readBytes);
//根据请求字符串,把一个图片文件列表或图片字节返回给客户机。请求字符串使用String.StartWith进行检查
if(request.StartsWith("LIST",StringComparison.Ordinal))
{
//LIST request - return list
//服务器把返回的数据写入网络流,将数据发送回客户机:
byte[] responseBuffer = PictureHelper.GetFileListBytes();
clientStream.Write(responseBuffer,0,responseBuffer.Length); //发送
}
else if(request.StartsWith("FILE",StringComparison.Ordinal))
{
//FILE request - return file
//get the filename
string[] requestMessage = request.Split(':');
string filename = requestMessge[1];
byte[] data = File.ReadAllByte(Path.Combine(Properties.Settings.Default.PictureDirectory,filename));
//Send the picture to the client.
clientStream.Write(data,0,data.Length); //发送
}
clientStream.Close();
}
}
}
创建TCP客户程序:
客户应用程序时一个Windows窗体应用程序,显示服务器上可用的图片文件,用户选择一个图片后,该图片就会显示在客户应用程序中。
①创建一个WPF项目。
②创建一个WPF窗口PictureClientWindow.xaml。
③将服务器名称和端口添加到程序属性设置中
服务器名的属性名是Server
服务器端口号属性名是SeverPort。
④设计UI
⑤在PictureClientWindow.xaml.cs文件中导入System.Net, System.Net.Sockets和System.IO名称空间
⑥在PictureClientWindow类中添加bufferSizge常量:
public partial class PictureClientWindow:window
{
pivate const int bufferSizge = 8192;
.......
}
⑦添加帮助方法ConnectToServer()及其执行代码:
TcpClient类通过客户机连接服务器。
private TcpClient ConnectToServer()
{
// Connect to the Server
① TcpClient client = new TcpClient();
② IPHostEntry host = Dns.GetHostEntry(Properties.Settings.Default.Server);
③ var addresss = (from h in host.AddressList
where h.AddressFamily == AddressFamily.InterNetWork
select h). First(); //pick the first IP
④ client.Connect(address.ToString(),Properties.Settings.Default.ServerPort); //获取端口号
return client;
}
⑧给buttonListPictures按钮添加一个Click事件处理程序
private void buttonListPictureList(object sender, RouteEventArgs e)
{
TcpClient client = ConnectToServer();
//Send a request to the server
//NetworkStream可以把数据发送给服务器。
NetworkStream clientStream = client.GetStream();
string request = "LIST";
byte[] requestBuffer = Encoding.ASCII.GetBytes(request); //Encoding类把字符串LIST转换为字节数组。
clientStream.Write(requestBuffer,0,requestBuffer.Length);
//Read the response from the server
byte[] responseBuffer = new byte[bufferSize];
MemoryStream memStream = new MemoryStream();
int bytesRead = 0;
do
{
bytesRead = clientStream.Read(resposneBuffer,0,bufferSize); //从服务器返回的数据用clientStream对象的Read()方法读取。
memStream.Write(responseBuffer,0,bytesRead); //接收,写入内存
} while(bytesRead»0);
clientStream.Close();
client.Close();
byte[] buffer = memStream.GetBuffer();
string response = Encoding.ASCII.GetString(buffer);
this.DataContext = response.Split(':');
}
⑨ 给buttonGetPicture按钮添加一个Click事件处理程序:
private void buttonGetPicture_Click(object sender, RouteEventArgs e)
{
TcpClient client = ConnectTtoServer();
NetworkStream clientStream = client.GetStream();
string request = "FILE:"+this.listFiles.SelectedItem.ToString();
byte[] requestBuffer = Encoding.ASCII.GetBytes(request);
clientStream.Write(requestBuffer,0,requestBuffer.Length);
byte[] responseBuffer = new byte[bufferSize];
MemoryStream memStream = new MemoryStream();
int bytesRead=0;
do
{
bytesRead = clientStream.Read(responseBuffer,0,bufferSize);
//写入内存
memStream.Write(responseBuffer,0,byteRead);
} while(bytesRead»0);
clientStream.Close();
client.Close();
//图片的读取方式与文件数据相同。内存流使用WPF的BitmapImage类转换为Image。
BitmapImage bitmapImage = new BitmapImage();
//从内存中读取图片
memStream.Seek(0,SeekOrign.Begin);
bitmapImage.BeginInit();
bitmapImage.StreamSource = memStream;
bitmapImage.EndInit();
pictureBox.Source = bitmapImage; //把图像赋予PictureBox的Image属性,使之显示在窗体上。
}
________________
GDI+简介
18.图形绘制概述
当创建一个窗口,并在该窗口进行绘图时,一般要声明一个派生于System.Windows.Forms.Form的类。
如果要编写一个定制控件,就要声明一个派生于System.Windows.Forms.UserControl的类。
在这两种情况下,都重写了虚拟函数OnPaint()。只要窗口的任何一部分需要重绘,Windows都会调用这个函数。
在这个事件中,PaintEventArgs类作为参数被传递。
PaintEventArgs中有两个重要信息:Graphics对象 和 ClipRectangle对象。
Graphics类
Graphics类封装了一个GDI+绘图接口。
有3种基本类型的绘图接口:
* 屏幕上的窗口和控件
* 要发送给打印机的页面
* 内存中的位图和图像
Graphics类提供了在这些绘图界面上绘图的功能。
在其他功能中,我们可以使用它绘制圆弧、曲线、Bezier曲线、椭圆、图像、线条、矩形和文本。
给窗口获得Graphics对象有两种不同的方式:
① 首先是重写OnPaint()事件,Form类从Control类中集成了OnPaint()方法。
这个方法是Paint事件的处理程序,在重新绘制控件时,就触发这个事件。
从利用该事件传入的PaintEventArgs中获取Graphics对象:
protected override void OnPaint(PaintEventArgs e)
{
Graphics g = e.Graphics;
//do our drawing here
}
② 有时,需要直接在窗口中绘图,而无需等OnPaint()事件。比如,拖动一些对象。
在窗体上调用CreateGraphics()方法就可以获得一个Graphics对象,
这是Form类继承自Control类的另一个方法:
protected void Form1_Click(object sender, System.EventArgs e)
{
Graphics g = this.CreateGraphics();
//Do our drawing here
g.Dispose(); //this is important
}
对象的删除
有几种数据类型对于调用Dispose()是非常重要的,否则一些资源就不能释放。
这些类执行IDisposable接口。Graphics是这些类中的一个。
如果是从OnPaint()事件中获得的Graphics对象,而不是创建了该对象,就不应该调用Dispose(),
但如果调用了CreateGraphics(),就必须调用Dispose()。
对于实现IDisposeable的许多类来说,Dispose()方法会在析构函数中自动调用。
但是,垃圾回收器(GC)不确定何时会运行。内存用尽后会触发GC,但资源用尽后不会触发GC。
Windows 2000和XP 对资源用尽的情况不是特别敏感。
根据规范,这两个操作系统对这些类型的资源没有任何限制,但当打开了太多的应用程序时,Windows 2000就会出错。
另一种简单的方式是,使用Using结构。
在对象超出作用域时,Using结构会自动调用Dispose()。
Using (Graphics g = this.CreateGraphics())
{
g.DrawLine(Pens.Black, new Point(0,0), new Point(3,5));
}
上面的代码等于:
Graphics g = this.CreateGraphics();
try
{
g.DrawLine(Pens.Black, new Point(0,0) , new Point(3,5));
}
finally
{
if(g != null)
((IDisposeable)g).Dispose();
}
坐标系统
在绘图时,常常用3中结构指定坐标:Point、Size和Rectangle
①Point
GDI+函数,例如DrawLine(),都把Point作为其参数。
声明和构造Point的代码如下:
Point p = new Point(1,1);
②Size
Size结构包含宽度和高度。
声明和构造Size的代码如下:
Size s = new Size(5,5);
Height 和 Width可以获得 和 设置 Size的宽度 和 高度。
③Rectangle
Rectangle有两个构造函数。
一个构造函数的参数是X坐标、Y坐标、宽度和高度;
另一个构造函数的参数是Point和Size结构。
声明和构造Rectangle的两个示例如下所示:
例1:
Rectangle r1 = new Rectangle(1,2,5,6);
例2:
Point p = new Point(1,2);
Size s = new Size(5,6);
Rectangle r2 = new Rectangle(p,s);
GraphicsPaths
GraphicsPaths类表示一系列连接的线条和曲线。
在构造一条路径时,可以添加线条、Bezier曲线、圆弧、饼形图、多边形和矩形等。
在构造一条复杂的路径后,可以用一个操作绘制路径:
调用DrawPath();
可以用FillPath()填充路径。
可以使用一个点数组 和 PathTypes构造GraphicsPath,
PathTypes是一个字节数组,其中的每个元素对应于点数组中的每一个元素,
并给出了路径如何通过这些点来构造的其他信息。通过点的信息可以使用PathPointType枚举来获得。
如果点式路径的起始点,那么这个点的路径类型就是PathPointType.Start;
如果点是两个线条的连接点,那么这个店的路径类型就是PathPointType.Line;
如果点用于购置一条从前一点到厚一点之间的Bezier曲线,路径类型就是PathPointType.Bezier。
例:
创建图形路径
using System;
.......
using System.Drawing.Drawing2D;
protected override void OnPaint(PaintEventArgs e)
{
GraphicsPath path;
path = new GraphicsPath(new Point[]{ new Point(10,10),
new Point(150,10),
new Point(200,150),
new Point(10,150),
new Point(200,160)},
new byte[] { new PathPointType.Start,
new PathPointType.Line,
new PathPointType.Line,
new PathPointType.Line,
new PathPointType.Line,});
e.Graphics.DrawPath(Pens.Black, path);
}
Regions
Region类是一个复杂的图形,由矩形和路径组成。
在构造了一个Region后,就可以使用FillRegion()方法绘制该区域。
创建区域:
①创建Windows应用程序
②添加using System.Drawing.Drawing2D;
③在Form1的主体中添加如下代码:
protected override void OnPaint(PaintEventArgs e)
{
Rectangle r1 = new Rectangle(10,10,50,50);
Rectangle r2 = new Rectangle(40,40,50,50);
Region r = new Region(r1);
r.Union(r2);
GraphicsPath path = new GraphicsPath(new Point[] {
new Point(45,45),
new Point(145,55),
new Point(200,150),
new Point(75,150),
new Point(45,45)},
new byte[] {
(byte)PathPointType.Start,
(byte)PathPointType.Bezier,
(byte)PathPointType.Bezier,
(byte)PathPointType.Bezier,
(byte)PathPointType.Line
});
r.Union(path); //如果决定使矩形和路径相交,就可以用Inersection()方法替代Union方法。
e.Graphics.FillRegion(Brushes.Blue,r);
}
使用Pen类绘制线条
该类定义了代码绘图时的颜色、线宽和线条的样式。样式表示该线条是实线,还是短线和点组成。
Pen类位于System.Drawing名称空间中。
例:
①创建Windows应用程序
②在From的主体中添加代码:
protected override void OnPaint(PaintEventArgs e)
{
Graphics g = e.Graphics; //因为传送了Graphics对象的引用,而没有创建它,所以不需要Dispose()
//因为使用了占用资源非常大的Pen对象,所以要把其余代码封装到using块中
//Pen对象应总是调用Dispose()
using(Pen blackPen = new Pen(Color.Black,1))
{
//每个窗口都有一个客户区域,定义了可以绘图的区域。
//可以利用ClientRectangle获取客户区域,这是一个只读公共属性(继承于Control)
if(ClientRectangle.Height/10»0)
for(int y=0;y«ClientRectangle.Height;y+=ClientRectangle.Height/10)
{
//在Pens类中获得该Pen
g.DrawLine(blackPen,new Point(0,0), new Point(ClientRectangle.Wdith,y));
}
}
}
Pen的功能:
可以创建一个绘制虚线的钢笔;也可以创建线宽比1个像素宽的钢笔。
Pen类有一个属性Alignment,该属性可以定义钢笔是否从左向右(或从上到下)绘制指定的线条。
设置StartCap和EndCap属性可以指定线条的末端箭头、菱形、方框或圆形。
CustomStartCap和CustomEndCap属性编写定制的起始端和终止端形状。
何使用Pen指定Brush,通过位图来绘制图像,而不是一种单色。
________________
19.使用Brush类绘制图形
Brush是一个抽象类,要实例化一个Brush对象,应使用派生于Brush的类,例如SolidBrush、TextureBrush和LinearGradientBrush。
Brush和SolidBrush类在System.Drawing名称空间,但TextureBrush和LinearGradientBrush类在System.Drawing.Drawing2D名称空间中。
* SolidBrush用一种单色填充图形
* TextureBrush用一个位图填充图形
* 在构造这个画笔时,还指定了边框矩形和填充模式
* 边框矩形指定画笔使用位图的哪一个部分——可以不用整个位图
* LinearGradientBrush封装了一个画笔,可以绘制两种颜色渐变的图形
* 其中第一种颜色以指定的角度逐渐过渡到第二种颜色
* 角度的单位是度。0°表示颜色从左向右过渡。90°表示颜色从上到下过渡
对于Brush对象总是要使用Dispose()
例:
①创建一个windows程序
②添加System.Drawing.Drawing2D名称空间
③在Form类的构造函数中,在InitializeComponent()调用的后面添加对SetStyle()的调用。
public Form1()
{
InitializeComponent();
//SetStyle()它是Form类的一个方法。
//下面的代码修改了Form类的作用,使之不能自动绘制窗口的背景。
SetStyle(ControlStyles.Opaque, true);
}
④给类添加一个OnPaint()方法
与Pens类一样,Brushes类包含的属性可以创建大约150种画笔。
protected override void OnPaint(PaintEventArgs e)
{
Graphics g = e.Graphics;
//调用FillRectangle填充客户区域的背景
g.FillRectangle(Brushes.White, ClientRectangle);
g.FillRectangle(Brushes.Red, new Rectangle(10,10,50,50));
//创建LinearGradientBrush要使用一个矩形指定矩形的大小,用两种颜色进行渐变,再制定角度
Brush linearGradientBrush = new LinearGradientBrush(new Rectangle(10,60,50,50), Color.Blue, Color.White, 45);
g.FillRectangle(linearGradientBrush, new Rectangle(10,60,50,50));
//Manually call Dispose()
linearGradientBrush.Dispose();
g.FillEllipse(Brushes.Aquamarine, new Rectangle(60,20,50,30));
g.FillPie(Brushes.Chartreuse, new Rectangle(60,60,50,50),90,210);
g.FillPolygon(Brushes.BlueViolet, new Point[])
}
________________
20.使用Font类绘制文本
Font类封装了字体的3个主要特性:字体系列、字体大小和字体样式。Font类在System.Drawing名称空间中。
字体的Size属性表示字体类型的大小。
它可以是点的大小,通过Unit属性可以改变GraphicsUint属性,Unit定义了字体的测量单位。
使用Graphics对象的MeasureString()方法可以计算出给定字体的字符串宽度。
字体的Style属性表示该类型是否为斜体、黑体、删除线 或 下划线。
对于Font对象总是要调用Dispose().
在绘制文本时,使用一个Rectangle指定要绘制文本的边框坐标。
一般情况下,这个矩形的高度应是字体的高度或字体高度的倍数。只有在使用剪切文本绘制某些特殊效果的文本时,高度才有可能是其他值。
StringFormat类封装了文本布局信息,包括对齐和行间距信息。
例:
①创建windows应用程序
②From1类的构造函数中:
public From1()
{
InitializeComponent();
SetStyle(ControlStyles.Opaque,true);
Bounds = new Rectangle(0,0,500,300);
}
③给类添加一个OnPaint()方法:
protected override void OnPaint(PaintEventArgs e)
{
Graphics g = e.Graphics;
int y = 0;
g.FillRectangle(Brushes.White, ClientRectangle);
//Draw left-justified text.
Rectangle rect = new Rectangle(0,y,400,Font.Height);
g.DrawRectangle(Pens.Blue, rect);
g.DrawString("This text is left justified.", Font, Brushes.Black, rect); //Font这个字体继承了Control的Font属性的中指定
y += Font.Height +20;
//Draw right-justified text.
Font aFont = new Font("Arial", 16, FontStyles.Bold|FontStyle.Italic); //创建了Font的一个新实例
rect = new Rectangle(0,y,400,aFont.Height);
g.DrawRectangle(Pens.Blue, rect);
StringFromat sf = new StringFormat(); //创建StringFormat对象,以便绘制右对齐和居中的文本
sf.Alignment = StringAlignment.Far; //Far是指右对齐;Near是指左对齐
g.DrawString("This text is right justified.", aFont, Brushed.Blue, rect, sf);
y += aFont.Height + 20;
//Manually call Dispose()
aFont.Dispose()
//draw centered text.
Font cFont = new Font("Courier New", 12, FontStyle.Underline);
rect = new Rectangle(0,y,400,cFont.Height);
g.DrawRectangle(Pens.Blue, rect);
sf = new StringFormat();
sf.Alignment = StringAlignment.Center;
g.DrawString("This text is centered and underlined.", cFont, Brushes.Red, rect, sf);
y += cFont.Height + 20;
//Manually call Dispose().
cFont.Dispose();
//Draw multiline text .
Font trFont = new Font("Times New Roman", 12);
rect = new Rectangle(0,y,400,trFont.Height*3);
g.DrawRectangle(Pens.Blue, rect);
String longString = "This text is much longer, and drawn";
longString += "into a rectangle that is higher than ";
longString += .......;
g.DrawString(longString, trFont, Brushes.Black, rect);
//Manually call Dispose()
trFont.Dispose();
}
GDI+和字体本身决定基线在什么位置,我们不能控制基线。
________________
21.使用图像进行绘制
可以用图像创建画笔(TextureBrush),再绘制用该图像填充的图形。
还可以从TextureBrush中创建钢笔,使用图像绘制线条。
在绘制文本时,可以提供一个TextureBrush,就可以用图像绘制文本了。
Image类在System.Drawing名称空间中。
Image本身是一个抽象类,它有两个子类:Bitmap和Metafile。
Bitmap类用于一般的图像,有高度和宽度属性。
对于Image对象总是要调用Dispose().
位图有几个来源:
可以从文件中加载位图;
可以从另一个现有的图像中创建位图。
位图可以创建为空白图像,以便在其上绘图。
在从文件中读取图像时,该图像可以是JPEG、GIF或BMP格式。
例:
读取并绘制图像
①创建Windows应用程序
②添加一个Image私有对象,来保存图像
partial class Form1:Form
{
private Image theImage;
}
③ 修改构造函数:
public Form1()
{
InitializeComponent();
SetStyle(ControlStyle.Opaque,true);
//需要把BMP文件Person.bmp放在Debug目录下。才能用下面的语句。
//可以用其他位置的图像,但要该代码。
theImage = new Bitmap("Person.bmp");
}
④ 给类添加OnPaintEvent()方法:
protected override void OnPaint(PaintEventArgs e)
{
Graphics g = e.Graphics;
//在绘制图像时,把Rectangle作为一个参数传递给DrawImage()方法。
//如果图像的大小与传递给DrawImage()的矩形不同,GDI+会自动重新设置图像大小。
//迫使GDI+不重新设置图像大小的一种方式是从Width 和 Height属性中获取图像的大小,再把图像的大小传递给DrawImage()方法
g.DrawImage(theImage, ClientRectangle);
}
⑤ 最后,需要删除存储在类的一个成员变量中的Image。修改Form类的Dispose()方法。
注意,在VS2008中,Dispose()方法在Form1.Designer.cs中。
protected override void Dispose(bool disposing)
{
if(disposing)
{
thdImage.Dispose();
}
if(disposing && (components != null ))
{
components.Dispose();
}
base.Dispose(disposing);
}
使用纹理画笔绘图
下面用图像创建一个TextureBrush,用该画笔执行3种不同的操作:
* 绘制椭圆
* 创建Pen
* 绘制文本
例:
用图像绘制椭圆
①给Form1类添加一个Image变量
partial class Form1:Form
{
private Image theImage;
private Image smallImage;
}
②在窗体构造函数中,从theImage中创建一个smallImage。
public Form1()
{
InitializeComponent();
SetStyle(ControlStyle.Opaque, true);
theImage = new Bitmap("Person.bmp");
smallImage = new Bitmap(theImage, new Size(theImage.Width/2, theImage.Width/2, theImage.Height/2));
}
③用下面的代码替换OnPaint()方法:
protected override void OnPaint(PaintEventArgs e)
{
Graphics g = e.Graphics;
g.FillRectangle(Brushes.White, ClientRectangle);
Brush tBrush = new TextureBrush(smallImage , new Rectangle(0,0,smallImage.Width, smallImage.Height));
//调用Graphics类的FillEllipse()方法,传递新创建的纹理画笔,ClientRectangle在窗口中绘制出椭圆
g.FillEllipse(tBrush, ClientRectangle);
tBrush.Dispose();
}
④最后,删除变量中的两个图像。
protected override void Dispose(bool disposing)
{
if(disposing)
{
theImage.Dispose();
smallImage.Dispose();
}
if(disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
使用钢笔绘制图像
例:
在图像中创建钢笔
修改OnPaint()方法:
protected override void OnPaint(PaintEventArgs e)
{
Graphics g = e.Gragphics;
g.FillRectangle(Brushes.White, ClientRectangle);
Brush tBrush = new TextureBrush(smallImage, new Rectangle(0,0,smallImage.Width, smallImage.Height));
Pen tPen = new Pen(tBrush, 40);
g.DrawRectangle(tPen,0,0, ClientRectangle.Width, ClientRectangle.Height );
tPen.Dispose();
tBrush.Dispose();
}
用图象绘制文本:
① 修改OnPaint()方法:
protected override void OnPaint(PaintEventArgs e)
{
Graphics g = e.Graphics;
g.FillRectangle(Brushes.White, ClientRectangle);
Brush tBrush = new TextureBrush(smallImage, new Rectangle(0,0,smallImage.Width, smallImage.Height));
Font trFont = new Font("Times new Roman", 32, FontStyle.Bold|FontStyle.Italic);
//调用DrawString()方法,参数是文本、字体、纹理画笔 和 边框矩形
g.DrawString("Hello from Beginning Visual C#" , trFont, tBrush, ClientRectangle);
tBrush.Dispose();
trFont.Dispose();
}
双倍缓冲
例:
①不使用双倍缓冲技术的程序
创建windows应用程序,添加OnPaint()方法:
protected override void OnPaint(PaintEventArgs e)
{
Graphics g = e.Graphics;
Random r = new Random();
g.FillRectangle(Brushes.White, ClientRectangle);
for(int x=0 ; x«ClientRectangle.Width ; x++)
{
for(int y=0; y«ClientRectangle.Height; y += 10)
{
Color C = Color.FromArgb(r.Next(255), r.Next(255), r.Next(255));
using (Pen p = new Pen(c,1))
{
g.DrawLine(p,new Point(0,0), new Point(x,y));
}
}
}
}
②使用双倍缓冲
protected override void OnPaint(PaintEventArgs e)
{
Graphics displayGraphics = e.Graphics;
Random r = new Random();
//在双倍缓冲的代码中,创建高度和宽度与ClientRectangle相同的新图像
Image i = new Bitmap(ClientRectangle.Width, ClientRectangle.Height);
//再使用下面的代码从图像中获取一个Graphics对象
Graphics g = Graphics.FromImage(i);
g.FillRectangle(Brush.White, ClientRectangle);
for(int x=0; x«ClientRectangle.Width; x++)
{
for(int y=0; y«ClientRectangle.Height; y+=10)
{
// 静态方法FromArgb(), 从3个指定的整数值中创建一个Color结构,对应于颜色的红、绿和蓝色部分。
Color c = Color.FromArgb(r.Next(255), r.Next(255), r.Next(255));
Pen p = new Pen(c,1);
g.DrawLine(p, new Point(0,0), new Point(x,y));
p.Dispose();
}
}
//把图像绘制到窗口中
displayGraphics.DrawImage(i, ClientRectangle);
i.Dispose();
}
所有的图像操作与前面的代码一样,但它们现在是绘制在图像中,而不是直接绘制在窗口中。
________________
22. GDI+高级功能介绍
在调用OnPaint()方法时,除了Graphics对象外,该方法传递了一个剪切矩形。
如果实现一个非常精细的、需要很长时间的绘图历程,在绘图前测试一下这个剪切矩形,就可以减少绘图时间。
我们需要绘制任何图形的边框矩形。如果这个边框矩形与剪切不相交,就可以跳过该绘图操作。
有时,在绘制时,如果需要绘制一个图形的一部分,可以先绘制整个图形,再把想要的部分剪切下来比较方便。
Graphics类的Clip属性的更多信息参见.net文档
System.Drawing.Drawing2D
这个名称空间中的类提供了二维图形 和 矢量图形的高级功能。我们可以使用这些类建立复杂的图形和图像处理程序。
这个空间包括:
* 高级画笔
* 前面介绍了LinearGradientBrush 和 PathGradientBrush,还有一个HatchBrush,它可以使用阴影线填充模式、前景色和背景色进行绘制。
* Matrix类
* 它定义了几何变换。使用这个类可以变换绘图操作,例如,使用Matrix类可以绘制倾斜的椭圆。
* GraphicsPath类
* 前面已经介绍过了。使用这个类可以定义复杂的路径,一次绘制出整个路径
System.Drawing.Imaging
这个名称空间中的类提供了高级的图像支持。
System.Drawing.Imaging名称空间类还可以扩展GDI+,以支持其他图像格式。
________________