C#应用程序设计教程


1 第一章 C#C#C#C#语言基础 本章介绍 C#语言的基础知识,希望具有 C语言的读者能够基本掌握 C#语言,并以此为 基础,能够进一步学习用 C#语言编写 window 应用程序和 Web 应用程序。当然仅靠一章的 内容就完全掌握 C#语言是不可能的,如需进一步学习 C#语言,还需要认真阅读有关 C#语 言的专著。(脚本之家 www.jb51.net) 1.11.11.11.1 C#C#C#C#语言特点语言特点语言特点语言特点 Microsoft.NET(以下简称.NET)框架是微软提出的新一代 Web 软件开发模型,C#语言 是.NET 框架中新一代的开发工具。C#语言是一种现代、面向对象的语言,它简化了 C++语 言在类、命名空间、方法重载和异常处理等方面的操作,它摒弃了 C++的复杂性,更易使用, 更少出错。它使用组件编程,和 VB 一样容易使用。C#语法和 C++和JAVA语法非常相似, 如果读者用过 C++和JAVA,学习 C#语言应是比较轻松的。 用 C#语言编写的源程序,必须用 C#语言编译器将 C#源程序编译为中间语言(MicroSoft Intermediate Language,MSIL)代码,形成扩展名为 exe或dll 文件。中间语言代码不是 CPU 可执行的机器码,在程序运行时,必须由通用语言运行环境(Common Language Runtime, CLR)中的既时编译器(JUSTIN Time,JIT)将中间语言代码翻译为 CPU 可执行的机器码,由 CPU 执行。CLR 为C#语言中间语言代码运行提供了一种运行时环境,C#语言的 CLR 和 JAVA 语言的虚拟机类似。这种执行方法使运行速度变慢,但带来其它一些好处,主要有: � 通用语言规范(Common Language Specification,CLS):.NET系统包括如下语言:C#、 C++、VB、J#,他们都遵守通用语言规范。任何遵守通用语言规范的语言源程序,都 可编译为相同的中间语言代码,由 CLR 负责执行。只要为其它操作系统编制相应的 CLR,中间语言代码也可在其它系统中运行。 � 自动内存管理:CLR 内建垃圾收集器,当变量实例的生命周期结束时,垃圾收集器负 责收回不被使用的实例占用的内存空间。不必象 C和C++语言,用语句在堆中建立的 实例,必须用语句释放实例占用的内存空间。也就是说,CLR 具有自动内存管理功能。 � 交叉语言处理:由于任何遵守通用语言规范的语言源程序,都可编译为相同的中间语言 代码,不同语言设计的组件,可以互相通用,可以从其它语言定义的类派生出本语言的 新类。由于中间语言代码由 CLR 负责执行,因此异常处理方法是一致的,这在调试一 种语言调用另一种语言的子程序时,显得特别方便。 � 增加安全:C#语言不支持指针,一切对内存的访问都必须通过对象的引用变量来实现, 只允许访问内存中允许访问的部分,这就防止病毒程序使用非法指针访问私有成员。也 避免指针的误操作产生的错误。CLR 执行中间语言代码前,要对中间语言代码的安全 性,完整性进行验证,防止病毒对中间语言代码的修改。 � 版本支持:系统中的组件或动态联接库可能要升级,由于这些组件或动态联接库都要在 注册表中注册,由此可能带来一系列问题,例如,安装新程序时自动安装新组件替换旧 组件,有可能使某些必须使用旧组件才可以运行的程序,使用新组件运行不了。在.NET 中这些组件或动态联接库不必在注册表中注册,每个程序都可以使用自带的组件或动态 联接库,只要把这些组件或动态联接库放到运行程序所在文件夹的子文件夹 bin 中,运 行程序就自动使用在 bin 文件夹中的组件或动态联接库。由于不需要在注册表中注册, 2 软件的安装也变得容易了,一般将运行程序及库文件拷贝到指定文件夹中就可以了。 � 完全面向对象:不象 C++语言,即支持面向过程程序设计,又支持面向对象程序设计, C#语言是完全面向对象的,在C#中不再存在全局函数、全局变量,所有的函数、变量 和常量都必须定义在类中,避免了命名冲突。C#语言不支持多重继承。 1.21.21.21.2 编写控制编写控制编写控制编写控制台应用程序应用程序应用程序应用程序 1.2.11.2.11.2.11.2.1使用 SDK SDK SDK SDK 命令行工具编写控制台程序 第一个程序总是非常简单的,程序首先让用户通过键盘输入自己的名字,然后程序在屏 幕上打印一条欢迎信息。程序的代码是这样的: using System;//导入命名空间。//为C#语言新增解释方法,解释到本行结束 class Welcome//类定义,类的概念见下一节 {/*解释开始,和C语言解释用法相同 解释结束*/ static void Main()//主程序,程序入口函数,必须在一个类中定义 { Console.WriteLine("请键入你的姓名:");//控制台输出字符串 Console.ReadLine();//从键盘读入数据,输入回车结束 Console.WriteLine("欢迎!"); } } 可以用任意一种文本编辑软件完成上述代码的编写,然后把文件存盘,假设文件名叫做 welcome.cs,C#源文件是以cs作为文件的扩展名。和C语言相同,C#语言是区分大小写的。 高级语言总是依赖于许多在程序外部预定义的变量和函数。在C或C++中这些定义一般放到头 文件中,用#include语句来导入这个头文件。而在C#语言中使用using语句导入名字空间, using System语句意义是导入System名字空间,C#中的using语句的用途与C++中#include 语句的用途基本类似,用于导入预定义的变量和函数,这样在自己的程序中就可以自由地使 用这些变量和函数。如果没有导入名字空间的话我们该怎么办呢?程序还能保持正确吗?答 案是肯定的,那样的话我们就必须把代码改写成下面的样子: class Welcome { static void Main() { System.Console.WriteLine("请键入你的姓名:"); System.Console.ReadLine(); System.Console.WriteLine("欢迎!"); } } 也就是在每个Console前加上一个前缀System.,这个小原点表示Console是作为System 的成员而存在的。C#中抛弃了C和C++中繁杂且极易出错的操作符象::和->等,C#中的复合名 字一律通过.来连接。System是.Net平台框架提供的最基本的名字空间之一,有关名字空间 的详细使用方法将在以后详细介绍,这里只要学会怎样导入名字空间就足够了。 程序的第二行class Welcome声明了一个类,类的名字叫做Welcome。C#程序中每个变量 或函数都必须属于一个类,包括主函数Main(),不能象C或C++那样建立全局变量。C#语言程 序总是从Main()方法开始执行,一个程序中不允许出现两个或两个以上的Main()方法。请牢 3 记C#中Main()方法必须被包含在一个类中,Main第一个字母必须大写,必须是一个静态方法, 也就是Main()方法必须使用static修饰。static void Main()是类Welcome中定义的主函数。 静态方法意义见以后章节。 程序所完成的输入输出功能是通过Console类来完成的,Console是在名字空间System 中已经定义好的一个类。Console类有两个最基本的方法WriteLine和ReadLine。ReadLine 表示从输入设备输入数据,WriteLine则用于在输出设备上输出数据。 如果在电脑上安装了Visual Studio.Net,则可以在集成开发环境中直接选择快捷键或 菜单命令编译并执行源文件。如果您不具备这个条件,那么至少需要安装Microsoft.Net Framework SDK,这样才能够运行C#语言程序。Microsoft.Net Framework SDK中内置了C# 的编译器csc.exe,下面让我们使用这个微软提供的命令行编译器对程序welcome.cs进行编 译。假设已经将welcome.cs文件保存在d:\Charp目录下,启动命令行提示符,在屏幕上输入 一行命令:d:回车,cd Charp回车,键入命令: C:\WINNT\Microsoft.NET\Framework\v1.0.3705\csc welcome.cs 如果一切正常welcome.cs文件将被编译,编译后生成可 执行文件Welcome.exe。可以在命令提示符窗口运行可执行 文件Welcome.exe,屏幕上出现一行字符提示您输入姓名: 请键入你的姓名:输入任意字符并按下回车键,屏幕将打印 出欢迎信息:欢迎! 注意,和我们使用过的绝大多数编译器不同,在 C#中 编译器只执行编译这个过程,而在 C和C++中要经过编译和 链接两个阶段。换而言之 C#源文件并不被编译为目标文件.obj,而是直接生成可执行文 件.exe 或动态链接库.dll,C#编译器中不需要包含链接器。 1.2.11.2.11.2.11.2.1使用 VisualVisualVisualVisual Studio.Net Studio.Net Studio.Net Studio.Net 建立控制台程序 (1) 运行 Visual Studio.Net 程序,出现如图 1.2.2A 界面。 (2) 单击新建项目按钮,出现如图 1.2.2B对话框。在项目类型(P)编辑框中选择 Visual C#项 目,在模板(T)编辑框中选择控制台应用程序,在名称(N)编辑框中键入 e1,在位置(L) 编辑框中键入 D:\csarp,必须预先创建文件夹 D:\csarp。也可以单击浏览按钮,在打开文 件对话框中选择文件夹。单击确定按钮,创建项目。出现如图 1.2.2C界面。编写一个应 用程序,可能包含多个文件,才能生成可执行文件,所有这些文件的集合叫做一个项目。 (3) 修改 class1.cs文件如下,有阴影部分是新增加的语句,其余是集成环境自动生成的。 using System; namespace e1 { /// /// Class1 的摘要说明。 /// class Class1 { /// /// 应用程序的主入口点。 /// [STAThread] 4 static void Main(string[] args) { // // TODO: 在此处添加代码以启动应用程序 // Console.WriteLine("请键入你的姓名:"); Console.ReadLine(); Console.WriteLine("欢迎!"); } } } (4) 按CTRL+F5 键,运行程序,如右图,和1.2.1 节运行效果 相同。屏幕上出现一行字符,提示您输入姓名:请键入 你的姓名:输入任意字符并按下回车键,屏幕将打印出 欢迎信息:欢迎!输入回车退出程序。 图1.2.2A 5 图1.2.2B 图1.2.2C(下载源码就到脚本之家: www.jb51.net) 6 1.31.31.31.3 类的基本概念类的基本概念类的基本概念类的基本概念 C#语言是一种现代、面向对象的语言。面向对象程序设计方法提出了一个全新的概念: 类,它的主要思想是将数据(数据成员)及处理这些数据的相应方法(函数成员)封装到类 中,类的实例则称为对象。这就是我们常说的封装性。 1.3.11.3.11.3.11.3.1 类的基本概念 类可以认为是对结构的扩充,它和 C中的结构最大的不同是:类中不但可以包括数据, 还包括处理这些数据的函数。类是对数据和处理数据的方法(函数)的封装。类是对某一类 具有相同特性和行为的事物的描述。例如,定义一个描述个人情况的类 Person如下: using System; class Person//类的定义,class是保留字,表示定义一个类,Person是类名 { private string name="张三";//类的数据成员声明 private int age=12;//private表示私有数据成员 public void Display()//类的方法(函数)声明,显示姓名和年龄 { Console.WriteLine("姓名:{0},年龄:{1}",name,age); } public void SetName(string PersonName)//修改姓名的方法(函数) { name=PersonName; } public void SetAge(int PersonAge) { age=PersonAge; } } Console.WriteLine("姓名:{0},年龄:{1}",name,age)的意义是将第二个参数变量 name 变为字符串填到{0}位置,将第三个参数变量 age 变为字符串填到{1}位置,将第一个 参数表示的字符串在显示器上输出。 大家注意,这里我们实际定义了一个新的数据类型,为用户自己定义的数据类型,是对 个人的特性和行为的描述,他的类型名为 Person,和 int,char等一样为一种数据类型。用 定义新数据类型 Person类的方法把数据和处理数据的函数封装起来。类的声明格式如下: 属性 类修饰符 class 类名{类体} 其中,关键字 class、类名和类体是必须的,其它项是可选项。类修饰符包括 new、public、 protected、internal、private、abstract 和sealed,这些类修饰符以后介绍。类体用于定义类的 成员。 1.3.21.3.21.3.21.3.2 类成员的存取控制 一般希望类中一些数据不被随意修改,只能按指定方法修改,既隐蔽一些数据。同样一 些函数也不希望被其它类程序调用,只能在类内部使用。如何解决这个问题呢?可用访问权 限控制字,常用的访问权限控制字如下:private(私有),public(公有)。在数据成员或函数成 员前增加访问权限控制字,可以指定该数据成员或函数成员的访问权限。 7 私有数据成员只能被类内部的函数使用和修改,私有函数成员只能被类内部的其它函数 调用。类的公有函数成员可以被类的外部程序调用,类的公有数据成员可以被类的外部程序 直接使用修改。公有函数实际是一个类和外部通讯的接口,外部函数通过调用公有函数,按 照预先设定好的方法修改类的私有成员。对于上述例子,name 和age 是私有数据成员,只 能通过公有函数 SetName()和SetAge()修改,既它们只能按指定方法修改。 这里再一次解释一下封装,它有两个意义,第一是把数据和处理数据的方法同时定义在 类中。第二是用访问权限控制字使数据隐蔽。 1.3.31.3.31.3.31.3.3 类的对象 Person类仅是一个用户新定义的数据类型,由它可以生成 Person类的实例,C#语言叫 对象。用如下方法声明类的对象:Person OnePerson=new Person();此语句的意义是建立 Person 类对象,返回对象地址赋值给 Person 类变量 OnePerson。也可以分两步创建 Person 类的对象:Person OnePerson;OnePerson=new Person();OnePerson 虽然存储的是 Person 类 对象地址,但不是 C中的指针,不能象指针那样可以进行加减运算,也不能转换为其它类 型地址,它是引用型变量,只能引用(代表)Person 对象,具体意义参见以后章节。和C、C++ 不同,C#只能用此种方法生成类对象。 在程序中,可以用 OnePerson.方法名或 OnePerson.数据成员名访问对象的成员。例如: OnePerson.Display(),公用数据成员也可以这样访问。注意,C#语言中不包括 C++语言中 的->符号。 1.3.41.3.41.3.41.3.4 类的构造函数和析构函数 在建立类的对象时,需做一些初始化工作,例如对数据成员初始化。这些可以用构造函 数来完成。每当用 new 生成类的对象时,自动调用类的构造函数。因此,可以把初始化的 工作放到构造函数中完成。构造函数和类名相同,没有返回值。例如可以定义 Person类的 构造函数如下: public Person(string Name,int Age)//类的构造函数,函数名和类同名,无返回值。 { name=Name; age=Age; } 当用 Person OnePerson=new Person(“张五”,20)语句生成 Person类对象时,将自动调用 以上构造函数。请注意如何把参数传递给构造函数。 变量和类的对象都有生命周期,生命周期结束,这些变量和对象就要被撤销。类的对象 被撤销时,将自动调用析构函数。一些善后工作可放在析构函数中完成。析构函数的名字为 ~类名,无返回类型,也无参数。Person 类的析构函数为~ Person()。C#中类析构函数不能显 示地被调用,它是被垃圾收集器撤销不被使用的对象时自动调用的。 1.3.51.3.51.3.51.3.5 类的构造函数的重载 在C#语言中,同一个类中的函数,如果函数名相同,而参数类型或个数不同,认为是 不同的函数,这叫函数重载。仅返回值不同,不能看作不同的函数。这样,可以在类定义中, 8 定义多个构造函数,名字相同,参数类型或个数不同。根据生成类的对象方法不同,调用不 同的构造函数。例如可以定义 Person类没有参数的构造函数如下: public Person()//类的构造函数,函数名和类同名,无返回值。 { name="张三"; age=12; } 用语句 Person OnePerson=new Person("李四",30)生成对象时,将调用有参数的构造 函数,而用语句 Person OnePerson=new Person()生成对象时,调用无参数的构造函数。由 于析构函数无参数,因此,析构函数不能重载。 1.3.61.3.61.3.61.3.6 使用 PersonPersonPersonPerson类的完整的例子 下边用一个完整的例子说明 Person类的使用:(VisualStudio.Net 编译通过) using System; namespace e1//定义以下代码所属命名空间,意义见以后章节 { class Person { private String name="张三";//类的数据成员声明 private int age=12; public void Display()//类的方法(函数)声明,显示姓名和年龄 { Console.WriteLine("姓名:{0},年龄:{1}",name,age); } public void SetName(string PersonName)//指定修改姓名的方法(函数) { name=PersonName; } public void SetAge(int PersonAge)//指定修改年龄的方法(函数) { age=PersonAge; } public Person(string Name,int Age)//构造函数,函数名和类同名,无返回值 { name=Name; age=Age; } public Person()//类的构造函数重载 { name="田七"; age=12; } } class Class1 { static void Main(string[] args) { Person OnePerson=new Person("李四",30);//生成类的对象 OnePerson.Display(); //下句错误,在其它类(Class1类)中,不能直接修改Person类中的私有成员。 //OnePerson.name="王五"; //只能通过Person类中公有方法SetName修改Person类中的私有成员name。 OnePerson.SetName("王五"); 9 OnePerson.SetAge(40); OnePerson.Display(); OnePerson=new Person(); OnePerson.Display(); } } } 键入CTRL+F5运行后,显示的效果是: 姓名: 李四,年龄:30 姓名: 王五,年龄:40 姓名: 田七,年龄:12 1.41.41.41.4 C#C#C#C#的数据类型的数据类型的数据类型的数据类型 从大的方面来分,C#语言的数据类型可以分为三种:值类型,引用类型,指针类型, 指针类型仅用于非安全代码中。本节重点讨论值类型和引用类型。 1.4.11.4.11.4.11.4.1 值类型和引用类型区别 在C#语言中,值类型变量存储的是数据类型所代表的实际数据,值类型变量的值(或实 例)存储在栈(Stack)中,赋值语句是传递变量的值。引用类型(例如类就是引用类型)的实例, 也叫对象,不存在栈中,而存储在可管理堆(Managed Heap)中,堆实际上是计算机系统中的 空闲内存。引用类型变量的值存储在栈(Stack)中,但存储的不是引用类型对象,而是存储引 用类型对象的引用,即地址,和指针所代表的地址不同,引用所代表的地址不能被修改,也 不能转换为其它类型地址,它是引用型变量,只能引用指定类对象,引用类型变量赋值语句 是传递对象的地址。见下例: using System; class MyClass//类为引用类型 { public int a=0; } class Test { static void Main() { f1(); } static public void f1() { int v1=1;//值类型变量 v1,其值 1存储在栈(Stack)中 int v2=v1;//将v1 的值(为1)传递给 v2,v2=1,v1值不变。 v2=2;//v2=2,v1 值不变。 MyClass r1=new MyClass();//引用变量 r1 存储 MyClass 类对象的地址 MyClass r2=r1;//r1 和r2 都代表是同一个 MyClass 类对象 r2.a=2;//和语句 r1.a=2 等价 } } 10 存储在栈中的变量,当其生命周期结束,自动被撤销,例如,v1 存储在栈中,v1 和函 数f1 同生命周期,退出函数 f1,v1 不存在了。但在堆中的对象不能自动被撤销。因此 C 和C++语言,在堆中建立的对象,不使用时必须用语句释放对象占用的存储空间。.NET 系 统CLR 内建垃圾收集器,当对象的引用变量被撤销,表示对象的生命周期结束,垃圾收集 器负责收回不被使用的对象占用的存储空间。例如,上例中引用变量 r1 及r2 是MyClass 类对象的引用,存储在栈中,退出函数 f1,r1 和r2 都不存在了,在堆中的 MyClass 类对象 也就被垃圾收集器撤销。也就是说,CLR 具有自动内存管理功能。 1.4.21.4.21.4.21.4.2 值类型变量分类 C#语言值类型可以分为以下几种: � 简单类型(Simple types) 简单类型中包括:数值类型和布尔类型(bool)。数值类型又细分为:整数类型、字符类 型(char)、浮点数类型和十进制类型(decimal)。 � 结构类型(Struct types) � 枚举类型(Enumeration types) C#语言值类型变量无论如何定义,总是值类型变量,不会变为引用类型变量。 1.4.31.4.31.4.31.4.3 结构类型 结构类型和类一样,可以声明构造函数、数据成员、方法、属性等。结构和类的最根本 的区别是结构是值类型,类是引用类型。和类不同,结构不能从另外一个结构或者类派生, 本身也不能被继承,因此不能定义抽象结构,结构成员也不能被访问权限控制字 protected修饰,也不能用 virtual和abstract修饰结构方法。在结构中不能定义析构函数。 虽然结构不能从类和结构派生,可是结构能够继承接口,结构继承接口的方法和类继承 接口的方法基本一致。下面例子定义一个点结构 point: using System; struct point//结构定义 { public int x,y;//结构中也可以声明构造函数和方法,变量不能赋初值 } class Test { static void Main() { point P1; P1.x=166; P1.y=111; point P2; P2=P1;//值传递,使 P2.x=166,P2.y=111 point P3=new point();//用new 生成结构变量 P3,P3 仍为值类型变量 }//用new 生成结构变量 P3 仅表示调用默认构造函数,使 x=y==0。 } 11 1.4.41.4.41.4.41.4.4 简单类型 简单类型也是结构类型,因此有构造函数、数据成员、方法、属性等,因此下列语句 int i=int.MaxValue;string s=i.ToString()是正确的。即使一个常量,C#也会生成结构类 型的实例,因此也可以使用结构类型的方法,例如:string s=13.ToString()是正确的。简 单类型包括:整数类型、字符类型、布尔类型、浮点数类型、十进制类型。见下表: 保留字 System 命名空间中的名字 字节数 取值范围 sbyte System.Sbyte 1 -128~127 byte System.Byte 1 0~255 short System.Int16 2 -32768~32767 ushort System.UInt16 2 0~65535 int System.Int32 4 -2147483648~2147483647 uint System.UInt32 4 0~4292967295 long System.Int64 8 -9223372036854775808~9223372036854775808 ulong System.UInt64 8 0~18446744073709551615 char System.Char 2 0~65535 float System.Single 4 3.4E-38~3.4E+38 double System.Double 8 1.7E-308~1.7E+308 bool System.Boolean (true,false) decimal System.Decimal 16 正负 1.0⋅10−28 到7.9⋅1028之间 C#简单类型使用方法和 C、C++中相应的数据类型基本一致。需要注意的是: � 和C语言不同,无论在何种系统中,C#每种数据类型所占字节数是一定的。 � 字符类型采用 Unicode字符集,一个 Unicode标准字符长度为 16 位。 � 整数类型不能隐式被转换为字符类型(char),例如 char c1=10 是错误的,必须写成: char c1=(char)10,char c='A',char c='\x0032';char c='\u0032'。 � 布尔类型有两个值:false,true。不能认为整数 0是false,其它值是 true。bool x=1 是错误的,不存在这种写法,只能写成 x=true 或x=false。 � 十进制类型(decimal)也是浮点数类型,只是精度比较高,一般用于财政金融计算。 1.4.51.4.51.4.51.4.5 枚举类型 C#枚举类型使用方法和 C、C++中的枚举类型基本一致。见下例: using System; class Class1 { enum Days {Sat=1, Sun, Mon, Tue, Wed, Thu, Fri}; //使用Visual Studio.Net,enum语句添加在[STAThread]前边 static void Main(string[] args) { Days day=Days.Tue; int x=(int)Days.Tue;//x=2 Console.WriteLine("day={0},x={1}",day,x);//显示结果为:day=Tue,x=4 } } 12 在此枚举类型 Days 中,每个元素的默认类型为 int,其中 Sun=0,Mon=1,Tue=2,依此 类推。也可以直接给枚举元素赋值。例如: enum Days{Sat=1,Sun,Mon,Tue,Wed,Thu,Fri,Sat}; 在此枚举中,Sun=1,Mon=2,Tue=3,Wed=4,等等。和 C、C++中不同,C#枚举元素类 型可以是 byte、sbyte、short、ushort、int、uint、long 和ulong 类型,但不能是 char 类型。见下例: enum Days:byte{Sun,Mon,Tue,Wed,Thu,Fri,Sat};//元素为字节类型 1.4.61.4.61.4.61.4.6 值类型的初值和默认构造函数 所有变量都要求必须有初值,如没有赋值,采用默认值。对于简单类型,sbyte、byte、 short、ushort、int、uint、long 和ulong 默认值为 0,char 类型默认值是(char)0,float 为0.0f,double 为0.0d,decimal 为0.0m,bool 为false,枚举类型为 0,在结构类型和 类中,数据成员的数值类型变量设置为默认值,引用类型变量设置为 null。 可以显示的赋值,例如 int i=0。而对于复杂结构类型,其中的每个数据成员都按此种方 法赋值,显得过于麻烦。由于数值类型都是结构类型,可用 new 语句调用其构造函数初始化 数值类型变量,例如:int j=new int()。请注意,用new 语句并不是把 int 变量变为引用变量, j仍是值类型变量,这里 new 仅仅是调用其构造函数。所有的数值类型都有默认的无参数的 构造函数,其功能就是为该数值类型赋初值为默认值。对于自定义结构类型,由于已有默认 的无参数的构造函数,不能再定义无参数的构造函数,但可以定义有参数的构造函数。 1.4.71.4.71.4.71.4.7 引用类型分类 C#语言中引用类型可以分为以下几种: � 类:C#语言中预定义了一些类:对象类(object类)、数组类、字符串类等。当然, 程序员可以定义其它类。 � 接口。 � 代表。 C#语言引用类型变量无论如何定义,总是引用类型变量,不会变为值类型变量。C#语 言引用类型对象一般用运算符 new 建立,用引用类型变量引用该对象。本节仅介绍对象类 型(object 类型)、字符串类型、数组。其它类型在其它节中介绍。 1.4.81.4.81.4.81.4.8 对象类(object (object (object (object 类)))) C#中的所有类型(包括数值类型)都直接或间接地以object类为基类。对象类(object类) 是所有其它类的基类。任何一个类定义,如果不指定基类,默认object为基类。继承和基类 的概念见以后章节。C#语言规定,基类的引用变量可以引用派生类的对象(注意,派生类的 引用变量不可以引用基类的对象),因此,对一个object的变量可以赋予任何类型的值: int x =25; object obj1; obj1=x; object obj2= 'A'; 13 object 关键字是在命名空间 System 中定义的,是类 System.Object 的别名。 1.4.91.4.91.4.91.4.9 数组类 在进行批量处理数据的时候,要用到数组。数组是一组类型相同的有序数据。数组按照 数组名、数据元素的类型和维数来进行描述。C#语言中数组是类System.Array类对象,比如 声明一个整型数数组:int[] arr=new int[5];实际上生成了一个数组类对象,arr是这个对 象的引用(地址)。 在C#中数组可以是一维的也可以是多维的,同样也支持数组的数组,即数组的元素还是 数组。一维数组最为普遍,用的也最多。我们先看一个一维数组的例子: using System; class Test { static void Main() { int[] arr=new int[3];//用new运算符建立一个3个元素的一维数组 for(int i=0;is2。此方法区分大小写。也 可用如下办法比较字符串: string s1="abc"; string s="abc"; string s2="不相同"; if(s==s1)//还可用!=。虽然String是引用类型,但这里比较两个字符串的值 s2="相同"; � 判断是否为空字符串 string s=""; string s1="不空"; if(s.Length==0) s1="空"; � 得到子字符串或字符 string s="取子字符串"; string sb=s.Substring(2,2);//从索引为2开始取2个字符,Sb="字符",s内容不变 char sb1=s[0];//sb1='取' Console.WriteLine(sb1);//显示:取 � 字符串删除函数 string s="取子字符串"; string sb=s.Remove(0,2);//从索引为0开始删除2个字符,Sb="字符串",s内容不变 15 � 插入字符串 string s="计算机科学"; string s1=s.Insert(3,"软件");//s1="计算机软件科学",s内容不变 � 字符串替换函数 string s="计算机科学"; string s1=s.Replace("计算机","软件");//s1="软件科学",s内容不变 � 把String 转换为字符数组 string S="计算机科学"; char[] s2=S.ToCharArray(0,S.Length);//属性Length为字符类对象的长度 � 其它数据类型转换为字符串 int i=9; string s8=i.ToString();//s8="9" float n=1.9f; string s9=n.ToString();//s8="1.9" 其它数据类型都可用此方法转换为字符类对象 � 大小写转换 string s="AaBbCc"; string s1=s.ToLower();//把字符转换为小写,s内容不变 string s2=s.ToUpper();//把字符转换为大写,s内容不变 � 删除所有的空格 string s="A bc "; s.Trim();//删除所有的空格 string 类其它方法的使用请用帮助系统查看,方法是打开 Visual Studio.Net 的代码编 辑器,键入 string,将光标移到键入的字符串 string 上,然后按 F1 键。 1.4.111.4.111.4.111.4.11 类型转换 在编写 C#语言程序中,经常会碰到类型转换问题。例如整型数和浮点数相加,C#会进 行隐式转换。详细记住那些类型数据可以转换为其它类型数据,是不可能的,也是不必要的。 程序员应记住类型转换的一些基本原则,编译器在转换发生问题时,会给出提示。C#语言 中类型转换分为:隐式转换、显示转换、加框(boxing)和消框(unboxing)等三种。 一.隐式转换 隐式转换就是系统默认的、不需要加以声明就可以进行的转换。例如从int类型转换到 long类型就是一种隐式转换。在隐式转换过程中,转换一般不会失败,转换过程中也不会导 致信息丢失。例如: int i=10; long l=i; 二.显示转换 显式类型转换,又叫强制类型转换。与隐式转换正好相反,显式转换需要明确地指定转 换类型,显示转换可能导致信息丢失。下面的例子把长整形变量显式转换为整型: long l=5000; int i=(int)l;//如果超过int取值范围,将产生异常 三.加框(boxing)和消框(unboxing) 加框(boxing)和消框(unboxing)是C#语言类型系统提出的核心概念,加框是值类型转换 16 为object(对象)类型,消框是object(对象)类型转换为值类型。有了加框和消框的概念,对 任何类型的变量来说最终我们都可以看作是object类型。 1 加框操作 把一个值类型变量加框也就是创建一个object对象,并将这个值类型变量的值复制给这 个object对象。例如: int i=10; object obj=i;//隐式加框操作,obj为创建的object对象的引用。 我们也可以用显式的方法来进行加框操作,例如: int i =10; object obj=object(i);//显式加框操作 值类型的值加框后,值类型变量的值不变,仅将这个值类型变量的值复制给这个object 对象。我们看一下下面的程序: using System class Test { public static void Main() { int n=200; object o=n; o=201;//不能改变n Console.WriteLine("{0},{1}",n,o); } } 输出结果为:200,201。这就证明了值类型变量n和object类对象o都独立存在着。 2. 消框操作 和加框操作正好相反,消框操作是指将一个对象类型显式地转换成一个值类型。消框的过 程分为两步:首先检查这个object对象,看它是否为给定的值类型的加框值,如是,把这个 对象的值拷贝给值类型的变量。我们举个例子来看看一个对象消框的过程: int i=10; object obj=i; int j=(int)obj;//消框操作 可以看出消框过程正好是加框过程的逆过程,必须注意加框操作和消框操作必须遵循类 型兼容的原则。 3. 加框和消框的使用 定义如下函数: void Display(Object o)//注意,o为Object类型 { int x=(int)o;//消框 System.Console.WriteLine("{0},{1}",x,o); } 调用此函数:int y=20;Display(y);在此利用了加框概念,虚参被实参替换:Object o=y, 也就是说,函数的参数是Object类型,可以将任意类型实参传递给函数。 1.51.51.51.5 运算符运算符运算符运算符 C#语言和 C语言的运算符用法基本一致。以下重点讲解二者之间不一致部分。 17 1.5.11.5.11.5.11.5.1 运算符分类 与C语言一样,如果按照运算符所作用的操作数个数来分,C#语言的运算符可以分为以 下几种类型: � 一元运算符:一元运算符作用于一个操作数,例如:-X、++X、X--等。 � 二元运算符:二元运算符对两个操作数进行运算,例如:x+y。 � 三元运算符:三元运算符只有一个:x? y:z。 C#语言运算符的详细分类及操作符从高到低的优先级顺序见下表。 类别 操作符 初级操作符 (x) x.y f(x) a[x] x++ x-- new type of sizeof checked unchecked 一元操作符 + -! ~ ++x –x (T)x 乘除操作符 */% 加减操作符 + - 移位操作符 << >> 关系操作符 < > <= >= is as 等式操作符 == != 逻辑与操作符 & 逻辑异或操作符 ^ 逻辑或操作符 | 条件与操作符 && 条件或操作符 || 条件操作符 ?: 赋值操作符 = *= /= %= += -= <<= >>= &= ^= |= 1.5.21.5.21.5.21.5.2 测试运算符 isisisis is操作符用于动态地检查表达式是否为指定类型。使用格式为:e is T,其中e是一个 表达式,T是一个类型,该式判断e是否为T类型,返回值是一个布尔值。例子: using System; class Test { public static void Main() { Console.WriteLine(1 is int); Console.WriteLine(1 is float); Console.WriteLine(1.0f is float); Console.WriteLine(1.0d is double); } } 输出为: True False True True 18 1.5.31.5.31.5.31.5.3 typeof typeof typeof typeof 运算符 typeof操作符用于获得指定类型在system名字空间中定义的类型名字,例如: using System; class Test { static void Main() { Console.WriteLine(typeof(int)); Console.WriteLine(typeof(System.Int32)); Console.WriteLine(typeof(string)); Console.WriteLine(typeof(double[])); } } 产生如下输出,由输出可知int和System.int32是同一类型。 System.Int32 System.Int32 System.String System.Double[] 1.5.41.5.41.5.41.5.4 溢出检查操作符 checkedcheckedcheckedchecked和uncheckeduncheckeduncheckedunchecked 在进行整型算术运算(如+、-、*、/等)或从一种整型显式转换到另一种整型时,有可能 出现运算结果超出这个结果所属类型值域的情况,这种情况称之为溢出。整型算术运算表达 式可以用checked或unchecked溢出检查操作符,决定在编译和运行时是否对表达式溢出进行 检查。如果表达式不使用溢出检查操作符或使用了checked操作符,常量表达式溢出,在编 译时将产生错误,表达式中包含变量,程序运行时执行该表达式产生溢出,将产生异常提示 信息。而使用了unchecked操作符的表达式语句,即使表达式产生溢出,编译和运行时都不 会产生错误提示。但这往往会出现一些不可预期的结果,所以使用unchecked操作符要小心。 下面的例子说明了checked和unchecked操作符的用法: using System; class Class1 { static void Main(string[] args) { const int x=int.MaxValue; unchecked//不检查溢出 { int z=x*2;//编译时不产生编译错误,z=-2 Console.WriteLine("z={0}",z);//显示-2 } checked//检查溢出 { int z1=(x*2);//编译时会产生编译错误 Console.WriteLine("z={0}",z1); } } } 19 1.5.51.5.51.5.51.5.5 newnewnewnew运算符 new操作符可以创建值类型变量、引用类型对象,同时自动调用构造函数。例如: int x=new int();//用new创建整型变量x,调用默认构造函数 Person C1=new Person ();//用new建立的Person类对象。Person 变量C1对象的引用 int[] arr=new int[2];//数组也是类,创建数组类对象,arr是数组对象的引用 需注意的是,int x=new int()语句将自动调用int结构不带参数的构造函数,给x赋初 值0,x仍是值类型变量,不会变为引用类型变量。 1.5.61.5.61.5.61.5.6 运算符的优先级 当一个表达式包含多种操作符时,操作符的优先级控制着操作符求值的顺序。例如,表 达式x+y*z按照x+(y*z)顺序求值,因为*操作符比+操作符有更高的优先级。这和数学运算中 的先乘除后加减是一致的。1.5.1节中的表总结了所有操作符从高到低的优先级顺序。 当两个有相同优先级的操作符对操作数进行运算时,例如x+y-z,操作符按照出现的顺 序由左至右执行,x+y-z按(x+y)-z进行求值。赋值操作符按照右接合的原则,即操作按照从 右向左的顺序执行。如x=y=z按照x=(y=z)进行求值。建议在写表达式的时候,如果无法确定 操作符的实际顺序,则尽量采用括号来保证运算的顺序,这样也使得程序一目了然,而且自 己在编程时能够思路清晰。 1.61.61.61.6 程序控制语句程序控制语句程序控制语句程序控制语句 C#语言控制语句和C基本相同,使用方法基本一致。C#语言控制语句包括:if语句、swith 语句、while语句、do…while语句、for语句、foreach语句、break语句、continue语句、goto 语句、return语句、异常处理语句等,其中foreach语句和异常语句是C#语言新增加控制语句。 本节首先介绍一下这些语句和C语言的不同点,然后介绍C#语言新增的控制语句。 1.6.11.6.11.6.11.6.1 和CCCC语言的不同点 � 与C不同,if语句、while语句、do…while语句、for语句中的判断语句,一定要用布尔 表达式,不能认为0为false,其它数为true。 � switch语句不再支持遍历,C和C++语言允许switch语句中case标签后不出现break语句, 但C#不允许这样,它要求每个case标签项后使用break语句或goto跳转语句,即不允许从 一个case自动遍历到其它case,否则编译时将报错。switch语句的控制类型,即其中控 制表达式的数据类型可以是sbyte、byte、short、ushort、uint、long、ulong、char、 string或枚举类型。每个case标签中的常量表达式必须属于或能隐式转换成控制类型。 如果有两个或两个以上case标签中的常量表达式值相同,编译时将会报错。执行switch 语句,首先计算switch表达式,然后与case后的常量表达式的值进行比较,执行第一个 与之匹配的case分支下的语句。如果没有case常量表达式的值与之匹配,则执行dafault 分支下的语句,如果没有dafault语句,则退出switch语句。switch语句中可以没有 dafault语句,但最多只能有一个dafault语句。见下例: 20 using System; class class1 { static void Main() { System.Console.WriteLine("请输入要计算天数的月份"); string s=System.Console.ReadLine(); string s1=""; switch(s) { case "1": case "3": case "5": case "7": case "8": case "10": case "12"://共用一条语句 s1="31";break; case "2": s1="28";break; case "4": case "6": case "9": goto case "11";//goto语句仅为说明问题,无此必要 case "11": s1="30";break; default: s1="输入错误";break; } System.Console.WriteLine(s1); } } 1.6.21.6.21.6.21.6.2 foreachforeachforeachforeach语句 foreach语句是C#语言新引入的语句,C和C++中没有这个语句,它借用Visual Basic中 的foreach语句。语句的格式为: foreach(类型 变量名 in 表达式) 循环语句 其中表达式必须是一个数组或其它集合类型,每一次循环从数组或其它集合中逐一取出 数据,赋值给指定类型的变量,该变量可以在循环语句中使用、处理,但不允许修改变量, 该变量的指定类型必须和表达式所代表的数组或其它集合中的数据类型一致。例子: using System; class Test() { public static void Main() { int[] list={10,20,30,40};//数组 foreach(int m in list) Console.WriteLine("{0}",m); } } 对于一维数组,foreach语句循环顺序是从下标为0的元素开始一直到数组的最后一个元 素。对于多维数组,元素下标的递增是从最右边那一维开始的。同样break和continue可以 出现在foreach语句中,功能不变。 21 1.6.31.6.31.6.31.6.3 异常语句 在编写程序时,不仅要关心程序的正常操作,还应该考虑到程序运行时可能发生的各类 不可预期的事件,比如用户输入错误、内存不够、磁盘出错、网络资源不可用、数据库无法 使用等,所有这些错误被称作异常,不能因为这些异常使程序运行产生问题。各种程序设计 语言经常采用异常处理语句来解决这类异常问题。 C#提供了一种处理系统级错误和应用程序级错误的结构化的、统一的、类型安全的方法。 C#异常语句包含try子句、catch子句和finally子句。try子句中包含可能产生异常的语句, 该子句自动捕捉执行这些语句过程中发生的异常。catch子句中包含了对不同异常的处理代 码,可以包含多个catch子句,每个catch子句中包含了一个异常类型,这个异常类型必须是 System.Exception类或它的派生类引用变量,该语句只扑捉该类型的异常。可以有一个通用 异常类型的catch子句,该catch子句一般在事先不能确定会发生什么样的异常的情况下使 用,也就是可以扑捉任意类型的异常。一个异常语句中只能有一个通用异常类型的catch子 句,而且如果有的话,该catch子句必须排在其它catch子句的后面。无论是否产生异常,子 句finally一定被执行,在finally子句中可以增加一些必须执行的语句。 异常语句捕捉和处理异常的机理是:当try子句中的代码产生异常时,按照catch子句的 顺序查找异常类型。如果找到,执行该catch子句中的异常处理语句。如果没有找到,执行 通用异常类型的catch子句中的异常处理语句。由于异常的处理是按照catch子句出现的顺序 逐一检查catch子句,因此catch子句出现的顺序是很重要的。无论是否产生异常,一定执行 finally子句中的语句。异常语句中不必一定包含所有三个子句,因此异常语句可以有以下 三种可能的形式: � try –catch语句,可以有多个catch语句 � try -finally语句 � try -catch-finally语句,可以有多个catch语句 请看下边的例子: 1. try–catch-finally语句 using System using System.IO//使用文件必须引用的名字空间 public class Example { public static void Main() { StreamReader sr=null;//必须赋初值null,否则编译不能通过 try { sr=File.OpenText("d:\\csarp\\test.txt");//可能产生异常 string s; while(sr.Peek()!=-1) { s=sr.ReadLine();//可能产生异常 Console.WriteLine(s); } } catch(DirectoryNotFoundException e)//无指定目录异常 { Console.WriteLine(e.Message); } catch(FileNotFoundException e)//无指定文件异常 22 { Console.WriteLine("文件"+e.FileName+"未被发现"); } catch(Exception e)//其它所有异常 { Console.WriteLine("处理失败:{0}",e.Message); } finally { if(sr!=null) sr.Close(); } } } 2. try -finally语句 上例中,其实可以不用catch语句,在finally子句中把文件关闭,提示用户是否正确打 开了文件,请读者自己完成。 3. try -catch语句 请读者把上例修改为使用try-catch结构,注意在每个catch语句中都要关闭文件。 1.71.71.71.7 类的继承类的继承类的继承类的继承 在1.3节,定义了一个描述个人情况的类Person,如果我们需要定义一个雇员类,当然 可以从头开始定义雇员类Employee。但这样不能利用Person类中已定义的函数和数据。比较 好的方法是,以Person类为基类,派生出一个雇员类Employee,雇员类Employee继承了Person 类的数据成员和函数成员,既Person类的数据成员和函数成员成为Employee类的成员。这个 Employee类叫以Person类为基类的派生类,这是C#给我们提出的方法。C#用继承的方法,实 现代码的重用。 1.7.11.7.11.7.11.7.1 派生类的声明格式 派生类的声明格式如下: 属性 类修饰符 class 派生类名:基类名 {类体} 雇员类Employee定义如下: class Employee:Person//Person类是基类 { private string department;//部门,新增数据成员 private decimal salary;//薪金,新增数据成员 public Employee(string Name,int Age,string D,decimal S):base(Name,Age) {//注意base的第一种用法,根据参数调用指定基类构造函数,注意参数的传递 department=D; salary=S; } public new void Display()//覆盖基类Display()方法,注意new,不可用override { base.Display();//访问基类被覆盖的方法,base的第二种用法 Console.WriteLine("部门:{0} 薪金:{1}",department,salary); } 23 } 修改主函数如下: class Class1 { static void Main(string[] args) { Employee OneEmployee=new Employee("李四",30,"计算机系",2000); OneEmployee.Display(); } } Employee类继承了基类Person的方法SetName()、SetAge(),数据成员name和age,即认 为基类Person的这些成员也是Employee类的成员,但不能继承构造函数和析构函数。添加了 新的数据成员department和salary。覆盖了方法Display()。请注意,虽然Employee类继承 了基类Person的name和age,但由于它们是基类的私有成员,Employee类中新增或覆盖的方 法不能直接修改name和age,只能通过基类原有的公有方法SetName()和SetAge()修改。如果 希望在Employee类中能直接修改name和age,必须在基类中修改它们的属性为protected。 1.7.21.7.21.7.21.7.2 basebasebasebase 关键字 base关键字用于从派生类中访问基类成员,它有两种基本用法: � 在定义派生类的构造函数中,指明要调用的基类构造函数,由于基类可能有多个构造函 数,根据base后的参数类型和个数,指明要调用哪一个基类构造函数。参见上节雇员类 Employee构造函数定义中的base的第一种用法。 � 在派生类的方法中调用基类中被派生类覆盖的方法。参见上节雇员类Employee的 Display()方法定义中的base的第二种用法。 1.7.31.7.31.7.31.7.3 覆盖基类成员 在派生类中,通过声明与基类完全相同新成员,可以覆盖基类的同名成员,完全相同是 指函数类型、函数名、参数类型和个数都相同。如上例中的方法Display()。派生类覆盖基 类成员不算错误,但会导致编译器发出警告。如果增加new修饰符,表示认可覆盖,编译器 不再发出警告。请注意,覆盖基类的同名成员,并不是移走基类成员,只是必须用如下格式 访问基类中被派生类覆盖的方法:base.Display()。 1.7.41.7.41.7.41.7.4 C#C#C#C#语言类继承特点 C#语言类继承有如下特点: � C#语言只允许单继承,即派生类只能有一个基类。 � C#语言继承是可以传递的,如果C从B派生,B从A派生,那么C不但继承B的成员,还 要继承A中的成员。 � 派生类可以添加新成员,但不能删除基类中的成员。 � 派生类不能继承基类的构造函数、析构函数和事件。但能继承基类的属性。 � 派生类可以覆盖基类的同名成员,如果在派生类中覆盖了基类同名成员,基类该成员在 派生类中就不能被直接访问,只能通过base.基类方法名访问。 24 � 派生类对象也是其基类的对象,但基类对象却不是其派生类的对象。例如,前边定义的 雇员类Employee是Person类的派生类,所有雇员都是人类,但很多人并不是雇员,可能 是学生,自由职业者,儿童等。因此C#语言规定,基类的引用变量可以引用其派生类对 象,但派生类的引用变量不可以引用其基类对象。 1.81.81.81.8 类的成员类的成员类的成员类的成员 由于C#程序中每个变量或函数都必须属于一个类或结构,不能象C或C++那样建立全局变 量,因此所有的变量或函数都是类或结构的成员。类的成员可以分为两大类:类本身所声明 的以及从基类中继承来的。 1.8.11.8.11.8.11.8.1 类的成员类型 类的成员包括以下类型: � 局部变量:在for、switch等语句中和类方法中定义的变量,只在指定范围内有效。 � 字段:即类中的变量或常量,包括静态字段、实例字段、常量和只读字段。 � 方法成员:包括静态方法和实例方法。 � 属性:按属性指定的get方法和Set方法对字段进行读写。属性本质上是方法。 � 事件:代表事件本身,同时联系事件和事件处理函数。 � 索引指示器:允许象使用数组那样访问类中的数据成员。 � 操作符重载:采用重载操作符的方法定义类中特有的操作。 � 构造函数和析构函数。 包含有可执行代码的成员被认为是类中的函数成员,这些函数成员有方法、属性、索引 指示器、操作符重载、构造函数和析构函数。 1.8.21.8.21.8.21.8.2 类成员访问修饰符 访问修饰符用于指定类成员的可访问性,C#访问修饰符有private、protected、public 和 internal4 种。Private声明私有成员,私有数据成员只能被类内部的函数使用和修改,私有函 数成员只能被类内部的函数调用。派生类虽然继承了基类私有成员,但不能直接访问它们, 只能通过基类的公有成员访问。protected 声明保护成员,保护数据成员只能被类内部和派生 类的函数使用和修改,保护函数成员只能被类内部和派生类的函数调用。public 声明公有成 员,类的公用函数成员可以被类的外部程序所调用,类的公用数据成员可以被类的外部程序 直接使用。公有函数实际是一个类和外部通讯的接口,外部函数通过调用公有函数,按照预 先设定好的方法修改类的私有成员和保护成员。internal 声明内部成员,内部成员只能在同 一程序集中的文件中才是可以访问的,一般是同一个应用(Application)或库(Library)。 1.91.91.91.9 类的字段和属性类的字段和属性类的字段和属性类的字段和属性 一般把类或结构中定义的变量和常量叫字段。属性不是字段,本质上是定义修改字段的 方法,由于属性和字段的紧密关系,把它们放到一起叙述。 25 1.9.11.9.11.9.11.9.1 静态字段、实例字段、常量和只读字段 用修饰符 static 声明的字段为静态字段。不管包含该静态字段的类生成多少个对象或根 本无对象,该字段都只有一个实例,静态字段不能被撤销。必须采用如下方法引用静态字段: 类名.静态字段名。如果类中定义的字段不使用修饰符 static,该字段为实例字段,每创建该 类的一个对象,在对象内创建一个该字段实例,创建它的对象被撤销,该字段对象也被撤销, 实例字段采用如下方法引用:实例名.实例字段名。用 const修饰符声明的字段为常量,常 量只能在声明中初始化,以后不能再修改。用readonly 修饰符声明的字段为只读字段,只读 字段是特殊的实例字段,它只能在字段声明中或构造函数中重新赋值,在其它任何地方都不 能改变只读字段的值。例子: public classTest { public const int intMax=int.MaxValue;//常量,必须赋初值 public int x=0;//实例字段 public readonly int y=0;//只读字段 public static int cnt=0;//静态字段 public Test(int x1,int y1)//构造函数 {//intMax=0;//错误,不能修改常量 x=x1;//在构造函数允许修改实例字段 y=y1;//在构造函数允许修改只读字段 cnt++;//每创建一个对象都调用构造函数,用此语句可以记录对象的个数 } public void Modify(int x1,int y1) {//intMax=0;//错误,不能修改常量 x=x1; cnt=y1; //y=10;//不允许修改只读字段 } } class Class1 { static void Main(string[] args) { Test T1=new Test(100,200); T1.x=40;//引用实例字段采用方法:实例名.实例字段名 Test.cnt=0;//引用静态字段采用方法:类名.静态字段名 int z=T1.y;//引用只读字段 z=Test.intMax;//引用常量 } } 1.9.21.9.21.9.21.9.2 属性 C#语言支持组件编程,组件也是类,组件用属性、方法、事件描述。属性不是字段,但 必然和类中的某个或某些字段相联系,属性定义了得到和修改相联系的字段的方法。C#中的 属性更充分地体现了对象的封装性:不直接操作类的数据内容,而是通过访问器进行访问, 26 借助于get和set方法对属性的值进行读写。访问属性值的语法形式和访问一个变量基本一 样,使访问属性就象访问变量一样方便,符合习惯。 在类的基本概念一节中,定义一个描述个人情况的类Person,其中字段name和age是私 有字段,记录姓名和年龄,外部通过公有方法SetName和SetAge修改这两个私有字段。现在 用属性来描述姓名和年龄。例子如下: using System; public class Person { private string P_name="张三";//P_name是私有字段 private int P_age=12;//P_age是私有字段 public void Display()//类的方法声明,显示姓名和年龄 { Console.WriteLine("姓名:{0},年龄:{1}",P_name,P_age); } public string Name//定义属性Name { get { return P_name;} set { P_name=value;} } public int Age//定义属性Age { get { return P_age;} set { P_age=value;} } } public class Test { public static void Main() { Person OnePerson= new Person(); OnePerson.Name="田七";//value="田七",通过set方法修改变量P_Name string s=OnePerson.Name;//通过get方法得到变量P_Name值 OnePerson.Age=20;//通过定义属性,既保证了姓名和年龄按指定方法修改 int x=OnePerson.Age;//语法形式和修改、得到一个变量基本一致,符合习惯 OnePerson.Display(); } } 在属性的访问声明中,只有 set 访问器表明属性的值只能进行设置而不能读出,只有 get 访问器表明属性的值是只读的不能改写,同时具有 set 访问器和 get 访问器表明属性的 值的读写都是允许的。 虽然属性和字段的语法比较类似,但由于属性本质上是方法,因此不能把属性当做变量 那样使用,也不能把属性作为引用型参数或输出参数来进行传递。 1.101.101.101.10 类的方法类的方法类的方法类的方法 方法是类中用于执行计算或其它行为的成员。所有方法都必须定义在类或结构中。 27 1.10.11.10.11.10.11.10.1 方法的声明 方法的声明格式如下: 属性 方法修饰符 返回类型 方法名(形参列表){方法体} 方法修饰符包括new、public、protected、internal、private、static、virtual、sealed、override、 abstract和extern。这些修饰符有些已经介绍过,其它修饰符将逐一介绍。返回类型可以是任 何合法的C#数据类型,也可以是void,即无返回值。形参列表的格式为:(形参类型 形参1, 形参类型 形参2,...),可以有多个形参。不能使用C语言的形参格式。 1.10.21.10.21.10.21.10.2 方法参数的种类 C#语言的方法可以使用如下四种参数(请注意和参数类型的区别): � 值参数,不含任何修饰符。 � 引用参数,以ref修饰符声明。 � 输出参数,以out修饰符声明。 � 数组参数,以params修饰符声明。 1. 值参数 当用值参数向方法传递参数时,程序给实参的值做一份拷贝,并且将此拷贝传递给该方 法,被调用的方法不会修改实参的值,所以使用值参数时,可以保证实参的值是安全的。如 果参数类型是引用类型,例如是类的引用变量,则拷贝中存储的也是对象的引用,所以拷贝 和实参引用同一个对象,通过这个拷贝,可以修改实参所引用的对象中的数据成员。 2. 引用参数 有时在方法中,需要修改或得到方法外部的变量值,C语言用向方法传递实参指针来达 到目的,C#语言用引用参数。当用引用参数向方法传递实参时,程序将把实参的引用,即实 参在内存中的地址传递给方法,方法通过实参的引用,修改或得到方法外部的变量值。引用 参数以ref修饰符声明。注意在使用前,实参变量要求必须被设置初始值。 3. 输出参数 为了把方法的运算结果保存到外部变量,因此需要知道外部变量的引用(地址)。输出参 数用于向方法传递外部变量引用(地址),所以输出参数也是引用参数,与引用参数的差别在 于调用方法前无需对变量进行初始化。在方法返回后,传递的变量被认为经过了初始化。值 参数、引用参数和输出参数的使用见下例: using System; class g{public int a=0;}//类定义 class Class1 { public static void F1(ref char i)//引用参数 { i='b';} public static void F2(char i)//值参数,参数类型为值类型 { i='d';} public static void F3(out char i)//输出参数 { i='e';} public static void F4(string s)//值参数,参数类型为字符串 { s="xyz";} public static void F5(g gg)//值参数,参数类型为引用类型 28 { gg.a=20;} public static void F6(ref string s)//引用参数,参数类型为字符串 { s="xyz";} static void Main(string[] args) { char a='c'; string s1="abc"; F2(a);//值参数,不能修改外部的a Console.WriteLine(a);//因a未被修改,显示c F1(ref a);//引用参数,函数修改外部的a的值 Console.WriteLine(a);//a被修改为b,显示b Char j; F3(out j);//输出参数,结果输出到外部变量j Console.WriteLine(j);//显示e F4(s1);//值参数,参数类型是字符串,s1为字符串引用变量 Console.WriteLine(s1);//显示:abc,字符串s1不被修改 g g1=new g(); F5(g1);//值参数,但实参是一个类引用类型变量 Console.WriteLine(g1.a.ToString());//显示:20,修改对象数据 F6(ref s1);//引用参数,参数类型是字符串,s1为字符串引用变量 Console.WriteLine(s1);//显示:xyz,字符串s1被修改 } } 4. 数组参数 数组参数使用params说明,如果形参表中包含了数组参数,那么它必须是参数表中最后 一个参数,数组参数只允许是一维数组。比如string[]和string[][]类型都可以作为数组型 参数。最后,数组型参数不能再有ref和out修饰符。见下例: using System; class Class1 { static void F(params int[] args)//数组参数,有params说明 { Console.Write("Array contains {0} elements:",args.Length); foreach (int i in args) Console.Write(" {0}",i); Console.WriteLine(); } static void Main(string[] args) { int[] a = {1,2,3}; F(a);//实参为数组类引用变量a F(10, 20, 30, 40);//等价于F(new int[] {60,70,80,90}); F(new int[] {60,70,80,90});//实参为数组类引用 F();//等价于F(new int[] {}); F(new int[] {});//实参为数组类引用,数组无元素 } } 程序输出 29 Array contains 3 elements: 1 2 3 Array contains 4 elements: 10 20 30 40 Array contains 4 elements: 60,70,80,90 Array contains 0 elements: Array contains 0 elements: 方法的参数为数组时也可以不使用params,此种方法可以使用一维或多维数组,见下例: using System; class Class1 { static void F(int[,] args)//值参数,参数类型为数组类引用变量,无params说明 { Console.Write("Array contains {0} elements:",args.Length); foreach (int i in args) Console.Write(" {0}",i); Console.WriteLine(); } static void Main(string[] args) { int[,] a = {{1,2,3},{4,5,6}}; F(a);//实参为数组类引用变量a //F(10, 20, 30, 40);//此格式不能使用 F(new int[,]{{60,70},{80,90}});//实参为数组类引用 //F();//此格式不能使用 //F(new int[,]{});//此格式不能使用 } } 程序输出 Array contains 3 elements: 1 2 3 4 5 6 Array contains 4 elements: 60,70,80,90 1.10.31.10.31.10.31.10.3 静态方法和实例方法 用修饰符 static 声明的方法为静态方法,不用修饰符 static 声明的方法为实例方法。不 管类生成或未生成对象,类的静态方法都可以被使用,使用格式为:类名.静态方法名。静 态方法只能使用该静态方法所在类的静态数据成员和静态方法。这是因为使用静态方法时, 该静态方法所在类可能还没有对象,即使有对象,由于用类名.静态方法名方式调用静态方 法,静态方法没有 this 指针来存放对象的地址,无法判定应访问哪个对象的数据成员。在类 创建对象后,实例方法才能被使用,使用格式为:对象名.实例方法名。实例方法可以使用 该方法所在类的所有静态成员和实例成员。例子如下: using System; public class UseMethod { private static int x=0;//静态字段 private int y=1;//实例字段 public static void StaticMethod()//静态方法 { x=10;//正确,静态方法访问静态数据成员 //y=20;//错误,静态方法不能访问实例数据成员 } 30 public void NoStaticMethod()//实例方法 { x=10;//正确,实例方法访问静态数据成员 y=20;//正确,实例方法访问实例数据成员 } } public class Class1 { public static void Main() { UseMethod m=new UseMethod(); UseMethod.StaticMethod();//使用静态方法格式为:类名.静态方法名 m.NoStaticMethod();//使用实例方法格式为:对象名.实例方法名 } } 1.10.41.10.41.10.41.10.4 方法的重载 在C#语言中,如果在同一个类中定义的函数名相同,而参数类型或参数个数不同,认 为是不相同的函数,仅返回值不同,不能看作不同函数,这叫做函数的重载。前边 Person 类中定义了多个构造函数就是重载的例子。在C语言中,若计算一个数据的绝对值,则需要 对不同数据类型求绝对值方法使用不同的方法名,如用 abc()求整型数绝对值,labs()求长 整型数绝对值,fabs()求浮点数绝对值。而在 C#语言中,可以使用函数重载特性,对这三 个函数定义同样的函数名,但使用不同的参数类型。下面是实现方法: using System; public class UseAbs { public int abs(int x)//整型数求绝对值 { return(x<0 ?-x:x);} public long abs(long x)//长整型数求绝对值 {return(x<0 ?-x:x);} public double abs(double x)//浮点数求绝对值 {return(x<0 ?-x:x);} } class Class1 { static void Main(string[] args) { UseAbs m=new UseAbs(); int x=-10; long y=-123; double z=-23.98d; x=m.abs(x); y=m.abs(y); z=m.abs(z); Console.WriteLine("x={0},y={1},z={2}",x,y,z); } } 类的对象调用这些同名方法,在编译时,根据调用方法的实参类型决定调用那个同名方 法,计算不同类型数据的绝对值。这给编程提供了极大方便。 31 1.10.51.10.51.10.51.10.5 操作符重载 操作符重载是将 C#语言中的已有操作符赋予新的功能,但与该操作符的本来含义不冲 突,使用时只需根据操作符出现的位置来判别其具体执行哪一种运算。操作符重载,实际是 定义了一个操作符函数,操作符函数声明的格式如下: static public 函数返回类型 operator 重新定义的操作符(形参表) C#语言中有一些操作符是可以重载的,例如:+ -! ~ ++ -- true false */%& | ^ << >> == != > < >= <=等等。但也有一些操作符是不允许进行重载的,例如:=,&&, ||,?:, new, typeof, sizeof, is等。 下边的例子,定义一个复数类,并且希望复数的加减乘除用符号+,-.*,/来表示。 using System; class Complex//复数类定义 { private double Real;//复数实部 private double Imag;//复数虚部 public Complex(double x,double y)//构造函数 { Real=x; Imag=y; } static public Complex operator -(Complex a)//重载一元操作符负号,注意1个参数 { return (new Complex(-a.Real,-a.Imag));} static public Complex operator +(Complex a,Complex b)//重载二元操作符加号 { return (new Complex(a.Real+b.Real,a.Imag+b.Imag));} public void Display() { Console.WriteLine("{0}+({1})j",Real,Imag);} } class Class1 { static void Main(string[] args) { Complex x=new Complex(1.0,2.0); Complex y=new Complex(3.0,4.0); Complex z=new Complex(5.0,7.0); x.Display();//显示:1+(2)j y.Display();//显示:3+(4)j z.Display();//显示:5+(7)j z=-x;//等价于z=opeator-(x) z.Display();//显示:-1+(-2)j z=x+y;//即z=opeator+(x,y) z.Display();//显示:4+(6)j } } 1.10.61.10.61.10.61.10.6 thisthisthisthis关键字 每个类都可以有多个对象,例如定义 Person类的两个对象: 32 Person P1=new Person("李四",30); Person P2=new Person("张三",40); 因此 P1.Display()应显示李四信息,P2.Display()应显示张三信息,但无论创建多少 个对象,只有一个方法 Display(),该方法是如何知道显示那个对象的信息的呢?C#语言用 引用变量 this 记录调用方法 Display()的对象,当某个对象调用方法 Display()时,this 便 引用该对象(记录该对象的地址)。因此,不同的对象调用同一方法时,方法便根据 this 所引 用的不同对象来确定应该引用哪一个对象的数据成员。this 是类中隐含的引用变量,它是被 自动被赋值的,可以使用但不能被修改。例如:P1.Display(),this 引用对象 P1,显示李 四信息。P2.Display(),this 引用对象 P2,显示张三信息。 1.111.111.111.11 类的多态性类的多态性类的多态性类的多态性 在面向对象的系统中,多态性是一个非常重要的概念。C#支持两种类型的多态性,第一 种是编译时的多态性,一个类的对象调用若干同名方法,系统在编译时,根据调用方法的实 参类型及实参的个数决定调用那个同名方法,实现何种操作。编译时的多态性是通过方法重 载来实现的。C#语言的方法重载以及操作符重载和C++语言的基本一致。 第二种是运行时的多态性,是在系统运行时,不同对象调用一个名字相同,参数的类型 及个数完全一样的方法,会完成不同的操作。C#运行时的多态性通过虚方法实现。在类的方 法声明前加上了virtual修饰符,被称之为虚方法,反之为非虚方法。C#语言的虚方法和C++ 语言的基本一致。下面的例子说明了虚方法与非虚方法的区别: using System; class A { public void F()//非虚方法 { Console.Write(" A.F");} public virtual void G()//虚方法 { Console.Write(" A.G");} } class B:A//A类为B类的基类 { new public void F()//覆盖基类的同名非虚方法F(),注意使用new { Console.Write(" B.F");} public override void G()//覆盖基类的同名虚方法G(),注意使用override { Console.Write(" B.G");} } class Test { static void F2(A aA)//注意,参数为A类引用变量 { aA.G();} static void Main() {B b=new B(); A a1=new A(); A a2=b;//允许基类引用变量引用派生类对象,a2引用派生类B对象b a1.F();//调用基类A的非虚方法F(),显示A.F a2.F();//F()为非虚方法,调用基类A的F(),显示A.F b.F();//F()为非虚方法,调用派生类B的F(),显示B.F a1.G();//G()为虚方法,因a1引用基类A对象,调用基类A的G(),显示A.G 33 a2.G();//G()为虚方法,因a2引用派生类B对象,调用派生类B的G(),显示B.G F2(b);//实参为派生类B对象,由于A aA=b,调用派生类B的函数G(),显示B.G F2(a1);//实参为基类A对象,调用A类的函数G(),显示A.G } } 那么输出应该是: A.F A.FB.FA.G B.G B.G A.G 注意例子中,不同对象调用同名非虚方法F()和同名虚方法G()的区别。a2虽然是基类引 用变量,但它引用派生类对象b。由于G()是虚方法,因此a2.G()调用派生类B的G(),显示G.F。 但由于F()是非虚方法,a2.F()仍然调用基类A的F(),显示A.F。或者说,如果将基类引用变 量引用不同对象,或者是基类对象,或者是派生类对象,用这个基类引用变量分别调用同名 虚方法,根据对象不同,会完成不同的操作。而非虚方法则不具备此功能。 方法F2(A aA)中,参数是A类类型,F2(b)中形参和实参的关系是:A aA=b,即基类引用 变量aA引用派生类对象b,aA.G()调用派生类B的函数G(),显示B.G。同理,F2(a1)实参为基 类A对象,调用A类的函数G(),显示A.G。 在类的基本概念一节中,定义一个描述个人情况的类Person,其中公有方法Display() 用来显示个人信息。在派生雇员类Employee中,覆盖了基类的公有方法Display(),以显示 雇员新增加的信息。我们希望隐藏这些细节,希望无论基类还是派生类,都调用同一个显示 方法,根据对象不同,自动显示不同的信息。可以用虚方法来实现,这是一个典型的多态性 例子。例子 using System; public class Person { private String name="张三";//类的数据成员声明 private int age=12; protected virtual void Display()//类的虚方法 { Console.WriteLine("姓名:{0},年龄:{1}",name,age); } public Person(string Name,int Age)//构造函数,函数名和类同名,无返回值 { name=Name; age=Age; } static public void DisplayData(Person aPerson)//静态方法 { aPerson.Display();//不是静态方法调用实例方法,如写为Display()错误 } } public class Employee:Person//Person类是基类 { private string department; private decimal salary; public Employee(string Name,int Age,string D,decimal S):base(Name,Age) { department=D; salary=S; } protected override void Display()//重载虚方法,注意用override { base.Display();//访问基类同名方法 34 Console.WriteLine("部门:{0} 薪金:{1} ", department,salary); } } class Class1 { static void Main(string[] args) { Person OnePerson=new Person("李四",30); Person.DisplayData(OnePerson);//显示基类数据 Employee OneEmployee=new Employee("王五",40,"财务部",2000); Person.DisplayData(OneEmployee);//显示派生类数据 } } 运行后,显示的效果是: 姓名: 李四,年龄:30 姓名: 王五,年龄:40 部门:财务部 薪金:2000 1.121.121.121.12 抽象类和抽象方法抽象类和抽象方法抽象类和抽象方法抽象类和抽象方法 抽象类表示一种抽象的概念,只是希望以它为基类的派生类有共同的函数成员和数据成 员。抽象类使用abstract修饰符,对抽象类的使用有以下几点规定: � 抽象类只能作为其它类的基类,它不能直接被实例化。 � 抽象类允许包含抽象成员,虽然这不是必须的。抽象成员用abstract修饰符修饰。 � 抽象类不能同时又是密封的。 � 抽象类的基类也可以是抽象类。如果一个非抽象类的基类是抽象类,则该类必须通过覆 盖来实现所有继承而来的抽象方法,包括其抽象基类中的抽象方法,如果该抽象基类从 其它抽象类派生,还应包括其它抽象类中的所有抽象方法。 请看下面的示例: abstract class Figure//抽象类定义 { protected double x=0,y=0; public Figure(double a,double b) { x=a; y=b; } public abstract void Area();//抽象方法,无实现代码 } class Square:Figure///类Square定义 { public Square(double a,double b):base(a,b) {} public override void Area()//不能使用new,必须用override { Console.WriteLine("矩形面积是:{0}",x*y);} } class Circle:Figure///类Square定义 { public Circle(double a):base(a,a) {} 35 public override void Area() { Console.WriteLine("园面积是:{0}",3.14*x*y);} } class Class1 { static void Main(string[] args) { Square s=new Square(20,30); Circle c=new Circle(10); s.Area(); c.Area(); } } 程序输出结果为: 矩形面积是:600 园面积是:314 抽象类Figure提供了一个抽象方法Area(),并没有实现它,类Square和Circle从抽象类 Figure中继承方法Area(),分别具体实现计算矩形和园的面积。 在类的基本概念一节中,定义一个描述个人情况的类Person,它只是描述了一个人最一 般的属性和行为,因此不希望生成它的对象,可以定义它为抽象类。 注意:C++程序员在这里最容易犯错误。C++中没有对抽象类进行直接声明的方法,而认 为只要在类中定义了纯虚函数,这个类就是一个抽象类。纯虚函数的概念比较晦涩,直观上 不容易为人们接受和掌握,因此C#抛弃了这一概念。 1.131.131.131.13 密封类和密封方法密封类和密封方法密封类和密封方法密封类和密封方法 有时候,我们并不希望自己编写的类被继承。或者有的类已经没有再被继承的必要。C# 提出了一个密封类(sealed class)的概念,帮助开发人员来解决这一问题。 密封类在声明中使用sealed修饰符,这样就可以防止该类被其它类继承。如果试图将一 个密封类作为其它类的基类,C#编译器将提示出错。理所当然,密封类不能同时又是抽象类, 因为抽象总是希望被继承的。 C#还提出了密封方法(sealed method)的概念。方法使用sealed修饰符,称该方法是一 个密封方法。在派生类中,不能覆盖基类中的密封方法。 1.141.141.141.14 接口接口接口接口 与类一样,在接口中可以定义一个和多个方法、属性、索引指示器和事件。但与类不同 的是,接口中仅仅是它们的声明,并不提供实现。因此接口是函数成员声明的集合。如果类 或结构从一个接口派生,则这个类或结构负责实现该接口中所声明的所有成员。一个接口可 以从多个接口继承,而一个类或结构可以实现多个接口。由于C#语言不支持多继承,因此, 如果某个类需要继承多个类的行为时,只能使用多个接口加以说明。 36 1.14.11.14.11.14.11.14.1 接口声明 接口声明是一种类型声明,它定义了一种新的接口类型。接口声明格式如下: 属性 接口修饰符 interface 接口名:基接口{接口体} 其中,关键字interface、接口名和接口体时必须的,其它项是可选的。接口修饰符可 以是new、public、protected、internal和private。例子: public interface IExample {//所有接口成员都不能包括实现 string this[int index] {get;set;}//索引指示器声明 event EventHandler E;//事件声明 void F(int value);//方法声明 string P{ get; set;}//属性声明 } 声明接口时,需注意以下内容: � 接口成员只能是方法、属性、索引指示器和事件,不能是常量、域、操作符、构造函数 或析构函数,不能包含任何静态成员。 � 接口成员声明不能包含任何修饰符,接口成员默认访问方式是public。 1.14.21.14.21.14.21.14.2 接口的继承 类似于类的继承性,接口也有继承性。派生接口继承了基接口中的函数成员说明。接口 允许多继承,一个派生接口可以没有基接口,也可以有多个基接口。在接口声明的冒号后列 出被继承的接口名字,多个接口名之间用分号分割。例子如下: using System; interface IControl { void Paint(); } interface ITextBox:IControl//继承了接口Icontrol的方法Paint() { void SetText(string text); } interface IListBox:IControl//继承了接口Icontrol的方法Paint() { void SetItems(string[] items); } interface IComboBox:ITextBox,IListBox {//可以声明新方法 } 上面的例子中,接口ITextBox和IListBox都从接口IControl中继承,也就继承了接口 IControl的Paint方法。接口IComboBox从接口ITextBox和IListBox中继承,因此它应该继承 了接口ITextBox的SetText方法和IListBox的SetItems方法,还有IControl的Paint方法。 37 1.14.31.14.31.14.31.14.3 类对接口的实现 前面已经说过,接口定义不包括函数成员的实现部分。继承该接口的类或结构应实现这 些函数成员。这里主要讲述通过类来实现接口。类实现接口的本质是,用接口规定类应实现 那些函数成员。用类来实现接口时,接口的名称必须包含在类声明中的基类列表中。 在类的基本概念一节中,定义一个描述个人情况的类Person,从类Person可以派生出其 它类,例如:工人类、公务员类、医生类等。这些类有一些共有的方法和属性,例如工资属 性。一般希望所有派生类访问工资属性时用同样变量名。该属性定义在类Person中不合适, 因为有些人无工资,如小孩。如定义一个类作为基类,包含工资属性,但C#不支持多继承。 可行的办法是使用接口,在接口中声明工资属性。工人类、公务员类、医生类等都必须实现 该接口,也就保证了它们访问工资属性时用同样变量名。例子如下: using System; public interface I_Salary//接口 { decimal Salary//属性声明 { get; set; } } public class Person {…//见1.9.2属性节Person类定义,这里不重复了。 } public class Employee:Person,I_Salary//Person类是基类,I_Salary是接口 {//不同程序员完成工人类、医生类等,定义工资变量名称可能不同 private decimal salary; public new void Display() { base.Display(); Console.WriteLine("薪金:{0} ",salary); } //工人类、医生类等都要实现属性Salary,保证使用的工资属性同名 public decimal Salary { get { return salary;} set { salary=value;} } } public class Test { public static void Main() { Employee S=new Employee(); S.Name="田七";//修改属性Name S.Age=20;//修改属性Age S.Salary=2000;//修改属性Salary S.Display(); 38 } } 如果类实现了某个接口,类也隐式地继承了该接口的所有基接口,不管这些基接口有没 有在类声明的基类表中列出。因此,如果类从一个接口派生,则这个类负责实现该接口及该 接口的所有基接口中所声明的所有成员。 1.151.151.151.15 代表代表代表代表 在这里要介绍的是C#的一个引用类型----代表(delegate),也翻译为委托。它实际上相 当于C语言的函数指针。与指针不同的是C#中的代表是类型安全的。代表类声明格式如下: 属性集 修饰符 delegate 函数返回类型 定义的代表标识符(函数形参列表); 修饰符包括new、public、protected、internal和private。例如我们可以声明一个返回类型 为int,无参数的函数的代表MyDelegate: public delegate int MyDelegate();//只能代表返回类型为int,无参数的函数 声明了代表类MyDelegate,可以创建代表类MyDelegate的对象,用这个对象去代表一个 静态方法或非静态的方法,所代表的方法必须为int类型,无参数。看下面的例子: using System; delegate int MyDelegate();//声明一个代表,注意声明的位置 public class MyClass { public int InstanceMethod()//非静态的方法,注意方法为int类型,无参数 { Console.WriteLine("调用了非静态的方法。"); return 0; } static public int StaticMethod()//静态方法,注意方法为int类型,无参数 { Console.WriteLine("调用了静态的方法。"); return 0; } } public class Test { static public void Main () { MyClass p = new MyClass(); //用new建立代表类MyDelegate对象,d中存储非静态的方法InstanceMethod的地址 MyDelegate d=new MyDelegate(p.InstanceMethod);//参数是被代表的方法 d();//调用非静态方法 //用new建立代表类MyDelegate对象,d中存储静态的方法StaticMethod的地址 d=new MyDelegate(MyClass.StaticMethod);//参数是被代表的方法 d();//调用静态方法 } } 程序的输出结果是: 调用了非静态的方法。 调用了静态的方法。 39 1.161.161.161.16 事件事件事件事件 事件是 C#语言内置的语法,可以定义和处理事件,为使用组件编程提供了良好的基础。 1.16.11.16.11.16.11.16.1 事件驱动 Windows 操作系统把用户的动作都看作消息,C#中称作事件,例如用鼠标左键单击按钮, 发出鼠标单击按钮事件。Windows 操作系统负责统一管理所有的事件,把事件发送到各个运 行程序。各个程序用事件函数响应事件,这种方法也叫事件驱动。 C#语言使用组件编制 Windows应用程序。组件本质上是类。在组件类中,预先定义了 该组件能够响应的事件,以及对应的事件函数,该事件发生,将自动调用自己的事件函数。 例如,按钮类中定义了单击事件 Click 和单击事件函数。一个组件中定义了多个事件,应用 程序中不必也没必要响应所有的事件,而只需响应其中很少事件,程序员编制相应的事件处 理函数,用来完成需要响应的事件所应完成的功能。现在的问题是,第一,如何把程序员编 制的事件处理函数和组件类中预先定义的事件函数联系起来。第二,如何使不需响应的事件 无动作。这是本节要节的解决问题。 1.16.21.16.21.16.21.16.2 事件的声明 在C#中,事件首先代表事件本身,例如按钮类的单击事件,同时,事件还是代表类引用 变量,可以代表程序员编制的事件处理函数,把事件和事件处理函数联系在一起。下面的例 子定义了一个Button组件,这个例子不完整,只是说明问题。实际在C#语言类库中已预定义 了Button组件,这里的代码只是想说明Button组件中是如何定义事件的。例子如下: public delegate void EventHandler(object sender,EventArgs e);//代表声明 //EventHandler可以代表没有返回值,参数为(object sender,EventArgs e)的函数 public class Button:Control//定义一个按钮类Button组件 {…//按钮类Button其它成员定义 public event EventHandler Click;//声明一个事件Click,是代表类引用变量 protected void OnClick(EventArgs e)//Click事件发生,自动触发OnClick方法 { if(Click!=null)//如果Click已代表了事件处理函数,执行这个函数 Click(this,e); } public void Reset() { Click=null;} } 在这个例子中,Click事件发生,应有代码保证(未列出)自动触发OnClick方法。Click 是类Button的一个事件,同时也是代表EventHandler类的引用变量,如令Click代表事件处 理函数,该函数完成Click事件应完成的功能,Click事件发生时,执行事件处理函数。 40 1.16.31.16.31.16.31.16.3 事件的预订和撤消 在随后的例子中,我们声明了一个使用Button类的登录对话框类,对话框类含有两个按 钮:OK和Cancel按钮。 public class LoginDialog: Form//登录对话框类声明 { Button OkButton; Button CancelButton; public LoginDialog()//构造函数 { OkButton=new Button();//建立按钮对象OkButton //Click代表OkButtonClick方法,注意+=的使用 OkButton.Click+=new EventHandler(OkButtonClick); CancelButton=new Button();//建立按钮对象OkButton CancelButton.Click += new EventHandler(CancelButtonClick); } void OkButtonClick(object sender, EventArgs e) {…//处理OkButton.Click事件的方法 } void CancelButtonClick(object sender, EventArgs e) {…//处理CancelButton.Click事件的方法 } } 在例子中建立了Button类的两个实例,单击按钮事件Click通过如下语句和事件处理方 法联系在一起:OkButton.Click+=new EventHandler(OkButtonClick),该语句的意义是使 OkButton.Click代表事件处理方法OkButtonClick,这样只要Click事件被触发,事件处理方 法OkButtonClick就会被自动调用。撤消事件和事件处理方法OkButtonClick的联系采用如下 语句实现:OkButton.Click-=new EventHandler(OkButtonClick),这时,OkButton.Click 就不再代表事件处理方法,Click事件被触发,方法OkButtonClick就不会被调用了。务必理 解这两条语句的用法。使用Visual Studio.Net集成环境可以自动建立这种联系,在自动生 成的代码中包括这两条语句。 1.171.171.171.17 索引指示器索引指示器索引指示器索引指示器 在C#语言中,数组也是类,比如我们声明一个整型数数组:int[] arr=new int[5],实 际上生成了一个数组类对象,arr是这个对象的引用(地址),访问这个数组元素的方法是: arr[下标],在数组类中,使用索引访问元素是如何实现的呢?是否可以定义自己的类,用 索引访问类中的数据成员?索引指示器(indexer)为我们提供了通过索引方式方便地访问类 的数据成员的方法。 首先看下面的例子,用于打印出小组人员的名单: using System class Team { string[] s_name = new string[2];//定义字符串数组,记录小组人员姓名 public string this[int nIndex]//索引指示器声明,this为类Team类的对象 { get//用对象名[索引]得到记录小组人员姓名时,调用get函数 41 { return s_name[nIndex]; } set//用对象名[索引]修改记录小组人员姓名时,调用set函数 { s_name[nIndex] =value;//value为被修改值 } } } class Test { public static void Main() { Team t1 = new Team(); t1[0]="张三"; t1[1]="李斯"; Console.WriteLine("{0},{1}",t1[0], t1[1]); } } 显示结果如下:张三,李斯 1.181.181.181.18 名字空间名字空间名字空间名字空间 一个应用程序可能包含许多不同的部分,除了自己编制的程序之外,还要使用操作系统 或开发环境提供的函数库、类库或组件库,软件开发商处购买的函数库、类库或组件库,开 发团队中其它人编制的程序,等等。为了组织这些程序代码,使应用程序可以方便地使用这 些程序代码,C#语言提出了名字空间的概念。名字空间是函数、类或组件的容器,把它们按 类别放入不同的名字空间中,名字空间提供了一个逻辑上的层次结构体系,使应用程序能方 便的找到所需代码。这和C语言中的include语句的功能有些相似,但实现方法完全不同。 1.18.11.18.11.18.11.18.1 名字空间的声明 用关键字namespace声明一个名字空间,名字空间的声明要么是源文件using语句后的第 一条语句,要么作为成员出现在其它名字空间的声明之中,也就是说,在一个名字空间内部 还可以定义名字空间成员。全局名字空间应是源文件using语句后的第一条语句。在同一名 字空间中,不允许出现同名名字空间成员或同名的类。在声明时不允许使用任何访问修饰符, 名字空间隐式地使用public修饰符。例子如下: using System; namespace N1//N1为全局名字空间的名称,应是using语句后的第一条语句 { namespace N2//名字空间N1的成员N2 { class A//在N2名字空间定义的类不应重名 { void f1(){};} class B { void f2(){};} } } 也可以采用非嵌套的语法来实现以上名字空间: 42 namespace N1.N2//类A、B在名字空间N1.N2中 { class A { void f1(){};} class B { void f2(){};} } 也可以采用如下格式: namespace N1.N2//类A在名字空间N1.N2中 { class A { void f1(){};} } namespace N1.N2//类B在名字空间N1.N2中 { class B { void f2(){};} } 1.18.21.18.21.18.21.18.2 名字空间使用 如在程序中,需引用其它名字空间的类或函数等,可以使用语句using,例如需使用上 节定义的方法f1()和f2(),可以采用如下代码: using N1.N2; class WelcomeApp {A a=new A(); a.f1(); } using N1.N2实际上是告诉应用程序到哪里可以找到类A。请读者重新看一下1.2.1节中 的例子。 1.191.191.191.19 非安全代码非安全代码非安全代码非安全代码 在C和C++的程序员看来,指针是最强有力的工具之一,同时又带来许多问题。因为指针 指向的数据类型可能并不相同,比如你可以把int类型的指针指向一个float类型的变量,而 这时程序并不会出错。如果你删除了一个不应该被删除的指针,比如Windows中指向主程序 的指针,程序就有可能崩溃。因此滥用指针给程序带来不安全因素。正因为如此,在C#语言 中取消了指针这个概念。虽然不使用指针可以完成绝大部分任务,但有时在程序中还不可避 免的使用指针,例如调用Windows操作系统的API函数,其参数可能是指针,所以在C#中还允 许使用指针,但必须声明这段程序是非安全(unsafe)的。可以指定一个方法是非安全的,例 如:unsafe void F1(int * p){…}。可以指定一条语句是非安全的,例如:unsafe int* p2=p1; 还可以指定一段代码是非安全的,例如:unsafe{ int* p2=p1;int* p3=p4;}。在编译时要 采用如下格式:csc 要编译的C#源程序 /unsafe。 43 习题习题习题习题 1. 从键盘输入姓名,在显示器中显示对输入姓名的问候。(提示:string 为字符串类型, 用语句 string s=Console.ReadLine()输入姓名) 2. 构造函数和析购函数的主要作用是什么?它们各有什么特性? 3. 定义点类,数据成员为私有成员,增加有参数和无参数构造函数,在主函数中生成点类 对象,并用字符显示点类对象的坐标。 4. 定义矩形类,数据成员为私有成员,增加有参数和无参数构造函数,在主函数中生成矩 形类对象,并用字符显示矩形类对象的长、宽和矩形左上角的坐标。 5. 设计一个计数器类,统计键入回车的次数,数据成员为私有成员,在主程序中使用此类 统计键入回车的次数。 6. 说明值类型和引用类型的区别,并和 C语言相应类型比较。 7. 定义点结构,在主函数中生成点结构变量,从键盘输入点的位置,并重新显示坐标。 8. 定义整型一维数组,从键盘输入数组元素数值后,用循环语句显示所有元素的值。 9. 输入字符串,将字符串第一个字母和每个空格后的字母变为大写,其余字母为小写后输 出。 10. 输入 5个数,在每两个数之间增加 3个空格后输出。 11. 编一个猜数程序,程序设定一个 1位十进制数,允许用户猜 3次,错了告诉比设定数大 还是小,用 switch 语句实现。 12. C#语言 for 语句可以这样使用:for(int i;i<10;i++),请问,i的有效使用范围。 13. 用字符*在CRT 上显示一个矩形。 14. 输入一个字符串,用 foreach 语句计算输入的字符串长度,并显示长度。 15. 输入两个数相加,并显示和。用异常语句处理输入错误。 16. 将1.6.3 节中 try–catch-finally 语句例子改为 try-finally 和try–catch 语句。 17. 定义点类,从点类派生矩形类,数据成员为私有成员,增加有参数和无参数构造函数, 在主函数中生成矩形类对象,并用字符显示矩形类对象的长、宽和矩形左上角的坐标。 18. 重做 12 题,将数据成员用属性表示。 19. 定义一个类,将类外部的 char 数组元素都变为大写。主程序输入一个字符串,将其变 为char 数组,变为大写后输出每一个 char 数组元素。分别用类对象和静态函数实现。 20. 定义分数类,实现用符号+,-,*,/完成分数的加减乘除。在主函数中输入两个数,完 成运算后输出运算结果。 21. 建立一个 sroot()函数,返回其参数的二次根。重载它,让它能够分别返回整数、长整数 和双精度参数的二次根。 22. 重新设计 complex 类,完成复数的+、-、*、/四则运算。 23. 定义点类,从点类派生矩形类和园类,主程序实现用同一个方法显示矩形和园的面积。 24. 重做 19 题,将点类定义为抽象类。 25. 重做 19 题,改为接口实现,即将点类改为接口。 44 第二章 Windows Windows Windows Windows 编程的基础知识 2.12.12.12.1 窗口窗口窗口窗口 Windows 应用程序一般都有一个窗口,窗口是运行程序与外界交换信息的界面。一个典 型的窗口包括标题栏,最小化按钮,最大/还原按钮,关闭按钮,系统菜单图标,菜单,工 具条,状态栏,滚动条,客户区等。程序员的工作之一是设计符合自己要求的窗口,C#用 控件的方法设计界面。编程另一个工作是在用户区显示数据和图形。 2.22.22.22.2 Windows Windows Windows Windows 的消息系统的消息系统的消息系统的消息系统 2.2.12.2.12.2.12.2.1 消息驱动(事件驱动) Windows 应用程序和 dos 程序(控制台程序)的最大区别是事件驱动,也叫消息驱动。 dos 程序运行时如要读键盘,则要独占键盘等待用户输入,如用户不输入,则CPU 一直执行 键盘输入程序,等待用户输入,即 dos 程序独占外设和 CPU。 Windows 操作系统是一个多任务的操作系统,允许同时运行多个程序,它不允许任何 一个程序独占外设,如键盘,鼠标等,所有运行程序共享外设和 CPU,各个运行程序都要随 时从外设接受命令,执行命令。 因此必须由 Windows 操作系统统一管理各种外设。Windows 把用户对外设的动作都看 作事件(消息),如单击鼠标左键,发送单击鼠标左键事件,用户按下键盘,发送键盘被按下 的事件等。Windows 操作系统统一负责管理所有的事件,把事件发送到各个运行程序,而各 个运行程序用一个函数响应事件,这个函数叫事件响应函数。这种方法叫事件驱动。每个事 件都有它自己的事件响应函数,当接到 Windows 事件后,自动执行此事件的事件响应函数。 程序员编程的主要工作就是编制这些事件的处理函数,完成相应的工作。 2.2.22.2.22.2.22.2.2 事件队列 Windows 把用户的动作都看作事件,Windows 操作系统负责管理所有的事件,事件发生 后,这些事件被放到系统事件队列中,Windows 操作系统从系统事件队列中逐一取出事件, 分析各个事件,分送事件到相应运行程序的事件队列中。而每个运行程序,则利用消息循环 方法(既循环取得自己事件队列中的事件)得到事件,并把他们送到当前活动窗口,由窗口 中的事件函数响应各个事件(消息)。因此,每个运行程序都有自己的事件队列。 2.2.32.2.32.2.32.2.3 注视窗口 Windows 操作系统允许多个程序同时运行,每个程序可能拥有多个窗口,但其中只有一 45 个窗口是活动的,我们能从窗口的标题栏的颜色来识别一个活动窗口,这个窗口接收 Windows 系统发来的大部分的事件。这个应用程序的窗口被称为注视(活动)窗口。 2.32.32.32.3 Windows Windows Windows Windows 编程接口和类库编程接口和类库编程接口和类库编程接口和类库 操作系统为了方便应用程序设计,一般都要提供一个程序库,一些设计应用程序的共用 代码都包含在这个程序库中。程序员可以调用这些代码,以简化编程。这节介绍一些常用程 序库。 2.3.12.3.12.3.12.3.1 WindowsWindowsWindowsWindows编程接口(API)(API)(API)(API) API(Application Programming Interface)是Windows98、2000 和XP操作系统中提供 的一组函数,这些函数采用 C语言调用格式,是为程序员编制 Windows 应用程序提供的编程 接口。程序员用C语言直接调用 API 也可以编制 Windows应用程序,但大量的程序代码必 须由程序员自己编写,而 API 函数非常庞大,给编程者带来很大的困难。 2.3.22.3.22.3.22.3.2 MFC MFC MFC MFC 类库 由于 API 函数十分庞大复杂,看不到函数之间的关系,使程序员不易使用。用C语言使 用API 函数编写 Windows 应用程序是十分困难的。微软的 VC++6.0 用类对 API 函数进行了封 装,为编程提供了 MFC 类库。使用 MFC 类库简化了 Windows 应用程序的编制。但是,MFC 类 库的使用还是比较复杂的,因此,VC++一直是一些专业人员的编程工具。 2.3.32.3.32.3.32.3.3 组件库 为了简化 Windows应用程序的设计,提出了组件(控件)的概念,组件也是类,按钮、菜 单、工具条等都可以封装为组件,组件采用属性、事件、方法来描述,其中属性描述组件的 特性,如按钮的标题,标签字体的颜色和大小。方法是组件类提供的函数,通过调用这些方 法,可以控制组件的行为。组件通过事件和外界联系,一个组件可以响应若干个事件,可以 为事件增加事件处理函数,以后每当发生该事件,将自动调用该事件处理函数处理此事件。 很多组件在设计阶段是可见的,支持可视化编程,这些组件又被叫做控件。用控件编制 Windows应用程序很象搭积木,将控件放到窗体中,设置好属性,漂亮的界面就设计好了。 组件编程的工具有很多,例如:VB6.0、VB.Net、C#、C++Builder、Java、Delphi 等快速开 发工具(RAD)。这些工具都有自己的组件库。 2.3.42.3.42.3.42.3.4 .NET .NET .NET .NET 框架类库 .NET 系统为编制 Windows应用程序、Web 应用程序、Web 服务,在.Net 框架(.Net FrameWork)中提供了基础类库(Base Class Library)。它是一个统一的、面向对象的、层次化 的、可扩展的类库,统一了微软当前各种不同的框架和开发模式,无论开发 Windows应用 程序,还是开发 Web 应用程序,采用相同的组件名称,组件具有相同的属性、方法和事件, 46 开发模式也类似,方便程序员学习。.Net 框架类库支持控件可视化编程,.Net 中的 VC++.Net、 VB.Net、C#语言都使用这个类库,消除了各种语言开发模式的差别。该类库包括以下功能: 基础类库(基本功能,象字符串、数组等)、网络、安全、远程化、诊断和调试、I/O、数据库、 XML、Web服务、Web 编程、Windows编程接口等等。 Windows98、2000 和XP操作系统并不包含.NET 框架类库,为了运行 C#程序,必须安 装.Net FrameWork。 2.42.42.42.4 Windows Windows Windows Windows 应用程序的基本结构应用程序的基本结构应用程序的基本结构应用程序的基本结构 Windows应用程序和控制台应用程序的基本结构基本一样,程序的执行总是从 Main() 方法开始,主函数 Main()必须在一个类中。但 Windows应用程序使用图形界面,一般有一 个窗口(Form),采用事件驱动方式工作。本节介绍 Windows应用程序的基本结构。 2.4.12.4.12.4.12.4.1 最简单的 Windows Windows Windows Windows 应用程序 最简单的 Windows应用程序如下: using System;//引入名字空间 using System.Windows.Forms; public class Form1:Form//类定义 { static void Main()//主函数 { Application.Run(new Form1()); } } 自定义类Form1以Form类为基类。Form类是.Net系统中定义的窗体类,Form类对象具有 Windows应用程序窗口的最基本功能,有标题栏、系统菜单、最大化按钮、最小化按钮和关 闭按钮、用户区。Form类对象还是一个容器,在Form窗体中可以放置其它控件,例如菜单控 件,工具条控件等等。System.Application类中的静态方法Run负责完成一个应用程序的初 始化,运行,终止等功能,其参数是本程序使用的窗体Form1类对象,Run方法还负责从操 作系统接受事件,并把事件送到窗体中响应。窗体关闭,方法Run退出,Windows应用程序 结束。假设已经将文件保存在d:\Charp目录下,文件名为:e1.cs。启动命令行提示符,在 屏幕上输入一行命令:d:回车,cd Charp回车,键入命令: C:\WINNT\Microsoft.NET\Framework\v1.0.3705\csc /t:winexe /r:system.dll,System.Windows.Forms.dll e1.cs 命令中的/t:winexe 表示要建立一个 Windows应用程序,/r 表示要引入的命名空间。 也可以用记事本建立一个批处理文件 g.bat,将以上命令内容拷 贝到文件中,运行 g.bat,和在命令行提示符键入命令效果相同。 以上方法在 FrameWork SDK 2000 中实现。如果一切正常 e1.cs 文件将被编译,编译后生成可执行文件 e1.exe。运行可执行文 件e1.exe,CRT 上出现一个窗口如右图。 可以在 Form1类中定义新的变量,由于主窗体关闭,程序也就结束了,因此定义在主 窗体 Form1 中的变量的生命周期和程序的生命周期是相同的,从这个意义上说,这些变量 是全局变量。可以为 Form1类定义构造函数,在构造函数中做一些初始化的工作,例如修 改Form1 标题栏中的标题。还可以在 Form1中定义控件类的对象,这些控件将在 Form1的 47 用户区显示出来,换句话讲,在 Form1中生成控件对象,也就是把控件放到窗体中。如在 窗体中增加了一个按钮(Button)控件,单击按钮,将产生单击按钮事件,完成一定功能,下 例说明了如何在窗体中增加控件,如何修改控件属性,如何增加控键的事件处理函数。 using System; using System.Windows.Forms; public class Form1:Form { Button button1;//生成Button类引用变量,和应用程序有相同生命周期 public Form1()//构造函数 {//下句修改主窗体标题,不指明属性(方法)所属对象,默认为 Form1 类的属性(方法) Text="我的第一个程序";//也可写为:this.Text="我的第一个程序"; button1=new Button();//生成 Button 类对象 button1.Location=new Point(25,25);//修改 button1 属性 location 即按钮位置 button1.Text="确定";//修改 button1 属性 Text,即按钮的标题 //下句指定 button1_Click 函数是按钮单击事件的单击事件处理函数 button1.Click+=new System.EventHandler(button1_Click); this.Controls.Add(button1);//按钮增加到窗体中,将在主窗体用户区显示出来 } static void Main() { Application.Run(new Form1()); } private void button1_Click(object sender, System.EventArgs e) {//事件处理函数 this.button1.Text="单击了我";//单击按钮事件执行的语句 } } 请注意在窗体中增加控件类的对象的步骤,首先生成 一个引用变量 button1,和主窗体 Form1 有相同的生命周 期,第二步在构造函数中用 new 生成 Button 类对象,第 三步在构造函数中修改 button1的属性,增加 button1 的事 件函数。这些步骤对于定义任何一个控件都是相同的。编 译运行结果如右图: 2.4.22.4.22.4.22.4.2 用VisualVisualVisualVisual Studio.NetStudio.NetStudio.NetStudio.Net建立 WindowsWindowsWindowsWindows应用程序框架 以上所做的工作,都是一些固定的工作,可以使用 Visual Studio.Net 自动建立,下面介 绍使用 Visual Studio.Net 创建 Windows应用程序的具体步骤。 (1) 运行 Visual Studio.Net 程序,出现如图 1.2.2A 界面。 (2) 单击新建项目按钮,出现如图 1.2.2B对话框。在项目类型(P)编辑框中选择 Visual C#项 目,在模板(T)编辑框中选 Windows应用程序,在名称(N)编辑框中键入 e2,在位置(L) 编辑框中键入 D:\csarp。也可以单击浏览按钮,在打开文件对话框中选择文件夹。单击 确定按钮,创建项目。出现如图 2.4.2A 界面。生成一个空白窗体(Form1)。 48 图2.4.2A (3) 在e2 文件夹中下有两个文件夹和 8个文件,一般只修改 Form1.cs 文件。右击 Form1窗 体,在快捷菜单中选择菜单项查看代码(C),可打开 Form1.cs 文件。Visual Studio.Net 生成的 Foem1.cs 文件如下,这是使用 Visual Studio.Net 创建 Windows应用程序的最基 本的形式。底色为黑色的字是作者增加的注解。 using System;//引入名字空间 using System.Drawing; using System.Collections; using System.ComponentModel; using System.Windows.Forms; using System.Data; namespace e2//定义名字空间,///为解释 {//此处可定义其它类 /// /// Form1 的摘要说明。 /// public class Form1 : System.Windows.Forms.Form//Forme1类定义 {//此处可定义自己的变量,这些变量和运行程序同生命周期 /// /// 必需的设计器变量。 /// private System.ComponentModel.Container components = null; public Form1()//构造函数 { // // Windows 窗体设计器支持所必需的 // 49 InitializeComponent();//此函数系统自动生成,不要修改,该函数做一些初始化工作 // // TODO: 在 InitializeComponent 调用后添加任何构造函数代码 //在构造函数增加自己的初始化代码,必须放在InitializeComponent()之后 } /// /// 清理所有正在使用的资源。 /// protected override void Dispose( bool disposing ) { if( disposing ) { if (components != null) { components.Dispose(); } } base.Dispose( disposing ); } #region Windows Form Designer generated code /// /// 设计器支持所需的方法 - 不要使用代码编辑器修改 /// 此方法的内容。 /// private void InitializeComponent() {//此函数系统自动生成,不要修改函数内容,函数做一些初始化工作 // // Form1 // this.AutoScaleBaseSize = new System.Drawing.Size(6, 14); this.ClientSize = new System.Drawing.Size(292, 273); this.Name = "Form1";//this 是Form1窗体对象 this.Text = "Form1"; } #endregion /// /// 应用程序的主入口点。 /// [STAThread] static void Main()//程序入口函数 ,一般不修改 { Application.Run(new Form1());//主程序建立窗体运行 }//程序入口函数之后可定义自己的方法、属性等 } 50 } (4) 下边在窗体中增加一个按钮,并为按钮增加单击事件函数。单击图2.4.2A 中标题为 Forms.cs[设计]的窗口标签,返回标题为 Forms.cs[设计]的窗口。向项目中添加控件需要 使用工具箱窗口,若看不到,可以用菜单命令视图/工具箱打开这个窗口(见图 2.4.2B 左图)。选中工具箱窗口中 Windows 窗体类型下的 Button 条目,然后在标题为 Forms.cs[设计]的窗口的 Form1 窗体中按下鼠标左键,拖动鼠标画出放置 Button 控件的 位置,抬起鼠标左键,就将 Button 控件放到 Form1 窗体中。选中按钮控件,属性窗口(见 图2.4.2B 中图)显示按钮属性,其中左侧为属性名称,右侧为属性值,用属性窗口修改 Button 的Text 属性值为:确定。单击属性窗体上的第 4个图标,打开事件窗口(见图 2.4.2B 右图),显示 Button 控件所能响应的所有事件,其中左侧为事件名称,右侧为事 件处理函数名称,如果为空白,表示还没有事件处理函数,选中 Click 事件,双击右侧 空白处,增加单击事件处理函数。 图2.4.2B 完成以上设计后,集成环境生成的 Foem1.cs 文件如下,底色为黑色的代码是新增代码。 using System; using System.Drawing; using System.Collections; using System.ComponentModel; using System.Windows.Forms; using System.Data; namespace e2 { /// /// Form1 的摘要说明。 /// public class Form1 : System.Windows.Forms.Form { private System.Windows.Forms.Button button1;//定义Button类引用变量 /// /// 必需的设计器变量。 /// private System.ComponentModel.Container components = null; 51 public Form1() { // // Windows 窗体设计器支持所必需的 // InitializeComponent(); // // TODO: 在 InitializeComponent 调用后添加任何构造函数代码 // } /// /// 清理所有正在使用的资源。 /// protected override void Dispose( bool disposing ) { if( disposing ) { if (components != null) { components.Dispose(); } } base.Dispose( disposing ); } #region Windows Form Designer generated code /// /// 设计器支持所需的方法 - 不要使用代码编辑器修改 /// 此方法的内容。 /// private void InitializeComponent() { this.button1 = new System.Windows.Forms.Button();//生成对象 this.SuspendLayout(); // // button1 // this.button1.Location = new System.Drawing.Point(96, 56);//修改属性 this.button1.Name = "button1"; this.button1.Size = new System.Drawing.Size(72, 32); this.button1.TabIndex = 0; this.button1.Text = "确定"; this.button1.Click += new System.EventHandler(this.button1_Click);//增加事件 // // Form1 52 // this.AutoScaleBaseSize = new System.Drawing.Size(6, 14); this.ClientSize = new System.Drawing.Size(292, 273); this.Controls.AddRange(new System.Windows.Forms.Control[] {this.button1}); this.Name = "Form1"; this.Text = "Form1"; } #endregion /// /// 应用程序的主入口点。 /// [STAThread] static void Main() { Application.Run(new Form1()); } private void button1_Click(object sender, System.EventArgs e) {//事件处理函数 } } } 请注意按钮放到窗体后,集成环境自动增加的语句。分析这些增加的语句,可知在窗体 中增加Button类对象的步骤:首先定义Button类变量button1,这是Form1类的一个字段,由 于主窗体关闭,程序也就结束了,因此定义在主窗体Form1中的变量的生命周期和程序的生 命周期是相同的,从这个意义上说,这样的变量是全局变量。因此变量button1和主窗体Form1 有相同的生命周期。第二步在构造函数中用new生成Button类对象,第三步在构造函数中修 改button1的属性,第四步增加button1的事件函数,函数button1_Click()是事件处理函数, 语句this.button1.Click += new System.EventHandler(this.button1_Click)把按钮 Button1的事件Click和事件处理函数button1_Click()联系到一起。程序员应在事件处理函 数button1_Click()中增加具体的事件处理语句。这些步骤对于增加任何控件都是相同的。 可以比较一下2.4.1节中的步骤,它们基本是相同的。应熟悉以上操作步骤,学会在窗体中增 加控件,修改控件属性,增加事件函数。 2.4.32.4.32.4.32.4.3 方案(Solution)(Solution)(Solution)(Solution)和项目(Project)(Project)(Project)(Project) 一个应用(Application)可能包括一个或多个可执行程序,例如,学生信息管理系统,可 能包括客户端程序和服务器端程序,所有这些可执行程序的集合叫做一个应用解决方案。为 了生成一个可执行程序,可能需要有一个或多个文件,例如,一般需要一个窗体文件,有时 还需要一个资源文件,若干图形或图像文件。所有这些文件的集合叫一个项目,因此项目是 为了创建一个可执行程序所必需的所有的文件的集合。而一个方案中可能包括多个项目。为 了方便管理项目和项目所在的方案,Visual Studio.Net 为开发人员提供了解决方案资源管 理器窗口(图2.4.3)。它可以为我们显示一个方案的树形结构,以及它所包含的项目及项目 中的文件。 一个项目一般要放在一个文件夹中,例如上边的例子,项目 e2 的所有文件都在文件夹 53 e2 中,共有两个文件夹和 8个文件,它们的用途如下: � bin 文件夹:包含 debug 子文件夹,存储生成带调试信息的可执行 C#程序。 � obj 文件夹:包含编译过程中生成的中间代码。 � AssemblyInfo.cs:创建项目自动添加。包含各种属性设置,例如,项目最终创建的 可执行文件或 DLL 文件中的信息,如标题、描述、公司名等。一般用工具修改该 程序,不要直接修改。 � Form1.cs:窗体文件,程序员一般只修改该文件。 � Form1.resx:资源文件。程序员用集成环境提供的工具修改,不要直接修改。 � e2.suo:解决方案用户选项文件,记录用户关于解决方案的选项。 � e2.csproj:项目文件,记录用户关于项目的选项。 � e2.sln:解决方案文件。 为了以后重新用 Visual Studio.Net 打开该解决方 案,必须保存除了两个文件夹以外的所有文件,实际上, 由于文件夹 e2 不太大,可以保存整个 e2 文件夹。如果 重新开始一个解决方案,首先用菜单项文件/关闭解决 方案,关闭当前项目,再新建一个项目。为了用 Visual Studio.Net 修改以前的程序,必须打开保存的项目文件 (扩展名为 csproj),或者使用菜单项文件/打开项目,打 开保存的项目,同时打开项目所在的解决方案。 图2.4.3 习题习题习题习题 (1)Windows 应用程序和 dos 程序有那些不同。 (2)以键盘操作为例说明什么是事件驱动。 (3)那些 Windows 操作系统提供了.NET 框架类库,那些提供了 API。 (4)运行 C#程序,应首先安装那些软件。 (5)定义一个和应用程序同生命周期的变量,该变量应定义在何处,说明该变量的使用范围。 (6)在窗体中增加一个控件,应如何操作,集成环境增加了那些代码。 (7)为控件增加事件函数,应如何操作,集成环境增加了那些代码。 (8)如何为窗体文件增加一个方法,说明该方法的使用范围。 54 第三章 常用控件和类的使用 Visual Studio.Net(简称 VS.NET)使用控件(组件)设计 Windows 应用程序。将VS.NET 工具箱窗口中的控件放到窗体中,使用属性窗口改变控件的属性,或在程序中用语句修改属 性,为控件增加事件函数,完成指定的功能。 3.13.13.13.1 控件通用属性控件通用属性控件通用属性控件通用属性 大部分控件,例如 Label、Button、TextBox 等,都是 Control 类的派生类。Control 类中 定义了这些派生类控件通用的一组属性和方法,这些属性是: � Name:控件的名称,区别控件类不同对象的唯一标志,例如建立一个 Button 控件类对 象,可用如下语句,Button button1=new Button(),那么 Name属性的值为 button1。 � Location:表示控件对象在窗体中的位置。本属性是一个结构,结构中有两个变量,x 和y,分别代表控件对象左上角顶点的 x和y坐标,该坐标系以窗体左上角为原点,x 轴向左为正方向,y轴向下为正方向,以像素为单位。修改 Location,可以移动控件的 位置,例如:button1.Location=new Point(100,200)语句移动按钮 button1 到新位置。 � Left 和Top:属性值等效于控件的 Location 属性的 X 和Y。修改 Left 和Top,可以移 动控件的位置,例如:button1.Left=100语句水平移动按钮 button1。 � Size:本属性是一个结构,结构中有两个变量,Width 和Height 分别代表控件对象的宽 和高,例如可用语句 button1.Size.Width=100 修改 Button 控件对象 button1 的宽。 � BackColor:控件背景颜色。 � Enabled:布尔变量,为 true表示控件可以使用,为 false表示不可用,控件变为灰色。 � Visible:布尔变量,为 true控件正常显示,为 false控件不可见。 � Modifier:定义控件的访问权限,可以是 private、public、protected 等。默认值为 private。 � Cursor:鼠标移到控件上方时,鼠标显示的形状。默认值为 Default,表示使用默认鼠标 形状,即为箭头形状。 3.23.23.23.2 Form Form Form Form 类类类类 Form 类是.Net 系统中定义的窗体类(WinForm),它属于 System.Windows.Forms 名字空 间。Form 类对象具有 Windows应用程序窗口的最基本功能。它可以是对话框、单文档或多 文档应用程序窗口的基类。Form 类对象还是一个容器,在 Form 窗体中可以放置其它控件, 例如菜单控件,工具条控件等等,还可以放置子窗体。 1.1.1.1. Form Form Form Form 类常用属性 � AutoScroll:布尔变量,表示窗口是否在需要时自动添加滚动条。 � FormBorderStyle:窗体边界的风格,如有无边界、单线、3D、是否可调整等。 � Text:字符串类对象,窗体标题栏中显示的标题。 � AcceptButton:记录用户键入回车时,相当于单击窗体中的那个按钮对象。 � CanceButton:记录用户键入 ESC 键时,相当于单击窗体中的那个按钮对象。以上两个 55 属性多用于对话框,例如打开文件对话框,用户键入回车,相当于单击确定按钮。 � MaxiMizeBox:窗体标题栏右侧最大化按钮是否可用,设置为 false,按钮不可用。 � MiniMizeBox:窗体标题栏右侧最小化按钮是否可用,设置为 false,按钮不可用。如果 属性 MaxiMizeBox 和MiniMizeBox 都设置为 false,将只有关闭按钮。在不希望用户改 变窗体大小时,例如对话框,将两者都设置为 false。 2. Form Form Form Form 类常用方法 � Close():窗体关闭,释放所有资源。如窗体为主窗体,执行此方法,程序结束。 � Hide():隐藏窗体,但不破坏窗体,也不释放资源,可用方法 Show()重新打开。 � Show():显示窗体。 3. Form Form Form Form 类常用事件 � Load:在窗体显示之前发生,可以在其事件处理函数中做一些初始化的工作。 3.33.33.33.3 标签标签标签标签(Label)(Label)(Label)(Label)控件控件控件控件 标签控件用来显示一行文本信息,但文本信息不能编辑,常用来输出标题、显示处理结 果和标记窗体上的对象。标签一般不用于触发事件。 1.1.1.1. LabelLabelLabelLabel控件常用属性 � Text:显示的字符串 � AutoSize:控件大小是否随字符串大小自动调整,默认值为 false,不调整。 � ForeColor:Label 显示的字符串颜色。 � Font:字符串所使用的字体,包括所使用的字体名,字体的大小,字体的风格等等,具 体修改方法见下边的例子。 2.2.2.2. 例子 e3_3e3_3e3_3e3_3:我的第一个程序 下面的例子在窗口中显示一行文本,该例虽然简单,但包括了用Visual Studio.Net 建立 C# Windows应用程序的基本步骤。具体实现步骤如下: (1)建立一个新项目,生成一个空白窗体(Form1),见图2.4.2A。可以用属性窗口(图2.4.2B 中图)修改窗体的属性,例如修改 Form1 的属性 Text,可以修改窗体的标题。用鼠标拖 动窗体的边界小正方形,可以修改窗体打开时的初始大小。 (2)双击工具箱窗口(图 2.4.2B 左图)中 Windows 窗体类型下的 Label 条目,在窗体 Form1 放置 一个 Label 控件。该控件用来显示一行文本。可以用鼠标拖放 Label 到窗体的任意位置, 并可拖动 Label 边界改变控件的大小。 (3)选中 Label 控件,在属性窗口中找到属性 text,把它的值由“Label1”修改为“我的第一个 程序”。接着在属性窗口中选中 Font属性,单击 Font 属性右侧的标题为…的按钮,打开 对话框,在对话框中可以修改 Label 控件显示字符串的字体名称和字号等,也可以单击 Font 属性左边的+号,在出现的子属性中编辑。编辑完成后,单击 Font 属性左边的-号, 隐藏 Font 的子属性。修改 ForeColor 属性可以修改 Label 控件显示字符串的颜色。这是 在设计阶段修改属性。 (4)编译,运行,可以看到窗口中按指定字体大小和颜色 显示:我的第一个程序。运行效果如右图。 (5)保存项目。生成一个可执行程序需要多个文件,这些 文件组成一个项目。一般把一个项目存到一个子目录 中。单击文件/存所有文件菜单项,保存所有文件。 (6) 关掉 VS.NET,再启动。用文件/打开项目菜单项打开刚才关闭的项目文件(扩展名为 sln)。 应能看到刚才关闭的设计界面。必须打开项目,才能完成编译工作。 56 3.43.43.43.4 按钮按钮按钮按钮(Button)(Button)(Button)(Button)控件控件控件控件 用户单击按钮,触发单击事件,在单击事件处理函数中完成相应的工作。 1.1.1.1. ButtonButtonButtonButton 控件的常用属性和事件 � 属性 Text:按钮表面的标题 � 事件 Click:用户单击触发的事件,一般称作单击事件。 2.2.2.2. 例子 e3_4e3_4e3_4e3_4 本例说明如何用程序修改属性,如何使用方法,增加事件函数。该例在窗口中显示一行 文字,增加 2个按纽,单击标题为红色的按纽把显示的文本颜色改为红色,单击标题为黑色 的按纽把显示的文本颜色改为黑色。实现步骤如下: (1)继续上例,放三个 Button 控件到窗体,修改属性 Text, 使标题分别为红色,黑色,退出。设计好的界面如右图。 (2)选中标题为红色的按纽,打开事件窗口(见图 2.4.2B 右 图),显示该控件所能响应的所有事件,其中左侧为事件 名称,右侧为事件处理函数名称,如果为空白,表示还 没有事件处理函数,选中 Click 事件,双击右侧空白处,增加单击(Click)标题为红色的 按钮的事件处理函数如下: private void button1_Click(object sender, System.EventArgs e) { label1.ForeColor=Color.Red;//运行阶段修改属性 }//注意 label1 是控件的名字(label 的Name属性),用它来区分不同的控件。 (3)单击(Click)标题为黑色的按纽的事件处理函数如下: private void button2_Click(object sender, System.EventArgs e) { label1.ForeColor=Color.Black;} (4)单击(Click)标题为退出的按纽的事件处理函数如下: private void button3_Click(object sender, System.EventArgs e) { Close();} Close()为窗体(Form)的方法,作用是关闭注窗体。由于关闭了主窗体,程序也就结束了。 注意,引用窗体的方法和属性时可不用指定对象名,换句话讲,如不指定属性或方法的对象 名,默认为窗体的属性或方法。而使用其它组件的属性及方法要指明所属组件对象,例如 label1.ForeColor=Color.Red; (5)编译,运行,单击标题为红色的按纽,窗体显示字符串颜色变为红色,单击标题为黑色 的按纽,窗体显示字符串颜色变为黑色,单击标题为退出的按纽,结束程序。 3.53.53.53.5 事件处理函数的参数事件处理函数的参数事件处理函数的参数事件处理函数的参数 事件处理函数一般有两个参数,第一个参数(object sender)为产生该事件的对象的属 性Name 的值,例如上例单击标题为红色的按钮,第一个参数 sender 的值为 button1。如上 例标题为红色的按钮和标题为黑色的按钮使用同一个单击事件处理函数,其事件处理如下: private void button1_Click(object sender,System.EventArgs e) { if(sender==button1) label1.ForeColor=Color.Red; else label1.ForeColor=Color.Black; 57 } 事件处理函数第二个参数(System.EventArgs e)代表事件的一些附加信息,事件不同, 所代表的信息也不相同,例如在后边的例子中可以看到,按下鼠标的事件处理函数中,e.X 和 e.Y 分别为发生事件时鼠标位置的 x 坐标和 y 坐标,e.Button 表示用户单击了鼠标那个键, 如为 MouseButtons.Left,表示单击了鼠标左键。 为了使这两个按钮使用相同的单击事件处理函数,首先为标题为红色的按钮增加单击事 件处理函数,即是上边的代码,事件函数名称为:button1_Click。选中标题为黑色的按钮, 打开事件窗体(见图 2.4.2B 右图),选中 Click 事件,从其右侧下拉列表中选择事件处理函 数为 button1_Click,这样两个按钮就使用相同的单击事件处理函数了。 3.6 文本框文本框文本框文本框(TextBox)(TextBox)(TextBox)(TextBox)控件控件控件控件 TextBox 控件是用户输入文本的区域,也叫文本框。 1.1.1.1. TextBox TextBox TextBox TextBox 控件属性和事件 � 属性 Text:用户在文本框中键入的字符串 � 属性 MaxLength:单行文本框最大输入字符数。 � 属性 ReadOnly:布尔变量,为 true,文本框不能编辑。 � 属性 PasswordChar:字符串类型,允许输入一个字符,如输入一个字符,用户在文本框 中输入的所有字符都显示这个字符。一般用来输入密码。 � 属性 MultiLine:布尔变量,为 true,多行文本框,为 false,单行文本框。 � 属性 ScrollBars:MultiLine=true时有效,有4种选择:=0,无滚动条,=1,有水平滚动 条,=2,有垂直滚动条,=3,有水平和垂直滚动条。 � 属性 SelLength:可选中文本框中的部分或全部字符,本属性为所选择的文本的字符数。 � 属性 SelStart:所选中文本的开始位置。 � 属性 SelText:所选中的文本 � 属性 AcceptsReturn:MultiLine=true时有效,布尔变量,为 true,键入回车,换行,为 false,键入回车键,相当于单击窗体中的默认按钮。 � 事件 TextChanged:文本框中的字符发生变化时,发出的事件。 2.2.2.2. 例子 e3_6e3_6e3_6e3_6 本例要求用户在编辑框中输入两个乘数,单击按钮 把相乘的结果在编辑框中显示出来。 (1)建立一个新的项目。放四个 Label 控件到窗体,Text 属性分别为:被乘数,乘数,积,*,=。 (2)放三个 textBox 控件到窗体,属性 Name 从左到右分 别为:textBox1、textBox2、textBox3,属性 Text 都为空。 (3)放三个 Button 控件到窗体,Text属性分别修改为求积,清空,退出。设计的界面如上图。 (4)标题为求积的按钮的单击事件处理函数如下: private void button1_Click(object sender, System.EventArgs e) { float ss,ee; ss=Convert.ToSingle(textBox1.Text); ee=Convert.ToSingle(textBox2.Text); textBox3.Text=Convert.ToString(ss*ee); } (5)标题为清空的按钮的单击事件处理函数如下: 58 private void button2_Click(object sender,System.EventArgs e) { textBox1.Text=""; textBox2.Text=""; textBox3.Text=""; } (6)标题为退出的按钮的单击事件处理函数如下: private void button3_Click(object sender, System.EventArgs e) { Close();} (7) 编译,运行,在文本框 textBox1,textBox2 分别输入 2和3,单击标题为求积的按纽, textBox3 中显示 6,单击标题为清空的按钮,三个文本框被清空,单击标题为退出的按 纽,结束程序。 3.73.73.73.7 ConvertConvertConvertConvert类类类类 Convert 类中提供了一些静态方法,用来把一种类型数据转换为另一种类型数据。例如, Convert.ToSingle(textBox1.Text) 把字符串textBox1.Text 转换为单浮点数。 Convert.ToString(3.14)把单浮点数 3.14 转换为字符串。其它转换函数还有:ToInt、 ToInt16 等等。 3.83.83.83.8 单选按钮单选按钮单选按钮单选按钮((((RadioButtonRadioButtonRadioButtonRadioButton))))和和和和GroupBox GroupBox GroupBox GroupBox 控件控件控件控件 RadioButton 是单选按钮控件,多个 RadioButton 控件可以为一组,这一组内的 RadioButton 控件只能有一个被选中。GroupBox 控件是一个容器类控件,在其内部可放其它 控件,表示其内部的所有控件为一组,其属性 Text 可用来表示此组控件的标题。例如把 RadioButton 控件放到 GroupBox 控件中,表示这些 RadioButton 控件是一组。有一些特性是 互斥的,例如性别,选择这类特性可用 RadioButton 和GroupBox 控件。 1.1.1.1. GroupBox GroupBox GroupBox GroupBox 控件常用属性 GroupBox 控件常用属性只有一个,属性 Text,指定 GroupBox 控件顶部的标题。 2.2.2.2. RadioButtonRadioButtonRadioButtonRadioButton控件属性和事件 � 属性 Text:单选按钮控件旁边的标题。 � 属性 Checked:布尔变量,为 true表示按钮被选中,为 false表示不被选中。 � 事件 CheckedChanged:单选按钮选中或不被选中状态改变时产生的事件。 � 事件 Click:单击单选按钮控件时产生的事件。 3.3.3.3. 例子 e3_8e3_8e3_8e3_8 该例用 RadioButton 控件修改 Label 控件字符串的字体为: 宋体、黑体、楷体。具体实现步骤如下: (1) 建立一个新的项目。 (2) 放Label 控件到窗体,属性 Text=“不同的字体”。字体为宋体。 (3) 放GroupBox 控件到窗体,其属性 Text=“选择字体”。 (4) 放三个RadioButton 控件到GroupBox中,其属性 Text分别为: 宋体、黑体、楷体。宋体 RadioButton 控件的属性 Checked=true。设计好的界面如右图。 (5) 为三个 RadioButton 控件的CheckedChanged 事件增加事件处理函数如下: 59 private void radioButton1_CheckedChanged(object sender, System.EventArgs e) { if(radioButton1.Checked) label1.Font=new Font("宋体",label1.Font.Size); }//label1显示的字体变为宋体,字体大小不变 private void radioButton2_CheckedChanged(object sender, System.EventArgs e) { if(radioButton2.Checked) label1.Font=new Font("黑体",label1.Font.Size); } private void radioButton3_CheckedChanged(object sender, System.EventArgs e) { if(radioButton3.Checked) label1.Font=new Font("楷体_GB2312",label1.Font.Size); } (6) 编译,运行,单击 RadioGroup1 中的三个 RadioButton 按钮,可以改变字体。注意三个 按钮只能选一个,既只能选一种字体。考虑一下,是否可用 Click 事件。 3.93.93.93.9 FontFontFontFont类类类类 Font 类有两个构造函数:第一个是 new Font(字体名称,字号),例如,label1.Font=new Font("黑体",9),用法还可参考例 e3_8。第二个是 new Font(字体名称,字号,字体风格), 其中第三个参数是枚举类型,具体定义如下: enum FontStyle{ Regular =0,//正常字体 Bold =1,//黑体 Italic =2,//斜体 BoldItalic =3,//黑斜体 Underline =4,//下划线,5=黑体下划线,6=斜体下划线,7=黑斜体下划线 Strikeout =8}//删除线,9=黑体删除线,10=斜体删除线,依此类推。 例如修改标签控件字体为斜体: label1.Font=new Font("黑体",9,label1.Font.Style|FontStyle.Italic); 或者:label1.Font=new Font("黑体",9,label1.Font.Style|(FontStyle)2); 修改标签控件字体不为斜体: label1.Font=new Font("黑体",9,label1.Font.Style&~FontStyle.Italic); 或者:label1.Font=new Font("黑体",9,label1.Font.Style&(FontStyle)(~2)); 用法还可参考例 e3_11。 3.103.103.103.10 多选框多选框多选框多选框(CheckBox)(CheckBox)(CheckBox)(CheckBox)控件控件控件控件 CheckBox 是多选框控件,可将多个 CheckBox 控件放到 GroupBox 控件内形成一组,这 一组内的 CheckBox 控件可以多选,不选或都选。可用来选择一些可共存的特性,例如一个 人的爱好。 1.1.1.1. CheckBoxCheckBoxCheckBoxCheckBox控件属性和事件 � 属性 Text:多选框控件旁边的标题。 � 属性 Checked:布尔变量,为 true表示多选框被选中,为 false不被选中。 60 � 事件 Click:单击多选框控件时产生的事件。 � 事件 CheckedChanged:多选框选中或不被选中状态改变时产生的事件。 2.2.2.2. 例子 e3_10Ae3_10Ae3_10Ae3_10A 在窗口中增加 2个CheckBox 控件,分别用来选择是否爱好音乐和是否爱好文学,用鼠 标单击 CheckBox 控件,改变爱好选择,用 Label 控件显示所选择的爱好。实现步骤如下: (1)建立新项目。放Label 控件到窗体,属性 Text=“你的爱好是:”。 (2)放GroupBox 控件到窗体,属性 Text=“爱好”。放两个 CheckBox 控件到GroupBox 中,属 性Text 分别为:音乐、文学。设计界面如下图。 (3)标题为音乐的多选框控件的CheckedChanged 事件处理函数如下: private void checkBox1_CheckedChanged(object sender, System.EventArgs e) { String text1="你的爱好是:"; if(checkBox1.Checked) text1=text1+checkBox1.Text; if(checkBox2.Checked) text1+=checkBox2.Text; label1.Text=text1; } (4)将标题为文学的多选框控件的CheckedChanged 事件处理函数,设 置为标题为音乐的多选框控件的CheckedChanged 事件处理函数,具体步骤见 3.5 节。 (5)编译,运行。选中音乐将在标签控件中显示:你的爱好是:音乐,再选中文学显示:你 的爱好是:音乐文学,…。 3.3.3.3. 例子 e3_10Be3_10Be3_10Be3_10B 该例同上例,但按选中音乐和文学的顺序在标签中显示爱好,实现步骤如下: (1)建立一个新项目。为 Form1 类增加私有变量 String s="你的爱好是:"。 (2)放Label 控件、GroupBox 控件、两个 CheckBox 到窗体,属性设置同上例。 (4)标题为音乐的多选框控件 CheckBox1 的CheckedChanged 事件处理函数如下: private void checkBox1_CheckedChanged(object sender,System.EventArgs e) { int n=s.IndexOf("音乐");//s中有字符串"音乐"吗?n=-1表示没有 if(n==-1)//n=-1,表示上次没选此项,此次选中,应增加"音乐" s+="音乐"; else//否则,表示上次已选此项,此次不选中,应删除"音乐" s=s.Remove(n,2); label1.Text=s; } (5)标题为文学的多选框控件 CheckBox2的CheckedChanged 事件处理函数如下: private void checkBox2_CheckedChanged(object sender,System.EventArgs e) { int n=s.IndexOf("文学");//s中有字符串"文学"吗?=-1表示没有 if(n==-1)//=-1,表示上次没选此项,此次选中,应增加"文学" s+="文学"; else//否则,表示上次已选此项,此次不选中,应删除"文学" s=s.Remove(n,2); label1.Text=s; } (6)编译,运行。选中音乐在标签中显示:你的爱好是:音乐,再选中文学显示:你的爱好 61 是:音乐文学,不选音乐显示:你的爱好是:文学,再选音乐显示:你的爱好是:文学音乐。 3.113.113.113.11 列表选择控件列表选择控件列表选择控件列表选择控件(ListBox)(ListBox)(ListBox)(ListBox) 列表选择控件列出所有供用户选择的选项,用户可从选项中选择一个或多个选项。 1.1.1.1. 列表选择控件的常用属性、事件和方法 � 属性 Items:存储 ListBox 中的列表内容,是 ArrayList 类对象,元素是字符串。 � 属性 SelectedIndex:所选择的条目的索引号,第一个条目索引号为 0。如允许多选,该 属性返回任意一个选择的条目的索引号。如一个也没选,该值为-1。 � 属性 SelectedIndices:返回所有被选条目的索引号集合,是一个数组类对象。 � 属性 SelectedItem:返回所选择的条目的内容,即列表中选中的字符串。如允许多选, 该属性返回选择的索引号最小的条目。如一个也没选,该值为空。 � 属性 SelectedItems:返回所有被选条目的内容,是一个字符串数组。 � 属性 SelectionMode:确定可选的条目数,以及选择多个条目的方法。属性值可以使: none( 可以不选或选一个)、one( 必须而且必选一个)、MultiSimple( 多选)或 MultiExtended(用组合键多选)。 � 属性 Sorted:表示条目是否以字母顺序排序,默认值为 false,不允许。 � 方法 GetSelected():参数是索引号,如该索引号被选中,返回值为 true。 � 事件 SelectedIndexChanged:当索引 号(即选项)被改变时发生的事件。 2.2.2.2. 例子 e3_11e3_11e3_11e3_11 根据列表框的选择,为字符串加下 划线、删除线、变斜体、变粗体。具体 步骤如下: (1) 建立一个新项目。放Label 控件到 窗体,其属性 Text=“字体风格”。 (2) 放置ListBox 控件到窗体中,属性 Name=listBox1。选中ListBox 控 件,在属性窗口中,单击 Items 属性右侧的三个小点,打开字符串集合编辑器对话框, 在其中输入四项:粗体、斜体、下划线、删除线,注意每一项要换行。如上图。 (3) 设置列表选择控件 ListBox1属性 SelectionMode 为MultiExtended,允许多选。 (4) 为列表选择控件的事件 SelectedIndexChenged 增加事件处理函数如下: private void listBox1_SelectedIndexChanged(object sender, System.EventArgs e) { int Style=0,k=1;//Style=0正常字体,1=黑体,2=斜体,3=黑斜体等,参见3.9节 for(int i=0;i1)//如果是双击左键 label1.Text="X:"+e.X.ToString()+",Y:"+e.Y.ToString(); } (4) 编译,运行,分别在指定区域和区域外双击鼠标左键,看一下效果。分别在指定区域和 区域外双击鼠标右键,看一下效果。 3.213.213.213.21 快捷菜单快捷菜单快捷菜单快捷菜单(ContextMenu)(ContextMenu)(ContextMenu)(ContextMenu) 使用过 Word 程序的人都知道,在其程序窗口的不同位置单击右键,会出现不同弹出菜 单,这个弹出菜单叫快捷菜单,这节介绍如何在应用程序中增加快捷菜单。快捷菜单和主菜 单的属性、事件和方法基本一致,只是快捷菜单没有顶级菜单项,因此这里就不多介绍了。 例子 e3.21 例子在窗口中显示一行字符串,加入两个按纽,单击按纽 button1 把字符串变为红色, 单击按纽 button2 把字符串变为黑色。为两个按钮建立快捷菜单,快捷菜单中有 2个菜单项, 单击菜单项把字符串变为红色或黑色。为窗体建立快捷菜单,菜单中仅有 1个退出菜单项, 单击退出菜单项,退出程序。具体实现步骤如下: (1) 建立一个新项目。放Label 控件到窗体。 (2) 放2个Button 控件到窗体,标题(属性 Text)分别为红色,黑色。 (3) 标题为红色的按钮的单击事件处理函数如下: private void button1_Click(object sender, System.EventArgs e) { label1.ForeColor=Color.Red;} (4) 标题为黑色的按钮的单击事件处理函数如下: private void button2_Click(object sender, System.EventArgs e) { label1.ForeColor=Color.Black;} (5) 放2个ContextMenu 控件到窗体,属性 Name 分别为 contextMenu1,contextMenu2。 (6) 选中 contextMenu1控件,在菜单编辑器中增加两个标题分别为红色和黑色的菜单项, 它们的单击事件处理函数分别是单击红色按钮和单击黑色按钮的事件处理函数。 (7) 选中 contextMenu2控件,在菜单编辑器中增加标题为退出的菜 单项,并为其增加单击事件处理函数,为事件处理函数增加语 句:Close(); (8) 将红色按钮和黑色按钮的属性 ContextMenu 指定为 contextMenu1。Form 的属性 ContextMenu 指定为 contextMenu2。 (9) 编译,运行,右击标题为红色的按钮,快捷菜单 contextMenu1 打开,单击快捷菜单中标题为红色的菜单项,将使窗体显示的字符串颜色变为红色,右 击标题为黑色的按钮,快捷菜单 contextMenu1打开,单击快捷菜单中标题为黑色的菜 71 单项,将使窗体显示的字符串颜色变为黑色,右击窗体,快捷菜单 contextMenu2打开, 单击快捷菜单中标题为退出的菜单项,将退出应用程序。运行效果如上图。 3.223.223.223.22 综合例子:计算器综合例子:计算器综合例子:计算器综合例子:计算器 具体步骤如下: (1) 建立一个新项目。Form 属性MaxiMizeBox=false,属性 MiniMizeBox=false。属性 FormBorderStyle=FixedDialog,窗口不能修改大小。 (2) 放textBox 控件到窗体,属性 Name=textBox1,属性 Text="0",属性 ReadOnly=true。 (3) 增加 10 个Button 控件,前 9个按钮属性 Name 分别为:Button1-Button9,最后一个为 Button0,属性 Text 分别为:1、2、3、4、5、6、7、8、9、0。 (4) 增加 7个Button 控件,属性 Name 分别为:btn_dot、btn_equ、btn_add、btn_sub、btn_mul、 btn_div、btn_C,属性 Text 分别为:.、=、+、-、*、/、C。设计界面如下图。 (5) 控件 Button0 单击事件处理函数如下: private void button0_Click(object sender, System.EventArgs e) { if(sender==button0) append_num(0); if(sender==button1) append_num(1); if(sender==button2) append_num(2); if(sender==button3) append_num(3); if(sender==button4) append_num(4); if(sender==button5) append_num(5); if(sender==button6) append_num(6); if(sender==button7) append_num(7); if(sender==button8) append_num(8); if(sender==button9) append_num(9); } (6) 为Form1类增加方法如下: public void append_num(int i) { if(textBox1.Text!="0") textBox1.Text+=Convert.ToString(i); else textBox1.Text=Convert.ToString(i); } (7) 将Button1-Button9 的单击事件处理函数设定为 Button0 单击事件处理函数。 (8) 为标题为.按钮增加事件处理函数如下: private void btn_dot_Click(object sender, System.EventArgs e) { int n=textBox1.Text.IndexOf("."); if(n==-1)//如果没有小数点,增加小数点,否则不增加 textBox1.Text=textBox1.Text+"."; } (9) 编译,单击数字按钮,在 textBox1 可以看到输入的数字,也可以输入小数。 (10) 先实现加法,必须定义一个浮点类型变量 sum,初始值为 0,记录部分和。 (11) 输入了第一个加数,然后输入任一运算符(+、-、*、\或=),应首先清除编辑框中显示 的第一个加数,才能输入第二个加数。为实现此功能,必须定义一个布尔变量 blnClear, 72 初始值为 false,表示输入数字或小数点前不清除编辑框中显示,输入运算符(+、-、*、 \或=)后,blnClear=true,表示再输入数字或小数点先清除编辑框中显示。修改前边程 序,输入数字或小数点前,要判断变量 blnClear,如为 true,清除编辑框中显示的内容 后,再显示新输入的数字或小数点,同时修改 blnClear=false。为此修改 append_num 方法如下: public void append_num(int i) { if(blnClear)//如果准备输入下一个加数,应先清除textBox1显示内容 { textBox1.Text="0";//阴影部分为新增语句 blnClear=false; } if(textBox1.Text!="0") textBox1.Text+=Convert.ToString(i); else textBox1.Text=Convert.ToString(i); } (12) 修改 btn_dot_Click 方法如下: private void btn_dot_Click(object sender, System.EventArgs e) { if(blnClear) //如果准备输入下一个数,应先清除textBox1显示内容 { textBox1.Text="0";//阴影部分为新增语句 blnClear=false; } int n=textBox1.Text.IndexOf("."); if(n==-1)//如果没有小数点,增加小数点,防止多次输入小数点 textBox1.Text=textBox1.Text+"."; } (13) 如果计算 1+2-3 的运算结果,先单击按钮 1,编辑框中显示 1,再单击按钮+,执行运算 sum=sum+1(注意此时 sum=0),显示 sum 到编辑框中(实际显示不变),记住此次输入的运 算符,这里为+号。单击按钮 2,编辑框中显示 2,再单击按钮-,按记录的运算符(这里是 +)计算 sum=sum+2,显示 sum 到编辑框中,记住此次输入的运算符,这里为-号,依此类 推。为实现此功能,必须定义一个字符串变量 strOper,记录输入的运算符,初始值为 "+",保证输入第一个运算符后,执行运算 sum=sum+第一个加数,由于初始 sum=0,也就 是sum=第一个加数。标题为+的按钮的单击事件处理函数如下: private void btn_add_Click(object sender, System.EventArgs e) { double dbSecond=Convert.ToDouble(textBox1.Text); if(!blnClear)//如果未输入第二个操作数,不运算 switch(strOper)//按记录的运算符号运算 { case "+": sum+=dbSecond; break; //在此增加其它运算符-、*、\代码 } if(sender==btn_add) strOper="+"; //在此增加运算符-、*、\、=代码 73 textBox1.Text=Convert.ToString(sum); blnClear=true; } (14) =号处理语句和+号处理基本一致,修改标题为+按钮的事件函数如下: private void btn_add_Click(object sender, System.EventArgs e) { double dbSecond=Convert.ToDouble(textBox1.Text); if(!blnClear)//如果未输入第二个操作数,不运算 switch(strOper)//按记录的运算符号运算 { case "+": sum+=dbSecond; break; //在此增加运算符-、*、\代码 } if(sender==btn_add) strOper="+"; if(sender==btn_equ)//为=号处理增加的语句 strOper="="; textBox1.Text=Convert.ToString(sum); blnClear=true; } 将btn_equ 按钮的单击事件函数设定为+按钮的单击事件函数。 (15) 为标题为 C按钮增加事件函数如下: private void btn_C_Click(object sender, System.EventArgs e) { textBox1.Text="0"; sum=0; blnClear=false; strOper="+"; } (16) 请读者自己补上减法,乘法,除法运算的语句。 习题:习题:习题:习题: (1) 在窗口中显示一行字符串,加入两个按纽,单击按纽 1把字符串改为红色,单击按纽 2 把字符串改为黑色。使字符串为红色时红色按纽不能使用,字符串为黑色时黑色按纽不 能使用。(提示:可以修改按钮的属性 Enabled 为false 使其不能使用。) (2) 将上题改为用按扭修改字体的大小,分别为大字体和小字体。(参见 3.9 节) (3) 加一文本框控件和一按纽,单击按纽将文本框控件输入内容显示标签控件上。(提示: 单击按钮事件处理函数中加语句 label1.Text=textBox1.Text)。 (4) 修改上题,使文本框控件和标签控件文本同步显示(提示:文本框控件的 TextChanged 事件处理函数中加语句 label1.Text=textBox1.Text)。 (5) 加一文本框控件和一按纽,单击按纽将文本框控件输入的文本中选中的内容显示在标签 控件上(提示:单击按钮事件处理函数中加语句 label1.Text=textBox1.SelText。) (6) 加一文本框控件和一按纽,单击按纽将文本框控件输入的文本的字符、选中的内容的字 符数和选中的内容的开始位置显示在标签控件上。 74 (7) 用控件 RadioButton 选择性别,把选择的结果用 Label 控件显示出来。 (8) 例子 e3_8 中如改为响应单击事件 Click,可能出现什么问题? (9) 用控件 ComboBox 修改标签控件字体的大小。(用属性 Item 在下拉列表中输入大小)。 (10)放ListBox 控件到窗体中,属性 Name=listBox1。列表框有三项分别为:苹果,梨子, 香蕉。允许多选。标签控件同步显示 ListBox 控件所做的选择。提示:为ListBox 控件 的SelectedIndexChenged 事件增加事件函数, label1.Text="所选择的是:"; for(int i=0;i=richTextBox1.Text.Length)//已查到文本底部 { MessageBox.Show("已到文本底部,再次查找将从文本开始处查找", "提示",MessageBoxButtons.OK); FindPostion=0; return; }//下边语句进行查找,返回找到的位置,返回-1,表示未找到,参数1是要找的字符串 //参数2是查找的开始位置,参数3是查找的一些选项,如大小写是否匹配,查找方向等 FindPostion=richTextBox1.Find(FindString, FindPostion,RichTextBoxFinds.MatchCase); if(FindPostion==-1)//如果未找到 { MessageBox.Show("已到文本底部,再次查找将从文本开始处查找", "提示", MessageBoxButtons.OK); FindPostion=0;//下次查找的开始位置 } else//已找到 { richTextBox1.Focus();//主窗体成为注视窗口 FindPostion+=FindString.Length; }//下次查找的开始位置在此次找到字符串之后 } (31)为在前边定义的 Form1主窗体的 ReplaceRichTextBoxString 方法增加语句如下: public void ReplaceRichTextBoxString(string ReplaceString) { if(richTextBox1.SelectedText.Length!=0)//如果选取了字符串 richTextBox1.SelectedText=ReplaceString;//替换被选的字符串 } (32)编译,运行,输入若干字符,选中菜单项: 编辑/查找和替换,打开对话框,注意该对 话框可以在不关闭的情况下,转到主窗体, 并且总是在其它窗体的前边,因此它是一 个典型的非模式对话框。在对话框中输入 查找和替换的字符,单击标题为查找下一 个的按钮,可以找到所选字符,并被选中, 单击标题为替换所选字符按钮,可以看到 查找到的字符被替换。运行效果如右图: 85 4.74.74.74.7 提示用户保存修改的文件提示用户保存修改的文件提示用户保存修改的文件提示用户保存修改的文件 用户在新建文本,打开其他文本或者退出文本编辑器时,如果编辑内容发生了改变,应 提示用户是否保存已修改的文本内容。因此就需要在用户关闭当前文件前,弹出提示对话框, 提醒用户是否保存当前文件。本节实现此功能。 4.7.14.7.14.7.14.7.1 对话框 MessageBoxMessageBoxMessageBoxMessageBox 使用 MessageBox 可以打开一个对话框,用法如下: MessageBox.Show(this,"要保存当前更改吗?","保存更改吗?", MessageBoxButtons.YesNoCancel,MessageBoxIcon.Question); 第一个参数是父窗口,第二个参数是提示信息,第三个参数是标题栏的内容,第四个参 数是有那些按钮,此例有 YES,NO,CANCEL 按钮,还可以使用 AbortRetryIgnore(中止、重试 和忽略按钮)、OK(确定按钮)、OKCancel(确定和取消按钮)、RetryCance(重试和忽略按钮)、 YesNo(是和否按钮)等选项。第五个参数是使用那一个图标,此例是一个问号图标,还可以 是Asterisk、Error、Exclamation、Hand、Stop、Warning 等图标,如为 None 则无图标。 返回值是 System.Windows.Forms.DialogResult 变量,代表用户按了那一个按钮。如果返回 值是 System.Windows.Forms.DialogResult.Yes,则表示按了 YES 键,表示要存修改的文件。 如果返回值是 System.Windows.Forms.DialogResult.Cancel,按Cancel 键,表示忽略此次 操作。如果返回值是 System.Windows.Forms.DialogResult.No,则表示按了 No 键,表示不 存修改的文件。以上设计的对话框 MessageBox 如下图: 4.7.24.7.24.7.24.7.2 提示用户保存修改的文件的实现 (33)为Form1 类增加一个 bool 变量 bSave=false 作为标记,用来跟踪 RichTextBox 中文本内 容改变的情况。在程序开始运行、建立和打开一个新文件时,bSave=false,表示不必保 存当前文本。RichTextBox 控件有一个 TextChanged 事件,当文本发生改变的时候,这 个事件就会被激活,在该事件处理函数中,使 bSave=true。 (34)首先增加一个函数,其功能是判断是否需要将已修改的文件存盘,之所以要增加这个函 数是因为有三处要用到此函数。该函数返回 true,表示继续操作,该函数返回 false,表 示忽略此次操作,该函数定义如下: public bool IfSaveOldFile() { bool ReturnValue=true; if(bSave) { System.Windows.Forms.DialogResult dr; dr=MessageBox.Show(this,"要保存当前更改吗?","保存更改吗?", 86 MessageBoxButtons.YesNoCancel,MessageBoxIcon.Question); switch(dr) { case System.Windows.Forms.DialogResult.Yes://单击了yes按钮,保存修改 bSave=false; if(s_FileName.Length!=0) richTextBox1.SaveFile(s_FileName,RichTextBoxStreamType.PlainText); else { SaveFileDialog saveFileDialog1=new SaveFileDialog(); saveFileDialog1.Filter="纯文本文件(*.txt)|*.txt|所有文件(*.*)|*.*"; saveFileDialog1.FilterIndex=1; if(saveFileDialog1.ShowDialog()==DialogResult.OK) { s_FileName=saveFileDialog1.FileName; richTextBox1.SaveFile(saveFileDialog1.FileName, RichTextBoxStreamType.PlainText); } } ReturnValue=true; break; case System.Windows.Forms.DialogResult.No://单击了no按钮,不保存 bSave=false; ReturnValue=true; break; case System.Windows.Forms.DialogResult.Cancel://单击了Cancel按钮 ReturnValue=false; break; } } return ReturnValue; } (35)在新建和打开菜单项的事件处理函数的头部增加如下语句: if(!IfSaveOldFile())//如果忽略,退出。 return; (36)修改存文件菜单项单击事件处理函数如下: private void menuItemSaveFile_Click(object sender, System.EventArgs e) { if(s_FileName.Length!=0) { bSave=false;//阴影为增加的语句 richTextBox1.SaveFile(s_FileName,RichTextBoxStreamType.PlainText); } else menuItemSaveAs_Click(sender,e); } (37)修改另存为菜单项单击事件处理函数如下: private void menuItemSaveAs_Click(object sender, System.EventArgs e) { SaveFileDialog saveFileDialog1=new SaveFileDialog(); 87 saveFileDialog1.Filter="纯文本文件(*.txt)|*.txt|所有文件(*.*)|*.*"; saveFileDialog1.FilterIndex=1; if(saveFileDialog1.ShowDialog()==DialogResult.OK) { s_FileName=saveFileDialog1.FileName; richTextBox1.SaveFile(saveFileDialog1.FileName, RichTextBoxStreamType.PlainText); bSave=false;//阴影为增加的语句 } } (38)为RichTextBox控件 TextChanged 事件增加事件处理函数如下: private void richTextBox1_TextChanged(object sender, System.EventArgs e) { bSave=true;} (39)为Form1窗体 Closing 事件是在关闭窗口之前发送的事件,此时,窗体中的控件还存在, 还可以保存修改的内容,也可以不退出。增加它的事件处理函数如下: private void Form1_Closing(object sender,System.ComponentModel.CancelEventArgs e) { if(!IfSaveOldFile()) e.Cancel=true;//不退出 } (40)编译,运行,键入若干字符,选中菜单项新建或打开,或退出,将看到提示信息,问是 否保存修改的文件。有三种选择:存文件,不存文件,忽略此次操作,试验单击不同按 钮的效果。 4.84.84.84.8 打印和打印预览打印和打印预览打印和打印预览打印和打印预览 打印和打印预览是一个编辑器必须具有的功能,本节介绍实现打印和打印预览的方法。 一般要实现如下菜单项:打印、打印预览、页面设置。 4.8.14.8.14.8.14.8.1 PrintDocument PrintDocument PrintDocument PrintDocument 类 PrintDocument 组件是用于完成打印的类,其常用属性、方法和事件如下: � 属性 DocumentName:字符串类型,记录打印文档时显示的文档名(例如,在打印状态 对话框或打印机队列中显示)。 � 方法 Print:开始文档的打印。 � 事件 BeginPrint:在调用 Print 方法后,在打印文档的第一页之前发生。 � 事件 PrintPage:需要打印新的一页时发生。 � 事件 EndPrint:在文档的最后一页打印后发生。 若要打印,首先创建 PrintDocument 组件的对象。然后使用页面设置对话框 PageSetupDialog 设置页面打印方式,这些设置作为要打印的所有页的默认设置。使用打印 对话框 PrintDialog 设置对文档进行打印的打印机的参数。在打开两个对话框前,首先设置 对话框的属性 Document 为指定的PrintDocument 类对象,修改的设置将保存到 PrintDocument 组件对象中。第三步是调用 PrintDocument.Print 方法来实际打印文档。当 调用该方法后,引发下列事件:BeginPrint、PrintPage、EndPrint。其中每打印一页都引 发PrintPage 事件,打印多页,要多次引发 PrintPage 事件。完成一次打印,可以引发一个 88 或多个 PrintPage 事件。 程序员应为这 3个事件编写事件处理函数。BeginPrint 事件处理函数进行打印初始化, 一般设置在打印时所有页的相同属性或共用的资源,例如所有页共同使用的字体、建立要打 印的文件流等。PrintPage 事件处理函数负责打印一页数据。EndPrint 事件处理函数进行打 印善后工作。这些处理函数的第 2个参数 System.Drawing.Printing.PrintEventArgs e提 供了一些附加信息,主要有: � e.Cancel:布尔变量,设置为 true,将取消这次打印作业。 � e.Graphics:所使用的打印机的设备环境,参见第五章。 � e.HasMorePages:布尔变量。PrintPage 事件处理函数打印一页后,仍有数据未打 印,退出事件处理函数前设置 HasMorePages=true,退出 PrintPage 事件处理函数 后,将再次引发 PrintPage 事件,打印下一页。 � e.MarginBounds:打印区域的大小,是Rectangle 结构,元素包括左上角坐标:Left 和Top,宽和高:Width 和Height。单位为 1/100 英寸。 � e.MarginBounds:打印纸的大小,是 Rectangle 结构。单位为 1/100 英寸。 � e.PageSettings:PageSettings 类对象,包含用对话框 PageSetupDialog 设置的页 面打印方式的全部信息。可用帮助查看 PageSettings 类的属性。 下边为这3个事件编写事件处理函数,具体步骤如下: (41)在最后一个 using 语句之后增加语句: using System.IO; using System.Drawing.Printing; (42)本例打印或预览 RichTextBox 中的内容,增加变量:StringReader streamToPrint=null。 如果打印或预览文件,改为:StreamReader streamToPrint,流的概念参见第六章。增 加打印使用的字体的变量:Font printFont。 (43)放PrintDocument 控件到窗体,属性 name 为printDocument1。 (44)为printDocument1增加 BeginPrint 事件处理函数如下: private void printDocument1_BeginPrint(object sender, System.Drawing.Printing.PrintEventArgs e) { printFont=richTextBox1.Font;//打印使用的字体 streamToPrint=new StringReader(richTextBox1.Text);//打印richTextBox1.Text }//如预览文件改为:streamToPrint=new StreamReader("文件的路径及文件名"); (45)printDocument1的PrintPage事件处理函数如下。streamToPrint.ReadLine()读入一段数 据,可能打印多行。本事件处理函数将此段数据打印在一行上,因此方法必须改进。 private void printDocument1_PrintPage(object sender, System.Drawing.Printing.PrintPageEventArgs e) { float linesPerPage=0;//记录每页最大行数 float yPos=0;//记录将要打印的一行数据在垂直方向的位置 int count=0;//记录每页已打印行数 float leftMargin=e.MarginBounds.Left;//左边距 float topMargin=e.MarginBounds.Top;//顶边距 string line=null;//从RichTextBox中读取一段字符将存到line中 //每页最大行数=一页纸打印区域的高度/一行字符的高度 linesPerPage=e.MarginBounds.Height/printFont.GetHeight(e.Graphics); //如果当前页已打印行数小于每页最大行数而且读出数据不为null,继续打印 while(countp2.Y? p1.Y:p2.Y;//计算矩形右下角点的 y坐标 right=p1.X>p2.X? p1.X:p2.X;//计算矩形右下角点的 x坐标 return(new Rectangle(left,top,right-left,bottom-top));//返回矩形 } (5) 为PictureBox 事件 OnMouseDown、OnMouseUp、OnMouseMove 增加事件处理函数如下: private void pictureBox1_MouseDown(object sender,//鼠标按下事件处理函数 System.Windows.Forms.MouseEventArgs e) { if(e.Button==MouseButtons.Left) { StartPoint.X=e.X; StartPoint.Y=e.Y; EndPoint.X=e.X; EndPoint.Y=e.Y; mark=true; } } private void pictureBox1_MouseMove(object sender,//鼠标移动事件处理函数 System.Windows.Forms.MouseEventArgs e) { if(mark) { Rectangle r1=MakeRectangle(StartPoint,EndPoint);//计算重画区域 r1.Height+=2; r1.Width+=2;//区域增大些 pictureBox1.Invalidate(r1);//擦除上次鼠标移动时画的图形,r1为擦除区域 pictureBox1.Update();//立即重画,即擦除 Graphics g=pictureBox1.CreateGraphics(); Pen pen1=new Pen(Color.Black); EndPoint.X=e.X; EndPoint.Y=e.Y; r1=MakeRectangle(StartPoint,EndPoint);//计算椭圆新位置 127 g.DrawEllipse(pen1,r1);//在新位置画椭圆 } } private void pictureBox1_MouseUp(object sender,//鼠标抬起事件处理函数 System.Windows.Forms.MouseEventArgs e) { Pen pen1=new Pen(Color.Black); EndPoint.X=e.X; EndPoint.Y=e.Y; Rectangle r1=MakeRectangle(StartPoint,EndPoint); bitG.DrawEllipse(pen1,r1); mark=false; pictureBox1.Image=bits; } (6) 运行,在 PictureBox 控件中拖动鼠标可以画圆或椭圆。 5.125.125.125.12 图像剪贴板功能图像剪贴板功能图像剪贴板功能图像剪贴板功能 Windows 中的许多程序都支持剪贴板功能。通过剪贴板可以完成显示数据的剪贴(Cut), 复制(Copy),粘贴(Paste)等功能。剪贴板可以理解为一块存储数据的公共区域,用户可 以用菜单项复制(Copy)或剪贴(Cut)把数据放入到剪贴板中,当本任务或其它任务要用 剪贴板中的数据时,可以用菜单项粘贴(Paste)从剪贴板中把数据取出。存入剪贴板中的 数据,可以是字符,位图,或者其它格式数据。在图形模式下使用剪贴板包括如下动作:选 定剪贴区域、剪贴(Cut)、复制(Copy)、粘贴(Paste)等。使过画图程序的读者都知道, 在使用剪贴和复制前,必须首先选定剪贴或复制区域,首先按一个按钮,通知程序要选定剪 贴或复制区域,然后在要选定区域的左上角按下鼠标左键,拖动鼠标画出一个矩形,抬起鼠 标后显示一个矩形既为要选定剪贴或复制区域。剪贴或复制后,矩形自动消失。下面详细介 绍实现以上功能的方法。 5.12.15.12.15.12.15.12.1 剪贴区域选定 剪贴区域选定的方法和前边章节中拖动鼠标方法绘制椭圆或圆的方法基本一样,只是在 这里绘制的是矩形,而且在鼠标抬起时,不把矩形存入 PictureBox 控件属性 Image 引用的 位图对象中,仅仅记录矩形的位置。请读者自己实现此功能。 5.12.25.12.25.12.25.12.2 剪贴板复制功能的实现 假定已选定剪贴区域,例如为区域 Rectangle(10,10,50,50),把此区域的图形或图像放到 剪贴板中。具体实现步骤如下: (1) 新建项目。放PictureBox 控件到窗体,修改属性 Dock=Fill。属性 Name=pictureBox1, 修改属性 Image,使其显示一幅图。 (2) 把Mainmenu 控件放到主窗体中。增加顶级菜单项:编辑,属性 Name=menuItemEdit。为 编辑弹出菜单增加菜单项:复制、剪贴、粘贴。属性Name 分别为 menuItemCopy、 128 menuItemCut、menuItemPaste。 (3) 为窗体菜单项复制增加单击事件函数如下: private void menuItemCopy_Click(object sender, System.EventArgs e) { Bitmap myBitmap=new Bitmap(pictureBox1.Image); Rectangle cloneRect=new Rectangle(10,10,50,50); System.Drawing.Imaging.PixelFormat format=myBitmap.PixelFormat; Bitmap cloneBitmap=myBitmap.Clone(cloneRect,format); Clipboard.SetDataObject(cloneBitmap); } (4) 运行,选中复制菜单项,复制图形到剪贴板。打开画图程序,选中画图程序粘贴菜单项, 可以看到被复制的图形能正确粘贴到画图程序中。 5.12.35.12.35.12.35.12.3 剪贴板剪贴功能的实现 (5) 剪贴是先复制,再把选中区域图形清除,菜单项剪贴单击事件处理函数如下: private void menuItemCut_Click(object sender,System.EventArgs e) { menuItemCopy_Click(sender,e);//调用复制菜单项单击事件处理函数 Bitmap bits=new Bitmap(50,50);//建立位图对象,宽和高为选中区域大小 Graphics g=Graphics.FromImage(bits);//得到位图对象的 Graphics 类的对象 g.Clear(Color.White);//用白色清除位图对象中的图像 Bitmap myBitmap=new Bitmap(pictureBox1.Image); g=Graphics.FromImage(myBitmap); g.DrawImage(bits,10,10,50,50); pictureBox1.Image=myBitmap;//位图对象在 pictureBox1 中显示,即清除 } (6) 运行,选中剪贴菜单项,拷贝图形到剪贴板,原位置图形被清空为白色,最小化后再最 大化,图形不变。打开画图程序,选中画图程序粘贴菜单项,可以看到被拷贝的图形能 正确粘贴到画图程序中。 5.12.45.12.45.12.45.12.4 剪贴板粘贴功能的实现 (7) 为窗体菜单项粘贴增加单击事件函数如下: private void menuItemPaste_Click(object sender, System.EventArgs e) { IDataObject iData=Clipboard.GetDataObject();//得到剪贴板对象 if(iData.GetDataPresent(DataFormats.Bitmap))//判断剪贴板有无位图对象 { Bitmap bits=(Bitmap)iData.GetData(DataFormats.Bitmap);//得到剪贴板位图 Bitmap myBitmap=new Bitmap(pictureBox1.Image); Graphics g=Graphics.FromImage(myBitmap); g.DrawImage(bits,30,30); pictureBox1.Image=myBitmap;//位图对象在pictureBox1中显示 } } (8) 运行画图程序,选中拷贝菜单项,拷贝图形到剪贴板。运行自己编制的程序,选中粘贴 129 菜单项,可以看到画图程序中被拷贝的图形能正确粘贴到自己编制的程序中。 (9) 画图程序粘贴后,能用鼠标移动粘贴的图形,现实现此功能。放PictureBox 控件到窗 体,属性 Name=pictureBox2,属性 Visable=false。这里把粘贴后的图形放到 PictureBox2中,使其可以移动。为Form1 类增加变量:bool mark=false;int x=0,y=0; 为pictureBox2控件的事件 OnMouseDown,OnMouseUp,OnMouseMove 增加事件函数如下: private void pictureBox2_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e) { mark=true; x=e.X; y=e.Y; } private void pictureBox2_MouseMove(object sender, System.Windows.Forms.MouseEventArgs e) { if(mark) { int x1,y1; x1=e.X-x; y1=e.Y-y; pictureBox1.Invalidate();//擦除上次鼠标移动时画的图形 pictureBox1.Update();//立即重画,即擦除 pictureBox2.Left+=x1; pictureBox2.Top+=y1; x=e.X;//原来没有此 2句 y=e.Y; } } private void pictureBox2_MouseUp(object sender, System.Windows.Forms.MouseEventArgs e) { mark=false;} (10)修改窗体菜单项粘贴单击事件函数如下: private void menuItemPaste_Click(object sender,System.EventArgs e) { IDataObject iData=Clipboard.GetDataObject(); if(iData.GetDataPresent(DataFormats.Bitmap)) { Bitmap bit=(Bitmap)iData.GetData(DataFormats.Bitmap); pictureBox2.Width=bit.Width;//阴影为修改部分 pictureBox2.Height=bit.Height; pictureBox2.Image=bit; pictureBox2.Top=pictureBox1.Top; pictureBox2.Left=pictureBox1.Left; pictureBox2.Parent=pictureBox1; pictureBox2.Visible=true; } } (11)在pictureBox1控件任意位置单击鼠标,表示已将粘贴图像拖到指定位置,需将粘贴图 像粘贴到 pictureBox1 控件。为 pictureBox1 控件的事件 OnMouseDown 增加事件函数如下: 130 private void pictureBox1_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e) { if(pictureBox2.Image!=null&&pictureBox2.Visible) { Bitmap bits=new Bitmap(pictureBox2.Image); Bitmap myBitmap = new Bitmap(pictureBox1.Image); Graphics g=Graphics.FromImage(myBitmap); g.DrawImage(bits,pictureBox2.Left,pictureBox2.Top); pictureBox1.Image=myBitmap;//位图对象在 pictureBox1 中显示 pictureBox2.Visible=false; } } (12)运行画图程序,选中拷贝菜单项,拷贝图形到剪贴板。运行自己编制的程序,选中粘贴 菜单项,可以看到画图程序中被拷贝的图形能正确粘贴到自己编制的程序中。拖动被拷 贝的图形,使其运动到指定位置,在 pictureBox2 外,单击鼠标右键,图形固定到指定 位置。 5.135.135.135.13 图像的处理图像的处理图像的处理图像的处理 本节介绍图像的处理的最基础知识,要想深入了解这方面的知识,还要读这方面的专著。 5.13.15.13.15.13.15.13.1 图像的分辨力 例子 e5.13.1:将原图形的分辨率降低 16 倍,其方法是将原图形分成 4*4 的图形块, 这16 个点的颜色都置成这 16 个点中某点的颜色,例如 4*4 的图形块左上角的颜色。 (1) 新建项目。放两个 PictureBox 控件到窗体,属性 Name分别为 pictureBox1,pictureBox2, 修改 pictureBox1属性 Image,使其显示一幅图。 (2) 放Button 控件到窗体,为其增加事件处理函数如下: private void button1_Click(object sender,System.EventArgs e) { Color c; int i,j,size,k1,k2,xres,yres; xres=pictureBox1.Image.Width;//pictureBox1 显示的图像的宽 yres=pictureBox1.Image.Height;//pictureBox1 显示的图像的高 size=4; pictureBox2.Width=xres;//令pictureBox2和pictureBox1 同宽,同高。 pictureBox2.Height=yres; Bitmap box1=new Bitmap(pictureBox1.Image); Bitmap box2=new Bitmap(xres,yres); for(i=0;i<=xres-1;i+=size) { for(j=0;j<=yres-1;j+=size) { c=box1.GetPixel(i,j); for(k1=0;k1<=size-1;k1++) { for(k2=0;k2<=size-1;k2++) box2.SetPixel(i+k1,j+k2,c); 131 } } } pictureBox2.Image=box2; } (3) 运行,单击按钮,在 PictureBox2 中可以看到分别率低的图形。 5.13.25.13.25.13.25.13.2 彩色图像变换为灰度图像 例子 e5.13.2:本例把彩色图像变换为灰度图像。其方法是将原彩色图形每一个点的颜 色取出,求出红色、绿色、蓝色分量的平均值,即(红色+绿色+蓝色)/3,作为这个点的红色、 绿色、蓝色分量,这样就把彩色图像变成了灰度图像。具体步骤如下: (1) 新建项目。放两个 PictureBox 控件到窗体,属性 Name分别为 pictureBox1,pictureBox2, 修改 pictureBox1属性 Image,使其显示一幅图。 (2) 放Button 控件到窗体,为其增加事件函数如下: private void button1_Click(object sender, System.EventArgs e) { Color c; int i,j,xres,yres,r,g,b; xres=pictureBox1.Image.Width; yres=pictureBox1.Image.Height; pictureBox2.Width=xres; pictureBox2.Height=yres; Bitmap box1=new Bitmap(pictureBox1.Image); Bitmap box2=new Bitmap(xres,yres); for(i=0;i170,d2=255。变换的效果是增强了对比度。具体步骤如下: (1) 新建项目。放两个 PictureBox 控件到窗体,属性 Name分别为 pictureBox1,pictureBox2, 修改 pictureBox1属性 Image,使其显示一黑白幅图。 (2) 放Button 控件到窗体,为其增加事件函数如下: private void button1_Click(object sender, System.EventArgs e) { Color c; int i,j,xres,yres,m; xres=pictureBox1.Image.Width; yres=pictureBox1.Image.Height; pictureBox2.Width=xres; pictureBox2.Height=yres; Bitmap box1=new Bitmap(pictureBox1.Image); Bitmap box2=new Bitmap(xres,yres); int[] lut=new int[256]; for(i=0;i<85;i++) lut[i]=0; for(i=85;i<=170;i++) lut[i]=(i-85)*3; for(i=171;i<256;i++) lut[i]=255; for(i=0;i0) { OutFile.Write(buffer,0,n); i++; OutFile.Close(); } else { mark=false; } } inFile.Close(); } 合并文件方法,参数 1时要合并的文件名,参数 2是被拆分的文件名,文件名后边有序 号,要将这些文件合并到一起,参数 3是要合并的文件数。合并方法定义如下: void MergeFile(string f1,string f2,int f2Num) { FileStream OutFile=new FileStream(f1,FileMode.OpenOrCreate,FileAccess.Write); int n,l; for(int i=0;i 这是我的第一个网页 以文件名 Test.htm存到 d:/asp 文件夹中,查找本机 IP地址,假如为:202.204.206.98。 在另一台计算机中打开浏览器,输入地址:http://202.204.206.98/Test.htm,在浏览器中应能 看到文字:这是我的第一个网页。由此可知,必须把创建的网页拷贝到宿主目录下。从本机 访问宿主目录下的网页时,可以在浏览器的 URL(地址)处键入:http://localhost/网页以宿 主目录为根目录的全路经。其他人访问时,可以在浏览器的 URL(地址)处键入:http://IP 地址或域名/网页以宿主目录为根目录的全路经。 也可以用建虚拟目录的方法,存放网页,具体步骤如下: (1) 打开控制面板,双击管理工具图标。 (2) 双击 Internet 服务管理器图标。 (3) 单击计算机名前的+号。 (4) 右击默认 Web 站点,出现弹出菜单,选择菜单项新建/虚拟目录,按向导步骤选择 D:/asp 为新虚拟目录。此时,在默认 Web站点下将会出现设定的虚拟目录,此目录允许其他人 访问。 请读者想一想,如把文件 Test.htm拷贝到新虚拟目录,在另一台计算机中如何访问此网 页。 9.29.29.29.2 HTML 标记语言 网页使用 HTML 标记语言写成。HTML 标记是用特殊的 ASCII 字符来定义网页中的格 式,字体等等特点。由于各种系统中,都支持 ASCII 字符标准,不同系统中的浏览器都可 以解释这些 ASCII 标记,将其所表示的网页在屏幕中显示。这样,不同的系统都可以使用 统一 ASCII 标记,访问其它系统中的网页。 如果有 WEB 服务器时,用 IE浏览器显示网页前,必须把网页拷贝到宿主目录下,例 如Windows2000 的IIS 服务器为\InetPub\wwwroot。访问此网页时,在浏览器的 URL(地址) 处键入此网页的 URL,回车即可。 为了在没有 WEB 服务器时,能用 IE浏览器显示静态网页,首先将 IE的默认网页设置 为about:blank。然后运行 IE,在地址栏中输入网页文件的路径,回车即可。修改 IE的默认 网页为 about:blank 及IE的默认编辑器为写字板的具体办法是:单击 IE菜单工具/Internet选 项,打开 Internet 选项对话框。在常规页中的主页选择使用空白页,程序页中,HTML 编辑 器选择 Windows Notepad。 169 9.2.19.2.19.2.19.2.1 HTML HTML HTML HTML 标记 HTML 标记是用特殊的 ASCII 字符来定义网页中的格式、字体、字符对齐方式等特点。 其格式为:<标记名称>被控制的文字。其中,<标记名称>为开始标记,为结束标记,一般用来定义被控制的文字的格式或字体等。例如,下列标记使被控制 的文字 ASP.NET 中间对齐:
ASP.NET
9.2.29.2.29.2.29.2.2 HTML HTML HTML HTML 文件结构 一个网页文件的最基本 HTML 标记结构如下: 显示在浏览器标题栏中的文字 这里是网页的内容 表示网页文件的开始,表示网页文件的的结束,网页的所有内容都应在 这两个标记之间。…之间可以设定网页的一些信息, 之间的 文字显示在 IE浏览器的标题栏中。…之间时网页的主题内容。由此可以看出, 在HTML 语法中,HTML 标记可以嵌套,一个 HTML 标记可以包含另一个 HTML 标记, 但标记的嵌套不能是交错的。下边是一个实际例子: 这是我的第一个网页
这是我的第一个网页。
其中<BR> 标记表示换行, 注意,仅键入回车时不能在网页中换行的。
注意空格的用法, 键入四个空格,网页中只有一个空格。
增加 4个空格,    是4个空格
网页中不区分大小写,不同浏览器显示的效果可能不同
注意,特殊符号的显示:<,>,",&,©,®
170 网页中可以增加一些标记,例如: ,<base>,<link>,<isindex>,<meta> </head> <body> HTML 文件的正文写在这里… </body> </html> 这些标记的用法见以下各节。 9.2.39.2.39.2.39.2.3 语言字符集的信息 语言字符集的信息用法见下边的网页。主要是选用网页使用的字符集,gb2312 是中文 字符集。 <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=gb2312"> <title>meta 的使用 meta的使用。 9.2.49.2.49.2.49.2.4 背景色彩和文字色彩 设置背景和文字色的语法如下:其 中 , bgcolor,text,link,alink,vlink 的意义见书,#表示颜色,可用 rrggbb 表示,rr,gg,bb分别是表示 红色,绿色,蓝色的 16 进制数,例如,红色为:ff0000。下例设置背景色为红色。 设置背景色为红色 设置背景色为红色。 171 9.2.59.2.59.2.59.2.5 页面空白 可以设置页面的上下左右边距,单位为像素。例如设置左边距为 20 个像素格式如下: 9.2.69.2.69.2.69.2.6 显示一幅图 下例在网页中显示一幅图。注意的写法。其中,file://是文件协议,用来选择本地 文件。一般网页中用 http://,即超文本协议,此时要求运行 WEB 服务器。 增加一幅图形! 9.2.79.2.79.2.79.2.7 超级链接 浏览网页时,当鼠标变为手形时,单击,可以打开另一个网页,下边的例子在当前窗口 打开另一个网页。 链接的例子 这是一个 链接的例子 点一下带下划线的文字! 9.2.89.2.89.2.89.2.8 超级链接在新窗口打开另一网页 如下例子: 172 开一个新窗口例子 开一个新窗口! 将光标移到”开一个新窗口”,光标变为手型,单击”开一个新窗口”,将打开另一个网页窗口, 即window.htm网页。window.htm网页如下: New Window Form HyperLink Dir 这是新开的窗口!

返回原先窗口开新一个(浏览器)窗口"一处

9.2.99.2.99.2.99.2.9 标尺线 标尺线标记格式为:
。其中,align 为标尺线对齐方式,可以取 left,right,center。Color 为标尺线颜色。Noshade设定标尺线 为没有三维效果的实心黑线。Size 为标尺线的厚度。width 为标尺线的宽度,可以是像素值, 也可以相对于窗口的百分比。实际使用见下例: 标尺线




173




9.2.109.2.109.2.109.2.10 网页中标题的字体 网页中一共有 6种标题字体,见下例: 标题字体

这是 1级标题!

这是 2级标题!

这是 3级标题!

这是 4级标题!

这是 5级标题!
这是 6级标题!
其中,标题字体是黑体,每个标题自动插入一个空行。 9.2.119.2.119.2.119.2.11 网页中正文字体 网页中正文字体只有 7种大小,可以用如下方法设置正文字体: 正文字体 字号为 7,最大字体 字号为 6 字号为 5 字号为 4 字号为 4 174 字号为 2 字号为 1,最小字体 注意字号只能是 1到7。 9.2.129.2.129.2.129.2.12 斜体、粗体字符及为字体增加下划线,删除线 可以使字体为斜体、粗体,为字体增加下划线,删除线。见下例: 标记内的字为黑体
标记内的字为斜体
标记内的字有下划线
标记内的字等宽,例如 w和i等宽
H2 ,2为下标
A2 ,2为上标
标记内的字加删除线
标记内的字加删除线
9.2.139.2.139.2.139.2.13 字体标记的组合使用 见下例: 字体标记的组合使用 今天 天气真好! 175 9.2.149.2.149.2.149.2.14 字体的颜色 见下例: Black& Red 9.2.159.2.159.2.159.2.15 客户端字体 每个操作系统都提供若干字体,网页可以使用操作系统提供的字体,下边的网页显示如 何使用这些字体。

客户端字体事例

Arial...
Comic Sans MS...
Courier...
Courier New...
Modern...
MS Serif...
MS-DOS CP 932...
Roman...
Script...
Small Fonts...
Symbol...
Times Roman...
Times New Roman...
WingDings...
9.2.169.2.169.2.169.2.16 网页中控件的概念 WEB 服务器为了和用户进行交互,必须解决如下问题:首先,用户应能输入一些数据, 例如,要查询的条件,用户登录的信息等等。第二,这些数据用什么方法传到 WEB 服务器。 176 第三,WEB 服务器用那个程序响应用户。为了实现以上功能,必须使用窗体控件,也叫表 单控件 form,Visual Studio.net 中叫 WebForm。同时还需要一些其它控件,例如,编辑框控 件,列表框控件,下拉列表框控件和按钮等。可以用 HTML 标记语言定义控件。IE浏览器 看到这些标记,就把它显示为相应的控件。控件有许多属性,也可以用 HTML 标记语言表 示,每个属性用空格分开,用属性名称=属性值格式定义。 9.2.179.2.179.2.179.2.17 窗体控件和其它控件的使用 窗体控件是其它控件的容器,所有其它控件都要放到窗体控件中。Form控件的基本语 法如下:
其中,
定义Form控件,action是WEB服 务器用响应用户程序的URL,method="POST"是数据用POST方法传到WEB服务器,也可以是 get方法。用户用交互控件输入信息。是 一个按钮控件,其类型为submit,按钮标题为:提交,代表此控件的name为B1。当用户单击 此按钮,form控件将把控件内的所有交互控件中的数据用POST方法,传递给action指定WEB 服务器的程序处理。也是一个按钮, 用户单击此按钮,将清空form控件内的所有交互控件。 form控件内可以增加交互控件,交互控件的使用标记,其语法如下: 各个属性意义如下: align为对齐方式,可以取top、middle和bottom。 Type为控件类型,可以是:type="submit"为提交按钮。type="reset"为全部重写按钮。 type="text"为编辑框。type="password"为口令编辑框。type="checkbox"为复选框。 type="radio"为单选框。type="image"为图形。type="hidden"用户不能看到,可以用来传 递网页的一些默认的数据。 [check]只有在type="checkbox"或"radio"时使用,表示缺省被选中。 Maxlength属性在type="text"编辑框时表示最大字符长度。 Name属性代表此控件的名字。 Size属性在type="text"编辑框时表示编辑框最大宽度。 Value 如为按钮,则为标题,为编辑框,则为缺省内容。此值将按 Name/Value 对的形 式传递给 WEB 服务器。 9.2.189.2.189.2.189.2.18 例子:文字输入和密码输入 Form 的例子 177
您的姓名:
您的主页的网址:
密码:

9.2.199.2.199.2.199.2.19 用FontPage FontPage FontPage FontPage 做网页的例子,使用复选框和单选按 钮 用FontPage 或Dreamerware 要比使用记事本编辑网页方便的多。下边用 FontPage 做网 页的例子。下边是具体步骤: (1) 运行 FontPage。 (2) 单击菜单文件/新建/网页。出现新建对话框,选择常规页的普通网页,单击确定按 钮,创建一个新网页。 (3) 单击菜单插入/表单/表单,增加一个新表单(form)。在表单中已有一个提交按钮和一 个全部重写按钮。将光标移到提交按钮前后回车,增加 form的尺寸。 (4) 右击提交按钮,在弹出菜单中选中菜单项表单域属性,出现按钮属性对话框,可修 改名称(Name属性)属性,值/标签(value 属性)=提交查询内容,按钮类型为提交。按 确定按钮。用同样方法修改另一个按钮的标题为重置。 (5) 将光标移到第一行,单击菜单插入/表单/复选框。右击复选框,在弹出菜单中选中 菜单项表单域属性,出现复选框属性对话框,修改名称(Name属性)属性=水果 1。 在复选框后键入字符:Banana。用同样方法增加另两个复选框,注意初始状态修改 为选中。 (6) 用FontPage创建的网页文件如下: New Page 2

Banana

Apple

Orange

178

9.39.39.39.3 ASP.NETASP.NETASP.NETASP.NET技术基础技术基础技术基础技术基础 设计静态网页有两种方法:一种是使用记事本,用 HTML 语言编写,另一种是使用可 视化工具,如 FrontPage,Dreamware等。显然,使用可视化工具要方便快捷的多。以往设 计服务器端动态网页时,例如 ASP,往往只能使用记事本一行一行的写,效率很低。程序 员迫切需要一种设计服务器端动态网页的可视化工具,能象使用 C#设计 Window 应用程序 一样设计动态网页,使用控件类、属性和事件等面向对象的概念。为了实现这个目的,引入 ASP.NET 服务器端控件概念。静态网页中一般有一个表单(Form),在表单中可以有多个控件, 例如,列表框、编辑框、按钮等等,通过这些控件,完成一定的功能。同样,ASP.NET 服 务器端控件首先引入运行在服务端 WebForm概念,在WebForm 中可以放入多个服务器端控 件,例如,列表框、编辑框、按钮等等,所有这些控件,都是.NET 框架类库中相应类的对 象,每个对象都有自己的属性、方法和事件。这些概念和编制 Windows应用程序相应的概 念基本相同。这些 ASP.NET 服务器端控件,也使用 HTML 标记描述,但这些服务器端控件 并不传送这些 HTML 标记给浏览器解释,而是由 Web 服务器负责解释,翻译为所有浏览器 都能解释的标准 HTML 标记后传送给浏览器解释,这样就极大地简化了服务器端动态网页 的设计,也保证了生成的网页的显示效果和浏览器无关。使用 ASP.Net 技术创建的服务器端 动态网页的扩展名为.aspx。 本节首先介绍 ASP.NET服务器端控件基本概念,然后介绍使用记事本编写 ASP.NET 动 态网页的方法,最后介绍如何使用 Visual Studio.NET 编写 ASP.NET 动态网页。 9.3.19.3.19.3.19.3.1 HTML HTML HTML HTML 服务器端控件 ASP.Net 中的 HTML 服务器端控件和标准的 HTML 控件有着对应关系,但功能更强大。可 以在程序中修改 HTML 服务器端控件的属性,能够在服务器端响应事件,支持数据绑定等。 例如增加一个 HTML 服务器端控件编辑框用如下 HTML 语句: 这里和标准的 HTML 控件的区别是增加了属性 RUNAT=”SERVER”。属性 ID是代表这 个控件的唯一标志,和Winndows应用程序中的控件属性 Name的意义是一样的。HTML 服 务器端控件是为了方便原来学习 HTML 或ASP 编写Web 应用程序的程序员而提供的。如果, 你以前是 Windows应用程序员,建议使用 Web 服务器端控件,这些控件不但功能更强大, 而使用上更象 Windows应用程序中的控件,因此学习更方便。因此这里就不介绍 HTML 服 务器端控件了。 179 9.3.29.3.29.3.29.3.2 WebWebWebWeb服务器端控件 在ASP.NET 系统中,除了常规的 HTML 控件外,还包括 Web 服务器端控件。同HTML 服务 器端控件一样,这些控件可以在程序中修改服务器端控件的属性,能够在服务器端响应事件, 支持数据绑定等。例如定义一个 Web 服务器端控件编辑框控件,方法如下: 服务器端控件不但功能更强大,而且和编制 Windows应用程序中的控件使用方法基本 一致,因此学习更方便。本书的所有例子都使用 Web服务器端控件。 9.3.39.3.39.3.39.3.3 WebWebWebWeb Form Form Form Form 的事件处理 象Windows应用程序一样,ASP.Net 应用程序也采用事件驱动的概念,用户对浏览器的 各种操作都被看作事件,事件产生后,Web 应用程序用事件处理函数响应事件。但 ASP.Net 的事件驱动和 Windows应用程序的事件驱动有着本质上的区别。Web应用程序的事件产生 后,由于事件处理程序在 Web服务器端,Web 应用程序把事件通过网络使用 HTTP协议由 浏览器传到 Web 服务器,在Web服务器执行事件处理程序,把运行结果转变为标准 HTML 标志的网页,传回浏览器。 在Web 事件处理机制中,每一次 Web 应用程序响应事件都会使得网页重新生成。事实上, 一旦服务器完成某一个网页的处理操作并将它传送至浏览器,则会随即移除该网页的所有信 息,也就是说,网页中定义的对象和变量在服务器端已不存在了,网页生命周期结束。当Web 应用程序再一次响应事件时,服务器又会将上述处理过程重做一次。基于此原因,我们说网 页是无状态的——即网页变量与控件的数据值并不会保留在服务器上。 因此,我们增加事件函数时,应考虑网络传播的速度的影响,不能象 Windows应用程 序那样,响应太多的事件。在网页中,每个控件都有属性 AutoPosBack,其值为 true,事件 才能自动调用事件处理函数,如果不希望响应该事件,应将该控件的属性 AutoPosBack 设为 false。 9.3.49.3.49.3.49.3.4 记事本编写 ASP.NET ASP.NET ASP.NET ASP.NET 动态网页 ASP.NET 中的服务器端控件也用 HTML 标记,但这些服务器端控件的 HTML 标记并不传送 给浏览器解释,而是由 Web 服务器负责解释,翻译为所有浏览器都能解释的标准 HTML 标记 后传送给浏览器解释。所有 ASP.NET 服务器端控件都要放到 Web 窗体(WebForm)中,Web 窗 体(WebForm)也由Web 服务器负责解释。下边是一个最简单的使用服务器端控件的例子: <%@ Page language="c#" %> 这是我的第一个 ASP.NET 网页

网页文件第一条语句表示网页中使用 C#语言。表示网页文件的开始,表 示网页文件的的结束,网页的所有内容都应在这两个标记之间。定义在标记和 之间的内容被分为三部分,第一部分:和之间可以设定网页的一些信息,之间的文字显示在 IE浏览器的标题栏中。第二部分:标记之间可以定义方法,变量或对象,language="c#"表示在此标记 之间定义的方法使用 C#语言,runat=server 表示在此标记之间定义的方法运行在 Web服务器 端,这里定义了两个方法,方法 Page_Load()是Web 服务器装载本网页时调用的方法,可以 在此方法中做一些初始化工作,方法 EnterBtn_Click()是"查看时间"按钮的事件函数。第三部 分:和之间是网页在浏览器中显示的内容。
标记定义 Web 窗体(WebForm),注意 runat=server 表示Web 窗体由 Web服务器解释。在 Web窗体中增加了两个控件对象,第一个是 Label 控件,asp:Label 表 示本控件是 Label 控件,id 相当 Windows应用程序中控件的 Name属性,用来区分不同对象, runat=server 表示次控件由 Web服务器解释,其余是设定属性值,注意不同属性用空格分隔。 第二个控件是按钮,请注意定义单击事件函数的方法。 将其以文件名 e1.aspx 存入 d:/asp 文件夹,如果 d:/asp 文件夹已被设定为 Web站点,可 以在 IE的地址栏输入:http://localhost/c411.aspx 后,看到这个网页。在浏览器端看不到这些 代码,用 IE菜单查看/源代码,可以看到用超文本编制的网页。 9.3.59.3.59.3.59.3.5 用VisualVisualVisualVisual Studio.NET Studio.NET Studio.NET Studio.NET 实现 ASP.NET ASP.NET ASP.NET ASP.NET 动态网页 用Visual Studio.NET 实现上节的例子。具体步骤如下: (1) 运Visaul C#后,则进入开始界面,选择新建项目。打开新建项目对话框,在项目类型中 选择 Visual C#项目,在模板中选择[ASP.NET Web 应用程序],指定项目项目放置的位置 为http://localhost/e1,这里 http://localhost 代表当前激活的宿主目录,即将本应 用的所有文件存入宿主目录下的文件夹 e1 中,点击“确定”,生成一个空白窗体 (WebForm)。用户可在窗体中放入控件。Visual Studio.NET 为我们建立了一个应用项目。 (2)向项目中添加控件需要使用[Toolbox]窗口,若看不到,可以用菜单视图/工具箱打开 这个窗口。 (3)先选中[Toolbox]窗口中[Web 窗体]类型下的[Label]条目,然后在设计的窗体中按 下鼠标左键并拖动鼠标,画出一个 Label 控件。该控件用来显示一行文本。 (4)使用[Properties]窗口修改 Label 控件的文本内容和文本字体属性。在右下侧属性窗口 中找到[text]属性,把它的值由“Label1”改为”现在的时间是:”;接着在属性窗口中找到 [Font]属性,选中 Font 所在的单元格,单击 Font 属性左边的“+”号,在出现的子属性中 181 编辑,可以修改 Label 控件中文本的字体和字号等属性。编辑完成后,单击变成“-”号的 方框隐藏Font的子属性;修改Label 控件的ForeColor属性,可以修改Label 中文本的颜色。 (5)从[Toolbox]窗口中选中一个 Button 控件到窗体,在[Properties]窗口中将按钮的[Text] 属性分别改为”查看时间”。 (6)为单击查看时间按钮事件(Click)函数增加语句(双击 Click 事件): private void Button1_Click(object sender, System.EventArgs e) { Label1.Text="现在的时间是:"+DateTime.Now; } (7)为Page_Load 事件函数增加语句: private void Page_Load(object sender, System.EventArgs e) { Label1.Text="现在的时间是:"+DateTime.Now; } (8) 单击工具栏中蓝色箭头按钮,运行,看一下效果。也可用浏览器看一下,地址为: http://localhost/e1/WebForm1.aspx。请仔细观察,每一步骤 Visual Studio.NET 都为 我们增加了什么语句。 9.3.69.3.69.3.69.3.6 CodeCodeCodeCode Behind Behind Behind Behind 技术 Code Behind 技术把界面设计代码和程序设计代码以不同的文件分开,对于代码的重复 使用,程序的调试和维护都是十分方便的。特别是在团队开发中,可以使不同人员编辑不同 文件,极大地提高了效率。Visual Studio.NET 使用了 Code Behind 技术,当我们使用 Visual Studio.Net 创建了一个 Web应用程序,将自动创建了两个文件,其中 ASP.NET Web网页文 件WebForm1.aspx如下: <%@ Page language="c#" Codebehind="WebForm1.aspx.cs" AutoEventWireup="false" Inherits="e2.WebForm1" %> WebForm1
语句<%@ Page language="c#" Codebehind="WebForm1.aspx.cs" AutoEventWireup="false" Inherits="e2.WebForm1" %>中的Codebehind="WebForm1.aspx.cs"表示网页的所有代码在文 件WebForm1.aspx.cs中,使设计网页的外观和设计代码分离,可同时进行设计,互不影响, 182 也是网页和代码的逻辑关系清楚,增加了易读性。代码文件 WebForm1.aspx.cs如下: using System; using System.Collections; using System.ComponentModel; using System.Data; using System.Drawing; using System.Web; using System.Web.SessionState; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.HtmlControls; namespace e2 { /// /// WebForm1 的摘要说明。 /// public class WebForm1 : System.Web.UI.Page { private void Page_Load(object sender, System.EventArgs e) { // 在此处放置用户代码以初始化页面 } #region Web Form Designer generated code override protected void OnInit(EventArgs e) { // // CODEGEN:该调用是 ASP.NET Web 窗体设计器所必需的。 // InitializeComponent(); base.OnInit(e); } /// /// 设计器支持所需的方法 - 不要使用代码编辑器修改 /// 此方法的内容。 /// private void InitializeComponent() { this.Load += new System.EventHandler(this.Page_Load); } #endregion } } 183 9.3.79.3.79.3.79.3.7 ASP.NET ASP.NET ASP.NET ASP.NET 和HTML HTML HTML HTML 兼容 任何一个静态网页只要把其扩展名修改为 aspx,在 ASP.NET 下仍可运行,运行效果和 以前相同。见下例,它是一个普通的静态网页。 学生选课系统

学生选课系统

姓名:

学号:

课程:

将其以文件名 c411.aspx存入 d:/asp 文件夹,如果 d:/asp 文件夹已被设定为 Web 站点, 可以在 IE的地址栏输入:http://localhost/c411.aspx 后,看到这个网页。 ASP.NET 的设计目标之一就是尽可能地保持和现有 ASP 页面的语法及运行库的兼容。 希望将现有 ASP 页面文件的扩展名改为.aspx,这些页面仍可以在 ASP.NET 中运行。在大多 数情况下该目标已经实现了,但一般要对某些基本代码作出修改,因为 ASP.NET 已不再支 持VBScript 了,而且 VB语言本身也发生了变化。 9.3.89.3.89.3.89.3.8 网页中使用 C#C#C#C#语句 在网页中,可以插入一些 C#语句,具体用法见下例: <% @ Page Language="C#"%>
<% for (int i=0;i<8;i++){ %> 这是我的第一个 ASP.NET 网页
<%}%>
184 在浏览器端看不到这些代码,用 IE菜单查看/源代码,可以看到用超文本编制的网页。 这样使用 C#语句,不是一个好的习惯,不建议使用。 185 第十章第十章第十章第十章 WebWebWebWeb服务器端控件服务器端控件服务器端控件服务器端控件 本章介绍常用的 Web 服务器端控件的属性、事件和方法,以及用 Web 服务器端控件编 制服务器端动态网页的方法。 10.110.110.110.1 常用的 常用的 常用的 常用的 ASP.NET ASP.NET ASP.NET ASP.NET 服务器端控件服务器端控件服务器端控件服务器端控件 10.1.110.1.110.1.110.1.1 Label Label Label Label 控件 Label 控件用如下方法定义: 或者 标签控件 下边介绍其常用的属性: � 属性 Text:显示的文本 � 属性 ForeColor 文本的颜色,颜色可以取:红色=System.Drawing.Color.Red。黑色= System.Drawing.Color.Black等等。 � 字体的属性:黑体为 Font.Bold=true,斜体为 Font.Italic=true 等等。 � 属性 BackColor:背景色 � 属性 id:相当 Windows应用程序中控件的 Name属性,用来区分不同对象。 � 属性 sp:Label:表示本控件是 Label 控件。 � 属性 runat=server 表示次控件运行在服务器段,由 Web 服务器解释。 10.1.2 TextBox控件 Label 控件用如下方法定义: 常用的属性如下: � 属性:Text 显示的文本 � 属性:TextMode=SingleLine 为编辑框,TextMode=MultiLine 为多行编辑框,可以 有滚动条。TextMode=PassWord 为口令编辑框。 � 属性:MaxLength 编辑框和口令编辑框时,允许输入的最多字符数。 � 属性:Rows 多行编辑框时表示行数 � 事件 TextChanged:控件中文本发生变化。 186 10.1.310.1.310.1.310.1.3 ButtonButtonButtonButton、LinkButton LinkButton LinkButton LinkButton 和ImageButton控件 Button 控件已介绍过了,Text为按钮的标题,单击事件为:Click。 LinkButton 控件:为超级链接形式的按钮,Text 为按钮超级链接形式的标题,单击事件为 Click。使用方法同 Button 控件,可为其增加单击事件 Click的事件函数。 ImageButton 控件:有些按钮需要在按钮上增加图案,例如工具条中的按钮,可以使用 ImageButton 控件。属性 ImageUrl 为图案的路径,一般最好和网页文件放在同一个目录下, 此时,控件定义如下:

一个服务器控件的属性绑定到另一个服务器控件的属性

苹果 香蕉 桔子 190

选定的水果: 网页中语句 text='<%# StateList.SelectedItem.Text %>'是将 Label 控件的 Text 属 性绑定到 DropDownList 控件的属性 StateList.SelectedItem.Text。符号%#表示数据绑定。 函数 SubmitBtn_Click 中的语句 Page.DataBind()更新页内的所有被绑定的数据。如果使用 Visual Studio.Net 实现,具体步骤如下: (6)创建一个 Web 应用程序框架,选择菜单命令建立一个新空白窗体。 (7)放工具箱的DrowDownList 控件到窗体。单击属性 Items 后的按钮,出现选择 ListItem 集合编辑器对话框,单击添加按钮,增加三项。修改每项的属性 Text,分别为:课程总 论、刚体静力学、弹性静力学。 (8) 放工具箱的Button 控件到窗体,为单击确定按钮事件(Click)函数增加语句(双击 Click 事件): private void Button1_Click(object sender, System.EventArgs e) { Page.DataBind(); } (9)放Label 控件到窗体,id 为Label1。单击属性 DataBinding 后标题为…的按钮,打开 Label1 数据绑定对话框,选择自定义绑定表达式(c),在其下编辑框中输入: DropDownList1.SelectedItem.Text。单击确定按钮。 (10) 运行。 10.2.210.2.210.2.210.2.2 基于变量的数据绑定 ASP.NET 数据绑定语法支持绑定到公共变量、页的属性和页上其他控件的属性。下面的 示例说明如何绑定到公共变量和页上的简单属性。注意这些值在 DataBind()调用前初始化。

到页属性的数据绑定

客户:<%# custID %>
未结的订单:<%# orderCount %>
用Visual Studio.Net 实现的方法见上例及书。 10.2.310.2.310.2.310.2.3 基于集合的绑定 像DataGrid、ListBox、DrowDownList 和HTMLSelect 这样的列表服务器控件的列表都 可以绑定到数据源。例如绑定到公共语言运行库的集合类型,如 ArrayList、DataView、 Hashtable 和DataReader 等。下面的示例说明如何将 DrowDownList 的列表绑定到 ArrayList。

数据绑定 DropDownList

192

下面的示例说明如何把数据表绑定到 DataGrid。注意使用 DataView 类要引用命名空间 System.Data,即语句<%@ Import namespace="System.Data" %>。具体例子如下: <%@ Import namespace="System.Data" %>

到 DataView 的数据绑定

下面的示例说明如何绑定到 Hashtable。

到哈希表的数据绑定

<%# ((DictionaryEntry)Container.DataItem).Key %> : <%# ((DictionaryEntry)Container.DataItem).Value %>
标记指定显示格式是按指定显示格式重复显示数据源 中的所有数据。本例中应显示 3组数据,指定显示格式为:键 1:值。 下面介绍如何将 ListBox、DrowDownList 和HTMLSelect 这样的列表服务器控件的列表 绑定到数据表的某一字段上。 <%@ Import namespace="System.Data" %> 194

到StateList 的数据绑定


195 10.2.410.2.410.2.410.2.4 基于表达式绑定 通常需要在绑定到页或控件之前操作数据。下面的示例说明如何绑定到表达式和方 法的返回值。

到方法和表达式的数据绑定

数字值:<%# Container.DataItem %><%--Container 表示数据源--%> 偶/奇:<%# EvenOrOdd((int) Container.DataItem) %><%--绑定到函数返回值--%> 196
10.2.510.2.510.2.510.2.5 基于 DataBinder.EvalDataBinder.EvalDataBinder.EvalDataBinder.Eval方法的数据绑定 为将绑定的数据按指定数据类型转化为字符串,可以使用 String.Format 方法。请看下 面的示例,该例要将数据表中字段名为"IntegerValue"的数据转换为货币的数据类型的字符 串输出。 <%# String.Format("{0:c}",((DataRowView)Container.DataItem)["IntegerValue"])%> 该语法可能比较复杂,难以记忆。ASP.NET 提供了一种静态方法 DataBinder.Eval,可 以将绑定的数据按指定数据类型转化为字符串。该方法使用很方便,因为它消除了开发人员 为强迫将数值转换为所需的数据类型而必须做的许多显式转换。这在数据绑定模板列表内的 控件时尤其有用,因为通常数据字段的类型都必须转换。为将整数显示为货币字符串,使用 #DataBinder.Eval 格式如下: <%#DataBinder.Eval(Container.DataItem, "IntegerValue", "{0:c}") %> DataBinder.Eval 是一个具有三个参数的方法,第一个参数是数据源的当前记录,在象 DataList、DataGrid 或Repeater 这样的模板列表中,该参数始终是 Container.DataItem, 第二个参数是数据表字段名,表示要将此字段的数据转换为第三个参数指定的数据类型的字 符串,第三个参数为格式字符串,{0:c}表示货币类型。格式字符串参数是可选的。如果省 略它,则 DataBinder.Eval 将此字段的数据转换为字段本身的数据类型的字符串,如下例 所示,输出为字符串"true"或"false"。 <%# (bool)DataBinder.Eval(Container.DataItem, "BoolValue") %> 具体的实例如下: <%@ Import namespace="System.Data" %>

使用 DataBinder.Eval 进行数据绑定

订购日期:<%# DataBinder.Eval(Container.DataItem, "DateTimeValue", "{0:d}") %>

数量:<%# DataBinder.Eval(Container.DataItem, "IntegerValue", "{0:N2}") %>

项:<%# DataBinder.Eval(Container.DataItem, "StringValue") %> 订购日期:

控件 DataList 中的 ItemTemplate 是模板控件,其功能是将控件 DataList 的数据源中的所 有数据,按 ItemTemplate 模板控件所指定的格式显示。 10.2.610.2.610.2.610.2.6 列表绑定控件 列表绑定控件通用属性 1. DataSource 属性 198 DataList、DataGraid 和Repeater 都提供了 DataSource 属性。使用 DataSource 属性 指定要绑定到上述三种数据列表控件的数据源,用数据源中的数据填充上述三种数据列表控 件。数据源必须是实现 System.Collections.ICollection 接口的具有相同对象的集合,例 如System.Data.DataView(见6.3 节最后例子)、System.Collections.ArrayList(见6.3 节 例1)和System.Collections.Hashtable(见6.3 节哈希表例子)。 2. Items 集合 Items 是一个集合属性,包含一些具有相同特征的若干对象的集合。DataList、 DataGraid 和Repeater 控件中包含多个 Items 集合属性,使用 Items 集合用来以编程的方 式控制 DataList、DataGraid 和Repeater 控件中的各项。例如: a) AlternatingItem:DataGraid 中所有奇数编号行的项的集合 b) SelectedItem:当前选中的所有项的集合。 c) EditItem:正在编辑的行中所有项的集合。例子见 7.2.4 更新数据的语句 MyDataGrid.EditItemIndex = (int)E.Item.ItemIndex;。 以上各项都和数据源有关,以下各项和数据源无关。 d) Header:所有列表表头项的集合 e) Footer: 所有列表表尾项的集合 f) Separtor:DataGraid 和Repeater 控件中分隔符线的集合 g) Page:DataGraid 在分页显示中,每页数据项的集合。 3.数据绑定和 Items 集合的创建 当为 DataList、DataGraid 和Repeater 等列表控件的属性 DataSource 指定数据源,并 执行数据绑定函数 DataBind 方法后,列表控件将创建 Items 集合,并从数据源取得显示所 需的数据,可以通过 Items属性来获得列表控件中各项的内容。注意,只有绑定到数据源的 项才包含在 Items 集合中。页眉、页脚和分隔符不包含在该集合中。下面的示例展示如何使 用 Items 集合来显示 DataList 控件中的项。 <%@ Import Namespace="System.Data" %>

DataList Items Example

Items 200 <%# DataBinder.Eval(Container.DataItem, "StringValue") %>



如果使用控件 DataGrid ,有时需要知道指定行的指定列的数据,表达式 DataGrid1.Items[2].Cells[1].Text 表示网格控件 DataGrid1 的第 2行第 1列的文本。具 体例子如下: <%@ Import namespace="System.Data" %>




3. Style 使用 DataList 和DataGraid 的属性 Style 可定义控件的外观。见下例: 202 其中定义了控件 DataGraid 的背景为白色,边界为黑实线,宽度为 1个象素,字体为 "x-small",字体名字为"verdana"。奇数行的背景颜色,标题的字的颜色,背景色。数据项 的背景色等等。显示效果见 7.1.2 的例子。 4. ItemTemplate 属性 控件 DataList 中的 ItemTemplate 是模板控件,其功能是将控件 DataList 的数据源中 的所有数据,按 ItemTemplate 模板控件所指定的格式显示。DataList 控件中项的外观由 ItemStyle 属性控制。还可以使用 AlternatingItemTemplate 属性来控制 DataList 控件 中交替项的内容。具体例子见 6.6 节中 3.数据绑定和 Items 集合的创建的例子。 DataGraid 控件没有 ItemTemplate 模板,可使用模板列控件 TemplateColumn,在模板 列控件中增加 ItemTemplate 模板自己定义该列的显示控件或显示格式,具体例子见 7.1.6 列类型的第 3和第 4个例子。还可以在其中增加 EditItemTemplate 模板自己定义该列在编 辑时使用的控件,具体例子见 7.2.5 节例子。 Repeater 控件的 ItemTemplate 模板见 8.4 节例子。 5. 模板中的数据绑定 模板中的数据绑定的例子见上一节中的例子。 6.6.2 使用列表绑定控件 <%@ Import Namespace="System.Data" %>

DataList Example

204 Items <%# DataBinder.Eval(Container.DataItem, "StringValue") %> * <%# DataBinder.Eval(Container.DataItem, "StringValue") %>


RepeatDirection: Horizontal Vertical 205
RepeatLayout: Table Flow
RepeatColumns: 1 2 3 4 5
Show Borders:

如果使用 Visual Studio.Net 实现模板,具体步骤如下: 本节小结 1. ASP.NET 声明性数据绑定语法使用 <%# %> 表示法。 2. 可以绑定到数据源、页或其他控件的属性、集合、表达式以及从方法调用返回 的结果。 3. 列表控件可以绑定到支持 ICollection、IEnumerable 或 IListSource 接口的 集合,如 ArrayList、Hashtable、DataView 和 DataReader。 206 4. DataBinder.EvalDataBinder.EvalDataBinder.EvalDataBinder.Eval 是用于晚期绑定的静态方法。它的语法可能比标准数据绑定语 法简单,但性能较低。 10.310.310.310.3 数据验证控件数据验证控件数据验证控件数据验证控件 用户输入了数据,在提交前,首先要对输入的数据进行验证。当然,可以自己编程序进 行验证。ASP.NET 提供了一些验证控件,可以不用编程完成对输入的数据进行验证。本节 介绍如何使用这些数据验证控件。 10.3.110.3.110.3.110.3.1 数据验证概述 对用户输入的数据进行验证,可以在客户端进行。实现原理是当用户输入了信息并单击 提交按钮后,用在客户端运行的 JavaScript 脚本或 VBScript 脚本对数据验证,只有所有数据 正确,才能发送到服务器端处理。此种方法的优点是运行在客户端,因此反应速度快,减轻 了服务器和网络的负载。缺点是由于 JavaScript 脚本或 VBScript 脚本是以明文的方式嵌入在 HTML 文档中,客户端可以看到这些脚本程序,如果用户把这段脚本删除,网页也就失去 了验证功能,因此这种方法是不安全的。 另一种数据验证方法是在服务器端进行,当用户输入了信息并单击提交按钮后,把数据 立刻发送到服务器端,在服务器端验证,如果验证不通过,返回错误信息。这种方法虽然在 响应速度比较慢,增加了服务器的负担,但可靠性上要强的很多。 ASP.NET 提供了一些验证控件,可以不用编程完成对输入的数据进行验证。下边是一 个使用验证控件简单的例子,该例以数据验证控件 RequiredFieldValidator 为例,介绍数据验 证控件属性的使用方法。有些数据用户是必须输入的,这些数据可以用编辑控件,单选或多 选按钮等控件输入。可以用控件 RequiredFieldValidator 对这些控件输入的数据进行验证,检 查用户是否输入了数据。控件 RequiredFieldValidator 的属性 ControlToValidate 的值选择要验 证的控件的 id 值,可以是编辑控件,单选或多选按钮等。属性 ErrorMessage是发生错误时, 提示的错误信息。用户用编辑控件 textBox1 输入姓名,要求必须输入。用控件 RequiredFieldValidator1 对其输入进行验证,因此属性 ControlToValidate= textBox1。属性 ErrorMessage=”必须输入姓名”。当单击提交按钮后,如果用户没有输入姓名,则用”必须输 入姓名”提示用户。

姓名:

207 10.3.210.3.210.3.210.3.2 常用的验证控件 .Net 框架类库中提供以下几种验证控件: � RequiredFieldValidator 控件 � 自定义数据验证控件 CustomValidator 控件 � ValidationSummary 控件 � CompareValidator 控件 � RegularExpressionValidator 控件 10.3.310.3.310.3.310.3.3 验证控件常用的属性 � 属性 ControlToValidate:要验证的控件的 id 值。 � 属性 ErrorMessage:发生错误时,提示的错误信息。 � 属性 Display: � 属性 IsValid: � 属性 Text: 10.3.410.3.410.3.410.3.4 RequiredFieldValidatorRequiredFieldValidatorRequiredFieldValidatorRequiredFieldValidator 上边已介绍用记事本编辑网页如何使用此控件,下边的例子用 Visual Studio.Net 编辑。 该例子增加一个 RadioList 控件,输入卡的类型,增加一个编辑控件,输入编号,两者都要 求必须输入,用两个 RequiredFieldValidator 控件验证。步骤如下: (1)创建一个 Web 应用程序框架,选择菜单命令建立一个新空白窗体。 (2)放工具箱的Label 控件到窗体,其属性[Text]=“RequiredFieldValidator 控件的使用”。 Id=Label1。 (3)放工具箱的Label 控件到窗体,其属性[Text]=“输入卡号”。 (4)放工具箱的RadioButtonList控件到窗体,id=RadioButtonList1。 (5)单击属性 Items 后的按钮,出现集合编辑器对话框。单击添加按钮,增加一个 RadioButton 按钮,修改其 Text 属性为”苹果卡”,修改其 Selected 属性为法 false。用同样方法增 加另一个 RadioButton 按钮,修改其 Text 属性为”橡胶卡”,修改其 Selected 属性为法 false。 (6)放工具箱的Label 控件到窗体,其属性[Text]=“输入编号”。 (7)放工具箱的TextBox 控件到窗体,id=TextBox1。 (8)放工具箱的RequiredFieldValidator 控件到窗体, 属性ControlToValidate= RadioButtonList1, 属性 ErrorMessage=”必须输入卡类型”。 (9)放工具箱的 RequiredFieldValidator 控件到窗体, 属性 ControlToValidate=TextBox1, 属性 ErrorMessage=”必须输入编号”。 (10) 放工具箱的Button 控件到窗体,为其增加单击事件函数如下: private void Button1_Click(object sender, System.EventArgs e) { if(Page.IsValid==true) Label1.Text="已输入了数据"; 208 else Label1.Text="至少有一项未输入了数据"; } (11) 如运行出错,把 C:\inetpub\wwwroot\aspnet_client 文件夹拷贝到 D:\Asp 文件夹下。 运行 10.3.510.3.510.3.510.3.5 自定义数据验证控件 CustomValidator CustomValidator CustomValidator CustomValidator 控件 CustomValidator 控件允许编程者自己定义一个函数对数据进行验证。一般数据验证分为 客户端验证和服务器端验证,可以修改验证控件的属性 ClientTarget改变在那端验证,例如: Page.ClientTarget=ClientTarget.Downlevel 语句表示要在服务器端验证,而语句 Page.ClientTarget=ClientTarget.Uplevel 表示在客户端验证,在客户端验证必须在发布目录下 包含 C:\inetpub\wwwroot\aspnet_client 文件夹。因此,编程者要根据在那一端验证,编 写不同的函数,在服务器端验证函数定义如下: void ServerValidate(object source, ServerValidateEventArgs args){//验证语句} 在客户端验证函数定义如下:? void ClientValidate(source,value){//验证语句} 书中的例子如下: <%@ Page Language="C#" %>

CustomValidator Example


  

210 用Visual Studio.NET实现此例,具体步骤如下: (1)创建一个 Web 应用程序框架,选择菜单命令建立一个新空白窗体。 (2)放工具箱的Label 控件到窗体,其属性[Text]=“CustomValidator 控件的使用”。 Id=Label1。 (3)放工具箱的Label 控件到窗体,其属性[Text]=“键入一个偶数”。 (4)放工具箱的TextBox 控件到窗体,id=TextBox1。 (5)放工具箱的CustomValidator 控件到窗体,id=CustomValidator1, 属性 ControlToValidate=TextBox1, 属性 ErrorMessage=Not an even number!。 (6)放工具箱的Button 控件到窗体,为其增加单击事件函数如下: void ValidateBtn_OnClick(Object sender,EventArgs e) { If (Page.IsValid) lblOutput.Text = "Page is Valid!"; else lblOutput.Text = "Page is InValid!"; } (7)为CustomValidator 控件 ServerValidate 事件增加事件函数如下 private void CustomValidator1_ServerValidate(object source, System.Web.UI.WebControls.ServerValidateEventArgs args) { try { int i = int.Parse(args.Value); args.IsValid = ((i%2) == 0); } catch { args.IsValid = false; } } (8) 10.3.610.3.610.3.610.3.6 ValidationSummary ValidationSummary ValidationSummary ValidationSummary 控件 当用户提交了数据后,所有验证控件对数据进行验证,如果没有错误,设置 Page.IsValid=true,否则=false。如果在页面中放置了控件 ValidationSummary,它将自动显 示发现错误的数据验证控件的属性 ErrorMessage 的内容。可以在 5.2.1 的例子中,增加一个 ValidationSummary 控件,运行后看一下效果。 211 10.3.710.3.710.3.710.3.7 CompareValidator CompareValidator CompareValidator CompareValidator 控件 CompareValidator 控件可以对两个控件输入的值进行比较。属性 ControlToValidate=控制 对象的 id,属性 ValueToCompare=要比较得值,属性 Type=比较的数据类型,属性 Operator= 如何比较,可以是:Equal、NotEqual、GreatrThan、GreatrThanEqual、LessThan、LessThanEqual。 用Visual Studio.NET 实现此例,具体步骤如下: (1)创建一个 Web 应用程序框架,选择菜单命令建立一个新空白窗体。 (2)放三个 Label 控件到窗体,其属性[Text]分别为”First string”、”Second string” 和”lblOutput”。id 分别为 Label1、Label2 和lblOutput。 (3)在Label1 和Label2 控件后,分别放置 TextBox 控件,id 分别为 TextBox1 和TextBox2。 (4)放工具箱的CompareValidator 控件到窗体, 属性ControlToValidate=TextBox1,属性 ControlToCompare=TextBox2,属性 ErrorMessage=Not an even number!,id=Compare1。 (5)放两个 ListBox 控件到窗体,id 分别为 ListOperator 和ListType。 (6)单击 ListOperator 属性 Items 旁的三个小点,在 ListItem 编辑器对话框中单击添加按 钮,增加 6个选项,属性 Text 分别为:Equal、NotEqual、GreatrThan、GreatrThanEqual、 LessThan、LessThanEqual,同时,属性 Value 也变为相应的值。 (7)单击 ListType 属性 Items 旁的三个小点,在 ListItem 编辑器对话框中单击添加按钮, 增加 5个选项,属性 Text 分别为:String、Integer、Double、Date、Currency,同时, 属性 Value 也变为相应的值。 (8)设置两个 ListBox 控件属性 SelectionMode 为Single,不允许多选。(为什么?) (9)设置两个 ListBox 控件属性 AutoPostBack=true。 (10) 为ListOperator 事件(SelectedIndexChenged)增加事件函数如下: private void ListOperator_SelectedIndexChanged(object sender, System.EventArgs e) { Compare1.Operator=(ValidationCompareOperator)ListOperator.SelectedIndex; Compare1.Validate(); } (11) 为ListType 事件(SelectedIndexChenged)增加事件函数如下: private void ListType_SelectedIndexChanged(object sender, System.EventArgs e) { Compare1.Type=(ValidationDataType)ListType.SelectedIndex; Compare1.Validate(); } (12) 放工具箱的Button 控件到窗体,为其增加单击事件函数如下: private void Button1_Click(object sender, System.EventArgs e) { //Label1.Text=DropDownList1.SelectedItem.Text; if(Page.IsValid==true) lblOutput.Text="数据有效"; else lblOutput.Text="数据无效"; } (13) 运行,看一下效果。增加列表内容用如下语句 212 listBox1.Items.Add("Item9");//在列表最后增加一项 listBox1.SetSelected(1, true);//第一项被选中 listBox1.EndUpdate();//更新 5.2.5 RangeValidatorRangeValidatorRangeValidatorRangeValidator 控件 RangeValidator 控件测试输入控件的值是否在指定范围内。RangeValidator 控件使用四 个关键属性执行验证。ControlToValidate 属性包含要验证的输入控件。MinimumValue 和 MaximumValue 属性指定有效范围的最大值和最小值。BaseCompareValidator.Type 属性用 于指定要比较的值的数据类型。在执行验证操作之前,要比较的值被转换为此数据类型。可 以进行比较的不同数据类型: 字符串数据类型 String、32 位有符号整数数据类型 Integer、 双精度浮点数数据类型 Double、日期数据类型 Date、一种可以包含货币符号的十进制数据 类型 Currency。 下面的示例说明如何在 Web 页上创建 RangeValidator 控件,以检查输入到输入控件的值 是否在比较范围内。 <%@ Page Language="C#" %>

RangeValidator Example

Enter a number from 1 to 10:





10.3.8 RegularExpressionValidator 控件 RegularExpressionValidator 控件也叫正则表达式控件,该控件用来检查输入控件的 值是否匹配正则表达式定义的模式。这类验证允许您检查可预知的字符序列,比如身份证号 码、电子邮件地址、电话号码和邮编中的字符序列。本节首先讲解一些正则表达式的基本知 识,然后将这些基本知识用于数据验证控件。 1.基本模式 模式,是正规表达式最基本的元素,它们是一组描述字符串特征的字符。模式可以很简 单,由普通的字符串组成;也可以非常复杂,可以用特殊的字符表示一个范围内的宇符、重 复出现等。最简单的匹配就是一个字符串,这种情况下,如果一个字符串含有这个字符串, 那么那个字符串就认为是符合匹配要求的。 � “^”头匹配 这个模式包含一个特殊的字符^,表示该模式只匹配那些以紧接其后的以字符串。例如: ^front,表示以”front”开头的字符串是匹配的,而不以”front”开头的字狩串是不匹配 的。 � “$”尾匹配 尾匹配“$”的意义是,只有那些以“$”号前面的字符串结尾的字符串才符合匹配的 要求,例如:tail$,表示那些以”tail”结尾的字符串是匹配的。结合使用“^”和“S”, 可以提供一种整个字符串匹配的模式:^whole$,就表示仅有“whole”字符串符合匹配要 求。 2.转义序列 所谓转义序列其实就是一些无法直接在正则表达式中使用的字符,例如标点符号、空格、 回车、换行、制表符等。所有的转义序列都用反斜杠(\)打头。和“C”语言中类似,转义 序列也是以“\”开头的,如: 换行:\n 回车:\r 214 制表符:\t 由于在正则表达式中“^”、“S”和“+”等都有特殊的意义,所以在正则表达式中 也需要使用转义序列来表示“\^”“\$”和“\+”。 3.字符簇 在Internet 中,正规表达式通常用来舰用户的输人,当用户提交一个 Form 以后,要判 断输入的电话号码、地址、Enail 地址、信用卡号码、邮政编码等是否有效,用普通的基于 字面的字符是不够的,需要一种可以设定一个字符集合的方法,在正则表达式中,这种方法 称为字符簇,一个字符簇是使用方挂号括起来的。下面举一个例子:[ABCabc],上面的字符 簇例子表示如果一个字符是“A”或“B”或“C“或“a”或“b”或“C“,那么就符合匹配 要求。 当需要一个有顺序的返回的时候,可以使用连字号来表示二个字符的范围,如:[a-z] 匹配所有的小写字母。[A-Z]匹配所有的大写字母。[az-AZ]匹配所有字母。[0-9]匹配所 有的数字。[0-9\.\-] 匹配所有的数字,句号和减号。[\f\r\t\n]匹配所有的 白字符。 前面曾经提到^表示字符串的开头,但它还有另外一个含义。当在一组方括号里使用^ 时,它表示“非”或“排除”的意思,常常用来剔除某个字符。例如,如果要求第一个字符 不能是小写字母:^[^a-z], 这个模式与“A4”、“7b”及“+a”是匹配的,但与”a2’、 “c6”是不匹配的。下面是几个排除特定字符字符的例子:[“a-z”],除了小写字母以外 的所有字符。[^“0-9”],除了数字之外的所有字符。[^\”\’],除了双引号(”)和 单引号(’)之外的所有字符。特殊字符”.”(点号)在正规表达式中用来表示除了“新 行”之外的所有字符。所以模式”^.5$”与任何两个字符的、以数字 5结尾和以其他非“新 行”字符开头的字符串匹配。模式”.”可以匹配任何字符串,除了空串和只包括一个“新 行”的字符串。 4.重复 到现在为止,已经讨论了如何去匹配一个字母或数字,但更多的情况下,可能要匹配一 个单词或一组数字。一个单词由若干个字母组成,一组数字由若干个单数组成。 正则表达式提供了“{}”来执行重复,跟在字符或字符簇后面的花括号({})用来 确定前面的内容重复出现的次数。其中{n,m}表示可能重复 n到m次并且包括 n和m次, {n,}表示可能重复 n次或大于 n次。例如:^a{4}$表示 aaaa。^a(2,4)$表示 aa,aaa, 或aaaa。^a{2,}$表示包含多于两个 a的字符串。.{2}表示所有的两个字符。 下面是常用的一些模式: ^[a-zA-Z0-9_]{1,}$表示所有包含一个以上的字母、数字或下划线的字符串。 ^[0-9]{1,}$ 表示所有的整数。 ^\-{0,1}[0-9]{0,}\.{0,1}[0-9]{0,}$ 表示所有小数。 正则表达式提供了一些简写的特殊字符,可以让表达式容易理解: ?{0,1} *{0,} + {1,} 例如:^[0-9]+$ 表示所有的整数。^[0-9]+$ 表示所有的整数。^\-?[0-9]*\.?[0-9]+$ 表示所有小数。 下例说明如何使用 RegularExpressionValidator 验证一个 5 位数的邮编。 <%@ Page Language="C#" %> 215

RegularExpressionValidator Example

Personal Information
Zip Code:
如用 Visual Studio.NET 实现,只需修改属性 ValidationExpressio 即可。 10.410.410.410.4 DataGraid DataGraid DataGraid DataGraid 控件控件控件控件 10.4.1 DataGrid控件概述 10.4.2 DataGrid控件绑定数据库表 例子 e10_4_1 联接一个数据库,用 DataGrid 控件显示的例子如下: <%@ Page Language="C#" %> <%@ Import Namespace="System.Data" %> <%@ Import Namespace="System.Data.SqlClient" %> DataGrid 显示数据库表
其中字符串 txtConn="DATABASE=Northwind;SERVER=localhost;UID=sa;PWD=;"叫联接 字符串,DATABASE 时数据源,SERVER 是数据库服务器,UID 是用户名,PWD 为密码。语句 SqlConnection conn=new SqlConnection(txtConn)用来建立一个连接。字符串 txtCommand ="SELECT employeeid, firstname, lastname FROM Employees"是一个 SQL 语句,用来从表 Employees 中取出字段 employeeid、firstname、lastname 的所有记录。语句 SqlDataAdapter da=new SqlDataAdapter(txtCommand,conn)用来建立一个 SqlDataAdapter 对象,它负责读 取数据库数据。用 DataGrid 控件显示表 Employees 中字段 employeeid、firstname、lastname 的数据。Page_Load()函数说明了显示数据的必要步骤。特别要注意 DataGrid 控件数据绑定 的方法。 10.4.3 DataGrid控件对数据库记录分页显示 <%@ Page Language="C#" %> <%@ Import Namespace="System.Data.SqlClient" %> <%@ Import Namespace="System.Data" %> 分页 218

分页

219
AllowPaging="True"表示允许分页,PageSize="3"表示每页 3个记录, 中PageButtonCount="3"表示有 3个按钮,Mode="NumericPages"表示按钮形式为数字,也 可以修改为 Mode="NextPrev",则在网格下部出现<和>,单击<,转向前页,单击>,转向后 页。也可以改为字符,例如修改为“前页”和“后页”,可以修改属性 PrevPageText=”前 页”,NextPageText=”后页”。OnPageIndexChanged="PageIndexChanged"表示单击按钮事 件函数是 PageIndexChanged。 10.4.4 DataGrid控件对记录排序 <%@ Page Language="C#" %> <%@ Import Namespace="System.Data" %> <%@ Import Namespace="System.Data.SqlClient" %> 按列排序

按列排序

AllowSorting="true"表示允许排序,允许排序的列标题有一下划线,单击标题将产生 事件,事件函数由 OnSortCommand="MyDataGrid_Sort"定义。现如果用手工产生每一列,排 序的例子如下: <%@ Page Language="C#" %> <%@ Import Namespace="System.Data" %> <%@ Import Namespace="System.Data.SqlClient" %> 按列排序

按列排序

222
在DataGrid的5列中,只有 DataField="lastname"和DataField="country"有 SortExpression 属性定义,分别为:"lastname"和"country",因此,只有此两列可以排序,表 达式 SortExpression= "lastname"表示此列可以排序,按字段"lastname"排序。如果增加语句 Source.Sort += "DESC",则降序排序,如无字符"DESC"为生序排序。 10.4.5 用BoundColumn列将标题改为中文 DataGrid 控件的属性 AutoGenerateColumns=true,将根据数据源的内容自动填充表格, 标题默认为是字段名,由于避免不兼容,字段名一般用英文,如希望如将标题改为中文,可 以置AutoGenerateColumns=false,使用列控件BoundColumn手工填充,其中属性HeaderText 是标题字符串,可以改成中文,属性 DataField 是该列显示的字段名。见下例: <%@ Page Language="C#" %> <%@ Import Namespace="System.Data" %> <%@ Import Namespace="System.Data.SqlClient" %> DataGrid 标题改为中文
10.4.6 增加按钮列 如果用手工创建 DataGrid 表格,除了以上介绍的列控件 BoundColumn 外,还包括控件 ButtonColumn,用来创建一列按钮,可以为按钮增加一个事件函数,控件 HyperLinkColumn 用来创建一列超级链接字符,控件 EditCommandColumn,将自动和编辑命令相关联。控件 TemplateColumn,将按指定模板创建所显示的列。 例子 e10_4_4 是控件 ButtonColumn 的用法: <%@ Page Language="C#" %> <%@ Import Namespace="System.Data" %> <%@ Import Namespace="System.Data.SqlClient" %> DataGrid 增加按钮列
网页中 OnItemCommand="HandleCommands"的HandleCommands 是按钮列按钮的事件函 数,如果有多个按钮列,都用此函数响应。按钮列按钮的事件函数 HandleCommands 中的语 句if(e.CommandName=="moreinfo")是判断是哪一个按钮发的事件。 10.4.7 增加 HyperLinkColumn列 例子 e10_4_5 下例是控件 HyperLinkColumn 的用法: <%@ Page Language="C#" %> <%@ Import Namespace="System.Data" %> <%@ Import Namespace="System.Data.SqlClient" %> DataGrid 增加 HyperLinkColumn 列
控件 HyperLinkColumn 中,DataNavigateUrlFormatString="例子 e10_4_5A.aspx? id ={0}"是超级链接的网页,本例是 c716-1A.aspx,?id={0}是传递的参数,{0}是一个变量, 对应 DataNavigateUrlField 指定的数据库表字段,本例为 employeeid, DataTextFormatString 为超级链接字符,本例为"关于{0}更多信息"。 例子 e10_4_5A.aspx 如下: <%@ Page Language="C#" %> <%@ Import Namespace="System.Data" %> <%@ Import Namespace="System.Data.SqlClient" %> More Info 228 10.4.8 增加 EditCommandColumn列 10.4.910.4.910.4.910.4.9 控件 TemplateColumn的用法 例子 e10_4_5A 是控件 TemplateColumn 的用法,本例用单选按钮显示 bool 字段。 <%@ Page Language="C#" %> <%@ Import Namespace="System.Data" %> <%@ Import Namespace="System.Data.SqlClient" %> 显示布尔变量
229
例子 e10_4_5B 用图形按钮显示 bool 字段。 <%@ Page Language="C#" %> <%@ Import Namespace="System.Data" %> <%@ Import Namespace="System.Data.SqlClient" %> 用图像显示布尔变量 231 7.1.9 使用自己的数据库 � 用Access 建立数据库 用Access 建立数据库: db1.mdb。建立Student 表,记录所有学生信息。包括字段 StudentNum(学生编号),字节类型,必填字段,默认值为空,StudentNum 为主关键字。字 段StudentName(学生姓名),文本,字段大小 8,必填字段,默认值为空。字段 StudentSex(性 别),文本,字段大小 2。增加若干数据。存数据库的路径为:D:\asp\StudentDb.mdb � 例子如下: <%@ Page Language="C#" %> <%@ Import Namespace="System.Data.OleDb" %> <%@ Import Namespace="System.Data" %> DataGrid 232

数据库的联接

10.510.510.510.5 AdRotator AdRotator AdRotator AdRotator 控件控件控件控件 Web 页上的广告通常采用广告条(小图片)的形式,单击时使用户重定向到广告商的 Web 页。使用 AdRotator Web 服务器控件能够显示广告条并在一系列广告条间循环。 AdRotator 自动进行循环处理,在每次刷新页面时更改显示的广告。使用 AdRotator 控件显 示广告步骤: (1) 创建一个 Web 应用程序框架,项目名为 UseAdRotator。 (2) 新建一个 XML 文件。单击菜单项”项目/添加新项”,弹出标题为添加新项的窗口,在窗 口中选中 XML 文件,文件名为 ads.xml,单击打开按钮,增加一个 XML 文件。文件如下: d:\\asp\\bookExample\\p2.JPG http://www.sohu.com search anything 10 Topic1 233 This is the caption for Ad#1 d:\\asp\\bookExample\\baobao048.jpg http://www.sina.com sina main site 10 Topic2 This is the caption for Ad#2 (3) 在窗体中放置控件 AdRotator,其属性 Name=AdRotator1。 (4) 修改控件 AdRotator 的属性 AdvertisementFile,单击其后的按钮,出现选择 XML 文件对 话框,在对话框中 URL(U)处填入 ads.xml。 (5) 运行,可以看到一幅图,鼠标移到图中,变为手形,单击可以转到搜狐网站。刷新,可 以看到另一幅图。 XML 文件包含以下预定义的属性。只有 ImageUrl 属性是必需的: � ImageURL 要显示的图像的 URL � NavigateURL 单击 AdRotator 控件时定位到的页面的 URL。 � AlternateText 图像不可用时显示的文本(常为图片浏览工具提示)。 � Keyword 可用于筛选特定广告的广告类别。通过设置 AdRotator 的KeywordFilter 属性以筛选出 XML 文件中特定类别的广告。 � Impression 指示广告的可能显示频率的数值。在 XML 文件中,所有 impression 值的总和不能超过 2,048,000,000 - 1 10.610.610.610.6 Calender Calender Calender Calender 控件控件控件控件 Calendar Web 服务器控件在 Web 窗体页上显示一个传统的单月份日历(包含该月在内 的6周)。用户可使用该日历查看和选择日期。Calendar Web 服务器控件最简单的用法如下: (C7-2A.aspx)

Calendar Example

234

其中事件 onselectionchanged="Date_Selected"是用户改变选择日期时产生的事件。 SelectionMode 属性设定 Calendar 控件中可选择的时间段,Day:可选择任一天;DayWeek:可 选择任一天或一周;DayWeekMonth:可选择任一天、一周或一月;None:不能选择日期。 C7-2A.aspx网页显示了 SelectionMode 属性选择不同质的效果。

Date Selection Modes

Choose a Selection Mode: None Day DayWeek DayWeekMonth

1、显示和选择日期 可视日期:该日期确定日历中显示哪个月份。在日历中,用户可在不同的月份之间移动,从 而在不影响当前日期的情况下更改可视日期。 选定的一个或多个日期:在该控件中用户可通过设置 SelectionMode 属性选择单个日、单个 周或单个月份,但只能选择连续的日期。 可设置日历的属性以更改日历的颜色、尺寸、文本以及其他可视特性。默认情况下,该控件 显示月中各天、周中各天的标头、带有月份名和年份的标题、用于选择月份中各天的链接及 用于移动到下个月和上个月的链接。可以通过设置控制控件中不同部分的样式的属性,来自 定义 Calendar 控件的外观。 SelectionMode 属性: 设定 Calendar 控件中可选择的时间段 Day:可选择任一天; DayWeek:可选择任一天或一周; DayWeekMonth:可选择任一天、一周或一月 None:不能选择日期 SelectDate属性实现选择日期:按所定义的外观样式显示运行时所选定的一天、一周或一月 控件的 DayRender 事件:当在 Calendar 控件中创建(显示)每个日期单元格时,均会引发 DayRender 事件。通过在 DayRender 事件的事件处理程序中提供代码,可以在创建日期单 236 元格时控制其内容和格式设置。事件处理程序接收一个 DayRenderEventArgs 类型的参数, 它包含与此事件相关的数据。DayRenderEventArgs 属性: Cell:获取表示 Calendar 控件单元格的 TableCell 对象。 Day:获取表示 Calendar 控件中日期的 CalendarDay。 例 dayreader.aspx 为 DayRender 事件编写处理程序,使所显示月份中日期的背景色为黄 色。它还说明如何通过向单元格添加 System.Web.UI.LiteralControl 来自定义单元格的内容。 10.710.710.710.7 VisualVisualVisualVisual Studio.Net Studio.Net Studio.Net Studio.Net 实现留言板实现留言板实现留言板实现留言板 本例有两个窗口,主窗口负责输入留言,包括输入用户名,留言主题,留言内容,用三 个编辑框,输入完毕后,单击提交按钮,将留言存入数据库。单击另一个查看留言按钮,可 链接到另一个显示留言窗口。显示留言窗口包括一个 DataGraid 控件,用来显示所有的留言 的用户名,主提,留言序号,及按钮列,单击相应按钮,显示当前记录的留言内容。单击返 回主窗口按钮,返回主窗口。下边是具体步骤: (1) 用Access2000 建立数据库: LiuYanBan.mdb。建立 LiuYanTable 表,记录所有留言信息。 包括字段 LiuYanID(留言编号),自动编号类型,为主关键字。字段 LiuYanName(留言者 姓名),文本,字段大小 10,必填字段,默认值为空。字段 LiuYanTitle(留言标题), 文本,字段大小 30,必填字段,默认值为空。字段 LiuYanTime(留言时间),时间类型。 字段 LiuYanContent(留言内容),备注字段,必填字段,默认值为空。增加若干数据。 存数据库的路径为:D:\asp\ LiuYanBan.mdb,假设文件夹 asp 已设为 Web 网站目录。 (2) 创建一个 Web 应用程序框架,选择菜单命令建立一个新空白窗体。项目名为 LiuYanBan。 (3) 修改 WebForm 属性,单击属性 Style 后标题为…的按钮,打开样式生成器对话框,可以 修改 WebForm 的各种风格。单击对话框左侧的各个选项:字体、背景、文本、位置、布 局、边缘、列表、其他,可以按自己的爱好修改相应的内容,这里不作修改,全部采用 默认值。 (4) 放工具箱的4个Label 控件到窗体。修改属性 Text 分别为:留言板主窗体、用户名、 留言主题、留言内容。 (5) 放工具箱的3个TexbBox 控件到窗体。修改属性 Text 都为空,ID=TexbBox1 编辑框用来 输入用户名,ID=TexbBox2 编辑框用来输入留言主题,ID=TexbBox3 编辑框用来输入留 言内容,其属性 TextMode=MultiLine。由于此三项要求必须输入数据,因此应增加 3 个验证控件。 (6) 放工具箱的Button 控件到窗体,Text=”提交留言”。 (7) 在窗体中放置控件 oleDbConnection,其属性 Name=oleDbConnection1。单击控件 oleDbConnection 属性 ConnectionString 的下拉列表的箭头,在列表中选择新建连接, 打开数据连接属性对话框,选择提供程序页,选择 OLE DB提供程序为 Microsoft Jet 4.0 OLE DB Provider 后,单击下一步按钮,选择数据库名称为 D:\asp\ LiuYanBan.mdb, 用户名称为 Admin,空白密码,单击测试连接按钮,应出现测试连接成功对话框。按确 定按钮退出。 (8) 在窗体中放置控件 oleDbCommand,其属性Name= oleDbCommand1。单击控件 oleDbCommand 属性Connection 的下拉列表的箭头,在列表中单击现有前的+后,选择已有的连接 oleDbConnection1。 (9) 为单击提交留言按钮事件(Click)函数增加语句(双击 Click 事件): private void Button1_Click(object sender, System.EventArgs e) 237 { oleDbConnection1.Open(); //自动增加字段不必写入 oleDbCommand1.CommandText="Insert Into LiuYanTable(LiuYanName,LiuYanTitle,LiuYanTime,LiuYanContent) Values('" + TextBox1.Text + "', '" + TextBox2.Text + "','" + DateTime.Now + "','" + TextBox3.Text + "')"; oleDbCommand1.ExecuteNonQuery(); oleDbConnection1.Close(); TextBox1.Text=""; TextBox2.Text=""; TextBox3.Text=""; } (10)单击文件/添加新项(w)…菜单项,出现添加新项对话框,选择 Web 窗体,窗体名为: WebForm2.aspx,单击打开按钮,创建新窗体。 (11)在WebForm1 放工具箱的HyperLink 控件到窗体,Text=”查看留言”,单击属性 NavigateUrl 后的按钮,出现选择 URL 对话框,选择 URL 类型为与根相关的,URL 编辑 框添入/LiuYanBan/WebForm2.aspx。 (12)在WebForm2 窗体中放置控件 oleDbConnection,其属性 Name=oleDbConnection1。单 击控件 oleDbConnection 属性 ConnectionString 的下拉列表的箭头,在列表中选择前 边建立的数据库连接。 (13)在WebForm2 窗体中放置控件 oleDbDataAdapter,出现添加数据适配器向导对话框,单 击下一步按钮,单击下拉列表的箭头,在列表中选择前边建立的数据库连接。单击下一 步按钮。 (14)选择使用 SQL语句单选按钮。单击下一步按钮。 (15)单击高级选项按钮,在高级 SQL选项对话框中,所有多选按钮都不选。单击确定按钮。 (16)单击查询生成器按钮,在添加表对话框中,选中 LiuYanBan 数据库,单击添加按钮。再 按关闭按钮,关闭添加表对话框。 (17)选中所有字段,按 LiuYanID 降序排列,单击确定按钮。 (18)单击确定按钮。单击完成按钮。 (19)单击 sqlDataAdapter1 选中它,单击菜单项数据/生成数据集…,打开生成数据集对话 框,他选择默认值。按确定按钮退出。增加控件 dataSet,其属性 Name=dataSet1。 (20)在WebForm2 窗体中放置控件 dataView,其属性 Name=dataView1。单击控件 dataView1 属性Table 的下拉列表的箭头,在列表中单击现有前的+后,选择 dataSet1 中的 LiuYanTable。 (21)在窗体中放置控件 Label,其属性 Name=Label1。 (22)在窗体中放置控件 DataGrid,其属性 Name=DataGrid1。右击 DataGrid1,在弹出菜单中 选择菜单项自动套用格式,在对话框中选用自己喜欢的格式。 (23)右击 DataGrid1,在弹出菜单中选择菜单项属性生成器,在 DataGrid 属性对话框中,选 中左侧的选项:常规。设置数据源为:dataView1。选中显示页眉,显示页脚,允许排 序。选中左侧的选项:列。不选中在运行时自动创建列。将字段:LiuYanName、 LiuYanTitle、LiuYanTime 从左侧的列表框移到右侧的列表框,表示显示此三个字段。 见页眉文本改成中文:留言者姓名、留言标题、留言时间。增加一个 Select 按钮,增 加一个按钮列,页眉为:单击按钮查看留言。命令名为:ReadContent。选中左侧的选 238 项:分页。选中允许分页。 (24)为按钮列增加事件函数,DataGraid 所有按钮都产生事件:ItemCommand,根据命令名加 以区分是哪一个按钮发的命令。事件函数如下: private void DataGrid1_ItemCommand(object source,System.Web.UI.WebControls. DataGridCommandEventArgs e) { if (e.CommandName == "ReadContent") { Label1.Text=dataSet11.Tables["LiuYanTable"].Rows[e.Item.ItemIndex]["LiuYanContent"]. ToString(); } } (25)为Page_Load 事件函数增加语句: private void Page_Load(object sender, System.EventArgs e) { oleDbDataAdapter1.Fill(dataSet11); if(!Page.IsPostBack) { DataGrid1.CurrentPageIndex=0; DataGrid1.DataBind(); } // 在此处放置用户代码以初始化页面 } (26)为DataGraid1 的DataGrid1_PageIndexChanged 事件函数增加语句: private void DataGrid1_PageIndexChanged(object source,System.Web.UI.WebControls.DataGridPageChangedEventArgs e) { DataGrid1.CurrentPageIndex=e.NewPageIndex; DataGrid1.DataBind(); } (27)在WebForm2放工具箱的HyperLink 控件到窗体,Text=”输入留言”,单击属性 NavigateUrl 后的按钮,出现选择 URL 对话框,选择 URL 类型为与根相关的,URL 编辑 框添入/LiuYanBan/WebForm1.aspx。 (28)运行,出现 WebForm1,可以输入一条留言,单击提交按钮,再单击超级链接查看留言, 转到 WebForm2,单击查看留言按钮,可以在 Label1 处看到留言,单击超级链接输入留 言,转到 WebForm1。 239 第十一章 ASP.NET ASP.NET ASP.NET ASP.NET 内建对象 ASP.NET 为保持浏览用户的数据和信息,内建了许多对象,包括 Application、Response、 Request、cookie、Sessions、Cache和Server 等对象,以及它们的大量的方法。通过这些对 象,可以提供网络开发必不可少的功能,例如当前目录的获得、在线人数、访问网站总人数、 网上商店中的购物筐等等。 11.111.111.111.1 Request Request Request Request 对象对象对象对象 Request 对象主要有以下用途:第一用来来在不同网页之间传递数据,第二是 Web服务 器可以使用 Request 对象获取用户所使用的浏览器的信息,第三是 Web 服务器可以使用 Request 对象显示 Web服务器的一些信息,最后,可以用 Request 对象获得 Cookie信息。本 节主要介绍前三种用途,后边有一节专门介绍 Cookie。 11.1.111.1.111.1.111.1.1 用Request Request Request Request 对象获取另一个网页传递的数据 从一个网页链接到另一个网页时,可能需要传递一些数据到另一个网页。两个 Web 网 页之间一般通过表单(From)传递,具体传递方法有两个:Post和Get。当数据传递到另一个 网页时,另一个网页用 Request 对象的方法取出这些数据。见下例:(e11_1A.aspx)

用户名: 其中action是用户单击此按钮后,响应用户程序网页的URL,这里是e11_1B.aspx。语 句method=POST是数据用POST方法传到e11_1B.aspx,也可以是get方法。在e11_1B.aspx网页中, 是不能用string s= textBox1.Text语句得到输入的内容的,因为textBox1是另一个网页的对象。 必须用语句string s=Request.Form("textBox1")得到输入的内容。如果将属性method="POST" 改为method="GET",用语句string s=Request.QueryString("textBox1")得到输入的内容。下边 是e11_1B.aspx网页完整文件:
如Button 按钮改为 HyperLink 控件如何使用 11.1.211.1.211.1.211.1.2 用Request Request Request Request 对象获取客户端浏览器的信息 不同浏览器或相同浏览器的不同版本支持不同的功能,Web 应用程序可能要根据不同 的浏览器采取不同的措施,可用 HttpRequest.Browser 属性的 HttpBrowserCapabilities 对象获 得用户使用的浏览器信息。见下例: 241
11.1.311.1.311.1.311.1.3 用Request Request Request Request 对象获取服务器信息
11.211.211.211.2 Response Response Response Response 对象对象对象对象 与 Request 是获取客户端 HTTP信息相反,Response 对象是用来控制发送给用户的信息, 包括直接发送信息在浏览器中显示、重定向浏览器到另一个 URL 以及设置 cookie的值。在 ASP.NET 中一般不用 Response对象发送信息给浏览器,可以用其它方法重定向浏览器到另 一个 URL,因此在 ASP.Net 中使用 Response对象的机会越来越少了,这里只对 Response对 象做简单介绍,设置 cookie方法在另一节介绍。 11.2.111.2.111.2.111.2.1 用Response Response Response Response 对象发送信息在浏览器中显示 (1) 在浏览器中显示数据,例如:(在 ASP.Net 不建议这样使用。) <%@ Page language="c#" %> <% 242 Response.Write(""); Response.Write("Response对象使用"); Response.Write(""); %> (2) 显示一个文件 <%@ Page language="c#" %> <% System.IO.FileStream fs=new System.IO.FileStream("d:\\asp\\g1.txt",FileMode.Open); IntPtr FileHandle=fs.Handle; Response.WriteFile(FileHand,0,fs.Length); Fs.Close(); %> 11.2.211.2.211.2.211.2.2 用Response Response Response Response 对象重定向浏览器 用Response对象重定向浏览器到新浪网主页的例子如下:
单击按钮打开新浪网主页
这里实现的功能完全可以用 HyperLink 控件实现,请读者试一试。但是如果根据条件用 语句实现转向其它网页,使用此语句还是必要的,例如,有些用户企图不经过登录直接访问 其它网页,在其它网页的 Page_Load 方法中要进行判断,如果未登录,可用上述方法直接转 向登录界面。 243 11.311.311.311.3 Cookie Cookie Cookie Cookie 对象对象对象对象 用户用浏览器访问一个网站,由于采用的 http 的特性,Web服务器并不能知道是哪一 个用户正在访问,但一些网站,希望能够知道访问者的一些信息,例如是不是第一次访问, 访问者上次访问时是否有未做完的工作,这次是否为其继续工作提供方便等等。用浏览器访 问一个网站,可以在此网站的网页之间跳转,当从第一个网页转到第二个网页时,第一个网 页中建立的所有变量和对象都将不存在。有时希望在这些被访问的网页中建立联系,例如一 个网上商店,访问者可能从不同的网页中选取不同的商品,那么用什么办法记录该访问者选 取的商品,也就是一般所说的购物筐如何实现。用 Cookie对象可以解决以上问题。 11.3.111.3.111.3.111.3.1 用Cookie Cookie Cookie Cookie 对象记录访问的次数
当然,浏览器的 Cookies 必须设置为允许使用。 244 11.3.211.3.211.3.211.3.2 网上商店购物筐实现 网上商店网站一般有多个网页,用户可以浏览这些网页,从每个网页中选择商品,网上 商店网站要记录这些要购买的商品,一般把这个功能叫做购物筐,下边的例子介绍购物筐的 实现方法。例子中有两个网页,每个网页有一个 CheckBoxList 控件,可以选不同商品,每 个网页都有两个按钮,一个按钮的标题是:把选中商品放入购物筐,另一个按钮的标题是: 结算。 (1) 第一个网页文件 e11_3_2A.aspx 如下:



选择花卉
(2) 第二个网页文件 e11_3_2B.aspx如下:



选择水果
(3) 两个个文件都存到宿主目录中,在浏览器中输入地址:http://Localhost/e11_3_2A.aspx, 选中某种水果,转到第二个网页 e11_3_2B.aspx,选中某种花卉,单击结算按钮,应显 示所选的所有商品。当然,本例只是说明问题,由许多不尽合理之处。读者可以采用数 据库,用 DataGraid 控件商品,增加一列,由两个按钮,标题分别是:放到购物筐和从 购物筐取出。还应时刻显示购物筐的内容。 Cookies 集合设置 cookie 的值。若指定的 cookie 不存在,则创建它。若存在,则设置新 的值并且将旧值删去。 语法 Response.Cookies(cookie)[(key)|.attribute]=value 这里的 cookie 是指定 cookie 的名称。而如果指定了 key,则该 cookie 就是一个字典。 Attribute 指定 cookie 自身的有关信息。Attribute 参数可以是下列之一 : 247 Domain 若被指定,则 cookie 将被发送到对该域的请求中去。 Expires 指定 cookie 的过期日期。为了在会话结束后将 cookie 存储在客户端磁盘上,必须 设置该日期。若此项属性的设置未超过当前日期,则在任务结束后 cookie 将到期。 HasKeys 指定 cookie 是否包含关键字。 Path 若被指定,则 cookie 将只发送到对该路径的请求中。如果未设置该属性,则使用应用 程序的路径。 11.411.411.411.4 Application Application Application Application 对象对象对象对象 Application 对象生存期和Web应用程序生存期一样长,生存期从Web应用程序网页被 访问开始,HttpApplication 类对象 Application 被自动创建,直到没有一个网页被访问时结束, Application 对象被自动撤销。因此 Application 对象中的变量也有相同生存期,并且变量可 以被 Web 应用程序中的所有网页访问。因此,可以在 Application 对象中建立一些全局的公 用变量,由于存储在 Application 对象中的数值可以被应用程序的所有网页读取,所以 Application 对象的属性也适合在应用程序的网页之间传递信息。Application 对象主要有以下 用途: � 存储记录在线人数或访问网站总人数的变量。 � 存储网站共用最新消息,供所有网页更新。 � 记录网站中个网页同一条广告被点击的次数或时间。 � 存储供所有网页使用的数据库数据。 � 不同用之间通讯,例如多用户聊天室,多用户游戏等 本节首先介绍 Application 对象的用法,然后介绍记录访问网站总人数的实现方法。 11.4.111.4.111.4.111.4.1 Application Application Application Application 对象属性 虽然 Application 对象没有内置的属性,但我们可以使用以下句法设置用户定义的属性 也可称为集合:Application("属性/集合名称")=值,例如,Application("MyVar")="Hello"。用 以下语句取出数据:string s=Application("MyVar")。 11.4.211.4.211.4.211.4.2 方法 Application 对象有两个方法,它们都是用于处理多个用户对存储在 Application 中的数 据进行写入的的同步问题。由于存储在 Application 对象中的数值可以被应用程序的所有网 页读取,因此一个用户在修改这个变量时,不允许其它用户修改,这两个方法就是解决这个 问题的。 � L ock 方 法 Lock 方法阻止其他客户修改存储在 Application 对象中的变量,以确保在同一时刻仅 248 有一个客户可修改和存取 Application 变量。如果用户没有明确调用 Unlock 方法,则服务 器将在 .asp 文件结束或超时后即解除对 Application 对象的锁定。 � Unlock 方法 和Lock 方法相反,Unlock 方法允许其他客户修改 Application 对象的属性。下例介绍一 个计数器变量的使用方法。 Application.Lock; Application["counter"]=(Int32)Application["counter"]+1; Application.UnLock; 11.4.311.4.311.4.311.4.3 事件 � Application_OnStart 事件 第一个浏览器访问 Web应用程序网页时,产生的事件。 � Application_OnEnd 事件 没有浏览器访问时 Web应用程序网页时,产生的事件。 Application_OnStart 和Application_OnEnd 事件的处理过程必须写在 global.asax 文件之 中。 11.4.411.4.411.4.411.4.4 例子::::显示访问网站总人数 (1) 建立一个主页文件 Default.aspx如下:

单击此处转到 e1.aspx,计数器不加 1。 249
(2) 建立 other.aspx网页文件如下:

单击此处转到 dault.aspx,计数器不加 1。
(3) 建立 global.asax 文件如下: (4) 三个文件都存到宿主目录中,在浏览器重输入 URL 地址:http://Localhost/,查看显示的 计数器数值,单击刷新按钮,查看显示的计数器数值是否改变,转到 Other.aspx网页, 在转回 dault.aspx 网页,查看显示的计数器数值是否改变。关闭所有网页,在打开 default.aspx网页,显示的计数器值从 0开始,这是因为没有网页访问网站时,Application 对象被自动撤销。在打开新网页,产生 Application_OnStart 事件,将 counter 值为 0。为 了解决此问题,可以建立一个文件,记录访问网站总人数,初值为 0,Application_OnStart 事件函数中,从文件取出已访问网站总人数,赋值给 counter,Application_OnEnd 事件函 数中,将 counter存到文件中。 (下载源码就到源码网:www.codepub.com) (5) 用记事本创建文件 counter_File.txt,其中内容为字符 0。存文件到宿主目录中。 (6) 修改 global.asax 文件如下: (7) 再一次访问 dault.aspx网页,看是否已解决以上提出的问题。这里还有一个问题,如果 用用如下 URL 访问网页:http://Localhost/Other.aspx,这样计数器就不能计数,解决的方 法见 Session 对象一节。 11.511.511.511.5 Session Session Session Session 对象对象对象对象 前边提到,用浏览器访问一个网站,当在网站的网页之间跳转时,希望在这些被访问的 网页中建立联系,例如一个网上商店的购物筐的实现,这些可以用 Cookie实现。用Session 对象也可以解决以上问题。 当浏览器开始访问网站的某网页时,Web服务器将自动创建一个Session对象,在Session 对象中可以建立一些变量,这个 Session 对象和 Session 对象中的变量只能被这个访问者使 用,其它访问者不能使用。当用户在网站的网页之间跳转时,Session 对象和存储在 Session 对象中的变量不会被清除,这些变量始终存在。当浏览器离开本网站或超过一定时间和网站 没有联系,Session 对象被撤销,同时存储在 Session 中的变量也不存在了。 在ASP 中,Session 对象的功能本质上是用 Cookie 实现的,如果用户将浏览器上面的 Cookies 设置为禁用,那么 Session 就不能工作。但在ASP.NET 中我们有解决方法,在 config.web 文件中,我们将<sessionstate cookieless="false" />设置为 true就可以了,也就说, 不使用 Cookies 也可以使用 Session。 11.5.111.5.111.5.111.5.1 属性 � SessionIDSessionID 属性返回用户的会话标识。在创建会话时,服务器会为每一个会话 生成一个单独的标识。会话标识以长整形数据类型返回。在很多情况下 SessionID 可以用 于 WEB 页面注册统计。 � TimeOut Timeout 属性以分钟为单位为该应用程序的 Session 对象指定超时时限。如果 251 用户在该超时时限之内不刷新或请求网页,则该会话将终止。 11.5.211.5.211.5.211.5.2 方法 Session 对象仅有一个方法,就是 Abandon,Abandon 方法删除所有存储在 Session 对 象中的对象并释放这些对象的源。如果您未明确地调用 Abandon 方法,一旦会话超时,服 务器将删除这些对象。当服务器处理完当前页时,下面示例将释放会话状态。 < % Session.Abandon %> 11.5.311.5.311.5.311.5.3 事件 Session 对象有两个事件可用于在 Session 对象启动和释放是运行过程。 � Session_OnStart 事件在服务器创建新会话时发生,当用户第一次浏览网页时,发生 Session_OnStart 事件。服务器在执行请求的页之前先处理该脚本。Session_OnStart 事件是 设置会话期变量的最佳时机,因为在访问任何页之前都会先设置它们。尽管在 Session_OnStart 事件包含 Redirect 或 End 方法调用的情况下 Session 对象仍会保持,然 而服务器将停止处理 Global.asa 文件并触发 Session_OnStart 事件的文件中的脚本。为了确 保用户在打开某个特定的 W eb 页 时始终启动一个会话,就可以在 S ession_OnStart 事 件 中调用 R edirect 方 法。当用户进入应用程序时,服务器将为用户创建一个会话并处理 S ession_OnStart 事 件脚本。您可以将脚本包含在该事件中以便检查用户打开的页是不是启 动页,如果不是,就指示用户调用 R esponse.Redirect 方 法启动网页。程序如下 : < SCRIPT RUNAT=Server Language=VBScript> Sub Session_OnStart startPage = "/MyApp/StartHere.asp" currentPage = Request.ServerVariables("SCRIPT_NAME") if strcomp(currentPage,startPage,1) then Response.Redirect(startPage) end if End Sub < /SCRIPT> 上述程序只能在支持 cookie 的 浏览器中运行。因为不支持 cookie 的浏览器不能返回 SessionID cookie,所以,每当用户 请求 Web 页时,服务器都会创建一个新会话。这样,对于每个请求服务器都将处理 Session_OnStart 脚本并将用户重定向到启动页中。 � Session_OnEnd 事件在会话被放弃或超时发生。如果用户在指定时间内没有请求或刷新 应用程序中的任何页,会话将自动结束。这段时间的默认值是 2 0 分 钟。可以通过在 I nternet 服 务管理器中设置“应用程序选项”属性页中的“会话超时”属性改变应用程序的 默认超时限制设置。应依据您的 W eb 应 用程序的要求和服务器的内存空间来设置此值。 例如,如果您希望浏览您的 W eb 应 用程序的用户在每一页仅停留几分钟,就应该缩短会 话的默认超时值。过长的会话超时值将导致打开的会话过多而耗尽您的服务器的内存资源。 对于一个特定的会话,如果您想设置一个小于默认超时值的超时值,可以设置 S ession 对 象的 T imeout 属 性。例如,下面这段脚本将超时值设置为 5 分 钟。 < % Session.Timeout = 5 %> 当然你也可以设置一个大于默认设置的超时值,Session.Timeout 属性决定超时值。 你还可以通过 Session 对象的 Abandon 方法显式结束一个会话。例如,在表格中提供一个 “退出”按钮,将按钮的 ACTION 参数设置为包含下列命令的 .asp 文件的 URL。 < % Session.Abandon %> � 252 11.5.411.5.411.5.411.5.4 用Session Session Session Session 对象实现网上商店购物筐 本例要求和用 Cookie 实现网上商店购物筐完全一样,只是用Session 对象实现,具体代 码如下: (1) 第一个网页文件 e11_5_4A.aspx 如下:
253


选择花卉
(2) 第二个网页文件 e11_5_4B.aspx如下:



选择水果
(3) 两个个文件都存到宿主目录中,在浏览器中输入地址:http://Localhost/e11_3_2A.aspx, 选中某种水果,转到第二个网页 e11_3_2B.aspx,选中某种花卉,单击结算按钮,应显 示所选的所有商品。象前边所说,本例不尽合理,读者可以采用数据库,用 DataGraid 控件商品,增加一列,由两个按钮,标题分别是:放到购物筐和从购物筐取出。还应时 刻显示购物筐的内容。 11.6 Server 对象 Server 对象提供对服务器上的资源进行访问的方法和属性,主要包括:得到服务器的计 算机名称,设置脚本程序的失效时间,将 HTML 的特殊标记转变为 ASCII 字符,得到文件 的真实路径等等,本节将逐一介绍这些方法。 11.6.111.6.111.6.111.6.1 属性 MachineName MachineName MachineName MachineName 和ScriptTimeoutScriptTimeoutScriptTimeoutScriptTimeout (1) 属性 MachineName 该属性用来获取当前运行 Web 应用程序的 Web服务器的计算机名称,使用方法如下: string s=Server.MachineName;这个计算机名称可以用如下办法查到:打开”控制面板”,选中” 系统”中的”计算机名”,应和用 Server 对象的属性 MachineName 获得计算机名称一致。 (2) 属性 ScriptTimeout Web 应用程序由于运行在计算机网络中,由于网络的原因,一些程序可能无法完成, 一直在等待,这将极大消耗 Web 服务器的资源,为了避免这种情况,可以设置程序运行的 255 最长时间,即设置属性 ScriptTimeout,在脚本程序运行超过属性 ScriptTimeout 指定时间之 后即作超时处理,也就停止程序运行。如以下代码指定服务器处理脚本程序在 100 秒后超时: Server.ScriptTimeout=100,其默认值为 90 秒。 11.6.211.6.211.6.211.6.2 HtmlEncode HtmlEncode HtmlEncode HtmlEncode 方法 HTML 标记语言中,有些 ASCII字符被作为标记,例如字符串:
中的<和>都是标 记,如需要显示这些字符,必须作特殊处理,例如为了在浏览器中正确显示如下字符串:”
是换行标记”,字符串必须写为如下形式: ; 也可以用 Server 对象的属性 HtmlEncode方法,用法如下: Server.HtmlEncode(”
是换行标记”)
; 11.6.311.6.311.6.311.6.3 URLEncode URLEncode URLEncode URLEncode 方法 URL 是 Uniform Resource Location(统一资源定位器)的简称,URL 用来定位一个网页的。 在URL 中,有些 ASCII 字符具有特殊含义,必须做特殊处理。例如 http://www.sina.com/中 的字符/,用 Server 对象 URLEncode方法处理, string s=”http://www.sina.com/”; 11.6.411.6.411.6.411.6.4 MapPath MapPath MapPath MapPath 方法 网页中网页文件的路径一般是以宿主目录为根目录,不同的系统中,宿主目录所在的实 际目录并不相同,而且网页也可能在虚拟目录中。因此网页文件的路径并不是网页文件的实 际路径。而在用 File 类处理文件时,则要求文件的地址必须是实际的全路径,Server 对象的 MapPath 方法提供这两种路径的转换方法,例如,f1.aspx 文件存在宿主目录下的 Test目录 下,用 Server 对象得到 f1.aspx 文件绝对路径方法如下: string s=Serve.MapPath(\Test\f1.aspx);//这里\表示以宿主目录 也可以用如下语句: string s=Serve.MapPath(Test\f1.aspx);//表示单前网页所在的目录的子目录 Test 习题习题习题习题 (1) 如何实现记录访问网站的在线人数。(提示:增加一个 Application 对象变量作为计数器, Application_Start 事件函数中计数器为 0,Session_Start 事件函数中计数器加 1, Session_End 事件函数中计数器减 1,每个网页的 Page_Load 事件函数中用 Label 控件显 示计数器值。) (2) 用Application 对象建立一个 2人聊天室。如果是多人聊天室,又如何实现。 (3) 用户不经过主页,直接访问网站的某网页,将不能时访问者总数加 1,如何防止。 (4) 将书中的例子用 Visual Studio.Net 实现。 256 第十二章 可扩展标记语言 12.112.112.112.1 HTML HTML HTML HTML 及其缺点及其缺点及其缺点及其缺点 Internet 提供了全球范围的网络互连与通信功能,Web技术的发展更是一日千里,其丰 富的信息资源给人们的学习和生活带来了极大的便利。特别是应运而生的 HTML(超文本 置标语言),以简单易学、灵活通用的特性,使人们发布、检索、交流信息都变得非常简单, 从而使 Web成了最大的环球信息资源库。然而,电子商务、电子出版、远程教育等基于 Web 的新兴领域的全面兴起使得传统的 Web 资源更加复杂化、多样化,数据量的日趋庞大对网 络的传输能力也提出更高的要求,人们对 Web服务功能的需求也达到更高的标准。而传统 的HTML 由于自身特点的限制,不能满足这些要求。HTML 主要有如下不足: � HTML 的标记都是预先定义的,用户不能自定义有意义的标记,可扩展性差。 � HTML 的显示方式内嵌在数据中,这样在创建文本时,要同时考虑输出格式,如 果因为需求不同而需要对同样的内容进行不同风格的显示时,要从头创建一个全新 的文档,重复工作量很大。不能对数据按照不同的需求进行多样化显示等个性化服 务。 � HTML 缺乏对数据结构的描述,对于应用程序理解文档内容、抽取语义信息都有 诸多不便。不能进行智能化的语义搜索。不能对不同平台、不同格式的数据源进行 数据集成和数据转化等。 � HTML 语言不能描述矢量图形、数学公式、化学符号等特殊对象。 12.212.212.212.2 SGMLSGMLSGMLSGML((((标准通用置标语言标准通用置标语言标准通用置标语言标准通用置标语言)))) SGML(Standard Generalized Markup Language)是一种通用的文档结构描述置标语言,为 语法置标提供了异常强大的工具,同时具有极好的扩展性,因此在数据分类和索引中非常有 用。但 SGML 复杂度太高,不适合网络的日常应用,加上开发成本高、不被主流浏览器所 支持等原因,使得 SGML 在Web上的推广受到阻碍。 12.312.312.312.3 XML(XML(XML(XML(可扩展置标语言可扩展置标语言可扩展置标语言可扩展置标语言)))) XML(eXtensible Markup Language)是由 W3C于1998 年2月发布的一种标准。它同样是 SGML 的一个简化子集,它将 SGML 的丰富功能与 HTML 的易用性结合到 Web的应用中, 以一种开放的自我描述方式定义了数据结构,在描述数据内容的同时能突出对结构的描述, 从而体现出数据之间的关系。XML 的优点如下: � XML 简单易用,功能强大。 � XML 允许各个组织、个人建立适合自己需要的标记集合,并且这些标记可以用通 用的工具显示。例如定义数学、化学、音乐等专用标记。 � XML 的最大优点在于它的数据存储格式不受显示格式的制约。一般来说,一篇文 档包括三个要素:数据、结构以及显示方式。XML 把文档的三要素独立开来,分 257 别处理。首先把显示格式从数据内容中独立出来,保存在样式表文件(Style Sheet) 中,这样如果需要改变文档的显示方式,只要修改样式表文件就行了。XML 的自 我描述性质能够很好地表现许多复杂的数据关系,使得基于 XML 的应用程序可以 在XML 文件中准确高效地搜索相关的数据内容,忽略其他不相关部分。 � XML 还有其他许多优点,比如它有利于不同系统之间的信息交流,完全可以充当 网际语言,并有希望成为数据和文档交换的标准机制。 由于以上优点,XML 必将在商务的自动化处理,信息发布,智能化的 Web应用程序和 数据集成等领域被广泛被使用。 12.412.412.412.4 XML XML XML XML 的文档格式的文档格式的文档格式的文档格式 首先介绍 XML 文档内容的基本单元——元素,它的语法格式如下: 〈标签〉文本内容〈/标签〉 元素是由起始标签、元素内容和结束标签组成。用户把要描述的数据对象放在起始标签 和结束标签之间。例如: <姓名>王平 无论文本内容有多长或者多么复杂,XML 元素中还可以再嵌套别的元素,这样使相关 信息构成等级结构。下面的例子中,在<学生>的元素中包括了所有学生的信息,每个学生 都由<学生>元素来描述,而<学生>元素中又嵌套了<编号>、<姓名>、<性别>和<年龄>元素。 完整 XML 文件 student.xml内容如下,例1: <学生> <编号>001 <姓名>张三 <性别>男 <年龄>20 除了元素,XML 文档中能出现的有效对象是:声明指令、注释、根元素、子元素和属性。 � 声明指令 声明指令给 XML 解析器提供信息,使其能够正确解释文档内容,它的起始标识是 “”。常见的 XML 声明就是一个处理指令: 该处理指令指明 XML 使用的版本号和文档的编码方式是"GB2312"。又如: 使用 student1.xsl样式表文件显示本 XML 文档。 � 注释 注释是 XML 文件中用作解释的字符数据,XML 处理器不对它们进行任何处理。 注释是用“ ”引起来的,可以出现在 XML 元素间的任何地方,但是不可 以嵌套: � 根元素和子元素 如果一个元素从文件头的序言部分之后开始一直到文件尾,包含了文件中所有的数 据信息,我们称之为根元素。XML 元素是可以嵌套的,那么被嵌套在内的元素称 258 为子元素。在前面的例子中,<编号>就是<学生>的子元素。 � 属性 属性给元素提供进一步的说明信息,它必须出现在起始标签中。属性以名称/取值 对出现,属性名不能重复,名称与取值之间用等号“=”分隔,并用引号把取值引起 来。例如: <工资 currency=“US$”> 25000 上例中的属性说明了薪水的货币单位是美元。 � XML 文档的基本结构 XML文档的基本结构由序言部分和一个根元素组成。序言包括了XML声明和DTD (或者是 XML Schema),DTD(Document Type Define,文档定义类型)和 XML Schema 都是用来描述 XML 文档结构的,也就是描述元素和属性是如何联系在一 起的。例如,在例 1的文档前面加上如下的序言部分,就构成了一个完整的 XML 文档: 一个 XML 文档中有且仅有一个根元素,其他所有的元素都是它的子元素,在例 1 中,<学生>就是根元素。 � 格式良好的”(Well-Formed)XML 文档 一个 XML 文档首先应当是“格式良好的”(Well-Formed),该规定的正式定义位于: http://www.w3.org/TR/REC-xml。“格式良好的”XML 文档除了要满足根元素唯一的 特性之外,还包括: (1) 起始标签和结束标签应当匹配:结束标签是必不可少的; (2) 大小写应一致:XML 对字母的大小写是敏感的,是 完全不同的两个标签,所以结束标签在匹配时一定要注意大小写一致; (3) 元素应当正确嵌套:子元素应当完全包括在父辈元素中,下面的例子就是嵌套 错误: 正确的嵌套方式如下: (4) 属性必须包括在引号中;元素中的属性是不允许重复的。 12.512.512.512.5 用用用用XSL XSL XSL XSL 文件显示 文件显示 文件显示 文件显示 XML XML XML XML 文档文档文档文档 由于 XML 文档只是定义数据的结构,并不包含显示的格式。如要按指定格式显示这些 数据,还要使用 CSS 文件或 XSL文件定义显示格式。这里使用三个 XSL文件按不同显示 格式显示同一个 XML 文件。首先定义一个 student1.xsl文件显示上边 XML 文档,文件如下: 259 将 student1.xsl 文件和文件 student1.xml 存到同一文件夹,双击 student1.xml 文件,打开 IE5.0, 显示效果如下: 001, 张三, 男, 20 可以定义不同的 xsl文件,以不同的显示方式显示 student1.xml文件。例如 student2.xsl 文件如下: 演示 2 260
编号
姓名
性别
年龄
例如用 student3.xsl以不同的显示方式显示 student1.xml文件。文件如下: 演示 3

学生信息

编号:
姓名: 261
性别:
年龄:

读者可以试一下。 262 12.612.612.612.6 .NET .NET .NET .NET 对对对对XML XML XML XML 的支持的支持的支持的支持 首先,创建一个 XML 文档,文件名为 MyXMLFile.xml,内容如下: <书名>SQL实用全书 <作者>Rafe Colburn <出版日期>2001 年6月 <价格>34.00 <书名>C#高级编程 <作者>Simon Robinson <出版日期>2002 年6月 <价格>128.00 <书名>ASP.NET 从入门到精通 <作者>Chris Payne <出版日期>2002 年1月 <价格>41.00 <书名>精通 C#与ASP.NET 程序设计 <作者>孙三才 <出版日期>2003 年6月 <价格>39.00 <书名>ASP.NET 实用全书 <作者>张三 <出版日期>2004 年6月 <价格>55.00 请读者用 IE浏览器(5.0以上版本)浏览 MyXMLFile.xml 文件,单击标记前的减号(或加 号),看一下效果。 网页文件 c8-1-1A.aspx 用来读出每本书的书名、作者、出版日期、价格等数据。 <%@ Page Language="C#" %> <%@ Import Namespace="System.Xml" %> 263 读XML 文件

读XML 文件

当用 XmlTextReader 读(dr.Read())Xml 文档时,每次读出一个节点的数据。 一个 Xml 文档的元素,可以分为两大类,第一类是文本,第二类是标记。文本是 Xml 文档的数据,在两个标记之间的文本被称为一个文本节点,例如,<书名>SQL实用全书中的”SQL实用全书”是一个文本节点。这个节点的类型是:Xml.XmlNodeType.Text。 第二类 Xml 文档的元素是标记,它可以分为以下几大类:注释标记、声明标记、开始 标记,结束标记,每类都被称为一个 Xml 文档的标记节点,例如,是 注释标记,注释标记的节点类型为:Xml.XmlNodeType.Comment,注释的内容为 dr.Value 。 是声明标记,其中包括两个声明:xml version="1.0"和encoding="GB2312",等号前内容的被称为声明的名称(dr.Name),等号后内 容的被称为声明的值(dr.Value) 。声明标记的节点类型为:Xml.XmlNodeType.XmlDeclartion。 是开始标记,book 被称为标记名称(dr.Name),出版社被 称为属性(dr.AttributeName),"电子工业出版社"被称为属性的值(Value)。开始标记的节点类 型为:Xml.XmlNodeType.Element。是结束标记,book 被称为标记名称(dr.Name)。 结束标记的节点类型为:Xml.XmlNodeType.EndElement。 本网页的 Page_Load 方法中,用 dr.Read()读Xml 文档,每次读出一个节点的数据,用 语句 if(dr.NodeType==XmlNodeType.Text)判断是否是文本节点,如果是文本节点,则把文本 内容加到ListBox1 。如果希望只显示书名,则判断语句可以改为: if(dr.NodeType==XmlNodeType.Text && dr.Name==”书名”)。 网页文件 c8-1-1B.aspx 用来读出标记 book 的属性,具体内容如下: <%@ Page Language="C#" %> <%@ Import Namespace="System.Xml" %> 读XML 文件

读XML 文件

如果,显示注释,改为下列语句: public void Page_Load(Object sender, EventArgs e) { string FileNameString="d:\\asp\\bookExample\\MyXMLFile.xml"; XmlTextReader dr= new XmlTextReader(FileNameString); while(dr.Read()) if(dr.NodeType==XmlNodeType.Comment) ListBox1.Items.Add(dr.Value); } 如果,显示声明,改为下列语句:(见c8-1-1c.aspx) <%@ Page Language="C#" %> <%@ Import Namespace="System.Xml" %> 读XML 文件

读XML 文件

265 下例用 DataGrid 控件显示 MyXMLFile.xml,(见c8-1-1D.aspx) <%@ Page Language="C#" %> <%@ Import Namespace="System.Xml" %> <%@ Import Namespace="System.Data" %> 读XML 文件

读XML 文件

12.712.712.712.7 ADO.NET ADO.NET ADO.NET ADO.NET 和和和和XMLXMLXMLXML 仔细察看 MyXMLFile.xml 文件,它和数据库的表有对应关系,标记之间的 内容可以看作一个数据库的表,标记之间的内容可以看作一个数据库的表的一个记 录,标记<书名>、<作者>、<出版日期>、<价格>可以看作一个数据库的表的字段,这些标 记之间的文本可以看作这些字段的数据。我们知道,一个字段还有一些其他属性,例如,字 段的数据类型,为了表示这些属性,可以使用 DTD(Document Type Define,文档定义类型) 和 XML Schema 来描述XML 文档的数据结构,也就是描述元素和属性是如何联系在一起的。 微软的.NET系统支持用XML Schema来描述XML 文档的数据结构,下例介绍如何使用XML Schema,见文件 C8-1-1F.aspx。 <%@ Page Language="C#" %> <%@ Import Namespace="System.Data" %> 266 <%@ Import Namespace="System.Data.SqlClient" %> <%@ Import Namespace="System.IO" %> DataGrid

将数据库表存为带 XML 架构和不带 XML 架构 XML 文件

267
单击两个按钮,可以创建带 XML 架构和不带 XML 架构XML 文件,文件名为 "XmlFile1.xml"和"XmlFile1.xml"。用浏览器察看这两个 XML 文件,可以看到它们的区别。 如创建了有架构的 XML 文件,可以修改该文件,例如,修改字段类型。用网页文件 C8-1-1G 可以重新打开带 XML 架构或不带 XML 架构 XML 文件。 <%@ Page Language="C#" %> <%@ Import Namespace="System.Xml" %> <%@ Import Namespace="System.Data" %> 读XML 文件 268

读带 XML 架构和不带 XML 架构 XML 文件

12.812.812.812.8 使用 使用 使用 使用 VisualVisualVisualVisual Studio.Net Studio.Net Studio.Net Studio.Net 建立和显示 建立和显示 建立和显示 建立和显示 XML XML XML XML 文档文档文档文档 (1) 创建一个 Web 应用程序框架,项目名为 UseXml。 (2) 在窗体中放置控件 DataGrid,其属性 Name=DataGrid1。 (3) 放工具箱的2个Button 控件到窗体,修改属性 Text 分别为:存为带XML 架构的 XML 文件,读带 XML 架构的 XML 文件。 (4) 新建一个 XML 文件。单击菜单项”项目/添加新项”,弹出标题为添加新项的窗口,在窗 口中选中 XML 文件,文件名为 MyXMLFile.xml,单击打开按钮,增加一个 XML 文件 (5) 在文件添加如下内容: <书名>SQL实用全书 <作者>Rafe Colburn <出版日期>2001 年6月 <价格>34.00 <书名>C#高级编程 <作者>Simon Robinson <出版日期>2002 年6月 <价格>128.00 <书名>ASP.NET 从入门到精通 <作者>Chris Payne <出版日期>2002 年1月 <价格>41.00 <书名>精通 C#与ASP.NET 程序设计 <作者>孙三才 <出版日期>2003 年6月 269 <价格>39.00 <书名>ASP.NET 实用全书 <作者>张三 <出版日期>2004 年6月 <价格>55.00 (6) 单击 MyXMLFile.xml 窗口下的数据,可以看到用表格显示的 XML 文件。 (7) 为Page_Load 事件函数增加语句: private void Page_Load(object sender, System.EventArgs e) { string FileNameString="MyXMLFile.xml"; DataSet ds = new DataSet(); ds.ReadXml(Server.MapPath(FileNameString)); DataGrid1.DataSource=ds; DataGrid1.DataMember="book"; DataGrid1.DataBind(); // 在此处放置用户代码以初始化页面 } (8) 运行,可以看到用表格显示的 XML 文件。 (9) 打开 MyXMLFile.xml 文件,单击菜单项”XML/创建架构”,将创建 MyXMLFile.xsd 文件, 打开此文件,可以修改每个字段的属性。 (10)为单击存为带XML架构的XML 文件按钮事件(Click)函数增加语句(双击Click事件): (11)为单击读带XML 架构的 XML 文件按钮事件(Click)函数增加语句(双击 Click 事件): 270 第十三章 Web Web Web Web 服务 Micosoft.Net平台架构中的分布式系统主要包括两部分:用 ASP.Net技术构建服务器端 动态网页,以及 Web 服务(Web Service或XML Web Service)。前边章节已详细介绍了构建服 务器端动态网页的方法,本节将介绍 Web服务的基本概念和构建方法。 13.113.113.113.1 WebWebWebWeb服务的概念和用途服务的概念和用途服务的概念和用途服务的概念和用途 Web 中无论是静态网页还是动态网页,数据都被嵌入到网页中,网页的服务对象都是 人,用户可以很容易阅读这些网页。但如果一个程序使用这种方式获得数据,会是十分困难 的,程序必须从网页中把数据分离,才能加以利用。而用一个程序在 Web中获得数据有时 又是十分必要的,例如:一个气象台总站希望通过 Internet网获得各个基层气象台的各种资 料,在网上以统一的网页对外发布。气象台总站希望各个基层气象台提供一个 Internet网的 服务,能根据总站的要求,自动提供相应的资料。类似的例子很多,例如一个很大的单位的 总部和下属单位之间信息系统的整合,一个综合网站希望自动获得其它网站提供的信息等 等。这种需求实际上就是 Web服务。 为实现这种功能有很多困难,各个基层气象台使用的系统可能完全不同,即使使用相同 的操作系统,也可能使用不同数据库系统,数据库中定义的字段可能不同,数据库应用程序 可能使用不同的语言编制,即使这些完全相同,还可能数据的表示方式不相同,数据格式, 数据的位数等等。为解决这些问题,已提出了许多方案,例如:微软的分布式控件对象模型 (DCOM)、对象管理组织(OMG)的公用对象请求代理程序体系结构(CORBA)、SUN 公司 的远程方法调用(RMI)等等,但这些方法都不能很好的解决以上问题。 Micosoft.Net 的Web 服务为实现这种功能提供了完整的解决方案。Web 服务使用 Http 协议在 Internet 网上传输数据和消息,用XML 扩展标记语言描述数据,用SOAP 表示消息, SOAP 是一个简单的、重量轻的基于 XML 的协议,用于交换 Web 上的结构化的和模式化 的信息。用 Micosoft.Net 的Web 服务实现气象台总站所需功能的大概思路是这样的,每个 基层气象台在自己的系统中提供一个 Internet 网远程调用函数,该函数用 Http协议接受用 SOAP 表示的调用,并把函数的返回值用 XML 扩展标记语言描述,用 SOAP 表示后,用 Http协议返回给调用者。气象台总站只要使用 Http和SOAP 协议逐一调用这些 Web远程函 数,就可以获得各个基层气象台的资料了。由于这些协议都是被广泛接受的协议,能被不同 的系统所接受,也就解决了以上所提出的问题。 有以上叙述可知,Web 服务不追求代码的可移植性,而是提供一个可行的解决方案来 增强数据和系统的互操作性。有许多 Web 服务的定义,比较简单又比较容易理解的描述是: Web服务是一个可通过 Http、SOAP 和XML 协议进行访问的 Web远程函数库。 刚才讨论的问题只是 Web服务的几个应用,还有许多其它用途,例如:。 ���� 应用程序集成 你可以使用 Web服务以一种集成的方式整合表面上看上去完全不同的现有应用程序。 例如许多公司的每个部门都有定制的软件,产生一系列有用但是孤立的数据和业务逻辑。为 了管理上的方便,非常有必要把这些应用程序功能集合到一起。利用 Web服务,就有可能 把现有的应用程序中的数据和功能以Web服务方式提供给其它部门。然后可以创建一个集 271 成的应用程序,增强各部门之间的互操作性。 ���� 代码复用 在软件开发行业,大部分开发者都依赖代码复用。过去开发者们为了利用他人已经实现 了的代码,或者将代码段复制到自己的代码中,做一些改动以适应自己得需要,或者在服务 器或个人计算机上安装一个控件库,让应用程序来访问这个库。这将使得代码有很多个版本, 而这些版本间可能只有细微差别,却分散在各个地方。当代码最初的开发者决定对代码更新 一下或者改正一下错误,要把这些改变告诉所有使用这些代码的开发者的时候,将是非常困 难的。如果我们把代码放在一个中心位置存储,让所有人都访问这儿,这不是很好吗?这样 原创者可以在做了一些增补或者修正之后,能够立即提供给所有使用它的人。用 Web服务 可以实现以上设想,远程调用 Web 服务中的方法,就象调用本地函数一样方便。 ���� 工作流程解决方案 有些工作是非常复杂的,例如,货物的运输,可能要使用多种交通工具,火车、汽车、 轮船等,商业上的一笔交易,都是一个非常复杂的流程,流程的每一个环节都由不同部门的 不同的程序进行控制,如何建立这些控制程序之间的联系,是十分重要的。使用 Web 服务 是一个很好的解决方案。通过 Web 服务,使各个流程控制程序建立联系,完全实现自动控 制和管理。 ���� 新的销售方式 现在软件的销售方式一般是用户把软件买回去,安装在自己的计算机中。有了 Web 服 务,就可以提供软件的服务,按次收费。 ���� 由Web Web Web Web 服务组成的自动化系统 不远的将来,信息家电将要联接到 Internet网上,PDA、手机,甚至各种嵌入式设备也 要上网,这些设备和其它设备之间通过 Web服务建立联系也是一种可行的方案。 13.2 建立 Web服务 Web 服务仍采用客户/服务器模式(Cient/Server)。本节介绍在服务器端应做的工作, 包括建立供客户端调用的 Web 服务方法,以及为了客户端使用 Web 服务方法,提供给客户端 描述该 Web 服务的 WSDL 文档。 13.2.113.2.113.2.113.2.1 用记事本建立 Web服务 建立一个 Web 服务文件和建立一个普通网页文件的步骤基本一样,下边是一个最简单的 Web 服务文件,其它程序访问其中的 Web服务方法时,将返回参数 a和b的和,具体程序代 码如下: <%@ WebService Language="C#" Class="MyClass"%> using System; using System.Web.Services; public class MyClass:WebService {[WebMethod] public int MyWebMethod (int a,int b) { return a+b; } //其它 WebMethod   272 } 在文件中,第一行的语句表示这是一个 Web服务文件,使用 C#语言,Web 服务的类名 是MyClass。由于建立的 Web 服务类必须以 WebService类为基类,所以必须引入命名空间 System.Web.Services,这个 Web 服务类必须是一个公有类。可供其它程序访问的方法叫 Web 服务方法,在其头部必须增加关键字[WebMethod],表示这个方法是一个 Web服务方法,这 个方法必须是一个公有方法。 建立文件后,以asmx为扩展名存盘,存到网站的宿主目录中或其任意子目录中。使用 URL 定位这个 Web服务文件。现在使用浏览器检验这个 Web 服务,如果把 Web 服务文件以 MyAdd.asmx 存到网站的宿主目录中,在浏览器中 URL 地址栏中输入如下地址: http://localhost/MyAdd.asmx,浏览器中显示如下: 点击 MyWebMethod,浏览器中显示如下: 在编辑框中输入两个加数分别为 10 和20,然后点击 invote 按钮,在浏览器上显示如 下内容,这是用 XML 标记表示的调用 Web服务方法 MyWebMethod 返回的结果。 30 13.2.213.2.213.2.213.2.2 用VisualVisualVisualVisual Studio.Net Studio.Net Studio.Net Studio.Net 建立 Web服务 如果使用 Visual Studio.Net 建立这个 Web服务文件,具体步骤如下: (1) 打开 vs.net,新建项目(asp.net web 服务),在位置中键入 http://localhost/webserver, 其中 webserver就是项目的名字。单击确定按钮,创建项目。 (2) 打开 Service1.asmx.cx文件如下: using System; using System.Collections; using System.ComponentModel; using System.Data; using System.Diagnostics; using System.Web; using System.Web.Services; namespace webserver { /// ///Service1的摘要说明。 /// //(1) public class Service1:System.Web.Services.WebService { public Service1() { //CODEGEN:该调用是 ASP.NET Web服务设计器所必需的 InitializeComponent(); } 273 #region Component Designer generated code //Web服务设计器所必需的 private IContainer components = null; /// /// 设计器支持所需的方法 - 不要使用代码编辑器修改 /// 此方法的内容。 /// private void InitializeComponent() { } /// ///清理所有正在使用的资源。 /// protected override void Dispose(bool disposing) { if(disposing&&components!=null) { components.Dispose(); } base.Dispose(disposing); } #endregion //WEB 服务示例 //HelloWorld()示例服务返回字符串 Hello World //若要生成,请取消注释下列行,然后保存并生成项目 //若要测试此 Web服务,请按 F5 键 //[WebMethod] //public string HelloWorld() //{ // return "Hello World"; //} } } (3) 下面在//(1)处加入[WebService(Namespace="http://localhost/webserver/")],这是因为 SOAP是基于 http 协议上的,客户端无法知道 webservice 位于那个服务器上。在实 际应用中,比如 http://www.ourfly.com 上放置这个 webservice,则 Namespace 改为 http://www.ourfly.com/webserver。 (4) 下面给这个 webservice 添加一个方法。微软帮我们写好了一个如下,以被注解掉。 //[WebMethod] //public string HelloWorld() //{ //return "Hello World"; //} 添加一个自己的方法。方法名称叫 show 274 [WebMethod] public string show(string yourname) { return “http://www.ourfly.com”+”欢迎”+yourname; } (5) 现在可以测试这个 Web服务,按F5运行,点击 show,输入你的名字,然后点击 invote 按钮,在浏览器上显示如下内容,这是用 XML 标记表示的调用 Web服务方法 Show 返回的结果。 http://www.ourfly.com 欢迎 yyg (6) 打开 bin 目录,Vs.net已经将 proxy 做好了.webserver.dll。 (7) 请注意,这里运行只是一种测试,实际上应在其它计算机上生成一个调用此 Web服 务的程序,可以是 Windows 应用程序,也可以是控制台程序,或者是 ASP.Net程序, 即可以是 Micosoft.Net系统程序,也可以是其它系统程序,例如 Java程序,Linux 程序等等,下节将介绍这方面的知识。 13.2.313.2.313.2.313.2.3 服务描述语言(WSDL)(WSDL)(WSDL)(WSDL) WSDL(Web Services Description Language)中文名称为 Web服务描述语言。 Web 服务提供了一种服务,允许 Internet 上的计算机使用 http 和SOAP 协议远程调用 Web服务方法。大家都知道,为了使用一个函数,首先要看一下函数的使用说明。Web 服 务方法也存在同样的问题,特别是 SOAP 协议,它采用 XML 标记语言描述 Web 服务中传 递的消息,而 XML 标记语言是可以定义自己的标记的,但 SOAP 并没有提供一种通用的 XML 标记供 Web服务使用,不同的 Web 服务中 SOAP 的XML 标记定义可能不同。因此, 为了使不同系统调用其它系统中的 Web 服务,必须对调用 Web 服务的方法及 Web服务返回 的数据的格式做详细说明即服务描述,而且这种描述也应采用被广泛接受的协议。 WSDL 就是 Web 服务描述语言。WSDL 是基于 XML 的,用 WSDL 生成一个 XML 文 档,可以提供关于 Web服务的操作信息,例如,抽象形式的服务接口信息、数据传输的具 体访问协议和格式、供客户端使用该 Web 服务的细节等等。服务描述是一个使用 WSDL 语 言的 XML 语法编写的 XML 文档,定义了 Web服务能理解的 Web 服务消息格式。服务描 述起一个协定的作用,用来定义一个 Web服务的行为并且指示潜在的客户如何与之交互。 由于在 micosoft.Net 中提供了一些工具,可以自动生成 WSDL 文档,这里就不介绍 WSDL 了,可以通过下边方法看到 micosoft.Net 自动生成的 WSDL 文档,例如查看上节生成的 Web 服务,在浏览器中 URL 地址中输入 http://localhost/MyAdd.asmx?WSDL,浏览器中显示该 Web 服务 WSDL 文档。 13.3 基于.Net的Web服务客户端程序 Web 服务客户端程序是用来调用服务器端的 Web 服务方法,前边使用浏览器调用 Web 服 务方法,只能算做一种测试,通过这种测试,可以验证 Web 服务方法的正确性,发现错误。 作为客户端程序,无论在何处,采用那种操作系统,希望只要知道 Web 服务的所在网址,就 可以调用其相关 Web 服务方法。Web 服务客户端程序一般应在 Web 网上的另一台计算机中, 单做实验或学习,也可以和 Web 服务在同一台计算机中。本节介绍如何实现基于.Net 的Web 275 服务客户端程序。 13.3.113.3.113.3.113.3.1 WebWebWebWeb服务客户端程序代理类 Web 服务客户端程序是用 http 和SOAP 协议来调用远程的 Web 服务方法,因此,Web 服务 客户端程序必须把程序的调用及其参数转化为 SOAP 协议,传送到 Web 服务。但这个工作比 较繁琐,程序员希望采用象普通编程语言调用一个方法那样调用 Web 方法。.NET Framework 的SDK 提供了一个程序 WSDL.EXE,可以自动为 Web 服务客户端程序生成一个代理程序,该 代理程序的功能是,Web 服务客户端程序用一般程序语言那样调用 Web 服务方法,代理程序 负责转换为 SOAP 协议,发送到 Web 服务方法,由代理程序负责获得 Web 服务方法返回的数 据,由于这些数据也用 SOPA 协议表示,也要由代理程序转换为转换为一般程序语言能够理 解的形式,传送给 Web 服务客户端程序。下边介绍生成代理程序的具体方法。WSDL.EXE 必 须在控制台界面下使用,使用的格式如下: WSDL /l:C# /OUT:Hello.cs /protocol:soap http://LocalHost/Hello.asmx?WSDL 其中,/l 参数指定编制 Web 服务客户端程序使用的语言,可以是 vb、C#和Jscript,默认 值为 C#;/OUT 参数指定生成的代理类文件的路径和文件名,默认值和 Web 服务 ASMX 文件同 名,扩展名为参数/l 指定的语言的扩展名;参数/protocol 指定调用 Web 服务方法使用的协 议,可以是 HTTP-GET、HTTP-POST和SOAP 协议;http://后边是 Web 服务 ASMX 文件的 URL。WSDL 运行的结果是生成一个 Web 服务客户端程序代理类的源程序。有了源程序, 还要编译源程序生成 dll 文件,格式如下:csc /t:librrary hello.cs。把生成的 hello.dll 文件存到 Web 服务客户端程序项目所在目录的子目录 bin 下,这个代理类就可以被项目的其它成代码 使用了。 13.3.213.3.213.3.213.3.2 HTTP-GETHTTP-GETHTTP-GETHTTP-GET、HTTP-HTTP-HTTP-HTTP-POST POST POST POST 和SOAP SOAP SOAP SOAP 协议 当构造一个 XML Web 服务时,它自动支持客户端使用 SOAP、HTTP-GET 和 HTTP-POST 协议通讯。HTTP-GET 和HTTP-POST 支持使用 URL 编码的变量名/变量值 对来传送消息,支持这两个协议的数据类型没有支持 SOAP 协议的数据类型丰富。SOAP 是一个简单的、重量轻的基于 XML 的协议,用于交换 Web上的结构化的和模式化的信息。 SOAP 的总体设计目标是使它保持尽可能的简单,并且提供最少的功能。这个协议定义了一 个不包含应用程序或传输语义的消息框架。因此,这个协议是模块化的并且非常利于扩展。 在SOAP中,使用 XML 把数据传送到 XML Web 服务或从 XML Web 服务取回消息,你可 以使用支持丰富的数据类型集。 更多 SOAP 规格的信息,请看 W3C Web 站点(http://www.w3.org/TR/soap)。 13.3.313.3.313.3.313.3.3 使用代理类的 WebWebWebWeb服务客户端程序 (1) 控制台应用程序 using System; class Welcome; { static void Main() 276 { string s; int x,y,z; Console.WriteLine("Please enter first number:"); s=Console.ReadLine(); x=Convert.ToInt(s); Console.WriteLine("Please enter second number:"); s=Console.ReadLine(); y=Convert.ToInt(s); Hollo h1=new Hollo();//代理类对象 z=h1.hello(x,y);//调用Web服务方法 Console.WriteLine("sum:={0}",z); } } (2) Windows应用程序 (3) ASP.Net 应用程序 13.3.4 Visual Studio.Net建立 Web服务客户端程序 使用 Visual Studio.Net 很容易建立 Web 服务客户端程序,这个客户端程序不必一定和 Web 服务在同一台计算机中,可以在任意一台 Internet 网中的计算机中。下边是具体步骤: (1) 打开 Visual Studio.Net,新建 windows 应用程序项目,命名为 AddServiceClient,在窗 体中增加一个按钮用来调用 Web服务的 Web方法,三个文本框,两个用来输入两个 加数,另一个用来显示调用 Web服务的 Web方法后返回的结果。 (2) 建立 Web 服务客户端程序一般要建立一个代理。选择菜单项”项目”|/”添加 Web引用”, 在弹出的对话框中的地址栏中输入 Web 服务的 URL,例如 Web 服务所在的计算机的 IP 地址是 202.206.96.20,Web 服务的文件 Service1.asmx 在网站宿主目录下的子目录 webserver中,地址为:http://202.206.96.20/webserver/Service1.asmx。按回车键, 出现添加 Web引用对话框,如图:单击添加引用按钮,在解决方案资源管理器中, 可以看到一个新的引用,以及从 Web服务端发到客户端的 DISCO和WSDL文档。 在解决方案资源管理器中,还可以看到新创建的类,这个类就是 Web 服务客户端程序 的代理程序,该类的用途是把 Web 服务客户端程序调用 Web 服务方法转换为 SOAP 格式。 (3) 为按钮增加事件函数如下: (4) 天出的对话框中再加入一个 system.web.webservices的引用,在列表中有。在form1.cs 里,加入 using System.Web.Services; using webserver; 然后在 private System.Windows.Forms.Button button1; 277 private System.Windows.Forms.TextBox textBox1; 后面,插入 private webserver.service1 Client 建立一个 service1的实例。双击按钮,代码如下: private void button1_Click(object sender, System.EventArgs e) { Client =new Service1(); string name; name=Client.show("龙卷风.NET"); textBox1.Text=name; } 按F5,运行项目,点击按钮,文本框中显示 http://www.ourfly.com 欢迎龙卷风.NET 2. Asp.NET web窗口的测试 方法与上面的一模一样,添加引用,建立 service1的实例 在此不在细说。 3.在VB中测试 这个就要相对来说复杂一些 首先在vb 中建立一个”标准EXE”的项目。添加引用:Microsoft Soap Type library。 注意:如果没有安装 Microsoft Soap Toolkit,是没有这个类型库的。 可以在 http://www.ourfly.com中下载。 添加一个 text Private Sub Form_Load() Text1.Text = add() End Sub Public Function Add() As String Dim objSoapClient As New SoapClient objSoapClient.ClientProperty("ServerHTTPRequest") = True Call objSoapClient.mssoapinit("http://localhost/webserver/service1.asmx?WSDL", "Service1", "Service1Soap") 这句也可以 objSoapClient.mssoapinit("http://localhost/webserver/service1.asmx?WSDL") Add = objSoapClient.Show("龙卷风.NET") End Function 13.4 建立 Web服务客户端程序一般方法 278 13.5 发布和发现 Web服务 完成 Web 服务开发,如何发布该 Web服务,通知客户使用,程序开发者如何发现并 定位所需功能的 Web 服务,是这节要解决的问题。 13.5.113.5.113.5.113.5.1 WebWebWebWeb服务目录 和使用因特网上任何其他的资源一样,如果没有某些查找方法的话,是不可能够找到一 个特定的 Web服务的。Web 服务目录提供了一个网址,例如:http://uddi.microsoft.org/, 可以让 Web 服务供应者在其上发布他们提供的 Web服务的信息。这样的目录甚至可以是 Web 服务本身,可以编程访问并且提供搜索结果来响应 Web服务客户端的查询。使用一个 Web 服务目录来定位一个提供 Web 服务的 URL,这是非常必要的。 UDDI(统一描述发现和集成规范)规格定义了一个标准方法来发布和发现 Web 服务的 信息,也就是通过 UDDI 发现指定 Web服务的服务描述,该描述是一个使用 WSDL 语言的 XML 语法编写的 XML 文档。与 UDDI 关联的 XML 模式定义了四个信息类型,能让开发者 使用一个发布的 Web服务。这些是:商业信息、服务信息、绑定信息和其他用于服务的规 范的信息。 作为 UDDI 项目的核心控件,UDDI Business Registry(业务登记)允许 Web 服务开 发者发布其 Web服务的信息。Web服务使用者可以使用 UDDI Business Registry来定位发 现Web 服务描述文件。更多信息,请看 UDDI Web 站点(http://uddi.microsoft.com)。 13.5.213.5.213.5.213.5.2 WebWebWebWeb服务发现 程序设计者可以通过以下步骤发现所需的 Web 服务: (1) 首先,访问 Web服务目录网址,例如 http://uddi.microsoft.org/,查找所需 Web服务, 将返回一个所需 Web 服务 URL。 (2) 按照返回 URL,访问这个网址,例如:http:// 返回 Web 服务 URL/default.discro。.disco 文件,是包含连接到其他描述 XML Web服务的资源的 XML 文件,能够编程发现一个 XML Web 服务。disco 是一个包含与其它发现文档、XSD 模式和服务描述连接的 XML 文档。换句话说,使用 ASP.NET 创建的 XML Web 服务自动地有提供一个产生发现文 档的能力。 (3) 使用 Web 服务的 WSDL 建立一个 Web服务客户端程序代理类。 (4) 建立 Web服务客户端程序,使用代理类访问 Web 服务方法。 Web 服务发现是使用 Web 服务描述语言 WSDL 定位或发现一个或多个描述特定的 XML Web 服务的文件的操作。它让 XML Web 服务客户端得知一个 XML Web服务是否存 在并且到哪里找到这个 XML Web服务的描述文件。 一个发布的.disco文件,是包含连接到其他描述 XML Web服务的资源的 XML 文件,能够 编程发现一个 XML Web服务。(脚本之家 www.jb51.net)
还剩278页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 8 金币 [ 分享pdf获得金币 ] 0 人已下载

下载pdf

pdf贡献者

yge3

贡献于2015-05-15

下载需要 8 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!