C#.NET程序设计


1 C#.NET 程序设计 内容提要 本书是在讲授 Visual C#.Net 应用程序课程的讲义和教案的基础上整理编辑而成。本书 内容几乎涵盖了使用 C#语言开发 Windows 应用程序和 Web 应用程序(ASP.Net)的各个方面。 为了说明问题,在介绍程序设计方法时,一般使用具有针对性的例子进行说明,在例子中尽 量避免不相关的的知识点和无关的代码,使例子短小精悍。对于比较复杂的问题,将问题分 解为多个步骤,分别讲解给出详细的设计步骤,减少读者阅读难度。书中的例子都比较完整, 绝大部分都可以在计算机中运行。 本书可以作为大学本专科计算机导论和 C 语言程序设计课程的后续课程,或作为学习 使用 C#语言开发 Windows 应用程序和 Web 应用程序(ASP.Net)的培训班的教材,也适合使 用 C#语言开发项目的程序员参考。 2 前言 计算机专业开设的第一门高级程序设计语言课程一般是 C 或 C++,学习高级程序设计 语言的基本知识,编写控制台应用程序或 dos 应用程序。但现在使用最广泛的操作系统是微 软的 Windows 操作系统,Windows 应用程序的设计方法和控制台应用程序设计方法有很大 不同。因此在学习 C 或 C++后,必须进一步学习面向对象 Windows 程序设计,一方面为后 续课程,例如操作系统、数据库、计算机网络等课程,提供程序设计基础。另一方面也尽早 为学生课外实践提供一个实用的工具。本课程就是为上述目的而设置的,是在学习了计算机 基础和 C 或 C++高级语言程序设计后的后续课程。本书是在讲授 Visual C#.Net 应用程序课 程的讲义和教案的基础上,整理编辑而成的。 学习面向对象 Windows 应用程序设计可采用不同的工具和语言,例如:VB、VB.Net、 JAVA、VC++、VC++.Net、C#、Delph、C++Builder 等。考虑到 VC++学习难度较大,VB 虽然易学,还要耗费很紧张的学时学习一门新语言,是得不偿失的。而 C#语言是.Net 框架 中新一代的开发工具,C#语言是一种现代的、面向对象的语言,它简化了 C++语言在类、 命名空间、方法重载和异常处理等方面的操作,它摒弃了 C++的复杂性,更易使用,更少出 错。它使用组件编程,和 VB 一样容易使用。C#语法和 C++、C 语法非常相似,在第一门高 级程序设计语言课程是 C 或 C++情况下,学习 C#语言是比较轻松的,可以节约很多学时, 因此把 C#作为学习面向对象 Windows 应用程序设计语言是比较合适的。 以往 Windows 应用程序和 Web 应用程序的设计方法有很大不同,一般作为两门课程开 设。微软提出 Microsoft.Net 框架是新一代软件开发模型,在 ASP.Net 中提供的设计 Web 应用程序的可视化工具,能象设计 Window 应用程序一样,使用控件的属性、事件和方法等 面向对象的概念,设计 Web 应用程序。因此在 Microsoft.Net 框架中,设计 Windows 应用 程序和 Web 应用程序的设计方法十分类似,而且两种设计方法联系紧密,例如文件系统、 ADO.Net 是两种方法都要使用的。在这种情况下,把 Windows 应用程序和 Web 应用程序的 设计作为一门课程开设时比较合适的。本课程的前半部分讲述 Windows 应用程序的设计方 法,后半部分讲述 Web 应用程序和 Web 服务的设计方法。 本书内容几乎涵盖了使用 C#语言开发 Windows 应用程序和 Web 应用程序的各个方面。 为了说明问题,在介绍程序设计方法时,一般使用具有针对性的例子进行说明,在例子中尽 量避免不相关的的知识点和无关的代码,使例子短小精悍。对于比较复杂的问题,将问题分 解为多个步骤,分别讲解给出详细的设计步骤,减少读者阅读难度。书中的例子都比较完整, 绝大部分都可以在计算机中运行。 由于时间仓促,加之水平有限,书中的缺点和不足之处在所难免,敬请读者批评指正。 联系方法:gengzhaoying@sina.com。 作者 2005-10-9 3 第一章 C#语言基础 本章介绍 C#语言的基础知识,希望具有 C 语言基础的读者能够基本掌握 C#语言,并以 此为基础,能够进一步学习用 C#语言编写 window 应用程序和 Web 应用程序。当然仅靠一 章的内容就完全掌握 C#语言是不可能的,如需进一步学习 C#语言,还需要认真阅读有关 C#语言的专著。 1.1 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)中的既时编译器(Just In 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 中,运 4 行程序就自动使用在 bin 文件夹中的组件或动态联接库。由于不需要在注册表中注册, 软件的安装也变得容易了,一般将运行程序及库文件拷贝到指定文件夹中就可以了。  完全面向对象:不象 C++语言,即支持面向过程程序设计,又支持面向对象程序设计, C#语言是完全面向对象的,在 C#中不再存在全局函数、全区变量,所有的函数、变量 和常量都必须定义在类中,避免了命名冲突。C#语言不支持多重继承。 1.2 编写控制台应用程序 在 dos 操作系统中运行的程序被称作 dos 程序。在 Win2000 及以后的操作系统中,dos 操作系统运行后,作为 Win2000 的一个任务,在其中运行的程序被称作控制台应用程序。 由于 dos 程序也可以以这种方式在 Win2000 下运行,也被称作控制台应用程序。本节介绍 编写控制台应用程序的方法。 1.2.1 使用 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 5 的成员而存在的。C#中抛弃了C和C++中繁杂且极易出错的操作符像::和->等,C#中的复合名 字一律通过"."来连接。System是.Net平台框架提供的最基本的命名空间之一,有关命名空 间的详细使用方法将在以后详细介绍,这里只要学会怎样导入命名空间就足够了。 程序的第二行class Welcome声明了一个类,类的名字叫做Welcome。C#程序中每个变量 或函数都必须属于一个类,包括主函数Main(),不能象C或C++那样建立全局变量。C#语言程 序总是从Main()方法开始执行,一个程序中不允许出现两个或两个以上的Main()方法。请牢 记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回车,键入命令: 编译器csc.exe所在的路径\csc welcome.cs 如果一切正常welcome.cs文件将被编译,编译后生成可 执行文件Welcome.exe。可以在操作系统命令提示符窗口运 行这个可执行文件Welcome.exe。运行后,屏幕上出现一行 字符:"请键入你的姓名:",提示您输入姓名。输入任意字 符并按下回车键,屏幕将打印出欢迎信息:"欢迎!"。键入 回车退出程序。运行结果如右图。 注意,和我们使用过的绝大多数编译器不同,在 C#中编译器只执行编译这个过程,而 在 C 和 C++中要经过编译和链接两个阶段。换而言之 C#源文件并不被编译为目标文件.obj, 而是直接生成可执行文件.exe 或动态链接库.dll,C#编译器中不需要包含链接器。 1.2.2 使用 Visual Studio.Net 建立控制台程序 (1) 运行 Visual Studio.Net 程序,出现如图 1.2.2A 界面。 (2) 单击"新建项目(J)"按钮,出现如图 1.2.2B 对话框。在"项目类型(P) "列表框中选择"Visual C#项目",在"模板(T) "列表框中选择"控制台应用程序",在"名称(N) "编辑框中键入 e1_2_1,在"位置(L) "编辑框中键入 D:\csarp,必须预先创建文件夹 D:\csarp。也可以单 击"浏览"按钮,在"打开文件"对话框中选择文件夹。单击"确定"按钮,创建项目。出现 如图 1.2.2C 界面。编写一个应用程序,可能包含多个文件,才能生成可执行文件,所有 这些文件的集合叫做一个项目。 (3) 修改 class1.cs 文件如下,有阴影部分是新增加的语句,其余是集成环境自动生成的。 using System; namespace e1_2_1 { /// /// Class1 的摘要说明。 6 /// class Class1 { /// /// 应用程序的主入口点。 /// [STAThread] static void Main(string[] args) { // // TODO: 在此处添加代码以启动应用程序 // Console.WriteLine("请键入你的姓名:"); Console.ReadLine(); Console.WriteLine("欢迎!"); } } } (4) 按 CTRL+F5 键,运行程序,运行结果如右图。和 1.2.1 节运行效果相同。屏幕上出现一 行字符:"请键入你的姓名:",提示您输入姓名。输入任意字符并按下回车键,屏幕将 打印出欢迎信息:"欢迎!"。键入回车退出程序。 图 1.2.2A 7 图 1.2.2B 图 1.2.2C 8 1.3 类的基本概念 C#语言是一种现代的、面向对象的语言。面向对象程序设计方法提出了一个全新的概 念:类,它的主要思想是将数据(数据成员)及处理这些数据的相应方法(函数成员)封装 到类中,类的实例则称为对象。这就是我们常说的封装性。 1.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.2 类成员的存取控制 一般希望类中一些数据不被随意修改,只能按指定方法修改,既隐蔽一些数据。同样一 些函数也不希望被其它类程序调用,只能在类内部使用。如何解决这个问题呢?可用访问权 限控制字,常用的访问权限控制字如下:private(私有),public(公有)。在数据成员或函数成 员前增加访问权限控制字,可以指定该数据成员或函数成员的访问权限。 9 私有数据成员只能被类内部的函数使用和修改,私有函数成员只能被类内部的其它函数 调用。类的公有函数成员可以被类的外部程序调用,类的公有数据成员可以被类的外部程序 直接使用修改。公有函数实际是一个类和外部通讯的接口,外部函数通过调用公有函数,按 照预先设定好的方法修改类的私有成员。对于上述例子,name 和 age 是私有数据成员,只 能通过公有函数 SetName()和 SetAge()修改,既它们只能按指定方法修改。 这里再一次解释一下封装,它有两个意义:第一是把数据和处理数据的方法同时定义在 类中;第二是用访问权限控制字使数据隐蔽。 1.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.4 类的构造函数和析构函数 在建立类的对象时,需做一些初始化工作,例如对数据成员初始化。这些可以用构造函 数来完成。每当用 new 生成类的对象时,自动调用类的构造函数。因此,可以把初始化的工 作放到构造函数中完成。构造函数和类名相同,没有返回值。例如可以定义 Person 类的构 造函数如下: public Person(string Name,int Age)//类的构造函数,函数名和类同名,无返回值。 { name=Name; age=Age; } 当用 Person OnePerson=new Person("张五",20)语句生成 Person 类对象时,将自动 调用以上构造函数。请注意如何把参数传递给构造函数。 变量和类的对象都有生命周期,生命周期结束,这些变量和对象就要被撤销。类的对象 被撤销时,将自动调用析构函数。一些善后工作可放在析构函数中完成。析构函数的名字为 ~类名,无返回类型,也无参数。Person 类的析构函数为~ Person()。C#中类的析构函数不 能显示地被调用,它是被垃圾收集器撤销不被使用的对象时自动调用的。 1.3.5 类的构造函数的重载 在 C#语言中,同一个类中的函数,如果函数名相同,而参数的类型或个数不同,认为 是不同的函数,这叫函数重载。仅返回值不同,不能看作不同的函数。这样,可以在类定义 中,定义多个构造函数,名字相同,参数类型或个数不同。根据生成类的对象方法不同,调 10 用不同的构造函数。例如可以定义 Person 类没有参数的构造函数如下: public Person()//类的构造函数,函数名和类同名,无返回值。 { name="张三"; age=12; } 用语句 Person OnePerson=new Person("李四",30)生成对象时,将调用有参数的构造 函数,而用语句 Person OnePerson=new Person()生成对象时,调用无参数的构造函数。由 于析构函数无参数,因此,析构函数不能重载。 1.3.6 使用 Person 类的完整的例子 下边用一个完整的例子说明 Person 类的使用: 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("王五"); OnePerson.SetAge(40); 11 OnePerson.Display(); OnePerson=new Person(); OnePerson.Display(); } } } 键入CTRL+F5运行后,显示的效果是: 姓名: 李四,年龄:30 姓名: 王五,年龄:40 姓名: 田七,年龄:12 1.4 C#的数据类型 从大的方面来分,C#语言的数据类型可以分为三种:值类型、引用类型、指针类型, 指针类型仅用于非安全代码中。本节重点讨论值类型和引用类型。 1.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 等价 } } 存储在栈中的变量,当其生命周期结束,自动被撤销,例如,v1 存储在栈中,v1 和函 12 数 f1 同生命周期,退出函数 f1,v1 不存在了。但在堆中的对象不能自动被撤销。因此 C 和 C++语言,在堆中建立的对象,不使用时必须用语句释放对象占用的存储空间。.Net 系 统 CLR 内建垃圾收集器,当对象的引用变量被撤销,表示对象的生命周期结束,垃圾收集 器负责收回不被使用的对象占用的存储空间。例如,上例中引用变量 r1 及 r2 是 MyClass 类对象的引用,存储在栈中,退出函数 f1,r1 和 r2 都不存在了,在堆中的 MyClass 类对象 也就被垃圾收集器撤销。也就是说,CLR 具有自动内存管理功能。 1.4.2 值类型变量分类 C#语言值类型可以分为以下几种:  简单类型(Simple types) 简单类型中包括:数值类型和布尔类型(bool)。数值类型又细分为:整数类型、字符类 型(char)、浮点数类型和十进制类型(decimal)。  结构类型(Struct types)  枚举类型(Enumeration types) C#语言值类型变量无论如何定义,总是值类型变量,不会变为引用类型变量。 1.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。 } 1.4.4 简单类型 简单类型也是结构类型,因此有构造函数、数据成员、方法、属性等,因此下列语句 13 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到 7.9之间 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.5 枚举类型 C#枚举类型使用方法和 C、C++中的枚举类型基本一致。见下例: using System; class Class1 { enum Days {Sat, 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=3 Console.WriteLine("day={0},x={1}",day,x);//显示结果为:day=Tue,x=3 } } 在此枚举类型 Days 中,元素的默认类型为 int,默认第 1 个元素的值为 0,其后元素按 1 递增,本例 Sat=0,Sun=1,Mon=2,…依此类推。也可以直接给枚举元素赋值。例如: enum Days{Sat=6,Sun=7,Mon=1,Tue,Wed,Thu,Fri}; 在此枚举 Days 中,Sat=6,Sun=7,Mon=1,Tue=2,…Fri=5,等等。和 C、C++中不同, 14 C#枚举元素类型可以是 byte、sbyte、short、ushort、int、uint、long 和 ulong 类型,但 不能是 char 类型。见下例: enum Days:byte{Sun,Mon,Tue,Wed,Thu,Fri,Sat};//元素为字节类型 1.4.6 值类型的初值和默认构造函数 C#语言要求所有变量都必须有初值,如没有赋值,采用默认值。对于简单类型,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.7 引用类型分类 C#语言中引用类型可以分为以下几种:  类:C#语言中预定义了一些类:对象类(object 类)、数组类、字符串类等。当然, 程序员可以定义其它类。  接口。  代表。 C#语言引用类型变量无论如何定义,总是引用类型变量,不会变为值类型变量。C#语 言引用类型对象一般用运算符 new 建立,用引用类型变量引用该对象。本节仅介绍对象类 型(object 类型)、字符串类型、数组。其它类型在其它节中介绍。 1.4.8 对象类(object 类) C#中的所有类型(包括数值类型)都直接或间接地以object类为基类。对象类(object类) 是所有其它类的基类。任何一个类定义,如果不指定基类,默认object为基类。继承和基类 的概念见以后章节。C#语言规定,基类的引用变量可以引用派生类的对象(注意,派生类的 引用变量不可以引用基类的对象),因此,对一个object的变量可以赋予任何类型的值: int x =25; object obj1; obj1=x; object obj2= 'A'; object 关键字是在命名空间 System 中定义的,是类 System.Object 的别名。 15 1.4.9 数组类 在进行批量处理数据的时候,要用到数组。数组是一组类型相同的有序数据。数组按照 数组名、数据元素的类型和维数来进行描述。C#语言中数组是类System.Array类对象,比如 声明一个整型数组:int[] arr=new int[5],实际上生成了一个数组类对象,arr是对数组 类对象的引用(地址),只能通过arr[索引]方法使用数组元素。请注意定义方法和C语言的不 同。在C#语言中,数组元素的索引是不允许越界的,否则将产生异常。 在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内容不变  插入字符串 17 string s="计算机科学"; string s1=s.Insert(3,"软件");//s1="计算机软件科学",s内容不变  字符串替换函数 string s="计算机科学"; string s1=s.Replace("计算机","软件");//s1="软件科学",s内容不变  把字符串转换为字符数组 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.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#语言类型系统提出的核心概念,加框是值类型转换 为object(对象)类型,消框是object(对象)类型转换为值类型。有了加框和消框的概念,对 18 任何类型的变量来说最终我们都可以看作是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.5 运算符 C#语言和 C 语言的运算符用法基本一致。以下重点讲解二者之间不一致部分。 19 1.5.1 运算符分类 与C语言一样,如果按照运算符所作用的操作数个数来分,C#语言的运算符可以分为以 下几种类型:  一元运算符:一元运算符作用于一个操作数,例如:-X、++X、X--等。  二元运算符:二元运算符对两个操作数进行运算,例如:x+y。  三元运算符:三元运算符只有一个:x? y:z。 C#语言运算符的详细分类及操作符从高到低的优先级顺序见下表。 类别 操作符 初级操作符 (x) x.y f(x) a[x] x++ x-- new typeof sizeof checked unchecked 一元操作符 + - ! ~ ++x –x (T)x 乘除操作符 * / % 加减操作符 + - 移位操作符 << >> 关系操作符 < > <= >= is as 等式操作符 == != 逻辑与操作符 & 逻辑异或操作符 ^ 逻辑或操作符 | 条件与操作符 && 条件或操作符 || 条件操作符 ?: 赋值操作符 = *= /= %= += -= <<= >>= &= ^= |= 1.5.2 测试运算符 is 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 20 1.5.3 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.4 溢出检查操作符 checked 和 unchecked 在进行整型算术运算(如+、-、*、/等)或从一种整型显式转换到另一种整型时,有可能 出现运算结果超出这个结果所属类型值域的情况,这种情况称之为溢出。整型算术运算表达 式可以用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); } } } 21 1.5.5 new 运算符 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仍是值类型变量,不会变为引用类型变量。这点和C++不同。 1.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.6 程序控制语句 C#语言控制语句和C基本相同,使用方法基本一致。C#语言控制语句包括:if语句、swith 语句、while语句、do…while语句、for语句、foreach语句、break语句、continue语句、goto 语句、return语句、异常处理语句等,其中foreach语句和异常语句是C#语言新增加控制语句。 本节首先介绍一下这些语句和C语言的不同点,然后介绍C#语言新增的控制语句。 1.6.1 和 C 语言的不同点  与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常量表达式的值与之匹配,则执行default 分支下的语句,如果没有default语句,则退出switch语句。switch语句中可以没有 default语句,但最多只能有一个default语句。见下例: using System; 22 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.2 foreach 语句 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语句中,功能不变。 23 1.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)//不存在指定文件异常 { Console.WriteLine("文件"+e.FileName+"未被发现"); 24 } 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.7 类的继承 在1.3节,定义了一个描述个人情况的类Person,如果我们需要定义一个雇员类,当然 可以从头开始定义雇员类Employee。但这样不能利用Person类中已定义的函数和数据。比较 好的方法是,以Person类为基类,派生出一个雇员类Employee,雇员类Employee继承了Person 类的数据成员和函数成员,既Person类的数据成员和函数成员成为Employee类的成员。这个 Employee类叫以Person类为基类的派生类,这是C#给我们提出的方法。C#用继承的方法,实 现代码的重用。 1.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); } } 25 修改主函数如下: 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.2 base 关键字 base关键字用于从派生类中访问基类成员,它有两种基本用法:  在定义派生类的构造函数中,指明要调用的基类构造函数,由于基类可能有多个构造函 数,根据base后的参数类型和个数,指明要调用哪一个基类构造函数。参见上节雇员类 Employee构造函数定义中的base的第一种用法。  在派生类的方法中调用基类中被派生类覆盖的方法。参见上节雇员类Employee的 Display()方法定义中的base的第二种用法。 1.7.3 覆盖基类成员 在派生类中,通过声明与基类完全相同新成员,可以覆盖基类的同名成员,完全相同是 指函数类型、函数名、参数类型和个数都相同。如上例中的方法Display()。派生类覆盖基 类成员不算错误,但会导致编译器发出警告。如果增加new修饰符,表示认可覆盖,编译器 不再发出警告。请注意,覆盖基类的同名成员,并不是移走基类成员,只是必须用如下格式 访问基类中被派生类覆盖的方法:base.Display()。 1.7.4 C#语言类继承特点 C#语言类继承有如下特点:  C#语言只允许单继承,即派生类只能有一个基类。  C#语言继承是可以传递的,如果C从B派生,B从A派生,那么C不但继承B的成员,还 要继承A中的成员。  派生类可以添加新成员,但不能删除基类中的成员。  派生类不能继承基类的构造函数、析构函数和事件。但能继承基类的属性。  派生类可以覆盖基类的同名成员,如果在派生类中覆盖了基类同名成员,基类该成员在 派生类中就不能被直接访问,只能通过base.基类方法名访问。  派生类对象也是其基类的对象,但基类对象却不一定是其派生类的对象。例如,前边定 义的雇员类Employee是Person类的派生类,所有雇员都是Person类的成员,但很多 26 Person类的成员并不是雇员,可能是学生、自由职业者、儿童等。因此C#语言规定,基 类的引用变量可以引用其派生类对象,但派生类的引用变量不可以引用其基类对象。 1.8 类的成员 由于C#程序中每个变量或函数都必须属于一个类或结构,不能象C或C++那样建立全局变 量,因此所有的变量或函数都是类或结构的成员。类的成员可以分为两大类:类本身所声明 的以及从基类中继承来的。 1.8.1 类的成员类型 类的成员包括以下类型:  局部变量:在for、switch等语句中和类方法中定义的变量,只在指定范围内有效。  字段:即类中的变量或常量,包括静态字段、实例字段、常量和只读字段。  方法成员:包括静态方法和实例方法。  属性:按属性指定的get方法和set方法对字段进行读写。属性本质上是方法。  事件:代表事件本身,同时是事件处理函数的代表。  索引指示器:允许象数组那样使用索引访问类中的数据成员。  操作符重载:采用重载操作符的方法定义类中特有的操作。  构造函数和析构函数。 包含有可执行代码的成员被认为是类中的函数成员,这些函数成员有方法、属性、索引 指示器、操作符重载、构造函数和析构函数。 1.8.2 类成员访问修饰符 访问修饰符用于指定类成员的可访问性,C#访问修饰符有 4 种:private、protected、public 和 internal。private 声明私有成员,私有数据成员只能被类内部的函数使用和修改,私有函 数成员只能被类内部的函数调用。派生类虽然继承了基类私有成员,但不能直接访问它们, 只能通过基类的公有成员访问。protected 声明保护成员,保护数据成员只能被类内部和派生 类的函数使用和修改,保护函数成员只能被类内部和派生类的函数调用。public 声明公有成 员,类的公有函数成员可以被类的外部程序所调用,类的公有数据成员可以被类的外部程序 直接使用。公有函数实际是一个类和外部通讯的接口,外部函数通过调用公有函数,按照预 先设定好的方法修改类的私有成员和保护成员。internal 声明内部成员,内部成员只能在同 一程序集中的文件中才是可以访问的,一般是同一个应用(Application)或库(Library)。 1.9 类的字段和属性 一般把类或结构中定义的变量和常量叫字段。属性不是字段,本质上是定义修改字段的 方法,由于属性和字段的紧密关系,把它们放到一起叙述。 27 1.9.1 静态字段、实例字段、常量和只读字段 用修饰符 static 声明的字段为静态字段。不管包含该静态字段的类生成多少个对象或根 本无对象,该字段都只有一个实例,静态字段不能被撤销。必须采用如下方法引用静态字段: 类名.静态字段名。如果类中定义的字段不使用修饰符 static,该字段为实例字段,每创建该 类的一个对象,在对象内创建一个该字段实例,创建它的对象被撤销,该字段对象也被撤销, 实例字段采用如下方法引用:实例名.实例字段名。用 const 修饰符声明的字段为常量,常 量只能在声明中初始化,以后不能再修改。用 readonly 修饰符声明的字段为只读字段,只读 字段是特殊的实例字段,它只能在字段声明中或构造函数中重新赋值,在其它任何地方都不 能改变只读字段的值。例子: public class Test { 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.2 属性 C#语言支持组件编程,组件也是类,组件用属性、方法、事件描述。属性不是字段,但 必然和类中的某个或某些字段相联系,属性定义了得到和修改相联系的字段的方法。C#中的 属性更充分地体现了对象的封装性:不直接操作类的数据内容,而是通过访问器进行访问, 28 借助于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.10 类的方法 方法是类中用于执行计算或其它行为的成员。所有方法都必须定义在类或结构中。 29 1.10.1 方法的声明 方法的声明格式如下: 属性 方法修饰符 返回类型 方法名(形参列表){方法体} 方法修饰符包括new、public、protected、internal、private、static、virtual、sealed、override、 abstract和extern。这些修饰符有些已经介绍过,其它修饰符将逐一介绍。返回类型可以是任 何合法的C#数据类型,也可以是void,即无返回值。形参列表的格式为:(形参类型 形参1, 形参类型 形参2,...),可以有多个形参。不能使用C语言的形参格式。 1.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)//值参数,形参类型为引用类型 30 { 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("数组包含{0}个元素:",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[] {10,20,30,40}); F(new int[] {60,70,80,90});//实参为数组类引用 F();//等价于F(new int[] {}); F(new int[] {});//实参为数组类引用,数组无元素 } } 程序输出 31 数组包含3个元素:1 2 3 数组包含4个元素:10 20 30 40 数组包含4个元素:60,70,80,90 数组包含0个元素: 数组包含0个元素: 方法的参数为数组时也可以不使用params,此种方法可以使用一维或多维数组,见下例: using System; class Class1 { static void F(int[,] args)//值参数,参数类型为数组类引用变量,无params说明 { Console.Write("数组包含{0}个元素:",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[,] {});//此格式不能使用 } } 程序输出 数组包含4个元素:1 2 3 4 5 6 数组包含4个元素:60,70,80,90 1.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;//错误,静态方法不能访问实例数据成员 } 32 public void NoStaticMethod()//实例方法 { x=10;//正确,实例方法访问静态数据成员 y=20;//正确,实例方法访问实例数据成员 } } public class Class1 { public static void Main() { UseMethod m=new UseMethod(); UseMethod.StaticMethod();//使用静态方法格式为:类名.静态方法名 m.NoStaticMethod();//使用实例方法格式为:对象名.实例方法名 } } 1.10.4 方法的重载 在 C#语言中,如果在同一个类中定义的函数名相同,而参数类型或参数个数不同,认 为是不相同的函数,仅返回值不同,不能看作不同函数,这叫做函数的重载。前边 Person 类中定义了多个构造函数就是重载的例子。在 C 语言中,若计算一个数据的绝对值,则需要 对不同数据类型求绝对值方法使用不同的方法名,如用 abs()求整型数绝对值,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); } } 类的对象调用这些同名方法,在编译时,根据调用方法的实参类型决定调用哪个同名方 法,计算不同类型数据的绝对值。这给编程提供了极大方便。 33 1.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.6 this 关键字 每个类都可以有多个对象,例如定义 Person 类的两个对象: 34 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.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 35 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.F B.F A.G B.G B.G A.G 注意例子中,不同对象调用同名非虚方法F()和同名虚方法G()的区别。a2虽然是基类引 用变量,但它引用派生类对象b。由于G()是虚方法,因此a2.G()调用派生类B的G(),显示B.G。 但由于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();//访问基类同名方法 36 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.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///类Circle定义,圆类 { public Circle(double a):base(a,a) {} 37 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.13 密封类和密封方法 有时候,我们并不希望自己编写的类被继承。或者有的类已经没有再被继承的必要。C# 提出了一个密封类(sealed class)的概念,帮助开发人员来解决这一问题。 密封类在声明中使用sealed修饰符,这样就可以防止该类被其它类继承。如果试图将一 个密封类作为其它类的基类,C#编译器将提示出错。理所当然,密封类不能同时又是抽象类, 因为抽象总是希望被继承的。 C#还提出了密封方法(sealed method)的概念。方法使用sealed修饰符,称该方法是一 个密封方法。在派生类中,不能覆盖基类中的密封方法。 1.14 接口 与类一样,在接口中可以定义一个和或个方法、属性、索引指示器和事件。但与类不同 的是,接口中仅仅是它们的声明,并不提供实现。因此接口是函数成员声明的集合。如果类 或结构从一个接口派生,则这个类或结构负责实现该接口中所声明的所有函数成员。一个接 口可以从多个接口继承,而一个类或结构可以实现多个接口。由于C#语言不支持多继承,因 此,如果某个类需要继承多个类的行为时,只能使用多个接口加以说明。 38 1.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.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方法。 39 1.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(); } 40 } 如果类实现了某个接口,类也隐式地继承了该接口的所有基接口,不管这些基接口有没 有在类声明的基类表中列出。因此,如果类从一个接口派生,则这个类负责实现该接口及该 接口的所有基接口中所声明的所有成员。 1.15 代表(delegate)类型 本节介绍C#的一个新的引用数据类型:代表类型,也翻译为委托类型。在功能上它类似 C语言的函数指针,目的是通过创建代表类型对象去调用函数。使用代表类型的第一步,是声 明一个新代表类型,指明这个新代表类型对象要调用的函数的返回值类型,参数的个数及类 型,因此C#中的代表类型是类型安全的。生成一个新代表类型声明格式如下: 属性集 修饰符 delegate 函数返回类型 定义的代表标识符(函数形参列表); 修饰符包括new、public、protected、internal和private。例如我们可以声明一个能代表返 回类型为int,无参数的函数的代表类型,类型名为MyDelegate,类型声明如下: public delegate int MyDelegate();//只能代表返回类型为int,无参数的函数 声明了代表类型MyDelegate,可以创建代表类型MyDelegate的对象,用这个对象去代表 一个静态方法或非静态的方法,所代表的方法必须为int类型,无参数。看下面的例子: using System; delegate int MyDelegate();//声明一个代表类型,类型名为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();//因d引用的对象代表了方法InstanceMethod,用此方式调用这个非静态方法 //用new建立代表类MyDelegate对象,d引用的对象代表了方法StaticMethod d=new MyDelegate(MyClass.StaticMethod);//参数是被代表的方法 d();//因d引用的对象代表了方法StaticMethod,用此方式调用这个静态方法 } } 程序的输出结果是: 调用了非静态的方法。 调用了静态的方法。 41 1.16 事件 事件是 C#语言内置的语法,可以定义和处理事件,为使用组件编程提供了良好的基础。 1.16.1 事件驱动 Windows 操作系统把用户的动作都看作消息,C#中称作事件,例如用鼠标左键单击按钮, 发出鼠标单击按钮事件。Windows 操作系统负责统一管理所有的事件,把事件发送到各个运 行程序。各个程序用事件处理函数响应事件,这种方法也叫事件驱动。 C#语言使用组件编制 Windows 应用程序。组件本质上是类。在组件类中,预先定义了 该组件能够响应的事件,以及对应的事件函数(不是程序员编制的事件处理函数),该事件发 生,将自动调用相应的预定义事件函数。例如,按钮类中定义了单击事件 Click 和单击事件 函数。一个组件可能定义了多个事件,应用程序中不必也没必要响应所有的事件,而只需响 应其中很少事件,程序员编制相应的事件处理函数,用来完成需要响应的事件所应完成的功 能。现在的问题是,第一,如何把程序员编制的事件处理函数和组件类中预先定义的事件函 数联系起来。第二,如何使不需响应的事件无动作。这是本节要解决的问题。 1.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为null,OnClick函数将不做任何事情。下节将介绍用何种语 句使Click代表程序员编制的事件处理函数。 42 1.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();//建立按钮对象CancelButton 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.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函数 43 { 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.18 命名空间 一个应用程序可能包含许多不同的部分,除了自己编制的程序之外,还要使用操作系统 或开发环境提供的函数库、类库或组件库,软件开发商处购买的函数库、类库或组件库,开 发团队中其它人编制的程序等。为了组织这些程序代码,使应用程序可以方便地使用这些程 序代码,C#语言提出了命名空间的概念。命名空间是函数、类或组件的容器,把它们按类别 放入不同的命名空间中,命名空间提供了一个逻辑上的层次结构体系,使应用程序能方便的 找到所需代码。这和C语言中的include语句的功能有些相似,但实现方法完全不同。 1.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(){};} } } 也可以采用非嵌套的语法来实现以上命名空间: 44 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.2 命名空间使用 如在程序中,需引用其它命名空间的类或函数等,可以使用语句using,例如需使用上 节定义的方法f1()和f2(),可以采用如下代码: using N1.N2; class WelcomeApp { A a=new A(); a.f1(); } using N1.N2实际上是告诉应用程序到哪里可以找到类A。请读者重新看一下1.2.1节中 的例子。 1.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。 45 习题 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 题,改为接口实现,即将点类改为接口。 46 第二章 Windows 编程的基础知识 2.1 窗口 Windows 应用程序一般都有一个窗口,窗口是运行程序与外界交换信息的界面。一个典 型的窗口包括标题栏、最小化按钮、最大/还原按钮、关闭按钮、系统菜单图标、菜单、工 具条、状态栏、滚动条、客户区等。程序员的工作之一是设计符合自己要求的窗口,C#用控 件的方法设计界面。编程另一个工作是在用户区显示数据和图形。 2.2 Windows 的事件系统 Windows 应用程序采用事件驱动方式工作,本节介绍事件驱动的基本概念。 2.2.1 事件驱动(消息驱动) Windows 应用程序和 dos 程序(控制台程序)的最大不同是事件驱动方式工作,也叫消 息驱动。dos 程序运行时如要从键盘输入数据,则要独占键盘等待用户输入,如用户不输入, 则 CPU 一直执行键盘输入程序,等待用户输入,即 dos 程序独占外设和 CPU。 Windows 操作系统是一个多任务的操作系统,允许同时运行多个程序,它不允许任何 一个程序独占外设,如键盘、鼠标等,所有运行程序共享外设和 CPU,各个运行程序都要随 时准备从外设接受命令,执行命令。 因此必须由 Windows 操作系统统一管理各种外设。Windows 把用户对外设的动作都看 作事件(消息),如单击鼠标左键,发送单击鼠标左键事件,用户按下键盘,发送键盘被按下 的事件等。Windows 操作系统统一负责管理所有的事件,把事件发送到各个运行程序,而各 个运行程序用一个函数响应事件,这个函数叫事件处理函数。这种方法叫事件驱动。每个事 件都有它自己的事件处理函数,当接到 Windows 事件后,自动执行此事件的事件处理函数。 程序员编程的主要工作就是编制这些事件的处理函数,完成相应的工作。 2.2.2 事件队列 Windows 把用户的动作都看作事件,Windows 操作系统负责管理所有的事件,事件发生 后,这些事件被放到操作系统事件队列中,Windows 操作系统从操作系统事件队列中逐一取 出事件,分析各个事件,分送事件到相应运行程序的事件队列中。每个运行程序都有自己的 事件队列,运行程序利用消息循环方法(既循环取得自己事件队列中的事件)得到事件,并 把事件送到当前活动窗口,由窗口中的事件处理函数响应各个事件(消息)。 47 2.2.3 注视窗口 Windows 操作系统允许多个程序同时运行,每个程序可能拥有多个窗口,但其中只有一 个窗口是活动的,我们能从窗口的标题栏的颜色来识别一个活动窗口,这个窗口接收 Windows 操作系统发来的大部分的事件。这个应用程序的窗口被称为注视(活动)窗口。 2.3 Windows 编程接口和类库 操作系统为了方便应用程序设计,一般都要提供一个程序库,一些设计应用程序的共用 代码都包含在这个库中。程序员可以调用这些代码,以简化编程。这节介绍一些常用程序库。 2.3.1 Windows 编程接口(API) API(Application Programming Interface)是 Windows98、2000 和 XP 操作系统中提供 的一组函数,这些函数采用 C 语言调用格式,是为程序员编制 Windows 应用程序提供的编程 接口。程序员用 C 语言直接调用 API 也可以编制 Windows 应用程序,但大量的程序代码必 须由程序员自己编写,而 API 函数非常庞大,给编程者带来很大的困难。 2.3.2 MFC 类库 由于 API 函数十分庞大复杂,看不到函数之间的关系,使程序员不易使用。用 C 语言使 用 API 函数编写 Windows 应用程序是十分困难的。微软的 VC++6.0 用类对 API 函数进行了封 装,为编程提供了 MFC 类库。使用 MFC 类库简化了 Windows 应用程序的编制。但是,MFC 类 库的使用还是比较复杂的,因此,VC++一直是一些专业人员的编程工具。 2.3.3 组件库 为了简化 Windows 应用程序的设计,提出了组件(控件)的概念,组件也是类,按钮、菜 单、工具条等都可以封装为组件,组件采用属性、事件、方法来描述,其中组件属性描述组 件的特性,如按钮的标题、标签字体的颜色和大小。组件方法是组件类提供的函数,通过调 用这些方法,可以控制组件的行为。组件通过事件和外界联系,一个组件可以响应若干个事 件,可以为事件增加事件处理函数,以后每当发生该事件,将自动调用该事件处理函数处理 此事件。很多组件在设计阶段是可见的,支持可视化编程,这些组件又被叫做控件。用控件 编制 Windows 应用程序很像搭积木,将控件放到窗体中,设置好属性,漂亮的界面就设计好 了。组件编程的工具有很多,例如:VB6.0、VB.Net、C#、C++Builder、Java、Delphi 等快 速开发工具(RAD)。这些工具都有自己的组件库。 2.3.4 .Net 框架类库 .Net 系统为编制 Windows 应用程序、Web 应用程序、Web 服务,在.Net 框架(.Net FrameWork)中提供了基础类库(Base Class Library)。它是一个统一的、面向对象的、层次 48 化的、可扩展的类库,统一了微软当前各种不同的框架和开发模式,无论开发 Windows 应用 程序,还是开发 Web 应用程序,采用相同的组件名称,组件具有类似的属性、方法和事件, 开发模式也类似,方便程序员学习。.Net 框架类库支持控件可视化编程,.Net 中的 VC++.Net、 VB.Net、C#语言都使用这个类库,消除了各种语言开发模式的差别。该类库包括以下功能: 基础类库(基本功能,像字符串、数组等)、网络、安全、远程化、诊断和调试、I/O、数据 库、XML、Web 服务、Web 编程、Windows 编程接口等等。 Windows98、2000 和 XP 操作系统并不包含.Net 框架类库,为了运行 C#程序,必须安 装.Net FrameWork。.Net FrameWork 目前有 1.0 版本和 1.1 版本。 2.4 Windows 应用程序的基本结构 Windows 应用程序和控制台应用程序的基本结构类似,程序的执行总是从 Main()方法 开始,主函数 Main()必须在一个类中。但 Windows 应用程序使用图形界面,一般有一个窗 口(Form),采用事件驱动方式工作。本节介绍 Windows 应用程序的基本结构。 2.4.1 最简单的 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。启动Windows操作系统命令行提 示符,在屏幕上输入一行命令:d:回车,cd Charp回车,键入命令: csc.exe所在的目录\csc /t:winexe /r:system.dll,System.Windows.Forms.dll e1.cs 命令中的/t:winexe 表示要建立一个 Windows 应用程序,/r 表示要引入的命名空间。也 可以用记事本建立一个批处理文件 g.bat,将以上命令内容拷贝到文件中,运行 g.bat,和 在命令行提示符键入命令效果相同。以上方法在 FrameWork SDK 1.0 中实现。如果一切正常 e1.cs 文件将被编译,编译后生成可执行文件 e1.exe。运行可执 行文件 e1.exe,屏幕上出现一个窗口如右图。 可以在 Form1 类中定义新的变量,由于主窗体关闭,程序也 就结束了,因此定义在主窗体 Form1 类中的变量的生命周期和程 序的生命周期是相同的,从这个意义上说,这些变量是全局变量。 可以为 Form1 类定义构造函数,在构造函数中做一些初始化的工作,例如修改 Form1 标题 49 栏中的标题。还可以在 Form1 中定义控件类的对象,这些控件将在 Form1 的用户区显示出 来,换句话讲,在 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.2 用 Visual Studio.Net 建立 Windows 应用程序 以上所做的工作,都是一些固定的工作,可以使用 Visual Studio.Net 自动建立,下面介 绍使用 Visual Studio.Net 创建 Windows 应用程序的具体步骤。 (1) 运行 Visual Studio.Net 程序,出现如图 1.2.2A 界面。 (2) 单击"新建项目(J)"按钮,出现如图 1.2.2B 对话框。在"项目类型(P) "列表框中选择"Visual C#项目",在"模板(T) "列表框中选择"Windows 应用程序",在"名称(N)"编辑框中键入 e2_4_2,在"位置(L)"编辑框中键入 D:\csarp。也可以单击"浏览"按钮,在打开文件对话 框中选择文件夹。单击"确定"按钮,创建项目。出现如图 2.4.2A 界面。生成一个空白窗 体(Form1)。 50 图 2.4.2A (3) 在 e2_4_2 文件夹中下有两个文件夹和 8 个文件,一般只修改 Form1.cs 文件。右击 Form1 窗体,在快捷菜单中选择菜单项"查看代码(C)",可打开 Form1.cs 文件。Visual Studio.Net 生成的 Form1.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_4_2//定义命名空间,///为解释 { //此处可定义其它类 /// /// Form1 的摘要说明。 /// public class Form1 : System.Windows.Forms.Form//Form1类定义 { //此处可定义自己的变量,这些变量和运行程序生命周期相同 /// /// 必需的设计器变量。 /// 51 private System.ComponentModel.Container components = null; public Form1()//构造函数 { // // Windows 窗体设计器支持所必需的 // 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] 52 static void Main()//程序入口函数 ,一般不修改 { Application.Run(new Form1()); }//程序入口函数之后可定义自己的方法、属性等 } } (4) 下边在窗体中增加一个按钮,并为按钮增加单击事件函数。单击图 2.4.2A 中标题为 "Forms.cs[设计]"的窗口标签,返回标题为"Forms.cs[设计]"的窗口。向 Form1 窗体中添 加控件需要使用工具箱窗口,若看不到,可以用"视图"|"工具箱"菜单项打开这个窗口 (见图 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 完成以上设计后,集成环境生成的 Form1.cs 文件如下,背景为黑色的代码是新增代码。 using System; using System.Drawing; using System.Collections; using System.ComponentModel; using System.Windows.Forms; using System.Data; namespace e2_4_2 { /// /// Form1 的摘要说明。 /// public class Form1 : System.Windows.Forms.Form { 53 private System.Windows.Forms.Button button1;//定义Button类引用变量 /// /// 必需的设计器变量。 /// private System.ComponentModel.Container components = null; 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); 54 this.button1.TabIndex = 0; this.button1.Text = "确定"; this.button1.Click += new System.EventHandler(this.button1_Click);//增加事件 // // Form1 // 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.3 方案(Solution)和项目(Project) 一个应用(Application)可能包括一个或多个可执行程序,例如,学生信息管理系统,可 能包括客户端程序和服务器端程序,所有这些可执行程序的集合叫做一个应用解决方案。为 了生成一个可执行程序,可能需要有一个或多个文件,例如,一般需要一个窗体文件,有时 还需要一个资源文件,若干图形或图像文件。所有这些文件的集合叫一个项目,因此项目是 55 为了创建一个可执行程序所必需的所有的文件的集合。而一个方案中可能包括多个项目。为 了方便管理项目和项目所在的方案,Visual Studio.Net 为开发人员提供了解决方案资源管 理器窗口(图 2.4.3)。它可以为我们显示一个方案的树形结构,以及它所包含的项目及项目 中的文件。这里的项目是 Windows 应用程序项目,和后边的 Web 项目有些区别。 一个项目一般要放在一个文件夹中,例如上边的例子,项目 e2_4_2 的所有文件都在文 件夹 e2_4_2 中,共有两个文件夹和 7 个文件,它们的用途如下:  bin 文件夹:其中包含 Debug 子文件夹,存储生成带调试信息的可执行 C#程序。还可以 包含 Release 子文件夹,存储生成不带调试信息的可执行 C#程序。不用打开 Visual Studio.Net,双击可执行 C#程序,就可运行这个程序。  obj 文件夹:包含编译过程中生成的中间代码。  AssemblyInfo.cs:创建项目自动添加。包含各种属性设置,例如,项目最终创建的可执 行文件或 DLL 文件中的信息,如标题、描述、公司名等。一般用工具修改该程序,不 要直接修改。  Form1.cs:窗体文件,程序员一般只修改该文件。  Form1.resx:资源文件。程序员用集成环境提供的工具修改,不要直接修改。  e2_4_2.suo:解决方案用户选项文件,记录用户关于解决方案的选项。  e2_4_2.csproj:项目文件,记录用户关于项目的选项。  e2_4_2.sln:解决方案文件。 为了以后重新用 Visual Studio.Net 打开该解决方案, 必须保存除了两个文件夹以外的所有文件,实际上,由 于文件夹 e2_4_2 不太大,可以保存整个 e2_4_2 文件夹。 如果重新开始一个解决方案,首先用"文件"|"关闭解决 方案"菜单项,关闭当前项目,再新建一个项目。为了用 Visual Studio.Net 修改以前的程序,必须打开保存的项目 文件(扩展名为 csproj),或者使用"文件"|"打开项目"菜 单项,打开保存的项目,同时打开项目所在的解决方案。 另外不同版本的 Visual Studio.Net 的项目文件和解决方 案文件可能不兼容。 图 2.4.3 习题 (1)Windows 应用程序和 dos 程序有哪些不同。 (2)以键盘操作为例说明什么是事件驱动。 (3)哪些 Windows 操作系统提供了.Net 框架类库,哪些提供了 API。 (4)运行 C#程序,应首先安装哪些软件。 (5)定义一个和应用程序同生命周期的变量,该变量应定义在何处,说明该变量的使用范围。 (6)在窗体中增加一个控件,应如何操作,集成环境增加了哪些代码。 (7)为控件增加事件函数,应如何操作,集成环境增加了哪些代码。 (8)如何为窗体文件增加一个方法,说明该方法的使用范围。 56 第三章 常用控件和类的使用 Visual Studio.Net(简称 VS.Net)使用控件(组件)设计 Windows 应用程序。将 VS.Net 工具箱窗口中的控件放到窗体中,使用属性窗口改变控件属性,或 在程序中用语句修改控件 属性,设计应用程序界面,为控件增加事件函数,完成指定的功能。本章所有例题都是使用 VS.Net 完成。 3.1 控件通用属性 大部分控件,例如 Label、Button、TextBox 等,都是 Control 类的派生类。Control 类定 义了这些派生类控件通用的一组属性和方法,以下是 Control 类的一些常用属性:  Name:控件的名称,区别控件类不同对象的唯一标志。例如用语句 Button button1=new Button()建立一个 Button 类对象,那么这个 Button 类对象 Name 属性的值为 button1。  Location:表示控件对象在窗体中的位置。本属性是一个结构,结构中有两个变量,x 和 y,分别代表控件对象左上角顶点的 x 和 y 坐标,该坐标系以窗体左上角为原点,x 轴向右为正方向,y 轴向下为正方向,以像素为单位。修改 Location,可以移动控件的 位置,例如:button1.Location=new Point(100,200)语句移动按钮 button1 到新位置。  Left 和 Top:这两个属性等效于 Location 属性的 Location.x 和 Location.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.2 Form 类 Form 类是.Net 系统中定义的窗体类(WinForm),属于 System.Windows.Forms 命名空间。 Form 类对象具有 Windows 应用程序窗口的最基本功能。它可以是对话框、单文档或多文档 应用程序窗口的基类。Form 类对象还是一个容器,在 Form 窗体中可以放置其它控件,例如 菜单控件、工具条控件等等,还可以放置子窗体。Form 类常用属性、方法和事件如下:  属性 AutoScroll:布尔变量,表示窗口用户区是否在需要时自动添加滚动条。  属性 FormBorderStyle:窗体边界风格,如有无边界、单线边界、3D 效果、可否调整等。  属性 Text:字符串类型,窗体标题栏中显示的标题。  属性 AcceptButton:记录一个按钮对象的 Name 属性值,用户键入回车时,等效单击这 个按钮对象。 57  属性 CancelButton:记录一个按钮对象的 Name 属性值,用户键 ESC 键时,等效单击这 个按钮对象。以上两个属性多用于对话框,例如打开文件对话框,用户键入回车,相当 于单击确定按钮。  属性 MaxiMizeBox:窗体标题栏右侧最大化按钮是否可用,设置为 false,按钮不可用。  属性 MiniMizeBox:窗体标题栏右侧最小化按钮是否可用,设置为 false,按钮不可用。 如果属性 MaxiMizeBox 和 MiniMizeBox 都设置为 false,将只有关闭按钮。在不希望用 户改变窗体大小时,例如对话框,将两者都设置为 false。  方法 Close():窗体关闭,释放所有资源。如窗体为主窗体,执行此方法,程序结束。  方法 Hide():隐藏窗体,但不破坏窗体,也不释放资源,可用方法 Show()重新打开。  方法 Show():重新显示窗体。  事件 Load:在窗体显示之前发生,可以在其事件处理函数中做一些初始化的工作。 3.3 标签(Label)控件 标签控件用来显示一行文本信息,但文本信息不能编辑,常用来输出标题、显示处理结 果和标记窗体上的对象。标签一般不用于触发事件。Label 控件常用属性如下:  Text:要显示的字符串  AutoSize:控件大小是否随显示字符串长度自动调整,默认值为 false,不调整。  ForeColor:控件 Label 显示的字符颜色。  Font:字符串所使用的字体,包括所使用的字体名、字体的大小、字体的风格等,具体 修改方法见下边的例子。 例子 e3_3:本例在窗口中显示一行文本,该例虽然简单,但包括了用 VS.Net 建立 C# Windows 应用程序的基本步骤。具体实现步骤如下: (1)用 2.4.2 节的方法建立一个新项目,生成一个空白窗体(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)。应能看到刚才关闭的设计界面。必须打开项目文件,才能完成编译工作。 58 3.4 按钮(Button)控件 用户单击按钮,触发单击按钮事件,自动调用单击按钮事件处理函数,完成指定的工作。 Button 控件常用属性和事件如下:  属性 Text:按钮的标题。  事件 Click:用户单击按钮触发的事件,一般称作按钮单击事件。 例子 e3_4:本例说明如何用程序修改控件属性,使用控件方法,如何增加事件处理函 数。本例在窗口中显示一行文字,增加 2 个按钮,单击标题为―红色―的按钮,把显示的文本 颜色改为红色,单击标题为―黑色―的按钮,把显示的文本颜色改为黑色。实现步骤如下: (1)继续例子 e3_3,放三个 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();}//调用窗体 Form1 类的方法 Close() Close()为窗体 Form1 类的方法,作用是关闭窗体。由于关闭了主窗体,程序也就结束了。 注意,引用主窗体的方法和属性时可不用指定对象名,换句话讲,如不指定属性或方法所属 的对象名,默认为主窗体的属性或方法。而使用其它组件的属性及方法要指明所属组件对象, 例如 label1.ForeColor=Color.Red; (5)编译,运行,单击标题为―红色―的按钮,窗体显示中文字符颜色变为红色,单击标题为―黑 色―的按钮,窗体显示中文字符颜色变为黑色,单击标题为―退出―的按钮,结束程序。 3.5 事件处理函数的参数 事件处理函数一般有两个参数,第一个参数(object sender)为产生该事件对象的属性 Name 的值。例如例子 e3_4 中,单击标题为―红色―的按钮,第一个参数 sender 的值为 button1。 如希望例子 e3_4 中标题为―红色―的按钮和标题为―黑色―的按钮使用同一个单击按钮事件处 理函数,首先为单击(Click)标题为―红色―的按钮的事件增加事件处理函数如下: private void button1_Click(object sender,System.EventArgs e) { if(sender==button1)//判断是否是 button1 按钮产生的事件 label1.ForeColor=Color.Red; 59 else label1.ForeColor=Color.Black; } 然后选中标题为―黑色―的按钮,打开事件窗体(见图 2.4.2B 右图),选中 Click 事件, 从其右侧下拉列表中选择标题为―红色―按钮的事件处理函数:button1_Click,这样两个按 钮就使用相同的单击事件处理函数了。 事件处理函数第二个参数(System.EventArgs e)代表事件的一些附加信息,事件不同, 所代表的信息也不相同,例如在后边的例子中可以看到,按下鼠标的事件处理函数中,e.X 和 e.Y 分别为发生事件时鼠标位置的 x 坐标和 y 坐标,e.Button 表示用户单击了鼠标哪个键, 如为 MouseButtons.Left,表示单击了鼠标左键。 3.6 文本框(TextBox)控件 TextBox 控件,也叫文本框,用户可以在该控件中输入文本数据。TextBox 控件常用属性 和事件如下:  属性 Text:记录用户在文本框中键入的字符串  属性 MaxLength:单行文本框最大输入字符数。  属性 ReadOnly:布尔变量,为 true,不能在文本框中输入或编辑数据,但可用代码修改。  属性 PasswordChar:如输入一个字符,用户在文本框中输入的所有字符都显示这个字符。 一般用来输入密码。如为空,则正常显示输入的字符串。  属性 MultiLine:布尔变量,为 true,多行文本框,为 false,单行文本框。  属性 ScrollBars:MultiLine=true 时有效,表示 TextBox 控件是否有滚动条,有 4 种选择: =0,无滚动条,=1,有水平滚动条,=2,有垂直滚动条,=3,有水平和垂直滚动条。  属性 SelLength:可选中文本框中的部分或全部字符,本属性为所选择的文本的字符数。  属性 SelStart:所选中文本的开始位置。  属性 SelText:所选中的文本  属性 AcceptsReturn:MultiLine=true 时有效,布尔变量,为 true,键入回车键,执行换 行操作;为 false,键入回车键,相当于单击窗体中的默认按钮。  事件 TextChanged:在文本框中输入或编辑了数据,文本内容发生变化时发出的事件。 例子 e3_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); } 60 (5)标题为―清空―的按钮的单击事件处理函数如下: 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.7 Convert 类 Convert 类中提供了一些静态方法,用来把一种类型数据转换为另一种类型数据。例如, Convert.ToSingle(textBox1.Text) 把 字 符 串 textBox1.Text 转 换 为 单 浮 点 数 。 Convert.ToString(3.14) 把 单 浮 点 数 3.14 转换为字符串。其它转换函数还有: ToInt16(string)、ToInt32(string)、ToByte(char)、ToChar(Byte)、ToChar(int16)、 ToDecimal(string)、ToDouble(string)、ToSByte(string)、ToUInt16(string)等。 3.8 单选按钮(RadioButton)和 GroupBox 控件 RadioButton 是单选按钮控件,多个 RadioButton 控件可以为一组,这一组内的 RadioButton 控件只能有一个被选中。GroupBox 控件是一个容器类控件,在其内部可放值其 它控件,表示其内部的所有控件为一组,其属性 Text 可用来表示此组控件的标题。一般把 RadioButton 控件放到 GroupBox 控件中,表示这些 RadioButton 控件是一组。有一些特性是 互斥的,例如性别,选择这类特性可用 RadioButton 和 GroupBox 控件。 GroupBox 控件常用属性属性只有一个,属性 Text,指定 GroupBox 控件顶部的标题。 RadioButton 控件常用属性和事件如下:  属性 Text:单选按钮控件旁边的标题。  属性 Checked:布尔变量,为 true 表示按钮被选中,为 false 表示不被选中。  事件 CheckedChanged:单选按钮有被选中和不被选中两种状态,该事件是单选按钮状 态改变时产生的事件。  事件 Click:单击单选按钮控件时产生的事件。 例子 e3_8:该例用 RadioButton 控件修改 Label 控件字符串 的字体为:宋体、黑体、楷体。具体实现步骤如下: (1) 建立一个新的项目。 (2) 放 Label 控件到窗体,属性 Text="不同的字体",字体为宋体。 (3) 放 GroupBox 控件到窗体,其属性 Text="选择字体"。 (4) 放三个 RadioButton 控件到 GroupBox 中,属性 Text 分别为:宋体、黑体、楷体。标题 为"宋体"的 RadioButton 控件的属性 Checked=true。设计好的界面如右图。 (5) 为三个 RadioButton 控件的 CheckedChanged 事件增加事件处理函数如下: 61 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.9 Font 类 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.10 多选框(CheckBox)控件 CheckBox 是多选框控件,可将多个 CheckBox 控件放到 GroupBox 控件内形成一组,这 一组内的 CheckBox 控件可以多选,不选或都选。可用来选择一些可共存的特性,例如一个 人的爱好。CheckBox 控件常用属性和事件如下:  属性 Text:多选框控件旁边的标题。  属性 Checked:布尔变量,为 true 表示多选框被选中,为 false 不被选中。  事件 Click:单击多选框控件时产生的事件。 62  事件 CheckedChanged:多选框控件有被选中和不被选中两种状态,该事件是多选框状 态改变时产生的事件。 例子 e3_10A:分别用 2 个 CheckBox 控件来选择是否爱好音乐和是否爱好文学,用鼠标 单击 CheckBox 控件,改变爱好选择,用 Label 控件显示所选择的爱好。实现步骤如下: (1) 建立新项目。放 Label 控件到窗体,属性 Text=―你的爱好是:‖。 (2) 放 GroupBox 控件到窗体,属性 Text=―爱好‖。放两个 CheckBox 控件到 GroupBox 中, 属性 Text 分别为:音乐、文学。设计界面如下图。 (3) 标题为"音乐"的多选框控件 checkBox1 的 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) 编译,运行。标题为"音乐"的多选框控件被选中,标签控件将显示"你的爱好是:音乐", 再选中标题为"文学"的多选框控件,标签控件将显示"你的爱好是:音乐文学",…。 例子 e3_10B:该例同上例,但按标题为"音乐"和"文学"的多选框控件被选中的顺序在 标签中显示爱好,实现步骤如下: (1)建立新项目。 (2)为 Form1 类增加私有变量 String s="你的爱好是:"。定义位置见 2.4.2 节。 (3)放 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) 编译,运行。选中"音乐"多选框,标签显示"你的爱好是:音乐";再选中"文学"多选框, 63 标签显示"你的爱好是:音乐文学";不选中"音乐"多选框,标签显示"你的爱好是:文 学";再选中"音乐"多选框,标签显示"你的爱好是:文学音乐"。 3.11 列表选择控件(ListBox) 列表选择控件列出所有供用户选择的选项,用户可从选项中选择一个或多个选项。列表 选择控件的常用属性、事件和方法如下:  属性 Items:存储 ListBox 中的列表内容,是 ArrayList 类对象,可以认为是字符串数组。  属性 SelectedIndex:所选条目的索引号,第一个条目索引号为 0。如允许同时选择多个 条目,该属性返回任意一个选择条目的索引号。如一个也没选,该值为-1。  属性 SelectedIndices:返回所有被选条目的索引号集合,是一个整型数组。  属性 SelectedItem:返回所选条目的内容,即列表中选中的字符串。如允许同时选择多 个条目,该属性返回所选索引号最小的条目。如一个也没选,该值为空。  属性 SelectedItems:返回所有被选条目的内容,是一个字符串数组。  属性 SelectionMode:确定可选的条目数,以及选择多个条目的方法。属性值可以是: none(可以不选或选一个)、one(必须而且只能选一个)、MultiSimple(允许同时选择多个条 目)或 MultiExtended(允许用组合键同时选择多个条目)。  属性 Sorted:表示条目是否以字母顺序排序,默认值为 false,不允许。  方法 GetSelected():参数是索引号,如该索引号条目被选中,返回值为 true。  事件 SelectedIndexChanged:当所选索引号(即选项)被改变时产生的事件。 例子 e3_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) 编译,运行,分别在控件 Panel 所在区域内和区域外双击鼠标左键,看一下效果。分别 在控件 Panel 所在区域内和区域外双击鼠标右键,看一下效果。 3.21 快捷菜单(ContextMenu) 使用过 Word 程序的人都知道,在 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 个 ContextMenuStrip 控 件 到 窗 体 , 属 性 Name 为 contextMenuStrip1 , contextMenuStrip2。 (6) 选中 ContextMenuStrip1 控件,用菜单编辑器增加标题为"红色"和"黑色"的两个菜单项, 它们的单击事件处理函数分别是单击标题为"红色"按钮和"黑 色"按钮的事件处理函数。 (7) 选中 ContextMenuStrip2 控件,用菜单编辑器增加标题为"退出 "的菜单项,并为其增加单击事件处理函数,在事件处理函数中 增加语句:Close(); (8) 将标题为"红色"和"黑色"的按钮属性 ContextMenuStrip 指定为 72 contextMenu1。主窗体 Form1 的属性 ContextMenuStrip 指定为 contextMenu2。 (9) 编译,运行,右击标题为"红色"的按钮,快捷菜单 contextMenu1 打开,分别单击快捷菜 单中标题为"红色"或"黑色"的菜单项,将使窗体中显示的字符串颜色变为红色或黑色。 右击标题为"黑色"的按钮,快捷菜单 contextMenu1 再次打开,分别单击快捷菜单中标 题为"红色"或"黑色"的菜单项,将使窗体中显示的字符串颜色变为红色或黑色。右击窗 体,快捷菜单 contextMenu2 打开,单击快捷菜单中标题为"退出"的菜单项,将退出应 用程序。运行效果如上图。 3.22 综合例子:计算器 【例 3.17】本例创建一个能进行加、减、乘、除的简单计算器,具体步骤如下: (1) 建立一个新项目。设置主窗体 Form1 属性 MaxiMizeBox=false,属性 MiniMizeBox=false。 属性 FormBorderStyle=FixedDialog,使窗口不能修改大小。 (2) 放 textBox 控件到窗体,属性 Text="0",属性 ReadOnly=true。RightToLeft=yes。 (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。设计界面如图 3.17。 (5) 按钮控件 Button0 单击事件处理函数如下: private void button0_Click(object sender,EventArgs e) { Button b1=(Button)sender; if(textBox1.Text!="0") //如果前边已输入非零数字,例如:12 textBox1.Text+= b1.Text; //此次如键入0,应为:120 else //如果已输入的数字为零,显示的数字应为:0,而不应为:00 textBox1.Text= b1.Text; } //请读者自己考虑,该事件处理函数是否能正确处理键入数字 1 到 9 (6) 按钮 Button1-Button9 的单击事件处理函数都设定为按钮 Button0 的单击事件处理函 数。 (7) 为标题为"."按钮增加事件处理函数如下: private void btn_dot_Click(object sender, EventArgs e) { int n=textBox1.Text.IndexOf("."); if(n==-1) //如果没有小数点,增加小数点,否则不增加 textBox1.Text=textBox1.Text+"."; } (8) 编译,运行,单击标题为"0"到"9"以及"."的按钮,在控件 textBox1 中可以看到输入的 数字,也可以输入小数。 (9) 先实现加法。为 Form1 类增加一个变量 double sum = 0,记录部分和。 (10) 输入了第一个加数,然后输入任一运算符(+、-、*、\或=),在输入第二个加数前,应 先清除编辑框中显示的第一个加数。为实现此功能,为 Form1 类增加一个布尔变量 blnClear,初始值为 false,表示输入数字或小数点前不清除编辑框中显示,输入运算 符(+、-、*、\或=)后,blnClear=true,表示如果再输入数字或小数点,先清除编辑框 中的显示的前一个加数。修改前边程序,输入数字或小数点前,要判断变量 blnClear, 如为 true,清除编辑框中显示的内容后,再显示新输入的数字或小数点,同时修改 blnClear=false。为此修改 Button0 单击事件处理函数如下: 73 private void button0_Click(object sender,EventArgs e) { if(blnClear) //如为真,输入下一个加数前,应先清除textBox1显示内容 { textBox1.Text="0"; //阴影部分为新增语句 blnClear=false; } Button b1=(Button)sender; if(textBox1.Text!="0") textBox1.Text+= b1.Text; else textBox1.Text= b1.Text; } (11) 修改 btn_dot_Click 方法如下: 图 3.17 private void btn_dot_Click(object sender, EventArgs e) { if(blnClear) //如为真,输入下一个加数前,应先清除textBox1显示内容 { textBox1.Text="0"; //阴影部分为新增语句 blnClear=false; } int n=textBox1.Text.IndexOf("."); if(n==-1)//如果没有小数点,增加小数点,防止多次输入小数点 textBox1.Text=textBox1.Text+"."; } (12) 如果要计算 1+2-3,先单击标题为"1"的按钮(简称按钮 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, EventArgs e) { double dbSecond=Convert.ToDouble(textBox1.Text); if(!blnClear) //如果未输入第二个操作数,不运算,例如连续键入+、-、*、\或= switch(strOper) //按上次记录的运算符号运算 { case "+": sum+=dbSecond; break; //在此语句后边增加其他运算符-、*、\的处理代码 } if(sender==btn_add) //记录此次键入的运算符 strOper="+"; //在此语句后边可增加运算符-、*、\、=的处理代码 textBox1.Text=Convert.ToString(sum); //显示部分和 blnClear=true; //设置标记使键入另1个加数前,要先清除显示的前个加数 } (13) 等号(=)处理语句和+号处理基本一致,修改标题为"+"按钮的事件函数如下: private void btn_add_Click(object sender, EventArgs e) { double dbSecond=Convert.ToDouble(textBox1.Text); 74 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; } 将标题为"="的按钮的单击事件处理函数设定为标题为"+"的按钮的单击处理事件函数。 (14) 为标题为"C"按钮增加事件处理函数如下: private void btn_C_Click(object sender, EventArgs e) { textBox1.Text="0"; sum=0; blnClear=false; strOper="+"; } (15) 请读者自己补上减法,乘法,除法运算的语句。 习题: (1) 在窗口中显示一行字符串,加入两个按钮,单击标题为"红色"的按钮把标签字符改为红 色,单击标题为"黑色"的按钮把标签字符改为黑色。当标签字符为红色时,标题为"红 色"的按钮不能使用;当标签字符为黑色时,标题为"黑色"的按钮不能使用。(提示:可 以修改按钮的属性 Enabled 为 false 使其不能使用。) (2) 将上题改为用按扭修改字体的大小,按钮的标题分别为"大字"和"小字"。(参见 3.9 节) (3) 加一文本框控件和一按钮,单击按钮将文本框控件输入的内容用标签控件显示。(提示: 单击按钮事件处理函数中加语句 label1.Text=textBox1.Text)。 (4) 修改上题,使文本框控件输入内容和标签控件显示内容同步。(提示:文本框控件的 TextChanged 事件处理函数中加语句 label1.Text=textBox1.Text)。 (5) 加一文本框控件和一按钮,单击按钮将文本框控件中选中的内容用标签控件显示。(提 示:单击按钮事件处理函数中加语句 label1.Text=textBox1.SelText。) (6) 加一文本框控件和一按钮,单击按钮将文本框控件中选中内容的字符数和选中内容的开 始位置用标签控件显示。 (7) 用控件 RadioButton 选择性别,把选择的结果用 Label 控件显示出来。 (8) 例子 e3_8 中如改为响应单击事件 Click,可能出现什么问题? (9) 用控件 ComboBox 修改标签控件字体的大小。(用属性 Item 在下拉列表中输入字号)。 (10)放 ListBox 控件到窗体中,属性 Name=listBox1。列表框有三项分别为:苹果、梨子、 香蕉。允许多选。标签控件同步显示 ListBox 控件所做的选择。提示:为 ListBox 控件 的 SelectedIndexChenged 事件增加事件函数, 75 label1.Text="所选择的是:"; for(int i=0;i=richTextBox1.Text.Length) //如已查到文本底部,提示用户 { MessageBox.Show("已到文本底部,再次查找将从文本开始处查找", "提示",MessageBoxButtons.OK); FindPostion=0; //下次查找的开始位置 return; } //用Find方法查找,返回找到的位置,返回-1,表示未找到,参数1是要找的字符串 //参数2是查找的开始位置,参数3是查找的一些选项,如大小写是否匹配,查找方向等 FindPostion=richTextBox1.Find(FindString,FindPostion,RichTextBoxFinds.MatchCase); if(FindPostion==-1) //-1表示未找到匹配字符串,提示用户 { MessageBox.Show("未找到匹配字符串,再次查找将从文本开始处查找", "提示", MessageBoxButtons.OK); FindPostion=0; //下次查找的开始位置 } else //找到匹配字符串 { richTextBox1.Focus(); //主窗体成为注视窗口 FindPostion+=FindString.Length; } //下次查找的开始位置在此次找到字符串之后 } (31) 第 26 步在 Form1 类增加了一个替换字符串的方法 ReplaceRichTextBoxString,这里为其增加语句如下: 图 4.7 查找和替换运行结果 public void ReplaceRichTextBoxString(string ReplaceString) { if(richTextBox1.SelectedText.Length!=0) //如果选定了被替换字符串 richTextBox1.SelectedText=ReplaceString; } //替换被选定的字符串 (32) 编译运行,输入若干字符,单击应用程序菜单"编辑|查找和替换"菜单项,打开标题为" 查找和替换"对话框。注意可以不关闭该对话框,操作主窗体,并且该对话框总是在主 窗体的前边,它是一个典型的非模式对话框。在对话框中输入查找和替换的字符,单击 标题为"查找下一个"的按钮,可以找到所选字符,并被选中,单击标题为"替换查到字 符"的按钮,可以看到已被选中的查找到的字符被替换。运行效果如图 4.7。 提示用户保存已被修改的文件 使用单文档文本编辑器,用户在新建文本,打开其他文件或者关闭文本编辑器时,如果 用户对已打开的文件做了修改,或者是新建的文件,还未保存,就需要在关闭当前文件前, 使用对话框提示用户是否保存已被修改的文本内容。本节实现此功能。 85 对话框 MessageBox 使用方法 MessageBox 类可以打开一个模式对话框,用法如下: MessageBox.Show(this,"要保存当前更改吗?","保存更改吗?", MessageBoxButtons.YesNoCancel,MessageBoxIcon.Question); 第一个参数是对话框的父窗口,第二个参数是对话框的提示信息,第三个参数是对话框 标题栏的内容,第四个参数是对话框有哪些按钮,此例有 Yes,No, Cancel 按钮,还可以使 用 AbortRetryIgnore(中止、重试和忽略按钮)、OK(确定按钮)、OKCancel(确定和取消按钮)、 RetryCance(重试和忽略按钮)、YesNo(是和否按钮)等选项。第五个参数是对话框使用哪一 个图标,此例是一个问号图标,还可以是 Asterisk、Error、Exclamation、Hand、Stop、 Warning 等图标,如为 None 则无图标。Show 方法返回值代表用户单击了哪一个按钮。如果 单击 Yes 按钮,返回值是 System.Windows.Forms.DialogResult.Yes,表示要保存已被修改 的 文 件 ; 如 果 单 击 Cancel 按 钮 , 返 回 值 是 System.Windows.Forms.DialogResult.Cancel , 表示忽略此次操作;如果单击 No 按钮,返回值是 System.Windows.Forms.DialogResult.No ,表示 不保存已被修改的文件。以上设计的对话 框 MessageBox 界面如图 4.8。 图 4.8 MessageBox 对话框 提示保存已被修改的文件 (33) 为 Form1 类增加一个 bool 变量 bSave=false 作为标记,用来跟踪控件 RichTextBox 中 的文本内容是否被修改。在程序开始运行、建立和打开一个新文件后,bSave=false, 表示如果关闭当前文件,不必提示用户保存当前文件。当 RichTextBox 控件中的文本被 修改,将激活 RichTextBox 控件 TextChanged 事件,在该事件处理函数中,使 bSave=true, 表示关闭当前文件前,要询问用户是否保存当前已被修改的文件。 (34) 首先在主窗体 Form1 类中增加一个函数,其功能是如果检查到当前文件已被修改,用对 话框询问用户是否保存当前文件,根据用户的选择作相应的处理,该函数返回 true,表 示继续操作,该函数返回 false,表示忽略此次操作。之所以要增加这个函数是因为有 三处要用到此函数。该函数定义如下: public bool IfSaveOldFile() { bool ReturnValue=true; if(bSave) { System.Windows.Forms.DialogResult dr; dr=MessageBox.Show(this,"要保存当前更改吗?","保存更改吗?", MessageBoxButtons.YesNoCancel,MessageBoxIcon.Question); switch(dr) //根据用户选择做相应处理 { case System.Windows.Forms.DialogResult.Yes: //单击了yes按钮,保存修改 bSave=false; //保存文件后,应使bSave为false if(s_FileName.Length!=0) richTextBox1.SaveFile(s_FileName,RichTextBoxStreamType.PlainText); else { SaveFileDialog saveFileDialog1=new SaveFileDialog(); 86 if(saveFileDialog1.ShowDialog()==DialogResult.OK) { s_FileName=saveFileDialog1.FileName; richTextBox1.SaveFile(s_FileName,RichTextBoxStreamType.PlainText); } } ReturnValue=true; //返回true,通知调用本方法的程序,本次操作继续 break; case System.Windows.Forms.DialogResult.No: //单击了no按钮,不保存 bSave=false; ReturnValue=true; break; case System.Windows.Forms.DialogResult.Cancel: //单击了Cancel按钮 ReturnValue=false; //返回false,通知调用本方法的程序,本次操作取消 break; } } return ReturnValue; } (35) 在菜单"文件|新建"和"文件|打开"菜单项的事件函数的第 1 条语句前增加如下语句: if(!IfSaveOldFile()) //如果返回false,本次操作取消 return; (36) 修改菜单"文件|保存... "菜单项单击事件处理函数如下: private void menuItemSaveFile_Click(object sender,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,EventArgs e) { SaveFileDialog saveFileDialog1=new SaveFileDialog(); saveFileDialog1.Filter="纯文本文件(*.txt)|*.txt|所有文件(*.*)|*.*"; saveFileDialog1.FilterIndex=1; if(saveFileDialog1.ShowDialog()==DialogResult.OK) { s_FileName=saveFileDialog1.FileName; richTextBox1.SaveFile(s_FileName,RichTextBoxStreamType.PlainText); bSave=false; //阴影为增加的语句 } } (38) 为 RichTextBox 控件 TextChanged 事件增加事件处理函数如下: private void richTextBox1_TextChanged(object sender,EventArgs e) { bSave=true; } 87 (39) 窗体 Form1 的 FormClosing 事件是在关闭窗口之前产生的事件,此时,窗体中的控件还 存在,还可以保存修改的内容,也可以不退出。增加 Closing 事件的事件处理函数如下: private void Form1_FormClosing(object sender, FormClosingEventArgs e) { if(!IfSaveOldFile()) e.Cancel=true; } //不退出,程序继续运行 (40) 编译运行,键入若干字符,单击菜单"文件"顶级菜单项的"新建"、"打开„"或"退出"菜 单项,将看到提示信息,询问是否保存已被修改的文件。有三种选择:存文件,不存文 件,忽略此次操作,试验一下单击不同按钮程序运行的效果。 打印和打印预览 打印和打印预览是一个编辑器必须具有的功能,本节介绍实现打印和打印预览的方法。 一般要实现如下菜单项:打印、打印预览、页面设置。 PrintDocument 类 PrintDocument 组件是用于完成打印的类,其常用属性、方法和事件如下:  属性 DocumentName:字符串类型,记录打印文档的名字,在打印状态对话框或打印机 队列中显示这个属性值。  方法 Print():调用此方法开始打印文档。  事件 BeginPrint:在调用 Print()方法后,在打印第一页之前发生。  事件 PrintPage:需要打印新的一页时发生。  事件 EndPrint:在打印完最后一页之后发生。 若要打印,第 1 步要创建 PrintDocument 组件的对象。第 2 步使用"页面设置"对话框 PageSetupDialog(图 4.9) 对打印页面进行设置,这些设置作为要打印的所有页的默认设 置。使用"打印"对话框 PrintDialog(图 4.11)设置打印机参数。打开两个对话框前,它们的 属性 Document 都要设置为第 1 步中创建的 PrintDocument 组件的对象,所做的设置都将保 存到 PrintDocument 组件对象中。第三步是调用 PrintDocument.Print()方法来实际打印文 档。当调用 Print()方法后,引发下列事件:BeginPrint、PrintPage、EndPrint。其中每 打印一页都引发 PrintPage 事件,打印多页,要多次引发 PrintPage 事件。完成一次打印, 可以引发一次或多次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.PageSettings : PageSettings 类 对 象 , 包 含 使 用 " 页面设置" 对 话 框 88 PageSetupDialog 设置的打印页面的全部信息。可用帮助查看 PageSettings 类的属性。 下边为这 3 个事件编写事件处理函数,完成打印和打印预览工作,具体步骤如下: (41) 在主窗体文件 Form1.cs 中的最后一个 using 语句之后增加语句: using System.IO; //处理文件必须引入的命名空间 using System.Drawing.Printing; //打印必须引入的命名空间 (42) 本例要打印或预览 RichTextBox 中的内容,在主窗体 Form1 类中增加变量:StringReader streamToPrint=null。如打印或预览文件则为:StreamReader streamToPrint,流的概 念参见第六章。在主窗体 Form1 类中增加打印使用的字体的变量:Font printFont。 (43) 放 PrintDocument 控件到窗体,属性 name 为 printDocument1。 (44) 为 printDocument1 增加 BeginPrint 事件处理函数如下: private void printDocument1_BeginPrint(object sender,PrintEventArgs e) { printFont=richTextBox1.Font; //打印使用的字体 streamToPrint=new StringReader(richTextBox1.Text); //创建读字符串对象 } //如打印文件改为:streamToPrint=new StreamReader("文件的路径及文件名"); (45) printDocument1 的 PrintPage 事件处理函数如下: private void printDocument1_PrintPage(object sender,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,bottom));//返回矩形 } (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;//StartPoint 记录矩形的这个顶点 EndPoint.X=e.X;//拖动鼠标移动的位置作为矩形另一顶点 EndPoint.Y=e.Y;//EndPoint 记录矩形的这个顶点,两个顶点定义一个矩形 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);//计算椭圆新位置 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); 124 bitG.DrawEllipse(pen1,r1);//最终椭圆画在 pictureBox1 属性 Image 引用的对象中 mark=false; pictureBox1.Image=bits;//显示画椭圆的最终结果 } (6) 运行,在 PictureBox 控件中拖动鼠标可以画圆或椭圆。 5.12 图像剪贴板功能 Windows 中的许多程序都支持剪贴板功能。通过剪贴板可以完成显示数据或图形图像的 剪切(Cut)、复制(Copy)、粘贴(Paste)等功能。剪贴板可以理解为一块存储数据的公共 区域,用户可以用复制(Copy)或剪切(Cut)菜单项把数据放入到剪贴板中,当本任务或 其它任务要用剪贴板中的数据时,可以用粘贴(Paste)菜单项从剪贴板中把数据取出。存 入剪贴板中的数据,可以是字符,位图,或者其它格式数据。在图形模式下使用剪贴板包括 如下菜单项:选定剪切区域、剪切(Cut)、复制(Copy)、粘贴(Paste)等。使过画图程序 的读者都知道,在使用剪切和复制前,必须首先选定剪切或复制区域,首先按一个按钮,通 知程序要设定剪切或复制区域,然后在要选定区域的左上角按下鼠标左键,拖动鼠标画出一 个矩形,抬起鼠标后显示一个矩形既为设定的剪切或复制区域。剪切或复制图形或图像到剪 贴板后,矩形自动消失。下面详细介绍实现以上功能的方法。 5.12.1 剪切复制区域选定 剪切复制区域选定的方法和前边章节中拖动鼠标绘制椭圆(圆)的方法基本一样,只是在 这里绘制的是矩形,而且在鼠标抬起时,不把矩形存入 PictureBox 控件属性 Image 引用的 位图对象中,仅仅记录矩形的位置。请读者自己实现此功能。 5.12.2 剪切板复制功能的实现 假定已选定复制区域,例如为区域 Rectangle(10,10,50,50),把此区域的图形或图像放到 剪贴板中。具体实现步骤如下: (1) 新建项目。放 PictureBox 控件到窗体,修改属性 Dock=Fill。属性 Name=pictureBox1, 修改属性 Image 为一图像文件全路径,使其显示一幅图。 (2) 把 Mainmenu 控件放到主窗体中。增加顶级菜单项:编辑,属性 Name=menuItemEdit。为 "编辑"顶级菜单项的弹出菜单增加菜单项:复制、剪切、粘贴。属性 Name 分别为 menuItemCopy、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) 运行,单击菜单"编辑"|"复制"菜单项,复制图形到剪贴板。打开 Windows 画图程序, 125 单击画图程序菜单"编辑"|"粘贴"菜单项,被复制的图形应能正确粘贴到画图程序中。 5.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.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);//粘贴图形左上角到坐标(30,30)位置 pictureBox1.Image=myBitmap;//位图对象在pictureBox1中显示 } } (8) 运行 Windows 画图程序,拷贝图形到剪贴板。运行自己编制的程序,单击菜单"编辑"|" 粘贴"菜单项,可以看到画图程序中被拷贝的图形能正确粘贴到自己编制的程序中。 (9) Windows 画图程序粘贴后,能用鼠标拖动粘贴的图形,现实现此功能。放 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; } 126 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 随鼠标移动 pictureBox2.Top+=y1; x=e.X; 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 增加事件函数如下: 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; } } 127 (12) 运行 Windows 画图程序,拷贝图形到剪贴板。运行自己编制的程序,单击菜单"编辑"|" 粘贴"菜单项,可以看到画图程序中被拷贝的图形能正确粘贴到自己编制的程序中。拖 动被拷贝的图形,使其运动到指定位置,在 pictureBox2 外,单击鼠标右键,图形固定 到指定位置。在拖动图像时,图像抖动。为避免抖动,不必每次都响应鼠标移动事件, 可以多次事件响应一次,读者可以试一下。 5.13 图像的处理 本节介绍图像的处理的最基础知识,要想深入了解这方面的知识,还要读这方面的专著。 5.13.1 图像的分辨率 例子 e5_13_1:将原图形的分辨率降低 16 倍,其方法是将原图形分成 4*4 的图形块, 这 16 个点的颜色都置成这 16 个点中某点的颜色,例如 4*4 的图形块左上角的颜色。 (1) 新建项目。放两个 PictureBox 控件到窗体,属性 Name 分别为 pictureBox1,pictureBox2, 设置 pictureBox1 属性 Image,使其显示一幅图。PictureBox2 显示变换后的图形。 (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;i170,d2=255。变换的效果是增强了对比度。具体步骤如下: (1) 新建项目。放两个 PictureBox 控件到窗体,属性 Name 分别为 pictureBox1,pictureBox2, 修改 pictureBox1 属性 Image,使其显示一黑白幅图。PictureBox2 显示变换后的图形。 (2) 放 Button 控件到窗体,为其增加事件函数如下: private void button1_Click(object sender, System.EventArgs e) { Color c; int i,j,xres,yres,m; 129 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++)//d1<85 lut[i]=0;//d2=0 for(i=85;i<=170;i++)//85<=d1<=170 lut[i]=(i-85)*3;//d2=(d1-85)*3 for(i=171;i<256;i++)//d1>170 lut[i]=255;//d2=255 for(i=0;i170,d2=212+0.5*(d1-170)。 (16) 实现彩色图像的逆反,即用 255 分别减去图像每点彩色值的 RGB(红绿蓝)分量,形成新 的 RGB 分量,用新的 RGB 分量组成的颜色置原来的点。 (17) 彩色图像的平滑,即求出图像每点和其相邻的 8 个点的颜色值,分别求出这 9 个点颜色 的 RGB(红绿蓝)分量平均值,用求出的平均 RGB 分量值组成新颜色作为这点的颜色。 (18) Windows 画图程序,选定区域后,能用鼠标拖动,请实现此功能。 (19) 5.12.4 节,擦除上次鼠标移动时画的图形时未指定重画区域,请指定重画区域。 (20) 完成 5.13.4 所要求的动画,并能走到窗体右边界后,从右向左走回来,到左边界后,再 向右走回去。如果有背景,如何处理。 (21) 制作自己的画图程序,看一看能完成哪些功能。 (22) 有些时候,为突出图形的分界,例如医院的 X 片是黑白的,为了使医生能更清楚的看到 肿瘤,将黑白图形变为彩色的图形,在分界两侧用不同颜色表示,这种方法叫伪彩色。 实现的原理,就是在黑白图像灰度变化很大处,认为是边界。试一下,能否实现黑白图 像的伪彩色。 131 第六章 文件和流 编程语言读写文件的方法已经过了很多变革。早期语言,例如 Basic 语言,使用 I/O 语句。后来的语言,例如 C 语言,使用标准的 I/O 库(stdio.h)。在 C++和 Java 语言中,引 入了抽象的概念:流。流的概念不仅可用于文件系统,也可用于网络。但在 C++和 Java 语 言中流的概念比较复杂。C#语言也采用了流的概念,但是使用起来要简单的多。本章介绍在 C#语言中,如何处理目录(文件夹),如何处理文件,如何使用流的概念读写文件。 文件是用路径来定位的,描述路径有 3 种方式:绝对路径、当前工作盘的相对路径、相 对路径。以 C:\dir1\dir2 为例(假定当前工作目录为 C:\dir1),C:\dir1\dir2 为绝对路径, \dir1\dir2 为当前工作盘的相对路径,dir2 为相对路径,都表示 C:\dir1\dir2。本章中所 说的路径,可以是路径的 3 种描述方式中的任意一种。在 C#中 "\"是转义字符,要表示它 的话需要使用"\\",因此用字符串表示路径 C:\dir1\dir2 应写为"C:\\dir1\\dir2"。由于 这种写法不方便,C#语言提供了简化写法,只要在字符串前加上@即可直接使用"\"。所以上 面的路径在 C#中可以用字符串表示为@"C:\dir1\dir2"、@"\dir1\dir2"、"dir2",@表示其 后字符串不包括转义字符。 6.1 用流读写文件 C#把所有文件都看成是顺序的字节流,用抽象类 Stream 代表一个流。Stream 类有许多 派生类,例如 FileStream 类,以字节为单位读写文件;BinaryRead 类和 BinaryWrite 类,以 基本数据类型为单位读写文件,可以从文件直接读写 bool、String、int16、int 等基本数据类 型数据;StreamReader 和 StreamWriter 类以字符或字符串为单位读写文件。本节首先介绍这 些类的用法,然后介绍数据的序列化。使用流读写文件必须引入命名空间:System.IO。 6.1.1 用 FileStream 类读写字节 使用 FileStream 类可以建立文件流对象,用来打开和关闭文件,以字节为单位读写文 件。也可对其它与文件相关的操作系统句柄进行操作,如管道、标准输入和标准输出。 FileStream 类对象能对输入输出进行缓冲,从而提高性能。该类常用属性、方法如下:  属性 CanRead、CanSeek、CanWrite:检查流对象是否可以读、定位、写。只读属性。  属性 Length:用字节表示的流对象长度,即文件的长度。只读属性。  属性 Position:获取或设置流对象当前读写位置。  构造函数 public FileStream(string path,FileMode mode,FileAccess access):参数 path 是文件的相对路径或绝对路径,构造函数将建立参数 path 指定文件的 FileStream 类对象。参数 mode 可以是如下模式:FileMode.Append,打开文件并将读写位置移到文 件尾,文件不存在则创建新文件,只能同 FileAccess.Write 一起使用;FileMode.Create, 创建新文件,如果文件已存在,文件内容将被删除;FileMode.CreateNew,创建新文件, 如果文件已存在,则引发异常;FileMode.Open,打开现有文件,如果文件不存在,则 引发异常;FileMode.OpenOrCreate,如果文件存在,打开文件,否则,创建新文件; FileMode.Truncate,打开现有文件,并将文件所有内容删除。参数 access 可以是: 132 FileAccess.Read(只读方式打开文件)、FileAccess.Write(只写方式打开文件)、 FileAccess.ReadWrite( 读 写 方 式 打 开 文 件 )。也可以没有第三个参数,默认为 FileAccess.ReadWrite。一共有 8 个构造函数,其它构造函数,请用帮助查看。  方法 void Write(byte[] array,int offset,int count):将数组中多个字节写入流, 参数 1 是要写入的数组,要写入流的第 1 个字节是 array[offset],参数 3 为要写入的 字节数。写字节数组数据到文件的程序如下,该程序将建立文件 d:/g1.bin。 using System; using System.IO;//使用文件必须引入的命名空间 class WriteFile { static void Main() { byte[] data=new byte[10];//建立字节数组 for(int i=0;i<10;i++)//为数组赋值 data[i]=(byte)i; FileStream fs=new FileStream("d://g1.bin",FileMode.Create);//建立流对象 fs.Write(data,0,10);//写 data 字节数组中的所有数据到文件 fs.Close();//不再使用的流对象,必须关闭。垃圾收集器不能自动清除流对象 } }  方法 int Read(byte[] array,int offset,int count):从流中读数据写入字节数组 array, 读入的第 1 个字节写入 array[offset],参数 3 为要读入的字节数。返回值为所读字节 数,由于可能已读到文件尾部,其值可能小于 count,甚至为 0。读一个文件所有字节 到数组并在屏幕显示的程序如下,请先用上例建一个文件,然后用此例读出。 using System; using System.IO;//使用文件必须引入的命名空间 class ReadFile { static void Main() { FileStream fs=new FileStream("d://g1.bin",FileMode.Open); byte[] data=new byte[fs.Length]; long n=fs.Read(data,0,(int)fs.Length);//n 为所读字节数 fs.Close(); Console.WriteLine("文件的内容如下:"); foreach(byte m in data) Console.Write("{0},",m); } }  方法 long Seek(long offset,SeekOrigin origin):该方法移动文件读写位置到参数 2 指定位置加上参数 1 指定偏移量处,参数 2 可以是 SeekOrigin.Begin、SeekOrigin.End、 SeekOrigin.Current,分别为开始位置、结束位置、当前读写位置。例子如下: using System; using System.IO;//使用文件必须引入的命名空间 class FileStreamProperty { static void Main() { FileStream fs=new FileStream("d://g1.bin",FileMode.Open);//无第 3 个参数 fs.Seek(-4,SeekOrigin.End);//文件读写位置移到从文件尾部向前 5 个字节 133 Console.WriteLine("读写位置:{0},能定位:{1}",fs.Position,fs.CanSeek); Console.WriteLine("能读:{0},能写:{1}",fs.CanRead,fs.CanWrite); fs.Close(); } } 注意建立流对象fs的构造函数无第3个参数,因此按读写方式打开。程序运行结果如下: 读写位置:6,能定位:true 能读:true, 能写:true 6.1.2 用 BinaryReader、BinaryWriter 类读写基本数据类型 使用BinaryReader和BinaryWriter类可以从文件直接读写bool、String、int16、int等基 本数据类型数据。常用BinaryWriter类方法如下:  构造函数 BinaryWriter(Stream input),参数为 FileStream 类对象。  方法 viod Write(数据类型 Value):写入参数指定的数据类型的一个数据,数据类型可以 是基本数据类型,例如,int、bool、float 等。写 int 类型数据程序如下: using System; using System.IO;//使用文件必须引入的命名空间 class WriteFile { static void Main() { FileStream fs=new FileStream("d://g1.dat",FileMode.Create); BinaryWriter w=new BinaryWriter(fs); for(int i=0;i<10;i++) w.Write(i);//写入整形数 w.Close(); } } 常用 BinaryReader 类方法如下:  构造函数 BinaryReader(Stream input),参数为 FileStream 类对象。  方法 ReadBoolean、ReadByte、ReadChar 等:返回指定类型一个数据。没有参数。  方法 ReadBytes:用法如下 byte[] array=r.ReadBytes(array.Length),其中 r 为 BinaryReader 类对象。 读int类型数据程序如下,请先用上例建一个文件,然后用此例读出。 using System; using System.IO;//使用文件必须引入的命名空间 class ReadFile { static void Main() { int[] data=new int[10]; FileStream fs=new FileStream("d://g1.dat",FileMode.Open); BinaryReader r=new BinaryReader(fs); for(int i=0;i<10;i++) data[i]=r.ReadInt32(); r.Close(); Console.WriteLine("文件的内容如下:"); 134 foreach(int m in data) Console.Write("{0},",m); } } 6.1.3 用 StreamReader 和 StreamWriter 类读写字符串 读写字符串可以用 StreamReader 和 StreamWriter 类。常用 StreamWriter 类方法如下:  构造函数 StreamWriter(string path,bool append),path 是要写文件的路径,如果该 文件存在,并且 append 为 false,则该文件被改写。如果该文件存在,并且 append 为 true,则数据被追加到该文件中。否则,将创建新文件。  方法 void Writer(string value):将字符串写入流。  方法 void Writer(char value):将字符写入流。 写字符串类型数据程序如下: using System; using System.IO;//使用文件必须引入的命名空间 class WriteFile { static void Main() { StreamWriter w=new StreamWriter("d://g.text",false); w.Write(100);//100 首先转换为字符串,再写入。 w.Write("100 个");//字符串之间用换行符 ("\n"或"\r\n")分隔 w.Write("End of file");//一个字符串叫做文件中的一行 w.Close(); } } 常用 StreamReader 类方法如下:  构造函数 StreamReader(string path),参数是要读文件的路径。  方法 Read():从流中读取一个字符,并使读字符位置移动到下一个字符。返回代表读出 字符 ASCII 字符值的 int 类型整数,如果没有字符可以读出,返回-1。如果 sr 是 StreamReader 对象,读取一个字符的用法如下:char c=(char)sr.Read()。  方法 ReadLine():从流中读取一行字符并将数据作为字符串返回。行的定义是:两个换 行符 ("\n"或"\r\n")之间的字符序列。返回的字符串不包含回车或换行符。 读字符串程序如下,请先用上例建一个文件,然后用此例读出。 using System; using System.IO;//使用文件必须引入的命名空间 using System.Collections;//使用ArrayList必须引入的命名空间 class ReadFile { static void Main() { string sLine=""; ArrayList arrText=new ArrayList(); using(StreamReader objReader=new StreamReader("d://g.text")) { do//使用 using 语句以确保所涉及的文件在写入或读取操作后正确关闭 { sLine=objReader.ReadLine(); if(sLine!=null) 135 arrText.Add(sLine); }while(sLine!=null); } Console.WriteLine("文件的内容如下:"); foreach(string m in arrText) Console.Write("{0}",m); } } 程序运行结果如下: 文件的内容如下: 100100 个 End of file 6.1.4 序列化 对于一个复杂的数据结构,例如数组,都用以上方法存入文件,就显得过于复杂了。为 了简化这类问题,C#提出了序列化的概念,序列化包括序列化和反序列化,所谓序列化就 是把类的对象作为一个整体存入文件,反序列化则是相反的过程。C#中的许多类都支持序 列化,可以用如下方法判别一个类是否支持序列化: Hashtable h=new Hashtable();//建立一个哈希表对象 Type myType=h.GetType(); bool myBool=myType.IsSerializable;//如为 true,支持序列化,这里 myBool=true 例子 e6_1_4A:下边的例子完成了一个哈希表对象的序列化。 using System; using System.IO; using System.Collections;//使用哈希表引用的命名空间 using System.Runtime.Serialization.Formatters.Binary;//使用序列化引用的命名空间 using System.Runtime.Serialization;//使用序列化引用的命名空间 class SerialFile { static void Main() { Hashtable h=new Hashtable();//建立一个哈希表对象 h.Add("键1","值1");//哈希表的每一个元素是一对键值 h.Add("键2","值2");//例如商品编号和商品名称 h.Add("键3","值3");//通过键值,可以很容易找到键值对应的值 //序列化数据,@表示其后字符串不包括转义字符 FileStream fs=new FileStream(@"d:/d.dat",FileMode.Create); BinaryFormatter formatter=new BinaryFormatter(); formatter.Serialize(fs,h); fs.Close(); //反序列化数据 fs=new FileStream(@"d:/d.dat",FileMode.Open); h.Clear(); h=(Hashtable)formatter.Deserialize(fs); fs.Close(); //显示反序列化数据 136 foreach(DictionaryEntry de in h) Console.WriteLine("{0}:{1};",de.Key,de.Value);//注意哈希表用法 } 运行结果如下: 键1:值1; 键2:值2; 键3:值3; 例子 e6_1_4B:自己定义的类也可以序列化,只要在类定义前增加[Serializable]即可, 下边例子首先定义了一个可序列化的类 Person,建立若干 Person 类对象,存到 ArrayList 类对象中,并将 ArrayList 类对象序列化。程序如下: using System; using System.IO; using System.Collections;//使用ArrayList类引用的命名空间 using System.Runtime.Serialization.Formatters.Binary;//使用序列化引用的命名空间 using System.Runtime.Serialization;//使用序列化引用的命名空间 namespace e6_1_4B { [Serializable] public class Person//[Serializable]表示该类可以序列化 { private string P_name="张三";//P_name是私有字段 private int P_age=12;//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;} } } class Class1 { [STAThread] static void Main(string[] args) { ArrayList h=new ArrayList(); Person p=new Person(); h.Add(p); p=new Person(); p.Name="李四"; p.Age=24; h.Add(p); //序列化数据 FileStream fs=new FileStream(@"d:/d.dat",FileMode.Create); 137 BinaryFormatter formatter=new BinaryFormatter(); formatter.Serialize(fs,h); fs.Close(); //反序列化数据 fs=new FileStream(@"d:/d.dat",FileMode.Open); h.Clear(); h=(ArrayList)formatter.Deserialize(fs); fs.Close(); foreach(Person de in h)//显示反序列化数据 Console.WriteLine("Name={0},Age={1};",de.Name,de.Age); } } } 运行结果如下: Name=张三,Age=12; Name=李四,Age=24; 6.1.5 Stream 类的其它派生类 Stream 类的其它派生类包括:MemoryStream、BuffereStream、NetworkStream(在 System.Net.Sockets 命名空间)。其中 MemoryStream 类把文件放到内存中,极大地提高了 文件的读写速度。BuffereStream 类为文件的读写建立一个缓冲区,写文件先把文件存到缓 冲区中,缓冲区满了以后,才写入物理设备。先把文件中较多数据读到缓冲区中,读文件先 从缓冲区读,缓冲区没有该数据,才再一次从物理设备读入缓冲区,用这样的方法改善文件 读写的性能。NetworkStream 类把网络传输的数据也看作流。请用帮助查看这些类的用法。 6.2 File 类和 FileInfo 类 C#语言中通过 File 和 FileInfo 类来创建、复制、删除、移动和打开文件。在 File 类中提 供了一些静态方法,使用这些方法可以完成上述功能,但 File 类不能建立对象。FileInfo 类 使用方法和 File 类基本相同,但 FileInfo 类能建立对象。在使用这两个类时需要引用 System.IO 命名空间。这里重点介绍 File 类的使用方法。 6.2.1 File 类常用的方法 File 类常用的方法如下,File 类所有方法都是静态方法。  AppendText:参数是文件路径,创建参数指定文件的 StreamWriter 类对象,可以向该 文件添加字符或字符串数据。如文件不存在,就创建该文件。  Copy:复制参数指定的文件到新文件夹。  Create:按参数指定的路径建立新文件  Delete:删除参数指定的文件。  Exists:检查参数指定路径的文件是否存在,存在,返回 true。  GetAttributes:返回参数指定文件的属性。 138  GetCreationTime:返回参数指定的文件或文件夹的创建日期和时间。  GetLastAccessTime:返回参数指定的文件或文件夹的最后一次访问日期和时间。  GetLastWriteTime:返回参数指定的文件或文件夹最后一次修改的日期和时间。  Move:移动参数指定的文件到新文件夹。  Open:参数是文件路径,创建参数指定文件的可以读写的 FileStream 类对象。  OpenRead:参数是文件路径,创建参数指定文件的只读 FileStream 类对象。  OpenWrite:参数是文件路径,返回参数指定文件的只写 FileStream 类对象。  SetAttributes:设置参数指定文件的属性。  SetCreationTime:设置参数指定文件的创建日期和时间。  SetLastAccessTime:设置参数指定文件的最后一次访问日期和时间。  SetLastWriteTime:设置参数指定文件最后一次修改的日期和时间。 下面通过程序实例来介绍其中主要方法的使用。 6.2.2 判断文件是否存在的方法 File.Exists 方法声明如下:public static bool Exists(string path);该方法判断参数指定的文件 是否存在,参数 path 指定文件路径。如果文件存在,返回 true,如果文件不存在,或者访 问者不具有访问此文件的权限,或者 path 描述一个目录,返回 false。下面的代码段判断是 否存在 c:\Example\e1.txt 文件: if(File.Exists(@"c:\Example\e1.txt")) {„}//处理代码 6.2.3 文件删除方法 File.Delete 方法声明如下:public static void Delete(string path);该方法删除参数指定的文件, 参数 path 指定要删除的文件的相对或绝对路径。下面的程序删除用户指定文件。 using System; using System.IO;//使用文件必须引入的命名空间 class DeleteFile { static void Main() { Console.WriteLine("请键入要删除的文件的路径:"); string path=Console.ReadLine();//从键盘读入路径,输入回车结束 if(File.Exists(@path))//@表示其后字符串不包括转义字符 File.Delete(@path); else Console.WriteLine("文件不存在!"); } } 6.2.4 文件复制方法 File.Copy 该方法声明如下: public static void Copy(string sourceFileName,string destFileName,bool overwrite); 该方法将参数 sourceFileName 指定文件拷贝到参数 destFileName 指定的目录,修改文 139 件名为参数 destFileName 指定的文件名,如果 OverWrite 为 true,而且文件名为 destFileName 的文件已存在的话,将会被复制过去的文件所覆盖。例子如下: using System; using System.IO;//使用文件必须引入的命名空间 class CopyFile { static void Main() { Console.WriteLine("请键入要拷贝的源文件的路径:"); string path=Console.ReadLine();//从键盘读入路径,输入回车结束 Console.WriteLine("请键入要拷贝的目的文件的路径(包括文件名):"); string path1=Console.ReadLine();//从键盘读入路径,输入回车结束 if(File.Exists(@path))//@表示其后字符串不包括转义字符 { if(!File.Exists(@path1))//如果不存在目的文件,拷贝 File.Copy(@path,@path1,true); else Console.WriteLine("目的文件存在或目的路径非法!"); } else Console.WriteLine("源文件不存在!"); } } 6.2.5 文件移动方法 File.Move 和 FileInfo.MoveTo 该方法声明如下: public static void Move(string sourceFileName,string destFileName); 该方法将参数 sourceFileName 指定文件移动到参数 destFileName 指定的目录,修改文 件名为参数 destFileName 指定的文件名,如果目标文件已经存在,或者路径格式不对,将 引发异常。注意,只能在同一个逻辑盘下进行文件转移。如果试图将 c 盘下的文件转移到 d 盘,将发生错误。下面的代码可以将 c:\Example 下的 e1.txt 文件移动到 c 盘根目录下。 File.Move(@"c:\Example\BackUp.txt",@"c:\BackUp.txt"); FileInfo 类方法 MoveTo 可以将一个逻辑盘的文件移到另一个逻辑盘,例子如下: using System; using System.IO;//使用文件必须引入的命名空间 class MoveFile { static void Main() { Console.WriteLine("请键入要移动的源文件的路径:"); string path=Console.ReadLine();//从键盘读入路径,输入回车结束 Console.WriteLine("请键入要移动的目的文件的路径(包括文件名):"); string path1=Console.ReadLine();//从键盘读入路径,输入回车结束 if(File.Exists(@path))//@表示其后字符串不包括转义字符 { if(!File.Exists(@path1)) { FileInfo fi=new FileInfo(@path);//使用 FileInfo 必须建立对象 fi.MoveTo(@path1);//fi 所代表的文件移到 path1 指定的位置 //File.Move(@path,@path1);//如在同一磁盘可使用此句替换前 2 句 140 } else Console.WriteLine("目的文件存在或路径非法!"); } else Console.WriteLine("源文件不存在!"); } } 6.2.6 设置文件属性方法 File.SetAttributes 方法声明如下: public static void SetAttributes(string path,FileAttributes fileAttributes); 参数path指定要修改属性的文件的路径;参数fileAttributes 指定要修改的文件属性, 可以是如下数值:Archive(存档)、Compressed(压缩文件)、Directory(目录文件)、 Encrypted(加密)、Hidden(隐藏)、Normal(普通文件)、ReadOnly(只读文件)、System(系统 文件)、Temporary(临时文件)。下面的代码可以设置文件 c:\e1.txt 的属性为只读、隐藏。 File.SetAttributes(@"c:\e1.txt",FileAttributes.ReadOnly|FileAttributes.Hidden); 6.2.7 得到文件的属性方法 File.GetAttributes 方法声明如下:public static FileAttributes GetAttributes(string path);方法返 回参数指定的文件的 FileAttributes,如果未找到路径或文件,返回-1, 例子如下: using System; using System.IO;//使用文件必须引入的命名空间 class GetFileAttributes { static void Main() { Console.WriteLine("请键入要得到属性的文件路径:"); string path=Console.ReadLine();//从键盘读入路径,输入回车结束 if(File.Exists(@path))//@表示其后字符串不包括转义字符 { FileAttributes attributes=File.GetAttributes(path); if((attributes&FileAttributes.Hidden)==FileAttributes.Hidden) Console.WriteLine("隐藏文件"); else Console.WriteLine("不是隐藏文件"); //得到文件其它信息 FileInfo fileInfo=new FileInfo(@path); Console.WriteLine(fileInfo.FullName+"文件长度="+ fileInfo.Length+",建立时间="+fileInfo.CreationTime); //也可用如下语句得到文件其它信息 Console.WriteLine("建立时间="+File.GetCreationTime(@path) +"最后修改时间="+File.GetLastWriteTime(@path)+ 141 "访问时间="+File.GetLastAccessTime(@path)); } else Console.WriteLine("文件不存在!"); } } 6.3 Directory 类和 DirectoryInfo 类 C#语言中通过 Directory 类来创建、复制、删除、移动文件夹。在 Directory 类中提供 了一些静态方法,使用这些方法可以完成上述功能。但 Directory 类不能建立对象。 DirectoryInfo 类使用方法和 Directory 类基本相同,但 DirectoryInfo 类能建立对象。在 使用这两个类时需要引用 System.IO 命名空间。这里重点介绍 Directory 类的使用方法。 6.3.1 Directory 类常用的方法 Directory 类常用的方法如下,这些方法都是静态方法。  CreateDirectory:按参数指定路径创建所有目录(文件夹)和子目录。  Delete:删除参数指定目录。  Exists:检查参数指定路径的文件夹是否存在,存在,返回 true。  GetCreationTime:返回参数指定文件或文件夹的创建日期和时间。  GetCurrentDirectory:获取应用程序的当前工作目录。  GetDirectories:返回字符串数组,该数组记录参数指定文件夹中所有子文件夹的名称。  GetDirectoryRoot:返回参数指定路径的卷信息、根信息或同时包括这两者的字符串。  GetFiles:返回字符串数组,该数组记录参数指定文件夹中所有文件的名称。  GetFileSystemEntries:返回字符串数组,记录参数指定目录中所有子目录和文件名称。  GetLastAccessTime:返回参数指定的文件或文件夹的最后一次访问日期和时间。  GetLastWriteTime:返回参数指定的文件或文件夹的最后修改日期和时间。  GetLogicalDrives:返回字符串数组,数组记录计算机所有驱动器名称,如 A:、 C:等。  GetParent:返回参数指定路径的父文件夹,包括绝对路径和相对路径。  Move:将参数指定文件或文件夹及包含的文件、子文件夹移动到新位置。  SetCreationTime:设置参数指定文件或文件夹的创建日期和时间。  SetCurrentDirectory:将参数指定的目录设置为应用程序的当前工作目录。  SetLastAccessTime:设置参数指定的文件或文件夹的最后一次访问日期和时间。  SetLastWriteTime:设置参数指定的文件夹的最后一次修改日期和时间。 6.3.2 判断目录是否存在的方法 Exists 方法声明如下:public static bool Exists(string path);该方法判断参数指定目录是 否存在,参数 path 指定目录的路径。如果目录存在,返回 true,如果目录不存在,或者访 问者不具有访问此目录权限,返回 false。下面代码判断是否存在 c:\Dir1\Dir2 目录。 if(Directory.Exists(@"c:\Dir1\Dir2")) {„}//处理代码 142 6.3.3 目录创建方法 CreateDirectory 方法声明如下:public static DirectoryInfo CreateDirectory(string path);方法 按参数 path 指定的路径创建所有目录及其子目录。如果由参数 path 指定的目录已存在,或 者参数 path 指定的目录格式不正确,将引发异常。下面的程序创建按用户输入的目录名创 建目录,具体程序如下: using System; using System.IO;//使用文件必须引入的命名空间 class CreateFileDirectory { static void Main() { Console.WriteLine("请键入要创建的目录路径:"); string path=Console.ReadLine();//从键盘读入路径,输入回车结束 if(!Directory.Exists(@path))//@表示其后字符串不包括转义字符 Directory.CreateDirectory(@path); else Console.WriteLine("目录已存在或目录非法!"); } } 6.3.4 目录删除方法 Delete 方法声明如下:public static void Delete(string path,bool recursive);该方法删 除参数 path 指定的目录,可以是相对或绝对路径。方法的第二个参数为 bool 类型,它可以 决定是否删除非空目录。如果该参数值为 true,将删除整个目录,即使该目录下有文件或 子目录;若为 false,则仅当目录为空时才可删除。下面的程序删除用户指定目录。 using System; using System.IO;//使用文件必须引入的命名空间 class DeleteFile { static void Main() { Console.WriteLine("请键入要删除的目录的路径:"); string path=Console.ReadLine();//从键盘读入路径,输入回车结束 if(Directory.Exists(@path))//@表示其后字符串不包括转义字符 Directory.Delete(@path); else Console.WriteLine("目录不存在或目录非法!"); } } 6.3.5 目录移动方法 Move 方法声明如下:public static void Move(string sourceDirName,string destDirName); 该方法将将文件或目录及其子目录移到新位置,如果目标目录已经存在,或者路径格式不对, 143 将引发异常。注意,只能在同一个逻辑盘下进行目录转移。如果试图将 c 盘下的目录转移到 d 盘,将发生错误。下面的代码可以将目录 c:\Dir1\Dir2 移动到 c:\Dir3\Dir4。 Directory.Move(@"c:\Dir1\Dir2",@"c:\Dir3\Dir4");} Directory Info 类方法 MoveTo 可以将一个逻辑盘的目录移到另一个逻辑盘,例子如下: using System; using System.IO;//使用文件必须引入的命名空间 class DeleteFile { static void Main() { Console.WriteLine("请键入要移动的源目录的路径:"); string path=Console.ReadLine();//从键盘读入路径,输入回车结束 Console.WriteLine("请键入要移动的目的目录的路径:"); string path1=Console.ReadLine();//从键盘读入路径,输入回车结束 if(Directory.Exists(@path))//@表示其后字符串不包括转义字符 { if(!Directory.Exists(@path1)) { DirectoryInfo dir=new DirectoryInfo(@path);//必须建立对象 dir.MoveTo(@path1);//dir 所代表的目录移到 path1 指定的位置 //Directory.Move(@path,@path1); }//如两个目录在同一磁盘中,可用被注解的语句替换前 2 句 else Console.WriteLine("目的目录已存在!"); } else Console.WriteLine("源目录不存在!"); } } 6.3.6 获取当前目录下所有子目录方法 GetDirectories 该方法声明如下:public static string[] GetDirectories(string path);下面的程 序读出用户指定目录下的所有子目录,并将其在屏幕显示。 using System; using System.IO;//使用文件必须引入的命名空间 class DeleteFile { static void Main() { Console.WriteLine("请键入目录的路径:");//例如键入:d:/ string path=Console.ReadLine();//从键盘读入路径,输入回车结束 if(Directory.Exists(@path))//@表示其后字符串不包括转义字符 { string[] Directorys; Directorys=Directory.GetDirectories(@path); foreach(string aDir in Directorys) Console.WriteLine(aDir); } else Console.WriteLine("目录不存在!"); 144 } } 获得所有逻辑盘符方法定义如下: string[] AllDrivers=Directory.GetLogicalDrives(); 6.3.7 获取当前目录下的所有文件方法 GetFiles 该方法声明如下:public static string[] GetFiles(string path);下面的程序读出 用户指定目录下的所有文件名,并将文件在屏幕上显示。 using System; using System.IO;//使用文件必须引入的命名空间 class DeleteFile { static void Main() { Console.WriteLine("请键入目录的路径:"); string path=Console.ReadLine();//从键盘读入路径,输入回车结束 if(Directory.Exists(@path))//@表示其后字符串不包括转义字符 { string[] files; files=Directory.GetFiles(@path); foreach(string aFile in files) Console.WriteLine(aFile); } else Console.WriteLine("目录不存在!"); } } 6.3.8 目录属性设置方法 Atttributes 与文件属性相同,目录属性也是使用 FileAttributes 来进行设置的。下面的代码设置 c:\Dir1\Dir2 目录为只读、隐藏。 DirectoryInfo DirInfo=new DirectoryInfo(@"c:\Dir1\Dir2"); DirInfo.Atttributes=FileAttributes.ReadOnly|FileAttributes.Hidden; 6.4 例子:查找文件 本节实现一个查找指定驱动器中指定文件的程序。 6.4.1 Panel 和 ListView 控件 Panel 是一个可以包含其它控件的控件,例如包含一组 RadioButton 控件。如果 Panel 控件的 Enabled 属性设置为 false,则也会禁用包含在 Panel 中的所有控件。默认情况下, Panel 控件在显示时没有任何边框。可以用 BorderStyle 属性提供标准或三维的边框,将窗 145 体分为不同区域。因为 Panel 控件派生于 ScrollableControl 类,所以可以用 AutoScroll 属性来启用 Panel 控件中的滚动条。当 AutoScroll 属性设置为 true 时,使用所提供的滚动 条可以滚动显示在 Panel 中,但不在其可视区域内的所有控件。 ListView 控件可用 4 种不同视图(详细资料、大图标、列表、小图标)中的一种显示一些 项的集合,ListView 控件的外观与 Windows 资源管理器的文件列表外观相似。其常用属性、 方法和事件如下:  属性 Columns:所有列标头的集合,可用"ColumnHeader 集合编辑器"对话框修改。  属性 MultiSelect:获取或设置一个值,该值指示是否可以选择多个项。  属性 SelectedIndices:获取控件中选定项的索引集合。用下式得到这个集合: ListView.SelectedIndexCollection I=ListView1.SelectedIndices; 其中 I.Length 表示选择了多少项,I[0]为选择的第 1 项的索引,I[1]为选择的第 2 项 的索引等。ListView1.Items[I[0]].SubItems[0].Text 表示第 1 个选项的第一个子项的 显示内容。  属性 SelectedItems:获取控件中选定项的集合。 用下式得到这个集合: ListView.SelectedListViewItemCollection L=ListView1.SelectedItems; 其中 L.Length 表示选择了多少项,L[0]为选择的第 1 项,L[1]为选择的第 2 项等等。 L[0].SubItems[0].Text 表示第 1 个选项的第一个子项的显示内容。  属性 View:获取或设置项在控件中的显示方式,可以是 Detail(详细资料)、LargeIcon(大 图标)、List(列表)、SmallIcon(小图标)。  方法 Clear:从控件中移除所有项的内容。  方法 DragDropEffects DoDragDrop(object data,DragDropEffects allowEffects): 开始拖放操作,参数 data 是要传送的数据,可以是 String、Bitmap、IDataObject、 Metafile 类的对象,或者是实现序列化的数据等。参数 2 确定哪些拖动操作可以发生, 可以是 DragDropEffects.Copy、All、Link、Move、None、Scroll。  事件 ItemDrag:鼠标开始拖动产生的事件。应用见例子 e6_5。  事件 DragEnter:鼠标进入拖动目的控件产生的事件。  事件 DragDrop:鼠标进入拖动目的控件,并抬起鼠标产生的事件。  事件 SelectedIndexChanged:当列表视图控件中选定的项的索引更改时发生。 6.4.2 在指定驱动器中查找文件 例子 e6_4_2:Windows 操作系统提供了一个查找文件的程序,可以查找指定文件夹中 的指定文件,本例实现了在指定驱动器中查找指定文件。具体实现步骤如下: (1) 新建项目。放 Panel 控件到窗体,属性 Dock=Left。Panel 控件可以把窗体分割为多个部 分,这里将窗体分割为左右两部分。 (2) 放置 Label 控件到 Panel 控件中,属性 Text 为"指定搜索文件的名称"。在 Panel 控件中 增加一个 TextBox 控件,位置在 Label 控件下方,属性 Name=FileBox,属性 Text 为空, 用来输入要搜索的文件名称。 (3) 放置 Label 控件到 Panel 控件中,属性 Text 为"指定在哪个驱动器中搜索"。在 Panel 控 件中增加一个 ComboBox 控件,属性 Name=DriverComBox,属性 Text 为空,属性 DropDownStyle=DropDownList,用来输入要搜索的驱动器。 (4) 为 Form1 类的构造函数增加如下语句: string[] AllDrivers=Directory.GetLogicalDrives();//获得所有逻辑盘符 for(int i=0;i0) { OutFile=new FileStream//如有同名文件,将被删除 (f2+i.ToString()+".fsm",FileMode.Create,FileAccess.Write); OutFile.Write(buffer,0,n); i++; OutFile.Close(); } else mark=false; } inFile.Close(); } 合并文件方法,参数 1 是要合并在一起的文件名(路径包括文件名及扩展名),参数 2 是 拆分的文件名(无扩展名,无序号),文件名后边由合并方法自动增加序号和扩展名 fsm,要 将这些文件合并到参数 1 指定的文件,参数 3 是要合并的文件数。合并方法定义如下: void MergeFile(string f1,string f2,int f2Num) { //如有同名文件,将被删除 FileStream OutFile=new FileStream(f1,FileMode.Create,FileAccess.Write); long n,l; byte[] buffer; for(int i=0;i 这是我的第一个网页 187 以文件名 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 服务管理器图标。出现图 9.12.1A 窗口。 (3) 单击计算机名前的+号,展开文件树。 (4) 右击默认 Web 站点,出现弹出菜单,在弹出快捷菜单中单击"新建"|"虚拟目录"菜单项, 打开向导对话框,按向导步骤选择 D:/asp1 为虚拟目录。此时,在默认 Web 站点下将会 出现设定的虚拟目录(想一想虚拟目录名是在何处设定的),此目录允许其他人访问。如 把文件 Test.htm 拷贝到这个虚拟目录,请读者写出此网页的 URL。 9.2 HTML 标记语言 网页使用 HTML 标记语言写成。HTML 标记是用特殊的 ASCII 字符来定义网页中的格 式,字体等特点。由于各种系统中,都支持 ASCII 字符标准,不同系统中的浏览器都可以 解释这些 ASCII 标记,然后使用本系统方法,将用 HTML 语言标记的网页在屏幕中显示。 这样,一个系统就可以访问其它不同系统中用 HTML 标记的网页。 不经过 Web 服务器,双击静态网页或者在浏览器地址栏中键入网页文件的全路径,也 可用浏览器显示浏览器所在计算机中的静态网页。 9.2.1 HTML 标记 HTML 标记是用特殊的 ASCII 字符来定义网页中的格式、字体、字符对齐方式等特点。 其格式为:<标记名称>被控制的文字。其中,<标记名称>为开始标记,为结束标记,一般用来定义被控制的文字的格式或字体等。例如,下列标记使被控制 的文字中间对齐:
ASP.Net
9.2.2 网页文件结构 一个网页文件的最基本 HTML 标记结构如下: 显示在浏览器标题栏中的文字 这里是网页的内容 188 表示网页文件的开始,表示网页文件的的结束,网页的所有内容都应在 这两个标记之间。…之间可以设定网页的一些信息,网页也允许不包括这个 标记,之间的文字显示在浏览器的标题栏中。…之间是网页 在浏览器中显示的内容。由此可以看出,在 HTML 语法中,HTML 标记可以嵌套,一个 HTML 标记可以包含另一个 HTML 标记,但标记的嵌套不能是交错的。下边是一个实际例子: 这是我的第一个网页
这是我的第一个网页。
其中<BR> 标记表示换行, 注意,仅键入回车是不能在网页中换行的。
注意空格的用法, 键入四个空格,网页中只有一个空格。
增加 4 个空格,     是 4 个空格
网页中不区分大小写,不同浏览器显示的效果可能不同

这是段落标记,下面文字另起一段

注意,特殊符号的显示:<,>,",&,©,®
网页用 IE 浏 览器显示效果如 右图,请注意显示 的内容和原文件 内容排版格式的 不同。 9.2.3 网页使用的语言字符集 网页中如果不指定语言字符集,将使用浏览器所在操作系统的默认语言字符集,为了保 证网页能正确显示信息,最好在网页中指定本网页使用的语言字符集。在网页中指定网页使 用的语言字符集方法见下边的网页,其中 gb2312 是中文字符集。 189 meta 的使用 meta 的使用。 9.2.4 背景和文字颜色及文字边距 设置背景和文字颜色及文字边距的语法如下:。其中 bgcolor 为背景颜色,text 为非超级链接字符颜色,link 为超级 链接字符颜色,alink 为正被点击的超级链接字符颜色,vlink 为已被访问的超级链接字符颜 色,#表示具体颜色,可用 rrggbb 表示,rr、gg、bb 分别是表示红色、绿色、蓝色的 16 进 制数,例如,红色为:ff0000。可以用 topmargin、bottommargin、leftmargin、rightmargin 设 置页面上下左右边距,单位为像素。下例设置背景色为红色,页面左右边距为 20 个像素。 设置背景色为红色,页面左右边距为 20 个像素。 9.2.5 显示一幅图 下例在网页中显示一幅图。注意网页中的 src="file:///D:/ASP/p1.jpg"的写法。其中,file:// 是文件协议,用来选择本地文件。一般网页中用 http://,即超文本协议,用 URL 定位图像 文件。如采用 URL 定位图像文件,所创建网页文件必须保存到 Web 服务器的宿主目录中, 在浏览器地址栏键入网页的 URL,才能访问所创建的网页,显示图像。 增加一幅图形!
9.2.6 超级链接 网页中有些文字带下划线,当鼠标经过带下划线文字时,鼠标变为手形,单击带下划线 文字,将调入和单击处文字有关的网页并显示。这种方法叫超级链接。本节例子用超级链接 方法在当前窗口打开网页 e9_2_5.htm。这里要求网页 e2_2_5.htm 和本网页在同一文件夹中。 这是一个 链接的例子 190 点一下带下划线的文字! 9.2.7 超级链接在新窗口打开另一网页 例子 e9_2_7A:本例使用超级链接在新窗口打开另一网页,网页文件如下: 开一个新窗口! 用浏览器打开网页 e9_2_7A,将光标移到‖开一个新窗口‖处,光标变为手型,单击‖开 一个新窗口‖,将在一个新窗口显示 e9_2_7B.htm 网页。 例子 e9_2_7B:e9_2_7B.htm 网页如下:

这是新开的窗口!

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

这是 1 级标题!

这是 2 级标题!

这是 3 级标题!

这是 4 级标题!

这是 5 级标题!
这是 6 级标题!
其中,标题字体是黑体,每个标题后自动插入一个空行。 9.2.9 网页中正文字体 可以设置网页中正文字体的字号,字号只能是 1 到 7。设置正文字体字号方法见例子: 字号为 7,最大字体 191 字号为 6 字号为 5 字号为 4 字号为 4 字号为 2 字号为 1,最小字体 9.2.10 设置字体风格 可以使在网页中显示的字体为斜体、粗体,为字体增加下划线,删除线。见下例: 标记内的字为黑体
标记内的字为斜体
标记内的字有下划线
标记内的字等宽,例如 w 和 i 等宽
H2 ,2 为下标
A2 ,2 为上标
标记内的字加删除线
标记内的字加删除线
9.2.11 字体标记的组合使用 通过标记的嵌套,可以使网页中的字符具有多种属性,见下例: 今天天气真好! 9.2.12 字体的颜色 可以使网页中的字符具有不同颜色,见下例: 黑色的字符串& 红色的字符串 192 9.2.13 网页中控件的概念 Web 服务器为了和用户进行交互,必须解决如下问题:首先,用户应能输入一些数据, 例如,要查询的条件,用户登录的信息等。第二,这些数据用什么方法传到 Web 服务器。 第三,Web 服务器用哪个程序响应用户。为了实现以上功能,必须使用窗体控件,也叫表 单控件 form。同时在表单控件 form 中还需要一些其它控件,例如,编辑框控件,列表框控 件,下拉列表框控件和按钮等,用来输入数据。可以用 HTML 标记语言标记这些控件。浏 览器看到这些标记,就把它显示为相应的控件。控件有许多属性,也可以用 HTML 标记语 言描述,格式为:属性名称=属性值,两个属性用空格分开。 9.2.14 窗体控件和其它控件的使用 窗体控件(表单控件 form)是其它控件的容器,所有其它控件都要放到窗体控件中。用 HTML 标记表单控件 form 的基本语法如下:
其中,标记
定义表单控件form,action=URL 中的URL是单击"提交"按钮后,对用户输入信息进行处理的网页URL,method="POST"表示用 POST方法将用户输入的数据传到Web服务器,也可以用get方法。传递格式为:Name=Value。 form控件内可以增加交互控件,用户用交互控件输入数据。交互控件的标记语法如下: ,input表示定义一个交互控件,其它各个属性意义如下: Type为控件类型,可以是:type="submit"为提交按钮;type="reset"为全部重写按钮; type="checkbox"为复选框;type="radio"为单选框;type="image"为图形;type="text" 为编辑框;type="password"为口令编辑框;type="hidden"用户不能看到,可以用来传递网 页的一些隐藏的数据。align为控件中文本对齐方式,可以取top、middle和bottom。[check] 只有在type="checkbox"或"radio"时使用,表示缺省被选中。Maxlength属性在type="text" 编辑框时表示最大字符长度。Name属性代表此控件的名字。Size属性在type="text"编辑框 时表示编辑框最大宽度。Value如为按钮,则为标题,为编辑框,则为缺省内容。 在form控件内已定义了两个按钮控件,其中是"提交"按钮控件,因此类型为submit,按钮标题为"提交",控件的name为"B1",当 用户单击此按钮,form控件将把控件内的所有交互控件中的数据用method属性指定方法,传 递给action指定的网页处理。也是一 个按钮,用户单击此按钮,将清空form控件内的所有可输入数据的交互控件。 9.2.15 例子:使用控件的网页 本例请用户用编辑框输入用户姓名、用户主页网址、用户密码,有两个按钮,单击"提 交"按钮,将把用户输入数据提交给网页 e1.htm 处理,e1.htm 网页和本例网页在同一文件 193 夹中。如希望重新填写,可以单击"全部重写"按钮,清空输入控件。网页的具体内容如下: 使用控件的网页例子
您的姓名:
您的主页的网址:
密码:

用记事本程序输入此网页,以 e9_2_15.htm 为 文 件 名 存 入 d:/asp 文 件 夹 , 这 里 已 假 设 d:/asp 文件夹为网站的宿主目 录。用浏览器打开此网页,浏览器 显示效果如右图。 9.2.16 用 FrontPage 做网页的例子 用 FrontPage 或 Dreamewarer 程序编辑网页要比使用记事本编辑网页方便的多,这两个 程序都是所见即所得的可视化网页设计工具。使用 FrontPage 设计网页的具体步骤如下: (1) 运行 FrontPage。单击主菜单"文件"|"新建"|"网页"菜单项。出现"新建"对话框,选择" 常规"选项卡页中的"普通网页",单击"确定"按钮,创建一个新网页。 (2) 单击主菜单"插入"|"表单"|"表单",增加一个表单(form)。在表单中已有"提交"按钮和 "全部重写"两个按钮。将光标移到提交按钮前,键入回车,增加 form 垂直方向尺寸。 (3) 右击"提交"按钮,单击弹出快捷菜单中"表单域属性"菜单项,出现"按钮属性"对话框, 在"名称(N)"编辑框健入按钮名称(Name 属性),"值/标签(V)"编辑框中健入按钮标题, 选择按钮类型为"提交"。退出对话框。用同样方法修改另一按钮的标题为"全部重写"。 (4) 将光标移到第一行,单击 VS.Net 菜单"插入"|"表单"|"复选框"菜单项,在第一行增加 一个复选框。右击复选框,单击弹出的快捷菜单中"表单域属性"菜单项,出现"复选框 属性"对话框,在"名称(N)"编辑框健入"水果 1",初始状态为选中,关闭对话框,在复 选框后键入字符:香蕉。用同样方法增加另外两个复选框:苹果、桔子。 (5) 用 FrontPage 创建的网页文件如下: New Page 2 194

Banana

Apple

Orange

9.3 ASP.Net 技术基础 上节用 Html 标记语言定义了一些标准的控件,例如编辑框控件,列表框控件,下拉列 表框控件和按钮等。但这些控件功能有限,如果要实现复杂的功能,程序员要编写大量的代 码。程序员迫切需要一种设计服务器端动态网页的可视化工具,能象使用 C#语言设计 Window 应用程序一样设计动态网页,使用控件的属性、事件和方法等面向对象的概念。 为了实现这个目的,引入 ASP.Net 服务器端控件概念。ASP.Net 首先引入运行在服务端 WebForm 概念,代替表单(form)。在 WebForm 中可以放入多个服务器端控件,除了列表框、 编辑框、按钮等控件,还包括一些新的功能更加强大的控件,例如 DataGrid,数据验证控件 等。所有这些控件,都是.Net 框架类库中相应的类,有自己的属性、方法和事件,能够完成 复杂的功能,例如自动响应事件,支持数据绑定等。这些概念和编制 Windows 应用程序相 应的概念基本相同。这些 ASP.Net 服务器端控件也使用 Html 语言标记。但由于浏览器并不 能解释这些服务器端控件的 Html 标记,因此并不传送包含这些服务器端控件的网页给浏览 器解释,而是由 Web 服务器负责翻译包含这些服务器端控件的网页为所有浏览器都能解释 的标准 Html 标记的网页,翻译后的网页在浏览器端执行的代码为所有浏览器都能解释的标 准 Jscript 脚本语言代码,传送给浏览器解释或执行。这样就极大地简化了服务器端动态网 页的设计,也保证了生成的网页的显示效果和浏览器无关。使用 ASP.Net 技术创建的服务器 端动态网页,网页文件的扩展名为.aspx。 本节首先介绍 ASP.Net 服务器端控件基本概念,然后介绍使用记事本编写 ASP.Net 动态 网页的方法,最后介绍如何使用 VS.Net 编写 ASP.Net 动态网页。 9.3.1 HTML 服务器端控件 ASP.Net 中的 HTML 服务器端控件和标准的 HTML 控件有着对应关系,但功能更强大。可 以在程序中修改 HTML 服务器端控件的属性,能够在服务器端响应事件,支持数据绑定等。 例如增加一个 HTML 服务器端控件编辑框用如下 HTML 语句: 和标准的 HTML 控件的区别是增加了属性 RUNAT="SERVER"。属性 ID 是代表这个控 件对象的唯一标志,和 Windows 应用程序中的控件属性 Name 的意义是一样的。HTML 服 195 务器端控件是为了方便原来学习 HTML 或 ASP 编写 Web 应用程序的程序员而提供的。如果, 你以前是 Windows 应用程序员,建议使用 Web 服务器端控件,这些控件不但功能更强大, 而使用上更象 Windows 应用程序中的控件,因此学习更方便。因此这里就不介绍 HTML 服 务器端控件了。 9.3.2 Web 服务器端控件 在 ASP.Net 系统中,除了 HTML 服务器端控件外,还包括 Web 服务器端控件。同 HTML 服务器端控件一样,这些控件可以在程序中修改服务器端控件的属性,能够在服务器端响应 事件,支持数据绑定等。例如定义一个 Web 服务器端编辑框控件,方法如下: 这里 asp:TextBox 表示这是一个 Web 服务器端编辑框控件,runat="server"表示运行在 服务器端,属性 id 是代表这个控件的唯一标志,相当于 Windows 应用程序中的控件属性 Name。服务器端控件不但功能更强大,而且和编制 Windows 应用程序中的控件使用方法基 本一致,因此学习更方便。除了提供和标准 HTML 控件功能类似的服务器端控件外,还增 加了一些新的服务器端控件,例如:DataGrid,数据验证控件等。本书的所有 Web 应用程序 例子都使用 Web 服务器端控件。 9.3.3 Web Form 的事件处理 象 Windows 应用程序一样,ASP.Net 应用程序也采用事件驱动的概念,用户对浏览器的 各种操作都被看作事件,事件产生后,Web 应用程序用事件处理函数响应事件。但 ASP.Net 的事件驱动模型和 Windows 应用程序的事件驱动模型有着本质上的区别。Web 应用程序中 的 Web 服务器端控件产生事件后,由于事件处理函数在 Web 服务器端,网页的 WebForm 控件把事件通过网络使用 HTTP 协议由浏览器传到 Web 服务器,在 Web 服务器执行事件处 理函数,把运行结果转变为标准 HTML 标记语言标记的网页,传回浏览器。 在 Web 事件处理机制中,每一次 Web 应用程序响应事件都会使得网页重新生成,即刷新 网页。在服务器完成对某一个网页的处理操作并将它传送至浏览器,随即移除该网页在服务 器端的所有数据,包括 Web 应用程序在服务器端运行时生成的方法,对象,变量,也就是说, 这些定义的对象和变量在服务器端已不存在了。当 Web 应用程序再一次响应事件时,运行在 服务器端的 Web 应用程序又会重新生成这些方法,对象,变量。基于此原因,我们说网页是 无状态的,即网页变量与控件的数据并不会保留在服务器上。 我们增加事件处理函数时,应考虑网络传播的速度的影响,不能象 Windows 应用程序 那样,响应太多的事件。在网页中,每个控件都有属性 AutoPostBack,其值为 true,事件 发生后才能自动调用事件处理函数,如果不希望响应该事件,应将该控件的属性 AutoPostBack 设为 false。 9.3.4 记事本编写 ASP.Net 动态网页 ASP.Net 中的服务器端控件也用 HTML 标记,但这些服务器端控件的 HTML 标记并不传送 给浏览器解释,而是由 Web 服务器负责翻译为所有浏览器都能解释的标准 HTML 标记后,传 送给浏览器解释。所有 ASP.Net 服务器端控件都要放到 Web 窗体(WebForm)中,Web 窗体也 由 Web 服务器负责解释。下边是一个简单的使用 Web 服务器端控件的例子: 196 <%@ Page language="c#" %> 这是我的第一个 ASP.Net 网页
现在的时间是:
网页文件第一条语句表示网页中使用 C#语言。表示网页文件的开始,表 示网页文件的的结束,网页的所有内容都应在这两个标记之间。定义在标记和 之间的内容被分为三部分,第一部分:和之间可以设定网页的一些信息, 之间的文字显示在浏览器的标题栏中。第二部分: 标记之间可以定义网页中使用的方法,变量或对象,language="c#" 表示在此标记之间使用 C#语言编制程序,runat=server 表示在此标记之间定义的方法运行在 Web 服务器端,这里定义了两个方法,方法 Page_Load()是网页每次被重新生成时调用的方 法,包括第一次调用本网页,单击浏览器刷新按钮,以及网页响应事件后,前两项使 IsPostBack=false,后一项使 IsPostBack=true,在此方法中可做一些初始化工作。语句 if(Page.IsPostBack)表示如果网页是由于响应事件后刷新,不执行其后语句。方法 EnterBtn_Click()是"查看时间"按钮的事件处理函数。第三部分:和之间是网 页在浏览器中显示的内容。
标记定义 Web 窗体(WebForm), 注意 runat=server 表示 Web 窗体由 Web 服务器解释。在 Web 窗体中增加了两个控件对象, 第一个是 Label 控件,asp:Label 表示本控件是 Label 控件,id 相当 Windows 应用程序中控件 的 Name 属性,用来区分不同对象,runat=server 表示此控件由 Web 服务器解释,其余是设 定属性值,注意不同属性用空格分隔。第二个控件是按钮,请注意定义单击事件处理函数的 方法。 将网页以文件名 e9_3_4.aspx 存入 d:/asp 文件夹,如果 d:/asp 文件夹已被设定为 Web 站 点,可以在 IE 的地址栏输入 http://localhost/ e9_3_4.aspx,在浏览器中可以看到此网页。在 浏览器端看不到这些代码,Web 服务器已将 ASP.Net 网页翻译为标准的 Html 标记,传递给 浏览器,单击 IE 主菜单"查看"|"源代码"菜单项,可以看到这些用标准 HTML 编制的网页。 197 9.3.5 用 VS.Net 创建 ASP.Net 动态网页 用 VS.Net 实现上节的例子。具体步骤如下: (1) 运行 VS.Net,进入开始界面(图 1.2.2A),单击"新建项目"按钮,打开"新建项目"对话框 (图 1.2.2B),在"项目类型(P) "列表框中选择"Visual C#项目",在"模板(T) "列表框中选 择"ASP.Net Web 应用程序",在"位置(L)"编辑框中键入:http://localhost/e9_3_5, http://localhost 代表要建站点的宿主目录,创建本 Web 应用程序项目的所有文件存入 宿主目录的文件夹 e9_3_5 中,点击确定按钮,生成一个空白窗体(WebForm1)。用户可在 窗体中放入其它服务器端控件。VS.Net 自动建立了一个 Web 应用程序项目。 (2)放置一个 Label 控件到 WebForm 窗体中,该控件用来显示一行文本。设置属性 text 为"现 在的时间是:",在属性窗口中找到 Font 属性,单击 Font 属性左边的"+"号,在出现的 子属性中,可以修改 Label 控件中文本的字体和字号等属性。编辑完成后,单击变成"-" 号的方框隐藏 Font 的子属性。修改 ForeColor 属性,可以修改 Label 中文本的颜色。 (3)放置一个 Button 控件到 WebForm 窗体,按钮的 Text 属性为"查看时间"。为"查看时间" 按钮增加单击(Click)事件处理函数如下: private void Button1_Click(object sender, System.EventArgs e) { Label1.Text="现在的时间是:"+DateTime.Now; } (4)已存在 Page_Load 函数,在 Page_Load 事件处理函数中增加语句: private void Page_Load(object sender, System.EventArgs e) { Label1.Text="现在的时间是:"+DateTime.Now; } (5) 单击工具栏中蓝色箭头按钮,运行,看一下效果。也可用浏览器看一下,在浏览器地址 栏输入地址:http://localhost/e9_3_5/WebForm1.aspx。请仔细观察,每一步骤 VS.Net 都为我们增加了什么语句。 9.3.6 Code Behind 技术 Code Behind 技术把用 HTML 标记语言标记的界面和用 C#语言编写的代码放在不同文件 中。这对于代码的重复使用,程序的调试和维护都是十分方便的。特别是在团队开发中,可 以使不同人员编辑不同文件,极大地提高了效率。下面是使用 Code Behind 技术制作的网页。 例子 e9_3_6.aspx:这是网页界面文件,其中不包括代码,代码在 e9_3_6.aspx.cs 文件 中。语句<%@ Page Inherits="MyPage" src="e9_3_6.aspx.cs" %>中的 src="e9_3_6.aspx.cs"表示 网页的所有 C#语言代码在文件 e9_3_6.aspx.cs 中,Inherits="MyPage"表示此网页的基类。 <%@ Page Inherits="MyPage" src="e9_3_6.aspx.cs" %> 使用 Code Behind 的技术网页

198 例子 e9_3_6.aspx.cs:这个文件是 e9_3_6.aspx 网页中使用的代码。 using System; using System.Web.UI; using System.Web.UI.WebControls; public class MyPage:Page { public Label Label1; public Button Button1; public void EnterBtn_Click(Object src,EventArgs e)//事件处理函数 { Label1.Text="现在的时间是:"+DateTime.Now; } } VS.Net 也使用了 Code Behind 技术,当我们使用 VS.Net 创建了一个 Web 应用程序,将 自动创建了两个文件,ASP.Net 网页界面文件 WebForm1.aspx 和网页中使用的代码文件 WebForm1.aspx.cs,请读者仔细察看这两个网页,看是如何使用 Code Behind 技术。 9.3.7 Web 应用目录 一个网站可能包含多种服务,例如,远程登录服务 Telnet、文件传送服务 FTP、电子邮 件服务 E-mail、电子公告板系统 BBS、万维网 WWW(Web)、电子商务、IP 电话等等。一般情 况下,每种服务可能包含多个网页及各种文件。用 VS.Net 开发,用解决方案(Solution)和 项目(Project)来管理所有的网页和文件。一种服务功能需要的所有网页文件和其它文件组 成一个 Web 项目,Web 项目中的所有网页文件和其它文件被保存到网站宿主目录的子目录中, 该子目录被称作"Web 应用目录"。所有应用项目组成一个解决方案。 如果一个 Web 项目要拷贝到其它计算机中运行,首先要把 Web 项目所在的"Web 应用目 录"拷贝到该计算机宿主目录下。在控制面板中,双击"管理工具"图标,再双击"Internet 服 务管理器"图标,出现对话框(图 9.1.12A)。右击项目所在的目录名,在快捷菜单中选择属性, 出现如下对话框,单击"创建",生成"Web 应用目录"后,网站能被浏览器访问。 199 9.3.8 ASP.Net 和 HTML 兼容 任何一个静态网页只要把其扩展名修改为 aspx,在 ASP.Net 下仍可运行,运行效果和 以前相同。见下例,它是一个普通的静态网页。

学生选课系统

姓名:
学号:
课程:
将网页以文件名 e9_3_7.aspx 存入 d:/asp 文件夹,如果 d:/asp 文件夹已被设定为 Web 站 点,可以在 IE 的地址栏输入:http://localhost/ e9_3_7.aspx 后,看到这个网页。 ASP.Net 的设计目标之一就是尽可能地保持和现有 ASP 页面的语法及运行库的兼容。希 望将现有 ASP 页面文件的扩展名改为.aspx,这些页面仍可以在 ASP.Net 中运行。在大多数 情况下该目标已经实现了,但一般要对某些基本代码作出修改,因为 ASP.Net 已不再支持 VBScript 了,而且 VB 语言本身也发生了变化。 200 9.3.9 网页 Html 标记中使用 C#语句 在网页的 HTML 标记中,可以插入一些 C#语句,具体用法见下例: <% @ Page Language="C#"%>
<% for (int i=0;i<8;i++){ %> 这是我的第一个 ASP.Net 网页
<%}%>
这样使用 C#语句,使程序变得十分难懂,不是一个好的习惯,不建议使用。 习题 (1) 举出一些静态网页、客户端动态网页和服务器端动态网页的例子。 (2) 创建 d:/geng 为网站的虚拟目录,请问如何访问 geng 目录中的网页。 (3) 创建一个链接到新浪网站主页的网页。 (4) 创建一个网页,显示一幅图片,用 URL 定位图片文件位置。 (5) 配置自己的计算机的 TCP/IP 设置,请说出配置中的网关的意义,如何获得网关 IP。 (6) 如何使自己创建的网站和默认的网站并存,请写出访问自己网站的 URL。 (7) 用记事本程序编写一个静态网页,包括两个单选按钮和一个"提交"按钮。 (8) 用 FrontPage 程序实现上题网页。 (9) 用记事本程序编写一个 ASP.Net 网页,包括两个按钮,标题分别为"红色"和"黑色",单击 按钮,将使标签控件字符颜色变为红色或黑色。 (10) 用 VS.Net 程序实现上题网页。 201 第十章 Web 服务器端控件 ASP.Net 提供了两类服务器端控件:Html 服务器端控件和 Web 服务器端控件。由于 Web 服务器端控件功能更强大,和 Windows 应用程序的控件使用方法类似,容易学习,因 此这里只介绍 Web 服务器端控件。Web 服务器端控件包括如下几大类:基本的 ASP.Net 服务器端控件、服务器端数据验证控件、数据列表控件(Repeater、DataList、DataGrid)、 复杂功能控件。本章介绍这些 Web 服务器端控件常用的属性、事件和方法,以及用 Web 服 务器端控件编制服务器端动态网页的方法。由于在 ASP.Net 中数据绑定和 Windows 应用程 序的数据绑定有一些区别,功能有所加强,因此数据绑定也是本章的重点。考虑到本书读 者可能是仅学习过 C 语言和计算机基础的学生,可能对微软的 SQL Server 数据库系统不 熟悉,因此本章例子中使用的数据库是第 8 章中用 ACCESS 数据库系统建立的学生信息系 统数据库 StudentI,该数据库文件的路径为:D:\asp\StudentI.mdb。 10.1 基本的 Web 服务器端控件 基本的 Web 服务器端控件包括:Label、TextBox、Button、LinkButton、ImageButton、 CheckBox、CheckBoxList、RadioButton、RadioButtonList、Image、HyperLink、Table、TableCell、 TableRow、DropDownList、ListBox、Panel。这些控件在网页中用 Html 语言标记,基本的 标记格式为:或者 ,其中 asp:控件名称、runat=server 两项是必须的,表示是 Web 服务器端 控件,由 Web 服务器解释、运行。其它属性是可选的。Web 服务器端控件一些共有的属性 如下:  id:控件名称,相当于 Windows 应用程序中控件的 Name 属性,用来区分不同对象。  forecolor:字符颜色,例如 forecolor ="red"表示字符的颜色为红色。  BackColor:背景色,例如 BackColor="white"表示背景的颜色为白色。  Enabled:布尔变量,为 true 表示控件可以使用,为 false 表示不可用,控件变为灰色。  Visible:布尔变量,为 true 表示控件正常显示,为 false 表示控件不可见。  ToolTip:当鼠标指针停留在控件上时显示的提示文本。  Width:获取或设置 Web 服务器控件的宽度。  Height:获取或设置 Web 服务器控件的高度。  AutoPostBack:布尔变量,指示控件事件是否自动调用在服务器端的事件处理函数。 10.1.1 Label 控件 Label 控件也叫标签控件,用来显示字符串,如提示信息。用 Html 标记 Label 控件的格 式为:,注意属性标记格式为:属性=属性值,属性之间用空格分割。也可采用如 下格式:标签控件显示的字符串 Label 控件常用的属性如下:  Font:该属性指定 Label 控件显示字体风格。此属性包含若干子属性:font-Bold=true 202 表示为黑体、font-Underline=true 表示加下划线、font-Strikeout=true 表示加删除线、 font-Italic=true 表示为斜体、font-Name="字体名字"、font-size="14"为字体大小等。请 注意,在 C#语句中则要使用如下格式:Label1.Font.Underline=true;  Text:Label 控件中显示的文本。 10.1.2 TextBox 控件 TextBox 控件,也叫文本框控件,可用来输入文本。TextBox 控件 Html 标记格式如下: TextBox 控件常用的属性和事件如下:  属性 Text:在 TextBox 控件中输入的文本。  属性 TextMode : TextBox 控 件 的 模 式 。 TextMode=SingleLine 为 单 行 编 辑 框 ; TextMode=MultiLine 为多行编辑框,可以有滚动条;TextMode=PassWord 为口令编辑框。  属性 MaxLength:TextBox 控件中允许输入的最多字符数。  属性 Rows:TextBox 控件为多行编辑框时,该属性有效,表示允许最大行数。  事件 TextChanged:控件中文本发生变化时产生的事件,例如用户输入内容改变。 10.1.3 Button、LinkButton 和 ImageButton 控件 Button 控件为普通按钮,LinkButton 控件为超级链接形式的按钮,ImageButton 控件 为图形按钮。用 Html 标记这 3 个按钮控件的格式如下: 请注意定义按钮单击事件处理函数的方法,这 3 个按钮控件常用的属性和事件如下:  属性 Text:Button 控件是按钮的标题,LinkButton 控件是带有下划线的超级链接字符。 ImageButton 控件无此属性,该控件显示指定图形文件的图形。  属性 ImageUrl:ImageButton 控件图形文件的 URL,最好和网页文件放在同一个目录下。  事件 Click:单击按钮产生的单击(Click)按钮事件,可为单击事件增加事件处理函数。 10.1.4 CheckBox 和 CheckBoxList 控件 CheckBoxList 控件中可以包含若干 CheckBox 多选框控件,可以选中这些多选框中的一 个或多个,也可以选中所有多选框或都一个也不选,可用来表示一些可共存的特性,例如一 个人的爱好。CheckBoxList 控件用如下方法标记: 音乐 文学 CheckBoxList 控件常用的属性和事件如下:  属性 SelectedItem:被选中的索引号最小的 CheckBox 按钮。参见 10.1.6 节例子。  属性 RepeatColumns:获取或设置 CheckBoxList 控件中 CheckBox 多选框显示的列数。  属性 RepeatDirection:多个 CheckBox 多选框是垂直还是水平显示。默认值为 Vertical。 203  属性 Items:该控件中所有 CheckBox 控件的集合,Item[i]表示第 i+1 个 CheckBox 控件。  事件 SelectedIndexChanged:被选中的多选框控件索引号改变时,将引发该事件。 CheckBox 多选框控件常用的属性和事件如下:  属性 Selected:为 true 多选框被选中,否则不被选中。下边例子音乐多选框被选中。  属性 Text: CheckBox 多选框控件的标题。  属性 Value: CheckBox 多选框控件所代表的数值。参见 10.1.6 节例子。  事件 CheckedChanged:用户单击多选框控件,选中或不选中状态改变时,引发的事件。 下面例子在窗口中用 Label 控件显示某人的爱好,有两个 CheckBox 多选框,一个代表 是否爱好音乐,一个代表是否爱好文学,用鼠标单击 CheckBox 多选框,选中或不选中标题 为"音乐"或"文学"多选框,Label 控件显示实际所做的选择。网页文件如下:
音乐 文学 用 VS.Net 集成环境实现具体步骤如下: (1)创建 Web 应用程序新项目。放置 Label 控件到 Web 窗体,其属性 Text="你的爱好是:"。 (2)放置 CheckBoxList 控件到窗体,单击属性 Items 右侧标题为"„" 的按钮,出现"集合编 辑器"对话框。单击"添加"按钮,增加两个 CheckBox 按钮,属性 Text 分别为:音乐、文 学。由于爱好是可以多选的,因此必须用 CheckBox 控件。设定属性 AutoPostBack=true。 (3)为 CheckBoxList 控件 SelectedIndexChanged 事件处理函数增加语句如下: private void CheckBoxList1_SelectedIndexChanged(object sender,System.EventArgs e) { String s="你的爱好是:"; for(int i=0;i<2;i++) { if(CheckBoxList1.Items[i].Selected) s=s+CheckBoxList1.Items[i].Text; } 204 Label1.Text=s; } (4)编译,运行,选中标题为"音乐"的多选框,标签控件显示"你的爱好是:音乐",再选中 标题为"文学"的多选框,标签控件显示"你的爱好是:音乐文学"。 10.1.5 RadioButton 和 RadioButtonList 控件 有一些特性是互斥的,例如性别,选择这些特性可用 RadioButtonList 控件,该控件中 可以生成多个 RadioButton 按钮,但只能选其中一个按钮。该控件用如下方法标记: RadioButtonList 控件常用的属性和 CheckBoxList 控件类似,这就不介绍了。 例子 e10_1_5:两个单选按钮,标题分别为男和女,初始标题为"男"的单选按钮被选中。 用 Label 控件显示选择的结果。网页文件如下:
用 VS.Net 实现的具体步骤如下: (1) 创建 Web 应用程序新项目。放置 Label 控件到 Web 窗体,属性 Text="男"。 (2) 放置 RadioButtonList 控件到窗体,单击属性 Items 右侧标题为"„" 的按钮,出现"集合 编辑器"对话框。单击"添加"按钮,增加 1 个 RadioButton 按钮,属性 Text 为"男", Selected 属性为 true。增加另一个 RadioButton 按钮,Text 属性为"女",Selected 属 性为 false。属性 AutoPostBack=true。 (3) 为 RadioButtonList 控件 SelectedIndexChanged 事件增加事件处理函数如下: private void RadioButtonList1_SelectedIndexChanged 205 (object sender,System.EventArgs e) { if(RadioButtonList1.SelectedIndex==0) Label1.Text="男"; else Label1.Text="女"; } (4) 编译,运行,分别单击 RadioButtonList 中的两个 RadioButton 按钮,标签控件显示所 作的选择。请想一想和 CheckBox 多选框的区别。 10.1.6 Image 控件 Image 控件用来在网页中显示一幅图像,该控件的 Html 标记格式如下: Image 控件常用的属性如下:  AlternateText:字符串类型,如果图像不被正确显示,则显示此字符串。  ImageAlign:图像对齐方式。  ImageUrl:显示在网页中的图像文件的 URL 地址。 例子 e10_1_6:该例有 2 个单选按钮,根据单选按钮哪个被选中,显示不同的图像。用 VS.Net 实现的具体步骤如下: (1) 创建 Web 应用程序新项目。放置 Image 控件到窗体,属性 id= Image1。 (2) 放置 RadioButtonList 控件到窗体。单击属性 Items 后标题为"„" 的按钮,出现"集合编 辑器"对话框,单击"添加"按钮,增加一个 RadioButton 按钮,Text 属性"图 1",Value 属性为"p1.jpg",Selected 属性为 true。增加另一个 RadioButton 按钮,Text 属性为" 图 2",Value 属性为"p2.jpg",Selected 属性为 false。属性 AutoPostBack=true。 (3) 为 RadioButtonList 控件的 SelectedIndexChanged 事件增加事件函数如下: private void RadioButtonList1_SelectedIndexChanged (object sender,System.EventArgs e) { Image1.ImageUrl=RadioButtonList1.SelectedItem.Value; } (4) 为 Page_Load 事件处理函数增加语句: private void Page_Load(object sender, System.EventArgs e) { Image1.ImageUrl=RadioButtonList1.Items[0].Value; } (5) 编译,运行,分别单击 RadioButtonList 中的两个 RadioButton 按钮,将显示不同图像。 10.1.7 HyperLink 控件 HyperLink 控件是超级链接控件,用来从一个网页跳转到另一个网页。用如下方法标记: HyperLink 控件常用的属性如下:  Text:设置的超级链接的文字。  ImageUrl:也可以使用图形完成超级链接,ImageUrl 为控件显示图像文件的 URL。 206  NavigateUrl:超级链接到另一个网页的 URL。  Target:表示打开的网页的位置。为_blank,在一个没有框架的新窗口打开新网页;为 _self,在原窗口打开,为_parent 在父窗口打开。 10.1.8 Table、TableCell 和 TableRow 控件 使用这 3 个控件可以在网页中建立一个表,使用方法如下: 第 0 行,第 0 列 第 0 行,第 1 列 第 1 行,第 0 列 第 1 行,第 1 列 Table 控件常用的属性如下:  BackImageUrl:背景图像文件的 URL。  CellPadding:列字符和列边框之间的间隔(以像素为单位)。默认值为 -1,表示还未设 置该属性。  CellSpacing:列和列之间的间隔(以像素为单位)。默认值为 -1,表示还未设置该属性。  GridLines:指定 Table 控件的网格线型。为 None 不显示网格线;为 Horizontal 仅显 示水平网格线。为 Vertical 仅显示垂直网格线;为 Both 同时显示水平和垂直网格线。  HorizontalAlign:表的每个单元格中的文字的水平对齐方式。为 NotSet,表示尚未设 置水平对齐方式;为 Left,表示左对齐;为 Center,表示居中对齐;为 Right 表示右对 齐。为 Justify 表示同时与页面的左右页边对齐。 该控件不支持数据绑定,在很多情况下,用 DataList 或 DataGrid 控件可完成同样功 能, Table 类主要由控件开发人员使用。用 VS.Net 创建表格的具体步骤如下: (1) 创建 Web 应用程序项目。放置 Table 控件到窗体,单击属性 Row 右侧标题为"„" 的按钮, 出现"选择 TableRow 集合编辑器"对话框,单击"添加"按钮,增加两行(TableRow 对象)。 (2) 选择索引号为 0 的 TableRow,单击属性 Cell 右侧标题为"„" 的按钮,出现"选择 TableCell 集合编辑器"对话框,单击"添加"按钮,增加三列。修改每列的属性 Text, 分别为:课程总论、刚体静力学、弹性静力学。 (3) 选择引号为 1 的 TableRow,用同样的方法增加三列。修改每列的 Text 属性,分别为: 数据结构、计算机组成原理、操作系统。 (4) 运行后,可以看到两行三列的表。 10.1.9 DropDownList 控件 DropDownList 控件是一个下拉列表文本框控件。控件 Html 标记格式如下: 207 白色 黑色 DropDownList 控件常用的属性和事件如下:  属性 SelectedIndex:从下拉列表中选定项的索引。默认值为 0,选择下拉列表第 1 项。  属性 SelectedItem:控件中的选定项。例如上边的例中,标题为"白色"的项被选中,则 有:SelectedItem.Text="白色";SelectedItem.Value="White"。  事件 OnSelectedIndexChanged:选定项被改变时产生的事件。 用记事本程序生成网页的例子可参考例子 e10_3_1。下例用 VS.Net 创建,该例增加一 个 DropDownList 控件用来选择课程,标签控件显示所做的选择,具体步骤如下: (1) 创建 Web 应用程序新项目。放置 DropDownList 控件到窗体。单击属性 Items 右侧标题 为"„" 的按钮,出现"选择 ListItem 集合编辑器"对话框,单击添加按钮,增加三项。 修改每项的 Text 属性分别为:课程总论、刚体静力学、弹性静力学。 (2) 放工具箱的 Label 控件到窗体,id=label1。 (3) 放置 Button 控件到窗体,增加单击按钮事件处理函数如下: private void Button1_Click(object sender, System.EventArgs e) { label1.Text=DropDownList1.SelectedItem.Text; } (4) 运行,从下拉列表选择"刚体静力学",单击按钮控件,标签控件显示"刚体静力学"。 10.1.10 ListBox 控件 ListBox 列表控件的 Html 标记格式如下: 白色 黑色 ListBox 控件常用的属性、事件和 DropDownList 控件基本一致,不相同的如下:  SelectionMode:指定控件的选择模式。为 Multiple 允许多选,为 Single 只能单选。  SelectedValue:等价于 SelectedItem.Value。如果未选定任何项,返回空字符串("")。  Rows:获取或设置 ListBox 控件中显示的行数。 请读者把 10.1.9 节中的例子中 DropDownList 控件改为 ListBox 控件。 10.2 数据验证控件 用户输入了数据,在提交前,首先要验证输入的数据是否正确。当然,可以自己编程序 进行验证。ASP.Net 提供了一些验证控件,可以不用编制验证程序完成对输入数据的验证。 本节介绍如何使用这些数据验证控件。 10.2.1 数据验证概述 对用户输入的数据进行验证,可以在客户端进行。实现原理是当用户输入了信息并单击 提交按钮后,用在客户端运行的 JavaScript 脚本或 VBScript 脚本对数据验证,只有所有数据 208 都正确,才发送数据到服务器端处理。此种方法的优点是程序运行在客户端,因此反应速度 快,减轻了服务器和网络的负载。缺点是由于 JavaScript 脚本或 VBScript 脚本是以明文的方 式嵌入在 HTML 文档中,客户端可以看到这些脚本程序,如果用户把这段脚本删除,网页 也就失去了验证功能,因此这种方法是不安全的。 另一种数据验证方法是在服务器端进行,当用户输入了数据并单击提交按钮后,把数据 立刻发送到服务器端,用服务器端程序验证数据的正确性,如果验证不通过,返回错误信息。 这种方法虽然响应速度比较慢,增加了服务器的负担,但可靠性上要强很多。 ASP.Net 提供了一些验证控件,可以不用编程完成在浏览器端或服务器端对输入数据进 行必要的验证。在浏览器端的验证采用 JavaScript 脚本语言,这些代码被保存到网站宿主目 录下的 aspnet_client 文件夹中。如果修改了宿主目录,请把 aspnet_client 文件夹拷贝到 修改后的宿主目录中。ASP.Net 提供如下一些验证控件:  RequiredFieldValidator:该验证控件检查是否输入了必须输入的数据。  CustomValidator:自定义数据验证规则的验证控件。  CompareValidator:对两个控件输入的值进行比较的验证控件。  RegularExpressionValidator:正则表达式验证控件。  ValidationSummary:集中显示验证错误信息的验证控件。 所有这些验证控件的共有的常用属性如下:  属性 ControlToValidate:被验证控件(可以是文本框,单选按钮或多选框等控件)的 id 值。  属性 ErrorMessage:验证不通过时,显示在控件放置处和 ValidationSummary 控件中的 错误提示信息。如果设置了 Text 属性,将只在 ValidationSummary 控件中显示。  属性 Text:显示在控件放置处的验证提示信息,显示方式由属性 Display 设定。  属性 Display:决定属性 Text 的显示方式,为 None,无提示信息;为 Static,总是显示 提示信息;为 Dynamic,发生错误时显示提示信息。  属性 IsValid:为 True,验证通过,否则不通过。网页也有此属性:Page.IsValid,只有 本网页中所有验证控件的验证都通过,Page.IsValid 才为 true。 10.2.2 RequiredFieldValidator 控件 数据可以用文本框,单选按钮或多选框等控件输入,如果要求用户必须输入指定数据, 可以用控件 RequiredFieldValidator 对这些控件输入的数据进行验证,检查用户是否输入了必 须输入的数据,如果没有输入,则显示提示信息。本控件用 html 标记的格式如下: 下边是一个使用 RequiredFieldValidator 验证控件的简单例子。本例用户用编辑控件 TextBox1 输入姓名,要求必须输入数据。用控件 RequiredFieldValidator 对 TextBox1 控件输 入的数据进行验证,检查用户是否输入了数据,因此属性 ControlToValidate=TextBox1。当 单击提交按钮后,如果用户没有输入姓名,则用"必须输入姓名"提示用户。
姓名:

上边介绍了用记事本编辑网页时如何使用此控件,下边是用 VS.Net 编辑网页使用该控件 的例子。该例增加一个 RadioList 控件,选择卡的类型,增加一个编辑控件,输入卡的编号, 两者都要求必须输入,用两个 RequiredFieldValidator 控件验证。步骤如下: (1) 创建 Web 应用程序新项目。放置 Label 控件到窗体,属性 Text="RequiredFieldValidator 控 件的使用"。放置 Label 控件到窗体,属性 Text="输入卡类型:"。 (2) 放置 RadioButtonList 控件到窗体,属性 id=RadioButtonList1。单击属性 Items 右侧标题 为"„" 的按钮,出现"集合编辑器"对话框。单击"添加"按钮,增加两个 RadioButton 按 钮,Text 属性分别为"苹果卡"、"橡胶卡",Selected 属性都为 false。 (3) 放置 Label 控件到窗体,属性 Text= "输入卡编号"。 (4) 放置 TextBox 控件到窗体,属性 id=TextBox1。 (5) 放置 RequiredFieldValidator 控件到窗体,属性 ControlToValidate= RadioButtonList1, 属性 ErrorMessage="必须输入卡类型",属性 Text="请输入卡类型"。属性 Display=Dynamic。 (6) 放置 RequiredFieldValidator 控 件 到 窗 体 , 属性 ControlToValidate=TextBox1, 属性 ErrorMessage="必须输入卡编号"。属性 Display=Static。 (7) 当用户提交了数据后,所有验证控件对数据进行验证,如果没有错误,自动设置 Page.IsValid=true,否则=false。放置 Button 控件到窗体,属性 Text="提交",为按钮 增加单击事件处理函数如下: private void Button1_Click(object sender, System.EventArgs e) { if(Page.IsValid==true) Label1.Text="已输入了数据"; else Label1.Text="至少有一项未输入数据"; } (8) 把 C:\inetpub\wwwroot\aspnet_client 拷贝到 D:\Asp 文件夹下。运行,如果不输入卡 编号,单击标题为"提交"按钮,显示效果如图 10.2.3,注意,图中增加了 ValidationSummary 控件,本例没有使用这个验证控件。 10.2.3 ValidationSummary 控件 如果希望在固定位置显示所有验证控件的提示信息,可以在页面中放置控件 ValidationSummary,它将自动显示发现错误的数据验证控件的属性 ErrorMessage 的内容。 控件用如下格式标记: 210 其中,属性 DisplayMode 的值为 BulletList,列表显示每个控件的属性 ErrorMessage 的错误提示信息,每个提 示信息占一行,每行前有一个圆点;为 List,显示效果和 BulletList 基本一致, 但每行前无圆点;为 SingleParagraph, 显示错误提示信息在一行中,如图10.2.3 所示。属性 ShowSummary 决定是否显 示错误信息。请读者在上节使用 VS.Net 的 例子中的最后一步,增加一个 ValidationSummary 控 件 , 修 改 属 性 HeaderText=" 标题", 修 改 属 性 DisplayMode 为 SingleParagraph。运行 后,如果不输入卡编号,单击标题为" 提交"按钮,显示效果如图 10.2.3。 图 10.2.3 10.2.4 自定义数据验证控件 CustomValidator CustomValidator 控件允许编程者自己定义一个函数对数据进行验证。控件定义如下: 一般数据验证分为浏览器端验证和服务器端验证,因此,编程者要根据在哪一端验证, 编写不同的函数。如果仅在服务器端验证,属性 OnServerValidate 为编写的服务器端验证函 数名,而属性 ClientValidationFunction 不设定任何值。如在浏览器端验证,在创建浏览器端 验证函数时,一定要同时创建服务器端验证函数,否则恶意代码就有可能使验证通过。 例子 e10_2_4:本例请用户输入密码,用 CustomValidator 控件在浏览器端验证,由于 在浏览器端验证函数中出现了密码,因此本例采用浏览器端验证是不合适的,这里只是为说 明如何实现浏览器端验证。运行效果如下图。网页文件如下:



用VS.Net实现上例,这里只用服务器端验证,具体步骤如下: (1)创建 Web 应用程序新项目。放置 Label 控件到窗体,属性 Text="键入密码"。 (2)放置 TextBox 控件到窗体, 属性 id=TextBox1,属性 Text=""。 (3)放置 CustomValidator 控件到窗体,属性 id=CustomValidator1,属性 ControlToValidate 为 TextBox1, 属性 ErrorMessage="密码错误!"。 (4)放置 Button 控件到窗体,为其增加单击事件处理函数如下: void Button1_OnClick(object sender, EventArgs e) { if (Page.IsValid) label1.Text = "密码通过"; else label1.Text ="密码不通过"; } (5)为 CustomValidator 控件 ServerValidate 事件增加事件函数如下: private void CustomValidator1_ServerValidate(object source, System.Web.UI.WebControls.ServerValidateEventArgs args) { if(args.Value=="12345") args.IsValid = true; else args.IsValid = false; } (6)运行,输入正确密码和不正确密码,看一下运行效果。如果不输入任何信息,也能验证 通过,因此,必须用控件 RequiredFieldValidator 检查用户是否输入了密码。 212 10.2.5 CompareValidator 控件 CompareValidator 控件可以对两个控件输入的值进行比较。控件 Html 标记格式如下: 其中,属性 Type 为比较的数据类型,可以是 Currency、Date、Time、Double、Integer、 String 等。属性 Operator 为比较运算符,可以是:Equal、NotEqual、GreatThan、LessThan、 GreatThanEqual、LessThanEqual。比较 2 个文本框输入,用 VS.Net 实现的步骤如下: (1) 新建一个 Web 应用程序项目。放三个 Label 控件到窗体,其属性 Text 分别为"要比较的第 1 个数"、"要比较的第 2 个数"、"比较结果"。id 分别为 Label1、Label2、label3。 (2) 在 Label1 和 Label2 控件后,分别放置 TextBox 控件,id 分别为 TextBox1 和 TextBox2。 (3) 放置 CompareValidator 控件到窗体, id=Compare1,属性 ControlToValidate=TextBox1, 属性 ControlToCompare=TextBox2,属性 ErrorMessage="比较结果不正确"。 (4) 放置 ListBox 控件到窗体,id 为 ListOperator。单击属性 Items 右侧标题为"„" 的按钮, 在"ListItem 编辑器"对话框中单击"添加"按钮,增加 6 项,属性 Text 分别为:Equal、 NotEqual、GreatrThan、GreatrThanEqual、LessThan、LessThanEqual。 (5) 放置 ListBox 控件到窗体,id 为 ListType。单击属性 Items 右侧标题为"„" 的按钮,在 "ListItem 编辑器"对话框中单击"添加"按钮,增加 5 项,属性 Text 分别为:String、 Integer、Double、Date、Currency。 (6) 设置两个 ListBox 控件属性 SelectionMode 为 Single,不允许多选。 (7) 设置两个 ListBox 控件属性 AutoPostBack=true。 (8) 为 ListOperator 控件的事件 SelectedIndexChenged 增加事件处理函数如下: private void ListOperator_SelectedIndexChanged(object sender, System.EventArgs e) { Compare1.Operator=(ValidationCompareOperator)ListOperator.SelectedIndex; Compare1.Validate();//对被验证的输入控件执行验证并更新IsValid 属性。 } (9) 为 ListType 控件的事件 SelectedIndexChenged 增加事件处理函数如下: private void ListType_SelectedIndexChanged(object sender, System.EventArgs e) { Compare1.Type=(ValidationDataType)ListType.SelectedIndex; Compare1.Validate(); } (10) 放置 Button 控件到窗体,为按钮控件增加单击事件处理函数如下: private void Button1_Click(object sender, System.EventArgs e) { if(Page.IsValid==true) label3.Text="数据验证正确"; else label3.Text="数据验证不正确"; } (11) 运行,ListOperator 列表框中选择 Equal,ListType 列表框中选择 String,在两个编辑 框中分别输入相同或不相同的字符串,单击按钮,看一下效果。 213 10.2.6 RangeValidator 控件 RangeValidator 控件检查被验证控件输入的值是否在指定范围内。控件标记格式如下: 其中 MinimumValue 和 MaximumValue 属性指定允许输入的最大值和最小值。Type 属性 用于指定要比较值的数据类型。在执行验证操作之前,要比较的值被转换为此数据类型。可 以进行比较的数据类型是: String、Integer、Double、Date、Currency。 下面的示例说明如何在 Web 页上增加 RangeValidator 控件,以检查文本框控件中输 入的数值是否在指定范围内。运行效果如右图。
键入一个 1 到 10 的数:


10.2.7 RegularExpressionValidator 控件 RegularExpressionValidator 控件也叫正则表达式验证控件,该 控件用来检查被验证控件输 入的数据是否匹配指定的验证正则表达式。这类验证允许您检查可预知的字符序列,比如身 份证号码、电子邮件地址、电话号码和邮编中的字符序列。本控件定义如下: 214 本控件依据正则表达式进行验证,因此首先讲解一些正则表达式的基本知识,然后介绍 如何使用正则表达式验证控件。正则表达式是由普通字符(例如 a-z)和特殊字符组成的字符 串模板,用这个字符模板与所要验证的字符串进行比较,查看是否匹配,若匹配则通过验证。 1. 正则表达式中的普通字符 正则表达式中包括一些要求匹配的普通字符,普通字符包括可打印字符和不可打印字符 两种。打印字符包含 a-z、A-Z、0-9 以及所有标点符号。常用的不可打印字符意义如下:  \n、\r、\f、\t、\v:分别匹配换行符、回车符、换页符、制表符、垂直制表符。  \s:匹配任何空白字符,可以是空格、换行符、回车符、换页符、制表符。等价于[\f\n\r\t\v]。  \S:匹配任何非空白字符。等价于[^\f\n\r\t\v]。不包括\n、\r、\f、\t、\v 的其它任意字符。  \w:匹配任何单词字符,可以是字母、数字和下划线等字符。等价于[a-zA-Z0-9_]。  \W:匹配任何非单词字符,不包括字母、数字和下划线的其它字符。即[^a-zA-Z0-9_]。  \b:匹配单词的结尾,例如:正则表达式"ve\b"匹配"have",但不匹配"very"。  \B:匹配单词的开头,例如:正则表达式"ve\B"匹配"very",但不匹配"have"。  \d:匹配一个数字字符,等价于[0-9]。例如:"bc\dd"匹配"bc2d"、"bc9d"等,不匹配"bcad"。  \D:匹配一个非数字字符,等价于[^0-9]。例如:"bc\Dde"匹配"bcade",不匹配"bc2de"。 2. 正则表达式中的特殊字符 正则表达式中包括一些特殊字符,特殊字符在正则表达式中表示匹配的一些特殊的含 义。正则表达式中要匹配这些特殊字符,例如要匹配字符*,必须使用格式:\*。  ^:头匹配字符,例如正则表达式"^ab"和"abxyZ"是匹配的,和"xyZab"是不匹配的。  $:尾匹配字符,例如:正则表达式"ab$"和"xyab"是匹配的,和"abxy"是不匹配的。  *:例如"ab*c"表示在"b*"处可以没有或有多个 b,因此和"ac"、"abc"、"abbc"匹配。  +:例如 ab+c 表示在 b+处可以有 1 个或多个 b,因此和"abc"、"abbc"、"abbbc"匹配。  .:匹配除换行符(\n)之外的所有字符,例如:(.)+表示不包括换行符的任意字符串。  ?:例如"\-?1"表示在"\-?"处可以没有或有 1 个问号前的字符,即可以为 1 或-1。  |:例如"abc|xyz"和"abc"或"xyz"匹配,"ab(c|x)yz"和"abcyz"或"abxyz"匹配。  {n}:匹配 n 次,n 为非负整数。例如"a{2}"和"aa"匹配,和"a"、"aaa"、"ab"不匹配。  {n,}:至少匹配 n 次(n 为非负整数)。例如"a{2,}"和"aa"、"aaa"匹配,和"a"、"ab"不匹配。  {m,n}:匹配 m 到 n 次,m 和 n 为非负整数。例如"a{2,3}"和"aa"、"aaa"匹配。  [若干字符]:例如[ACab],表示只能是以下字符:"A"、"C"、"a"、"b"。  [^若干字符]:当在方括号里使用^时,它表示"非"或"排除"的意思,常常用来剔除某些 字符。例如:[^ACab],表示不是"A"、"C"、"a"、"b"字符的其它字符,符合匹配要求。  [a-z]和[A-Z]:[a-z]匹配所有的小写字母。[A-Z]匹配所有的大写字母。[a-zA-Z]匹 配所有字母。[0-9]匹配所有的数字。[0-9\.\-] 匹配所有的数字,句号和减号。  [^m-o]:匹配所有不包括"m"、"n"、"o"字符的字符。 3. 正则表达式中的优先级 优先级分为 4 级,按优先级顺序运算,同级优先级从左到右运算。最高优先级:\;2 级优先级:()、[];3 级优先级:*、+、?、{n}、{n,}、{m,n};最低级:^、$。 下面是常用的一些正则表达式:  "^[0-9]*$"只能输入数字。"^\d{5}$"只能输入 5 位数字。  "^\d{2,}$"只能输入至少 2 位数字。"^\d{2,5}$"只能输入 2 位到 5 位数字。  "^(0|[1-9][0-9]*)$"只能输入 0 或非 0 开头的数字。  "^[0-9]+(.[0-9]{1,3})?$"只能输入有 1 位到 3 位小数的正实数。  "^\+?[1-9][0-9]*$"只能输入非 0 正整数。"^\-?[1-9][0-9]*$"只能输入非 0 整数。  "^.{3}$"匹配非换行符的 3 个字符。"^[A-Za-z]+$" "匹配由 26 个字母组成的字符串。 215  "^12xY$"只能匹配字符串"12xY"。"^[A-Za-z]\w{5,7}$"只能输入由 26 个字母开头,仅 包含字符、数字、下划线,长度为 6 到 8 位组成的字符串。  "^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$"验证 E_Mail 地址。  "^http://([\w-]+\.)+[\w-]+(/[\w-./?%&=]*)?$"验证因特网 URL。  "^(\(\d{3,4}\)|\d{3,4}-)?\d{7,8}$"验证电话号码。  "^\d{15}|\d{18}$"验证 15 位获 18 位身份证号。 下例说明如何使用 RegularExpressionValidator 验证一个 6 位数的邮编。
键入邮政编码


如用 VS.Net 实现,只需修改属性 ValidationExpress 即可。 10.3 Web 服务器端控件数据绑定 所谓数据绑定技术是把数据集的某个或某些数据与控件的某些能够显示的属性绑定在 一起的技术,这些控件可以是 Label 控件、ListBox 控件、DataGrid 控件等,当这些控件完 成数据绑定后,这些被绑定的属性显示的值将随着数据集中被绑定的数据变化而变化。 ASP.Net 引入了新的数据绑定语法。这种语法不仅允许控件属性绑定到 DataTable、 DataView;还可以绑定到实现了 Icollection 接口的集合,例如 ArrayList、数组、哈希表、堆 栈、队列;甚至可以绑定到其它简单属性、表达式和方法调用返回的结果。 10.3.1 绑定到其它控件属性 DataBind 是页和所有服务器控件都有的方法。当需要更新被绑定的数据时,必须调用 216 此方法。当在父控件上调用 DataBind 方法时,该控件的所有子控件也同时调用自己的 DataBind 方法。例如,在调用页的 DataBind 方法,既 Page.DataBind(),会导致调用页上 的所有控件的 DataBind 方法,更新页上所有绑定数据。下面的示例说明如何将一个服务器 控件的属性绑定到另一个服务器控件的属性。

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

课程总论 刚体静力学 弹性静力学

选定的课程: 网页中语句 text='<%# StateList.SelectedItem.Text %>'是将 Label 控件的 Text 属性绑定到 DropDownList 控件的属性 StateList.SelectedItem.Text。符号%#表示数据绑定。按钮单击事件 处理函数 SubmitBtn_Click 中的语句 Page.DataBind()更新页内的所有被绑定的数据。因此如 果从下拉列表中选中某课程,单击按钮,标签控件显示所作的选择。使用 VS.Net 实现上例 的具体步骤如下: (1) 新建 Web 应用程序项目。放 DrowDownList 控件到窗体。单击属性 Items 右侧标题为"„" 的按钮,出现"选择 ListItem 集合编辑器"对话框,单击"添加"按钮,为控件下拉列表 增加三项。修改每项的属性 Text,分别为:课程总论、刚体静力学、弹性静力学。 (2) 放 Button 控件到窗体,单击按钮事件处理函数如下: private void Button1_Click(object sender, System.EventArgs e) { Page.DataBind();} (3) 放 Label 控件到窗体,id 为 Label1。单击属性 DataBinding 右侧标题为"„" 的按钮,打 开"Label1 数据绑定"对话框,选中"自定义绑定表达式(c)"单选按钮,在单选按钮下边 编辑框中输入:DropDownList1.SelectedItem.Text。单击"确定"按钮退出对话框。 (4) 运行,从下拉列表中选中某课程,单击按钮,标签控件显示所选的课程。 10.3.2 绑定到变量 ASP.Net 数据绑定语法支持绑定到公共变量、页的属性和页上其他控件的属性。下面的 示例说明如何绑定到变量和页上的简单属性。注意这些值在 DataBind()调用前初始化。 217

页属性和公共变量的数据绑定

绑定到公共变量:<%#custID%>
绑定到属性:<%#orderCount%>
10.3.3 绑定到函数返回值 下例说明如何绑定到函数返回值。在编辑框中输入数字,单击按钮判断该数的奇偶。

绑定到函数返回值


runat=server />
218 10.3.4 绑定到集合类对象 像 DataGrid、ListBox、DrowDownList 这样的服务器控件的列表都可以绑定到公共语言 运行库的集合类对象,如 ArrayList、Hashtable 等。下面的示例说明如何将 DrowDownList 控件的下拉列表绑定到 ArrayList 类对象。单击按钮,标签将显示所作的选择。

DropDownList 控件下拉列表绑定到 ArrayList 类对象



请读者将 ArrayList 类对象绑定到 ListBox 控件的列表。绑定到哈希表(Hashtable)的 例子见例子 e10_5_1。 10.3.5 绑定到数据库表 下 面 介 绍 如 何 将 数 据 库 表 绑 定 到 ListBox 、 DrowDownList 、 RadioButtonList 、 CheckBoxList 的方法。这些控件和绑定数据库表有关的属性有 3 个:  DataSource:指定要绑定的数据库表的视图。  DataTextField:此属性绑定到 DataSource 指定的数据库表的某字段。这些数据将出现 在 ListBox 和 DrowDownList 的列表中,或作为 RadioButtonList 和 CheckBoxList 的标题。  DataValueField:此属性绑定到 DataSource 指定的数据库表的某字段。 下例将学生信息库 SdudentI 的学生情况表 Student 中的学生姓名和学号字段绑定到 DrowDownList 控件的下拉列表中,网页文件如下: <%@ Import namespace="System.Data" %> <%@ Import Namespace="System.Data.OleDb" %> 219

数据库表绑定到 DrowDownList



请读者将学生情况表 Student 的学生姓名和 学号字段绑定到 RadioButtonList、CheckBoxList 和 ListBox 控件。请注意控件属性 DataSource 应赋值为 ds.Tables["Student"]。 图 10.3.5 10.4 Repeater 控件 Repeater 控件是一个简单的模板控件,它没有内置的布局或样式,因此必须在此控件的 模板内由网页设计者声明所有的 HTML 布局、格式设置和样式标记。它可以绑定到数据源, 按照模板中设定的可视化界面,显示数据源中的所有数据。Repeater 控件没有可视化设计方 法,只能用 Html 标记语言标记。本节介绍 Repeater 控件的使用方法。 220 10.4.1 Repeater 控件概述 Repeater 控件有 5 个模板,使用 Repeater 控件至少要定义 ItemTemplate 模板,其它模 板可以根据需要增加。Repeater 控件以及 5 个模板的 Html 标记格式如下: 其中 ItemTemplate 和 AlternatingItemTemplate 模板可以绑定数据源,这两个模板一般称 作 Repeater 控件数据显示模板,其它 3 个模板不能绑定数据源。5 个模板的用途如下:  HeaderTemplate:定义头部显示的内容和布局。如果没有定义,则不显示任何内容。  ItemTemplate:定义要显示的数据和布局。此数据显示模板为必选,可以绑定数据源。  AlternatingItemTemplate:数据显示模板要重复使用显示数据,本属性是重复次数(从 零开始)为奇数时的数据显示模板。如无定义使用 ItemTemplate。此模板可绑定数据源。  SeparatorTemplate:数据显示模板重复使用显示数据,该属性定义两次用数据显示模 板显示的数据之间的分隔符。如果未定义,则不呈现分隔符。  FooterTemplate:定义底部的显示内容和布局。如果没有定义,则不显示任何内容。 该控件首先按照 HeaderTemplate 模板显示头部内容。然后按照模板 ItemTemplate 和 AlternatingItemTemplate 显示绑定的数据,要重复多次,直到把绑定的数据显示完。模板 SeparatorTemplate 为两组数据之间增加分隔符。最后按照模板 FooterTemplate 显示底部内容。 Repeater 控件常用属性、事件和方法如下:  属性 Items:数据显示模板重复使用,建立多个 RepeaterItem 对象记录按照数据显示模 板显示的数据,该只读属性是 Repeater 控件中 RepeaterItem 对象的集合。见例 e10_4_4。  属性 DataSource:要绑定的数据源。如数据源是 DataSet,必须指定属性 DataMember。 其它数据源,例如 DataTable、DataView、ArrayList 等,不必指定属性 DataMember,  属性 DataMember:如数据源是 DataSet,该属性为 DataSet 中的表名。  属性 Controls:数据显示模板中可以放置控件,RepeaterItem 对象记录这些控件,该属 性是 RepeaterItem 对象中子控件的集合。使用方法见例 e10_4_4。又见下例: if (Repeater1.Items.Count>0)//如果 Items 项数>0 { Label1.Text="Repeater 控件 Items 第 1 列包含的所有控件名称如下:
"; foreach(RepeaterItem item in Repeater1.Items) { Label1.Text+=((DataBoundLiteralControl)item.Controls[0]).Text+"br"; } }  事件 ItemCommand:单击 Repeater 控件中的按钮时发生。例子见 10.4.4 节。  方法 DataBind:该方法使控件及子控件用绑定的数据源更新数据,并用指定格式显示。 例子 e10_4_1:下面的示例说明如何将 Repeater 控件绑定到 ArrayList 类对象,本例只 使用了 ItemTemplate 模板。网页文件如下:

Repeater 控件绑定到 ArrayList 类对象

<%#Container.DataItem%>
运行后显示效果如右图,因为 Citys 集合中有两个元素,所以显示了两行。 10.4.2 用 Repeater 控件显示数据库表 例子 e10_4_2:下面的示例说明如何将 Repeater 控件绑定到 StudentI 数据库的 Student 表。网页文件如下: <%@ Import namespace="System.Data" %> <%@ Import Namespace="System.Data.OleDb" %>
222 学生情况表
学 号 姓 名 性 别
<%# DataBinder.Eval(Container.DataItem, "StudentNum") %> <%# DataBinder.Eval(Container.DataItem, "StudentName")%> <%# DataBinder.Eval(Container.DataItem, "StudentSex")%>
此网页把学生情况表用表的形式显示出来,显示效果如下图。用 Html 语言标记一个表 采用如下格式:
第 1 行第 1 列第 1 行第 2 列第 1 行第 3 列
第 2 行第 1 列第 2 行第 2 列第 2 行第 3 列
请注意 Repeater 控件是如何使用表的 Html 标记的。 Repeater 控件是唯一可以把 Html 标记分开的控件(例如此 例 Table 标记)。一般数据表格为了增加可读性,把两个记 录之间用分隔符分开,将下边的标记放到上边网页的适当 位置,在两个记录之间用虚线分开。 ------------ ------------ ----------- 也可以像定义 ItemTemplate 模板一样定义 AlternatingItemTemplate 模板,这样奇数行 将按照 AlternatingItemTemplate 模板定义的模式显示。 10.4.3 DataBinder.Eval 方法 上例用到了方法 DataBinder.Eval,它有三个参数,第一个参数是数据源的当前记录,在 像 DataList、DataGrid 或 Repeater 这样的模板控件中,该参数始终是 Container.DataItem, 第二个参数是数据表字段名,第三个参数为格式字符串,例如"{0:c}"表示货币类型、 223 "{0:N2}"表示整型、"{0:d}"表示时间类型。该方法把数据库表当前记录(参数 1 指定)中某 字段(参数 2 指定)数据转换为参数 3 指定数据类型的字符串。参数 3 是可选的。如果省略它, 则 DataBinder.Eval 将此字段的数据转换为字段本身的数据类型的字符串。具体的实例如下: <%@ 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")%> 布尔变量:
显示效果如右图:请注意, 不同类型的不同显示格式。 224 10.4.4 Repeater 控件事件 ItemCommand 从上节的例子中可以看出,可以在 Repeater 控件模板 ItemTemplate 中放置服务器端控 件。如果放置按钮控件,应能响应事件,由于按钮是自动生成的,分别为每一个按钮增加事 件处理函数是不可能的。用户单击 Repeater 控件中的任何按钮,都会产生 ItemCommand 事 件,用 ItemCommand 事件处理函数的参数 2 可以判断单击了哪个按钮:产生事件的按钮对 象的 id 为((Button)e.CommandSource);通过 e.CommandName 也可以区分不同按钮; Repeater1.Items[e.Item.ItemIndex]可得到与该事件关联的 RepeaterItem 对象。 例 e10_4_4:用 Repeater1 控件显示学生情况表 Student,将 Student 表的学号列变为 按钮列,标题为学生学号,单击此按钮,Repeater2 控件显示学号为按钮标题的学生成绩。 <%@ Import namespace="System.Data" %> <%@ Import Namespace="System.Data.OleDb" %>
<%# DataBinder.Eval(Container.DataItem,"StudentName")%> <%# DataBinder.Eval(Container.DataItem,"StudentSex")%>
<%# DataBinder.Eval(Container.DataItem, "ClassName")%> <%# DataBinder.Eval(Container.DataItem, "Score")%>
运行,按钮列的标题为学生学号,单击标题为"2"的按 钮,Repeater2 控件显示学号为 2 的学生的成绩。运行效果 如右图。Repeater2 也可以不用视图作为数据源,直接用 Sql 语句选择指定学生的学习成绩,这个请读者完成。 226 10.5 DataList 控件 DataList 控件也是使用模板来显示被绑定数据源数据的模板控件,它除了具有 Repeater 控件的大部分功能外,还提供了更多的模板以及各种模板的样式,增加了一些属性,例如控 制控件布局属性、与检索有关的属性等,提供了更多的事件,使控件的功能更加强大。该控 件支持可视化设计,也为编程提供了便利。 10.5.1 DataList 控件概述 DataList 控件有 7 个模板,使用 DataList 控件至少定义 ItemTemplate 模板,其它模板 可以根据需要增加。用 Html 标记 DataList 控件模板的格式和 Repeater 控件相同,只是多了 模板 EditItemTemplate 和 SelectedItemTemplate。2 个模板用途如下:  EditItemTemplate:如果定义了该模板,DataList 中当前要编辑的项将按照模板布局显 示数据。如果未定义,则使用 ItemTemplate 模板。可以绑定数据源。  SelectedItemTemplate:如果定义了该模板,DataList 中当前选定项将按照模板布局显 示数据。如果未定义,则使用 ItemTemplate 模板。可以绑定数据源。 DataList 控件常用属性和事件、方法如下,这里只介绍和 Repeater 控件不同部分,相同 部分参见 Repeater 控件。  属性 RepeatDirection:指定显示方向。Vertical 表示垂直显示方向;Horizontal 表示水平 显示方向。默认值为 Vertical。如果有 2 行 3 列,下面显示两种显示方向的不同: 1 2 3 1 3 5 4 5 6 2 4 6 水平显示方向 垂直显示方向  属性 RepeatColumns:获取或设置要在 DataList 控件中显示的列数。  属性 ShowFooter 和 ShowHeader:布尔变量,显示或隐藏页脚或页眉。  属性 RepeatLayout :为 RepeatLayout.Flow 将 以 流 布 局 方 式 显 示 DataList 。为 RepeatLayout.Table 将以表的形式显示 DataList。  属性 SelectedIndex:获取或设置 DataList 控件中选定项的索引。若不选定任何项,请 将 SelectedIndex 属性设置为-1。  属性 SelectedItem :获取 DataList 控件中的选定项。如果 ItemTemplate 模板中包括一 个 Label 控件,其 id 值为 Label1,用如下语句找到这个控件: ((Label)DataList1.SelectedItem.FindControl("Label1"));  模板样式属性 AlternatingItemStyle、EditItemStyle、FooterStyle、HeaderStyle、ItemStyle、 SelectedItemStyle、SeparatorStyle。控件外观的属性很多,这里不一一列举了,见下例: 其中定义了控件 DataList 的背景为白色,边界为黑实线,宽度为 1 个象素,字体为 227 "x-small",字体名字为"verdana"。奇数行的背景颜色,数据项的背景色等,页眉中的 字的颜色,背景色。。  除了支持事件 ItemCommand,还支持事件 CancelCommand(单击 Cancel 按钮时发生)、 DeleteCommand(单击 Delete 按钮时发生)、EditCommand(单击 Edit 按钮时发生)、 SelectedIndexChanged (选择了不同的项时发生)。按钮属性 CommandName 决定产生哪 一个事件,例如:CommandName="Cancel",产生 CancelCommand 事件。这些事件参 数的意义和事件 ItemCommand 参数意义相同。 例子 e10_5_1:将 e10_4_1 网页文件中的 Repeater 改为 DataList,其显示效果不变。下 例说明如何将 DataList 控件绑定到 Hashtable 类对象,本例只使用了 ItemTemplate 模板。网 页文件如下:

DataList 到哈希表的数据绑定

<%# ((DictionaryEntry)Container.DataItem).Key %> : <%# ((DictionaryEntry)Container.DataItem).Value %>
10.5.2 使用模板 SelectedItemTemplate 例子 e10_5_2:该例要求未选中行只显示 Student 表中的学生姓名和按钮。单击按钮, 将显示该按钮所在行学生的详细信息。 网页文件在 ItemTemplate 模板中绑定 Student 表中的学生姓名字段,并增加了 1 列按钮。 在 DataList 控件的 Html 标记中声明了按钮单击事件处理函数名称:OnItemCommand ="DataList_ItemCommand"。在 script 标记中定义了事件处理函数 DataList_ItemCommand, 单击按钮,将自动调用这个事件处理函数,将使当前选定行使用 SelectedItemTemplate 模 228 板显示数据,即该行学生的详细信息。网页文件如下: <%@ Import Namespace="System.Data" %> <%@ Import Namespace="System.Data.OleDb" %>
学生情况表 姓名:<%# ((DataRowView)Container.DataItem)["StudentName"] %> 学号:<%# ((DataRowView)Container.DataItem)["StudentNum"] %> 姓名:<%# ((DataRowView)Container.DataItem)["StudentName"] %> 性别:<%# ((DataRowView)Container.DataItem)["StudentSex"] %> 229
运行,显示效果如右图,图中选中了第 1 行。其实这样显示的效果并不好。本例可以这 样处理,用 DataList 显示学号和姓名,并增加 一个标题为"详细资料"的按钮,单击按钮,在 其右侧显示该学生的详细资料,再用一个 Repter 控件显示该学生的所有课程成绩,请读 者自己实现此功能。 10.5.3 使用模板 EditItemTemplate 例子 e10_5_3:该例用网页把学生情况表用名片的形式显示出来,并增加一个按钮,单 击按钮,提供 TextBox 控件修改姓名,提供 DropDownList 修改性别,不允许修改学号,并 能删除该记录,保存或忽略所作修改。运行效果见下图。 网页文件在 ItemTemplate 模板中绑定 Student 表中的所有字段,并增加了一例按钮,按 钮的 CommandName 为"Edit",单击"Edit"按钮,产生 OnEditCommand 事件,在 DataList 控件 Html 标记中声明了该事件处理函数名称:OnEditCommand="Edit_Command"。在 script 标记 中定义了 OnEditCommand 事件处理函数 Edit_Command,单击按钮,将自动调用这个事件处 理函数,将使当前选定项使用 EditItemTemplate 模板显示数据。在 EditItemTemplate 模板 中,StudentNum 字段绑定到 Label 控件的 Text 属性,因此不能被修改;StudentName 字段 绑定到 TextBox 控件的 Text 属性;StudentSex 字段绑定到 DropDownList 控件的 Text 属性。 同时定义了 3 个按钮,标题分别为:"保存修改"、"删除记录"、"忽略修改",按钮 CommandName 属性分别为:"Update"、"Delete"、"Cancel";在 DataList 控件 Html 标记中声明了 3 个按 钮的单击事件处理函数名称分别是:Update_Command、Delete_Command、Cancel_Command, 在 script 标记中定义了这些事件处理函数。 编辑完成后,如果单击"保存修改"或"删除记录"按钮,应保存所作修改。可以把所作的 修改用 SQL 语句直接写回源数据库,再一次执行 DataList1.DataBind()语句,将显示新数 据。这样做的缺点是要频繁和数据库建立连接。另一个办法是保存到 Web 服务器端 DataSet 对象中。但是 Web 服务器完成网页的处理操作并将它传送至浏览器后,随即移除该网页在服 务器端的所有信息,包括网页中定义的 DataSet 对象的引用 ds,引用不存在,垃圾收集器将 清除 DataSet 对象。在第 11 章中将介绍 Session 对象,当浏览器进入网站访问网站的第一个 网页时,Web 服务器将自动创建该用户 Session 对象,在 Session 对象中可以建立一些变量, 这个 Session 对象和 Session 对象中的变量只能被这个访问者使用,其它访问者不能使用。 当用户在网站的网页之间跳转时,Session 对象和存储在 Session 对象中的变量不会被清除, 这些变量始终存在。当浏览器离开本网站或超过一定时间和网站没有联系,Session 对象被 撤销,同时存储在 Session 中的变量也不存在了。因此可以把 DataSet 对象的引用 ds 存到 Session 对 象 中 , 这 样 DataSet 对 象 就 可 以 保 留 了 。 Session 使 用 很 简 单 , 语 句 Session["MyDataSet"]=ds 将 ds 保存 Session 中;语句 ds=(DataSet)Session["MyDataSet"] 从 Session 中取出 ds;语句 if(Session["MyDataSet"]==null)判断 Session 变量 MyDataSet 是否存在。本网页使用 Session 保存 DataSet 引用变量 ds。网页文件如下: 230 <%@ Import Namespace="System.Data" %> <%@ Import Namespace="System.Data.OleDb" %>
学生情况表 学号:<%# DataBinder.Eval(Container.DataItem,"StudentNum") %>
姓名:<%# DataBinder.Eval(Container.DataItem,"StudentName")%>
性别:<%# DataBinder.Eval(Container.DataItem,"StudentSex")%>
学号:/>
姓名:/>
性别: >
运行,显示效果如右图。 10.5.4 使用 VS.Net 编辑 DataList 例 e10_5_4:用 VS.Net 实现例子 e10_5_3,但把所作的修改用 SQL 语句直接写回源数据 库。具体步骤如下: (1) 创建 Web 应用程序项目。 (2) 按照例子 e8_12 中的步骤(4)到(8)创建 oleDbConnection、oleDbDataAdapter 和 dataSet 类对象 oleDbConnection1、oleDbDataAdapter1 和 dataSet11。 (3) 在 Web 窗体中添加 DataList 控件。在“属性”窗口中,从 DataSource 属性的下拉列表 选择 dataSet11。从 DataMember 属性的下拉列表选择学生情况表 Student。 (4) 在文件 WebForm1.aspx.cs 中 WebForm1 类的 Page_Load 方法中增加语句如下: void Page_Load(Object sender, EventArgs e) { oleDbDataAdapter1.Fill(dataSet11);//每次刷新页面都要执行 if (!IsPostBack) DataList1.DataBind();//只有第一次显示网页时执行 } (5) 在 VS.Net 的"设计"视图中,右击 DataList 控件,在弹出的快 捷菜单中,单击"编辑模板"菜单项子菜单中的"项模板"菜单 项,在"设计"视图中显示模板编辑器如右图。将光标移到 ItemTemplate 下的编辑框,键入"学号:",双击工具箱中的 Label 控件,添加 Label 控件到 ItemTemplate 模板中。单击 Label 属性 DataBindings 右侧标题为"„" 的按钮,出现"Label 数据绑定"对话框如图 10.5.4A。按图中那样选择,绑定 Label 控件的 Text 属性到数据表 Student 的字段 StudentNum。采用同样的步骤用 Label 控件显 示学生姓名和学生性别,并绑定到数据表 Student 的相应字段。增加 LinkButton 按钮, Text 属性为"编辑当前记录",CommandName 属性为"Edit"。在 EditItemTemplate 模板中, 增加 Label 控件,其 Text 属性绑定到学生的学号字段,增加 TextBox 控件,其 Text 属性 绑定到学生的姓名字段,增加 DropDownList 控件,其下拉列表增加男和女两项,其 Text 属性绑定到学生性别字段。增加 2 个 LinkButton 按钮,Text 属性分别为:保存修改、忽 233 略修改。CommandName 属性分别为"Update"和"Cancel"。右击模板编辑器,在弹出快捷 菜单中,单击"结束模板编辑"菜单项,退出模板编辑状态。 图 10.5.4A (6) 为 DataList 控件增加事件 EditCommand、UpdateCommand、CancelCommand 的事件处理函 数如下: private void DataList1_EditCommand(object source, System.Web.UI.WebControls.DataListCommandEventArgs e) { DataList1.EditItemIndex=e.Item.ItemIndex; DataList1.DataBind(); } private void DataList1_UpdateCommand(object source, System.Web.UI.WebControls.DataListCommandEventArgs e) { String sNum=((Label)e.Item.FindControl("Label7")).Text; String sName=((TextBox)e.Item.FindControl("TextBox1")).Text; String sSex= ((DropDownList)e.Item.FindControl("DropDownList1")).SelectedItem.Text; sNum="StudentNum="+sNum; DataRow[] foundRows=dataSet11.Tables["Student"].Select(sNum); foundRows[0]["StudentName"]=sName; foundRows[0]["StudentSex"]=sSex; oleDbDataAdapter1.Update(dataSet11);//保存到源数据库 DataList1.EditItemIndex=-1; DataList1.DataBind(); } private void DataList1_CancelCommand(object source, System.Web.UI.WebControls.DataListCommandEventArgs e) { DataList1.EditItemIndex=-1; DataList1.DataBind(); 234 } (7)运行,效果和例子 e10_5_3 基本一致。但本例修改数据后,数据将被直接保存到源数据 库中。用 VS.Net 生成的程序,每次网页重新装载时,自动执行 OnInit()程序,将重建 oleDbConnection、oleDbDataAdapter 和 dataSet 类对象,网页可以使用这些对象。而 DataList1_UpdateCommand()方法中语句 oleDbDataAdapter1.Update(dataSet11)将保 存修改数据到源数据库。 (8)可以修改 DataList 控件的外观属性,修改控件外观。右击 DataList 控件,在弹出快捷 菜单中,单击"属性生成器"菜单项,在打开的"DataList 属性"对话框中,可以更加方便 的修改属性。另外,右击 DataList 控件,打开快捷菜单,单击"自动套用格式"菜单项, 在打开的"自动套用格式"对话框中,可以选择已定义好的一些 dataList 外观样式,这些 样式的 dataList 对象都有比较漂亮的外观。 10.6 DataGraid 控件 DataGraid 控件是一个数据绑定列表控件,使用 DataGrid 控件可以显示数据库中的表, 将数据库表的字段作为表中的列显示,DataGrid 控件中的每一行表示数据库表的一个记录。 DataGrid 控件支持选择、编辑、删除、分页、排序、增加标题行和脚注行等功能。本节介 绍该控件的使用方法。本节的例子都是用记事本程序编写的,使用 VS.Net 的例子见 10.9 节 VS.Net 实现留言板。 10.6.1 DataGrid 控件概述 该控件属性众多,使用复杂,这里就不全部列出,以后将逐步介绍,包括部分属性的 DataGrid 控件的 html 标记格式如下: 这里列出的属性大部分是定义控件的外观的,这里定义控件 DataGrid 的背景为白色, 边界为黑实线,宽度为 1 个象素,字体为"x-small",字体名字为"verdana"。奇数行的背景 颜色,标题的字的颜色,背景色。数据项的背景色等,这里就不一一解释了。使用 VS.Net 集成环境在 Web 窗体中放置 DataGrid 控件后,右击该控件,在弹出菜单中单击"自动套用格 式"菜单项,在打开对话框中选用自己喜欢的格式,将自动设定这些属性。 10.6.2 DataGrid 控件绑定数据库表 例子 e10_6_2:该例把数据库 StudentI 中的表 Student 绑定到 DataGrid 控件,显示学生 的信息。在 DataGrid 控件 Html 标记中有一些设置外观的属性,请读者把这些有关外观的属 性设置和实际显示效果对照,理解这些属性的意义。网页文件如下: 235 <%@ Import Namespace="System.Data.OleDb" %> <%@ Import Namespace="System.Data" %>
Page_Load()函数说明了显示数据 的必要步骤。特别注意 DataGrid 控件 数据绑定的方法。显示效果如右图。 10.6.3 DataGrid 控件对数据库记录分页显示 如果要显示的数据库表的记录很多,无法一页显示,可采用分页的办法。DataGrid 控 件和分页有关的属性和事件如下:  属性 AllowPaging:为 True 表示允许分页,为 false 表示不允许分页。如果允许分页, 则在 DataGrid 控件底部出现分页导航栏,导航栏有若干按钮,单击按钮显示指定页。  属性 PageSize:表示每页显示的记录数。 236  属性 CurrentPageIndex:获取或设置当前显示页的索引号。  属性 PageCount:根据属性 PageSize 设定的每页记录数,计算的总页数。只读属性。  属性 PagerStyle:该属性设置分页导航栏风格。PagerStyle.Mode 表示导航栏的模式, 如 PagerStyle.Mode=NumericPages,则导航栏按钮标题为页号,外观如下图,单击按 钮,将显示按钮标题指定的页。PagerStyle.PageButtonCount 为分页导航栏按钮的个数。 如果 PagerStyle.Mode=NextPrev,则导航栏按钮标题为"<"和">",分别单击标题为"<" 或">"的按钮,则显示前页或后页。也可修改按钮标题为字符,例如修改为"前页"和" 后页",可修改属性 PagerStyle.PrevPageText="前页",PagerStyle.NextPageText="后页"。  事件 OnPageIndexChanged:单击分页导航栏 中的按钮,要求 DataGrid 控件显示新页时产 生的事件。事件处理函数修改当前显示页的 索引号为新页的索引号(e.NewPageIndex)。 例子 e10_6_3:把数据库 StudentI 的表 Student 绑定到 DataGrid 控件,分页显示,每页 3 个记录。 网页文件如下: <%@ Import Namespace="System.Data"%> <%@ Import Namespace="System.Data.OleDb"%>
237
显示效果如上图,这里分页导航栏使用标题为页编号的按钮,请读者修改为使用标题为 "前页"和"后页"的按钮。为了使程序更加清楚,对 DataGrid 控件 Html 标记中有关外观的属 性设置作了简化,界面不如上例漂亮。以后的例题都是用这个简单的 DataGrid 控件标记。 10.6.4 DataGrid 控件列模板类型 前两节例子,用 DataGrid 控件显示数据库 StudentI 中的 Student 表,将 Student 表的 字段作为表中的列,列标题为字段名。DataGrid 控件中的每一行表示数据库表的一个记录, 所有这些工作都是自动完成的。为了避免不兼容,字段名一般用英文,但显示时要将 DataGrid 列标题改为中文。有时还希望改变字段的显示顺序。为了实现这些功能,就不能自动创建 DataGrid 列,而要在 DataGrid 控件 Html 标记中自己增加 Html 标记创建列,为此必须置 AutoGenerateColumns=false。DataGrid 控件支持 5 种列模板类型,这些列模板是:  BoundColumn:创建 1 列显示被绑定数据表的 1 个字段的数据,数据以纯文本显示。  ButtonColumn: 创建 1 列按钮,按钮标题可以是增加记录、删除等,按钮标题也可以绑 定到数据源的指定字段。  EditCommandColumn: 创建 1 列按钮,单击按钮,执行编辑按钮所在行的命令。  HyperLinkColumn: 创建 1 列超级链接,超级链接字符可以是固定字符,也可以绑定到 数据源的指定字段。单击超级链接字符,可以超级链接到其它网页。  TemplateColumn: 按照指定的模板创建列,可以在列中增加 Web 服务器端控件。 所有这些列的 Html 标记要放到 DataGrid 的子标记之间,以后各 节将详细介绍这些列类型的使用方法。 10.6.5 用 BoundColumn 列将标题改为中文 如果 DataGrid 控件属性 AutoGenerateColumns=true,DataGrid 控件将根据数据库表的内 容自动填充表格,列标题是字段名。由于避免不兼容,字段名一般用英文,如希望显示时要 将 DataGrid 列标题改为中文,可以置 AutoGenerateColumns=false,在 DataGrid 控件 Html 标记中,用 Html 标记 BoundColumn 列模板创建列。BoundColumn 列模板类常用属性如下:  DataField:该列要绑定的数据表字段名称。  HeaderText:列标题(页眉)字符串,可以改为中文。  DataFormatString:定义该列数据显示格式。数据格式字符串用冒号分隔为两部分,格 式为{0:Bxx}。其中的 B 可以是以下各值:C、D、E、F、G、N、X,分别表示:以货 币格式、以十进制格式、以科学记数法(指数)格式、以定点小数格式、以常规格式、以 整数格式、以十六进制格式显示数据。格式中的 xx 指定显示数据的有效位数或小数位 数。例如,格式化字符串{0:F2}将显示带两位小数的定点数。  ReadOnly:如不允许编辑 BoundColumn 列的数据,为 true;否则为 false。默认值为 false。  SortExpression:列进行排序时所用的排序表达式。  属性 FooterStyle、FooterText、HeaderImageUrl、HeaderStyle、ItemStyle 请用帮助查看。 用 Html 标记 BoundColumn 列模板创建列的格式如下: 238 其中属性 HeaderText 是列标题字符串,可以改为中文,属性 DataField 是该列绑定的字 段名。 是本列的外观属性。希望显示多少列,就要使用多少次列模板控件 BoundColumn 创建 DataGrid 列。 例子 e10_6_5:把数据库 StudentI 的表 Student 绑定到 DataGrid 控件,显示学生信息, 列标题改为中文。网页文件如下: <%@ Import Namespace="System.Data.OleDb" %> <%@ Import Namespace="System.Data" %>
239 10.6.6 按钮列模板 ButtonColumn 如不自动生成 DataGrid 列,除了可用列模板 BoundColumn 创建列,还可使用列模板 ButtonColumn 增加按钮列,为按钮增加事件处理函数,完成一定功能,例如删除按钮所在 行的记录,在按钮所在行前(或后)插入新纪录等。列模板类 ButtonColumn 一些属性和列模 板类 BoundColumn 相同,这里只介绍和列模板类 BoundColumn 不同的常用属性:  ButtonType:按钮的类型,可以是 LinkButton(默认值)或 PushButton。  Text:按钮中显示的标题。如果设置了 Text 属性,则该列中的所有按钮均共享该标题。  CommandName:字符串类型。可以创建多列按钮,但所有列按钮都产生 ItemCommand 事 件,在该事件处理函数中,要根据 CommandName 确定用户单击了哪列按钮。 例子 e10_6_6:用一个 DataGrid 控件显示学 生的信息,并增加一列按钮列,单击按钮,在另 一 DataGrid 控件中显示按钮所在行的学生的学习 成绩。网页文件如下,运行效果如右图。 <%@ Import Namespace="System.Data.OleDb"%> <%@ Import Namespace="System.Data" %>

运行效果如上图,图中第 2 个 DataGrid 控件中是显示的学号为 2 的学生的学习成绩。 按钮标题也可以绑定到数据源的指定字段。 10.6.7 增加 HyperLinkColumn 列 控件 HyperLinkColumn 用来创建超级链接列,超级链接列的字符可以是固定字符,也 可以绑定到数据源的指定字段。单击超级链接,可以超级链接到其它网页。 例子 e10_6_7A:用一个 DataGrid 控件显示学生的信息,并增加 1 列超级链接列,单击 超级链接,在另一网页中显示超级链接所在行学生的学习成绩。网页文件如下: <%@ Import Namespace="System.Data.OleDb" %> <%@ Import Namespace="System.Data" %>

在 HyperLinkColumn 列中,DataNavigateUrlFormatString="e10_6_7B.aspx? id={0}" 是超级链接的网页,本例是 e10_6_7B.aspx,?id={0}是传递的参数,{0}是变量,对应属性 DataNavigateUrlField 指定的数据库表字段,本例为 SdudentNum;DataTextFormatString 是超级链接字符串,本例为"{0}同学成绩",{0}对应 DataTextField 指定的数据库表字段。 例 e10_6_7B:网页显示在网页 e10_6_7A 中指定学号的同学的学习成绩,网页文件如下: <%@ Import Namespace="System.Data.OleDb" %> <%@ Import Namespace="System.Data" %>
显示效果如上图。右边网页显示的是学号为 3 的学生的学习成绩。 10.6.8 TemplateColumn 列模板的用法 使用列模板 TemplateColumn 的目的是在 DataGrid 控件的网格中增加 Web 服务器控件。 使用 TemplateColumn 列模板创建 DataGrid 控件列的 Html 标记格式如下: 在此增加服务器控件,用来在非编辑状态下显示数据 在此增加服务器控件,用来在编辑状态下显示数据 其中,itemtemplate 标记是必须的。可以没有 edititemtemplate 标记,此时在编辑状 态下采用 TextBox 控件修改数据。本节的两个例子都没有使用 edititemtemplate 标记。使 用 edititemtemplate 标记的例子见下一节。 例子 e10_6_8A:该例用列模板 TemplateColumn 把学生性别列用单选按钮显示。 <%@ Import Namespace="System.Data.OleDb" %> <%@ Import Namespace="System.Data" %>
例子 e10_6_8B:该例用列模板 TemplateColumn 把学生性别列用图形显示。 <%@ Import Namespace="System.Data.OleDb" %> <%@ Import Namespace="System.Data" %>
10.6.9 EditCommandColumn 列模板 列模板 EditCommandColumn 可以在 DataGrid 控件中增加 1 列按钮,单击按钮可以进 入编辑状态。在编辑状态下,如果用列模板 TemplateColumn 创建的列中,指定了在编辑状 态下完成编辑所用的服务器控件,则用该控件修改数据,否则使用 TextBox 控件修改数据。 使用列模板 EditCommandColumn 在 DataGrid 控件中增加 1 列按钮的 Html 标记格式如下: 245 这里定义了 3 个按钮,在非编辑状态下,该列显示标题为"编辑"的按钮,单击此按钮, 产生 OnEditCommand 事件,该事件的事件处理函数将使按钮所在的行进入编辑状态,并将该 行的标题为"编辑"的按钮替换为标题为"确定"和"忽略"两个按钮,单击两个按钮分别产生事 件 OnUpdateCommand 和 OnCancelCommand,这两个事件的事件处函数分别执行保存修改、忽 略修改的操作,并返回非编辑状态。在 DataGrid 控件 Html 标记中,指定了 3 个按钮的单击 事件和其相联系的事件处理函数,例如 OnEditCommand="Edit_Command"指定标题为"编辑" 的按钮的 OnEditCommand 事件的事件处理函数是 Edit_Command。 例子 e10_6_9:该例网页把学生情况表 Student 用 DataGrid 控件显示出来,Student 表每 个字段是 DataGrid 的一列,并增加一个按钮,单击按钮,提供 TextBox 控件修改姓名,提 供 DropDownList 修改性别,不允许修改学号,能保存或忽略所作修改。 学生情况表共有 3 个字段,不允许自动生成每个字段的列。其中字段"StudentNum"和 "StudentName"用 BoundColumn 列模板显示;而字段"StudentSex"用 TemplateColumn 列模板 显示,在非编辑状态下用 Label 控件显示学生性别,在编辑状态下用 DropDownList 显示学 生性别。网页文件如下: <%@ Import Namespace="System.Data" %> <%@ Import Namespace="System.Data.OleDb" %>
247
运行效果如右图,图中已单击了第 2 行的"编辑"按钮,第 2 行进入了编辑 状态,因此第 2 行出现了进行编辑的控 件,右侧按钮也变为标题为"确定"和" 忽略"的按钮。请注意,由于编号列为只读,因此没有显示编辑控件;姓名列没有指定编辑 控件,因此使用默认的编辑控件 TextBox 编辑;性别列用列模板 TemplateColumn 创建的列 显示,该模板列中指定了编辑控件为 DropDownList,则用该控件修改性别。 10.6.10 DataGrid 控件的排序 使用过 Excel 程序的读者都知道,如果希望对某一列排序,只要双击列标题即可。 DataGrid 控件也提供了类似的功能。要想启用 DataGrid 控件的排序功能,必须修改其属性 AllowSorting 为 true(默认值为 false),表示允许排序,允许排序的列标题有一下划线, 单击允许排序的列标题将产生 OnSortCommand 事件,事件处理函数名在网页 DataGrid 控件 Html 标记中用 OnSortCommand="事件处理函数名"声明。网页 script 标记中应定义排序事 件 OnSortCommand 的事件处理函数。如果自动产生数据库表字段的 DataGrid 列,则每一列 都可以排序,列标题有下划线,单击列标题,将执行 OnSortCommand 事件处理函数,对该列 进行排序。排序的有关设置见下例中背景色为黑 色的语句。运行效果见右图。 例子 e10_6_10A:自动生成 Student 表字段 DataGrid 列的排序例子,网页文件如下: <%@ Import Namespace="System.Data.OleDb"%> <%@ Import Namespace="System.Data" %>
DataView 类的属性 Sort 为排序表达式,例如要对 DataView 类对象 Source 的姓名字段 StudentNum 排序,则应采用如下语句 Source.Sort="StudentNum",数据绑定后,将对 StudentNum 字段排序。默认为升序排序,如希望 降 序排序可以采用如下格式: Source.Sort="StudentNum DESC"。也可以对多个字段排序,例如希望首先按 StudentNum 字段排序,再按 StudentName 字段排序可以采用如下格式:Source.Sort="StudentNum DESC, StudentName"。如果不自动生成 DataGrid 控件列,必须为 DataGrid 每一列的属性 SortExpression 设定排序表达式,一般为 字段名,见下例中背景色为黑色的语句。 例子 e10_6_10B:不自动生成控件 DataGrid 列的排序例子 如下,运行效果入右图。 <%@ Import Namespace="System.Data.OleDb" %> <%@ Import Namespace="System.Data" %>
在 DataGrid 的 3 列中,只有标题为"编号"和"姓名"的列为属性 SortExpression 指定了 排序表达式,因此,只有这两列可以排序,表达式 SortExpression="StudentNum"表示此列 按照学生编号字段"StudentNum"从小到大顺序排序。 10.6.11 使用微软 SQL Server 数据库系统 例子 e10_6_11:把微软数据库 Northwind 中的表 Employees 绑定到 DataGrid 控件,显 示所选定字段的数据。注意,要运行此网页,Web 服务器所在的计算机,必须安装微软 SQL Server7.0 或以后版本数据库系统以及其自带的数据库例子 Northwind。网页文件如下: <%@ Import Namespace="System.Data" %> <%@ Import Namespace="System.Data.SqlClient" %>
请把本网页背景色为黑色的语句和例子 e10_6_2 网页相应语句比较,看有什么不同。请 读者把本节所有例子改为使用微软 SQL Server 自带数据库 Northwind 中的表 Employees。 10.7 AdRotator 控件 AdRotator 控件用于随机并自动循环显示一组广告图片,每刷新一次页面,改变一次显 示广告图片。可以控制广告图片的优先级,从而使某些广告图片的显示频率高于其他广告图 片。单击 AdRotator 控件的广告图片,将超级链接到指定网页。控件常用属性意义如下:  AdvertisementFile:AdRotator 控件用一个 XML 文件指定每个广告图片的 URL,以及 单击广告图片超级链接的网页 URL 等信息。该属性记录 XML 文件的位置。  KeywordFilter:用于筛选出 XML 文件中相应关键字的图片广告。  Target:单击 AdRotator 控件的广告图片,将超级链接到指定网页,该属性指定这个网 页如何打开,为_blank 将在新窗口中打开;_parent 将在父窗口中打开;_self 将在 具有焦点的窗口中打开;_search 在搜索窗口打开。 例子 e10_7:本例使用 AdRotator 控件显示 2 个广告图片,使用 VS.Net 创建步骤如下: (1) 创建新 Web 应用程序项目。新建一个 XML 文件。单击 VS.Net 菜单"项目"|"添加新项"菜 单项,弹出标题为"添加新项"的对话框,在对话框中选中"XML 文件",文件名为 "ads.xml",单击"打开"按钮,增加一个 XML 文件。文件如下: p2.JPG http://www.sohu.com 图像不可用时显示的文本 10 Topic1 251 第 1 个图的标题 p1.jpg http://www.sina.com 图像不可用时显示的文本 10 Topic2 第 2 个图的标题 (2) 将两个图形文件 p1.jpg 和 p2.jpg 拷贝到网页 文件 WebForm1.aspx 所在文件夹中。 (3) 在窗体中放置控件 AdRotator,其属性 Name= AdRotator1。单击其属性 AdvertisementFile 右侧标题为"„" 的按钮,出现"选择 XML 文件" 对话框,在对话框的"URL(U)"编辑框中选中控 件 AdRotator 使用的 XML 文件"ads.xml"。 (4) 运行,可以看到一幅图,鼠标移到图中,变为 手形,单击可以转到搜狐网站。单击浏览器刷 新按钮,可以看到另一幅图。 XML 文件包含以下预定义的属性。只有 ImageUrl 属性是必需的:  ImageURL:显示在网页上的广告图片文件的 URL 。  NavigateURL:单击 AdRotator 控件的广告图片,超级链接到指定页面的 URL。  AlternateText: 图像不能正确显示时显示的文本,也用来作为鼠标停留在广告图片一 段时间后,显示的提示信息。  Keyword:指定广告类别,可用于筛选特定类别广告。通过设置 AdRotator 的 KeywordFilter 属性以筛选出 XML 文件中相应类别的广告。  Impression 指示广告的可能显示频率的数值。在 XML 文件中,所有 impression 值的 总和不能超过 2,048,000,000 – 1。 10.8 Calender 控件 Calendar 控件在 Web 窗体页上显示一个月历,包括本月和该月前后各一周的日期,因 此,一次总共显示六周。用户可使用该日历查看和选择日期。常用属性事件如下:  属性 SelectionMode:控制日期的选择方式。默认设置为 Day 只允许用户选择一天;设 置为 DayWeek 使用户可以选择一天或连续的一周;设置为 DayWeekMonth 使用户可以 选择一天、一周或一个月。后两种选择方式只能选择连续的日期。  属性 SelectDate:用户选择的日期,如允许选择 1 周或 1 个月,为选择的第 1 天。  属性 SelectDates:用户选择的日期的集合。可通过其属性 Count 知道用户选择了多少天; 用 SelectDates[i]得到所选的第 i 个日期,第 1 个选择的日期索引号为 0。  属性 TodaysDate:今天的日期。  外观属性:见例子 e10_8B 中 Calendar 控件定义,有多个外观属性。可以通过设置控件 不同部分的样式属性,定义 Calendar 控件的外观。例如可更改日历的颜色、尺寸、文 252 本以及其它可视特性。外观属性采用 默认值的显示效果见例子 e10_8A,如 右图。  事件 OnSelectionChanged:用户改变 选择日期时产生的事件。 例子 e10_8A:Calendar 控件最简单的 用法如下,运行效果如右图。
运行效果如上图。如果选择某一天,标签控件将显示为所作的选择。 例子 e10_8B:本例网页显示了属性 SelectionMode 为不同值时 Calendar 控件的显示效果。
选择模式: 不能选择 只能选择 1 天 可选 1 天或 1 周 可选 1 天、1 周或 1 月 运行效果如右图。可以从控件 DropDownList 中 选择日历控件不同选择模式,选择模式包括:不能 选择任何日期、只能选择 1 天、可选 1 天或 1 周、 可选 1 天、1 周或 1 月。注意控件 DropDownList 的 属性 AutoPostBack 为 true,因此 DropDownList 控 件选择的改变产生的事件立刻发送到服务器端,重 新生成页面,在 Page_Load 方法中选择了模式。 10.9 VS.Net 实现留言板 例子 e10_9:本例有两个网页。留言网页负责输入留言,包括输入用户名,留言主题, 留言内容,用三个编辑框输入这些信息。输入完毕后,单击"提交"按钮,将留言存入数据库。 单击"查看留言"按钮,可链接到另一个显示留言的网页。显示留言的网页包括一个 DataGraid 控件,用来显示所有的留言的用户名,主题,留言时间,还包括 1 列按钮,单击按钮,显示 按钮所在行的留言内容。单击"返回主窗口"按钮,返回留言网页。下边是具体步骤: (1) 用 Access 建立数据库 LiuYanBan.mdb。创建 LiuYanTable 表,记录留言信息,该表包 括留言编号字段 LiuYanID,自动编号类型,主关键字;留言者姓名字段 LiuYanName, 文本,字段大小 10,必填字段,默认值为空;留言标题字段 LiuYanTitle,文本,字段 254 大小 30,必填字段,默认值为空;留言时间字段 LiuYanTime,时间类型;留言内容字 段 LiuYanContent,备注字段,必填字段,默认值为空。增加若干数据。数据库文件路 径为 D:\asp\LiuYanBan.mdb,文件夹 asp 已设为 Web 网站宿主目录。步骤参见 8.3 节。 (2) 创建 Web 应用程序项目,项目名为 e10_9。最终设计界面如图 10.9。 (3) 放置 4 个 Label 控件到 WebForm1 窗体,属性 Text 分别修改为:留言板主窗体、用户名、 留言主题、留言内容。具体位置如图 10.9 左图。 (4) 单击 WebForm1 属性 Style 右侧标题为"„" 的按钮,打开"样式生成器"对话框,可以修 改 WebForm1 各种风格。单击对话框左侧的各个选项:字体、背景、文本、位置、布局、 边缘、列表、其它,可以按自己爱好修改相应的选项,这里不作修改,全部采用默认值。 (5) 放置 3 个 TextBox 控件到 WebForm1 窗体,属性 Text 都为空,ID=TextBox1 编辑框用来 输入用户名,ID=TextBox2 编辑框用来输入留言主题,ID=TextBox3 编辑框用来输入留 言内容,TextBox3 编辑框属性 TextMode=MultiLine。由于此三项要求必须输入数据, 因此应增加 3 个验证控件。请读者自己加入。具体位置如图 10.9 左图。 (6) 放置 oleDbConnection 控件到 WebForm1 窗体,其属性 Name=oleDbConnection1,从其 属性 ConnectionString 右侧下拉列表中选中新建连接,打开"数据连接属性"对话框, 选择"提供程序"选项卡页,选中 OLE DB 提供程序为 Microsoft Jet 4.0 OLE DB Provider, 单击"下一步"按钮,在"连接"选项卡页中,选择数据库名称为 D:\asp\ LiuYanBan.mdb, 用户名称为 Admin,选中"空白密码"多选框,单击"测试连接"按钮,应出现"测试连接 成功"对话框。按"确定"按钮退出"数据连接属性"对话框。参见例 e8_10B 中的第一步。 (7) 在窗体中放置控件 oleDbCommand,其属性 Name=oleDbCommand1,从其属性 Connection 右侧的下拉列表中选中 oleDbConnection1,指定 oleDbCommand 使用的数据连接对象。 (8) 放 Button 控件到 WebForm1 窗体,属性 Text="提交留言"。按钮单击事件处理函数如下: private void Button1_Click(object sender, System.EventArgs e) { oleDbConnection1.Open();//自动增加字段不必写入,自动产生 oleDbCommand1.CommandText="Insert Into LiuYanTable (LiuYanName,LiuYanTitle,LiuYanTime,LiuYanContent) Values('" + TextBox1.Text + "', '" + TextBox2.Text + "','" + DateTime.Now + "','" + TextBox3.Text + "')"; oleDbCommand1.ExecuteNonQuery();//执行SQL语句 oleDbConnection1.Close(); TextBox1.Text=""; TextBox2.Text=""; TextBox3.Text=""; } (9) 单击 VS.Net 菜单"文件"|"添加新项(w)…"菜单项,出现"添加新项"对话框,在右侧选 中"Web 窗体",窗体名为:WebForm2.aspx,单击"打开"按钮,创建新窗体 WebForm2。 (10)放置 HyperLink 控件到 WebForm1 窗体(留言窗体),属性 Text="查看留言",单击属性 NavigateUr 右侧标题为"„" 的按钮,打开"选择 URL"对话框,从对话框中标题为"URL 类型:"的 ComboBox 控件右侧下拉列表中选择"与根相关的"项,在标题为"e10_9 的内容 (C)"列表框中选中 WebForm2.aspx。标题为"URL(U):" ComboBox 控件的编辑框中出现 如下内容:/e10_9/WebForm2.aspx。单击"确定"按钮退出对话框。见图 10.9B (11)在 WebForm2 窗体中放置控件 oleDbDataAdapter,打开"数据适配器配置向导"对话框, 单击"下一步"按钮,出现对话框如图 8.12A,从 ComboBox 控件下拉列表中选中 ACCESS.D:\ASP\LiuYanBan.mdb.Admin。参见例子 e8_12 中的第 4 步到第 8 步。 (12)单击图 8.12A 中"下一步"按钮,出现对话框(未给出图)。在对话框中,选择标题为"使 255 用 SQL 语句(S)"的单选按钮。 (13)单击"下一步"按钮,出现图 8.12B 对话框。单击对话框中"高级选项"按钮,打开"高级 SQL 选项"对话框,所有多选框都不选,单击"确定"按钮,退出"高级 SQL 选项"对话框。 (14)单击 8.12B 对话框中"查询生成器"按钮,在打开的"添加表"对话框(图 8.12C 但表名为 LiuYanTable)中,选中 LiuYanTable 数据库表,单击"添加"按钮。关闭"添加表"对话框。 (15)在打开的标题为"查询生成器"对话框(图 8.12D 但数据库表是 LiuYanTable)中,选中 5 个字段前的多选框,字段 LiuYanID 按降序排列,使新留言在前边显示,生成的 SQL 语 句为:SELECT LiuYanContent, LiuYanID, LiuYanName, LiuYanTime, LiuYanTitle FROM LiuYanTable ORDER BY LiuYanID DESC。单击"确定"按钮退出。 (15)在图 8.12B 的编辑框中自动增加以上 SQL 语句。单击图 8.12B 中的"下一步"按钮,出 现对话框,列出所作的选择。单击"完成"按钮退出。可以看到新增的 OleDbConnection 和 OleDbDataAdapter 对象,并已配置好设置。 (16)选中组件 oleDbDataAdapter1,单击 VS.Net 菜单"数据"|"生成数据集"菜单项。打开"生 成数据集"对话框(图 8.10E 但数据库表名为 LiuYanTable),不做修改,按"确定"按钮 退出。自动增加数据集 DataSet 对象 dataSet11。保存所有文件。 (17)放置控件 dataView 到 WebForm2 窗体,属性 Name=dataView1。从控件 dataView1 属性 Table 右侧的下拉列表中,选中 dataSet11 中的 LiuYanTable。 (18)在 WebForm2 窗体中放置 2 个 Label 控件,其属性 Name 分别为 Label1、Label2,Label1 的属性 Text 为空,用来显示留言,Label2 的属性 Text 为:留言内容。 (19)放置 DataGrid 控件到 WebForm2 窗体,属性 Name=DataGrid1。右击 DataGrid1,在弹 出的快捷菜单中,单击"自动套用格式"菜单项,在出现的对话框中选用自己喜欢的方案。 (20)右击控件 DataGrid1,在弹出的快捷菜单中单击"属性生成器"菜单项,打开"DataGrid 属性"对话框。在对话框左侧选中"常规",如图 10.9C,从标题为"数据源(D):"的 ComboBox 控件下拉列表选中"dataView1",选中"显示页眉"、"显示页脚"、"允许排序" 多选框。在"DataGrid 属性"对话框左侧选中"列",如图 10.9D,不选中"在运行时自动 创建列"多选框,用单击标题为">"的按钮方法,将 LiuYanName、LiuYanTitle、 LiuYanTime 字段及一个按钮列从"可用列(A)"列表框移到"选定的列(S)"列表框,表示 DataGrid 控件仅显示这 3 个字段。3 个字段的"页眉文本"分别为:留言者姓名、留言标 题、留言时间。增加的按钮列的列标题为:单击按钮查看留言,命令名为:ReadContent。 在"DataGrid 属性"对话框左侧选中"分页",选中"允许分页"多选框。单击"确定"按钮 退出"DataGrid 属性"对话框。 (21)为控件 DataGrid1 新增按钮列增加事件函数,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(); } } (22)为 Page_Load 事件函数增加语句: private void Page_Load(object sender, System.EventArgs e) { oleDbDataAdapter1.Fill(dataSet11); if(!Page.IsPostBack) 256 { DataGrid1.CurrentPageIndex=0; DataGrid1.DataBind(); } } (23)为 DataGraid1 的 DataGrid1_PageIndexChanged 事件函数增加语句: private void DataGrid1_PageIndexChanged(object source, System.Web.UI.WebControls.DataGridPageChangedEventArgs e) { DataGrid1.CurrentPageIndex=e.NewPageIndex; DataGrid1.DataBind(); } (24)放置 HyperLink 控件到 WebForm2 窗体(显示留言窗体),属性 Text="增加留言",单击 属性 NavigateUr 右侧标题为"„" 的按钮,打开"选择 URL"对话框,从对话框中标题为 "URL 类型:"的 ComboBox 控件右侧下拉列表选中"与根相关的"项,在标题为"e10_9 的内 容(C)"列表框中选中 WebForm1.aspx。标题为"URL(U):" ComboBox 控件的编辑框中出 现如下内容:/e10_9/WebForm1.aspx。单击"确定"按钮退出对话框。参见图 10.9B。 (25)运行,出现 WebForm1,可以输入一条留言,单击提交按钮,再单击标题为"查看留言" 的超级链接,转到 WebForm2,单击 DataGrid 按钮列中的"查看留言"按钮,可以在标签 控件 Label1 处看到留言,单击标题为"增加留言"的超级链接,转到 WebForm1。 图 10.9 257 图 10.9B 图 10.9C 图 10.9D 258 习题 (1) 不使用 VS.Net,用记事本编写两数相加求和的网页及显示一幅图像的网页。 (2) 10.1.4 节例子中,修改程序,按照选中爱好的顺序,在标签控件中显示所选爱好。 (3) 用 CheckBoxList 控件选择标签控件字体的风格,如黑体、下划线、斜体、删除线等。 (4) 用 RadioButtonList 控件选择标签控件字体的名称,如宋体、楷体等。 (5) 用 ListBox 控件选择标签控件字体的风格,如黑体、下划线、斜体、删除线等。 (6) 用 DropDownList 控件选择标签控件字体的名称,如宋体、楷体等。 (7) 自定义数据验证控件 CustomValidator 要求用户必须输入偶数。 (8) 写出只能输入由 26 个大写字母组成的字符串的正则表达式。 (9) 写出只能输入由 26 个字母及数字组成的字符串的正则表达式。 (10) 将 ArrayList 类对象绑定到 ListBox 控件的列表。 (11) 将 Student 表的所有学生姓名绑定到 RadioButtonList、CheckBoxList 和 ListBox 控件。 (12) 修改例 e10_4_4,Repeater2 直接用 Sql 语句选择指定学生的学习成绩。 (13) 用 DataList 显示 SdudentI 数据库 Student 表的学号和姓名,并增加一个标题为"详细资 料"的按钮,单击按钮,在其右侧显示该学生的详细资料及该学生的所有课程成绩。 (14) 在例子 e10_6_3 中,设置 PageButtonCount="3",为什么分页导航栏只有 2 个按钮? (15) 修改例子 e10_6_3 中的分页导航栏按钮为使用标题为"前页"和"后页"的按钮。 (16) 例子 e10_6_6 中,将按钮列标题该为如下形式:张三成绩、李四成绩等。 (17) 把本章中所有使用 StudentI 数据库 Student 表的例子改为使用微软 SQL Server 自带数据 库 Northwind 中的表 Employees。 (18) 参照网站留言板,修改 10.9 实现的留言板,使其实用化。 259 第十一章 ASP.Net 内建对象 ASP.Net 为保持用户的数据和信息,内建了许多对象,包括 Application、Response、 Request、cookie、Sessions、Cache、Server 和 ViewState 等对象。通过这些对象,可以提供 网站一些必不可少的功能,例如当前目录的获得、在线人数、访问网站总人数、网上商店购 物筐等。本章介绍这些内建对象的属性和用法。 11.1 Response 对象 使用 Response 对象可以向浏览器发送信息,包括直接发送信息在浏览器中显示、重定 向浏览器去显示另一个网页、设置 cookie 的值。在 ASP.Net 中一般不用 Response 对象发送 信息到浏览器中显示,可以用其它方法重定向浏览器去显示另一个网页,因此在 ASP.Net 中使用 Response 对象的机会越来越少了,本节只对 Response 对象做简单介绍,设置 cookie 方法在另一节介绍。 11.1.1 用 Response 发送 html 标记在浏览器中显示 例子 e11_1_1:用 Response 发送 html 标记在浏览器中显示的网页文件如下: <%@ Page language="c#" %> <% Response.Write(""); Response.Write("Response 对象使用"); Response.Write(""); %> 11.1.2 用 Response 对象重定向浏览器 例子 e11_1_2:用 Response 对象重定向浏览器显示新浪网主页的例子如下: 260
单击按钮打开新浪网主页
这里实现的功能完全可以用 HyperLink 控件实现,请读者试一试。但是如果根据条件用 语句实现转向不同网页,使用此语句还是必要的,例如,有些用户企图不经过登录直接访问 网站网页,在网站网页的 Page_Load 方法中要进行判断,如果未登录,可用上述方法直接转 向登录界面。例子 e11_2_1C 中使用 Response 对象重定向时,向另一个网页传递数据。 11.2 Request 对象 Request 对象主要有以下用途:第一用来在不同网页之间传递数据,第二是 Web 服务器 可以使用Request 对象获取用户所使用的浏览器的信息,第三是Web 服务器可以使用 Request 对象显示 Web 服务器的一些信息,最后,可以用 Request 对象获得 Cookie 信息。本节主要 介绍前三种用途,有一节专门介绍 Cookie。 11.2.1 用 Request 对象获取另一个网页传递的数据 从一个网页链接到另一个网页时,可能需要传递一些数据到另一个网页。一般采用如下 格式:URL?数据名称=数据值&数据名称=数据值…,其中?表示 URL 后边要传递数据,数 据传递的格式为:数据名称=数据值,两个数据之间用&分割。当数据传递到另一个网页时, 另一个网页用 Request["数据名称"]的方法取出这个数据。e10_6_7 也是两个网页之间传递数 据的例子。 例子 e11_2_1A:本例用 HyperLink 打开 e11_2_1B 网页,传递固定数据,网页文件如下:
例子 e11_2_1B:本例网页显示另一网页传递来的数据,网页文件如下: 261
例子 e11_2_1C:如传递的数据是变量,例如 TexBox 控件输入的内容,可用如下办法:
在 URL 中,有些 ASCII 字符具有特殊含义,必须做特殊处理,例如字符/,用 HttpUtility.UrlEncode 方法将对具有特殊含义字符做特殊处理。以后将看到,通过 Application 和 Session 对象也可以在两个网页之间传递数据。 11.2.2 用 Request 对象获取客户端浏览器的信息 不同浏览器或相同浏览器的不同版本支持不同的功能,Web 应用程序可能要根据不同的 浏览器采取不同的措施,可用 HttpRequest.Browser 属性的 HttpBrowserCapabilities 对象 获得用户使用的浏览器信息。见下例:
11.2.3 用 Request 对象获取服务器信息 例子 e11_2_3:用 Request 对象获取 Web 服务器信息的例子如下:
11.3 Cookie 对象 用户用浏览器访问一个网站,Web 服务器并不能知道是哪一个用户正在访问。但一些 263 网站,希望能够知道访问者的一些信息,例如是不是第一次访问,访问者上次访问时是否有 未做完的工作,这次是否为其继续工作提供方便等。用浏览器访问一个网站,可以在此网站 的网页之间跳转,当从第一个网页转到第二个网页时,第一个网页中建立的所有变量和对象 都将不存在。有时希望为这些被访问的网页中的数据建立某种联系,例如一个网上商店,访 问者可能从网站中不同的网页选取各类商品,那么用什么办法记录该访问者选取的商品,也 就是一般所说的购物筐如何实现。用 Cookie 对象可以解决以上问题。 11.3.1 Cookie 对象的用法 Cookie 为 Web 应用程序保存用户相关信息提供了一种有用的方法。支持 Cookie 对象的 浏览器允许 Web 应用程序将一小段文本信息存储到浏览器所在的计算机中。当用户访问网 站时,web 应用程序可以利用 Cookie 保存用户的一些信息,这样,当用户下次访问网站时, Web 应用程序就可以检索到以前保存的信息。 Cookie 对象采用键/值对的方法记录数据,用法如下: HttpCookie MyCookie=new HttpCookie("UserInfo");//UserInfo 为键 myCookie.Value=2.ToString();//UserInfo 键的值为 2 myCookie.Expires=DateTime.Now.AddHours(1);//数据 1 小时后无效,否则退出网页失效 Response.Cookies.Add(MyCookie);//将 MyCookie 写到浏览器 语句 myCookie.Expires=DateTime.Now.AddHours(1)指定 Cookie 中数据何时失效,这里 是 1 小时后失效。如果不指定失效时间,退出网页立即失效。取出 Cookie 的值的方法如下: HttpCookie myCookie=Request.Cookies["UserInfo"];//得到键为 UserInfo 的 Cookie Int Num=Convert.ToInt16(myCookie.Value); 一个键还可以有若干子键,可以在一个 Cookie 中保存多个键/值对,具体方法如下: HttpCookie MyCookie=new HttpCookie("UserInfo");//主键="UserInfo" MyCookie.Values.Add("UserName","张三");//子键 1="UserName",其值为"张三" MyCookie.Values.Add("UserAge","13");//子键 2="UserAge",其值为"13" Response.Cookies.Add(MyCookie);//写到用户计算机中 取出 Cookie 的值的方法如下: HttpCookie myCookie=Request.Cookies["UserInfo"];//得到主键为"UserInfo"的 Cookie string s=myCookie.Value("UserName");//得到子键 1 的值 string s=myCookie.Value("UserAge");//得到子键 2 的值 11.3.2 用 Cookie 对象记录访问的次数 例子 e11_3_1:本例用 Cookie 对象记录访问者是第几次访问本站,并将次数显示出来。 <%@ Page Language="C#" Debug="true" %>
当然,浏览器的 Cookies 必须设置为允许使用。 11.3.3 网上商店购物筐实现 网上商店一般有多个网页,每个网页提供不同种类 的商品,供用户选择。用户可以浏览这些网页,从每个 网页中选择商品,网上商店网站要记录用户要购买的这 些商品,一般把这个功能叫做购物筐,下边的例子介绍 用 Cookies 实现购物筐的方法。 首先用 ACCESS 数据库系统创建一个仓库管理系 统数据库 DepotI,仅有 1 个表 goods,记录仓库中的所 有商品。一般商品表要包括很多字段:编号、货物名称、 包装类型、单价、数量等。这里只是说明问题,为了简 单,只包括 3 个字段:货物编号字段 gID,自动编号, 主关键字;货物名称字段 gName,文本,字段大小 26, 必填字段,默认值为空;货物数量字段 gNum,整型,必 填字段,默认值为 0。增加 4 个记录,字段 gID、gName、 gNum 的值分别为:1、香蕉、20;2、苹果、50;3、菊 花、30;4、茉莉、20。 例子中有两个网页,一个网页显示水果,这里只有两种:香蕉和苹果;一个网页显示花 卉,这里也只有两种:菊花和茉莉。分别在 2 个网页中用网格控件 DataGrid 显示,每个网 格控件有 4 列,数据库 DepotI 的 Goods 表的 3 个字段列,还包括 1 列按钮列,单击按钮把 所选货物放到购物筐,单击 1 次,数量增加 1 个。 例子 e11_3_2A:第一个网页显示水果,网页文件如下:(运行效果如上图) 265 <%@ Import Namespace="System.Data.OleDb" %> <%@ Import Namespace="System.Data" %>

购物筐

例子 e11_3_2B:第二个网页显示花卉,它和第一个网页文件只有 2 条语句不同(背景 为黑色的语句),即 Page_Load 方法的 string s2="SELECT * FROM Goods WHERE gID>2"和 ,其余完全一样,这里就不列出第二个网页文件了。其实,本例根本不必 用两个网页,一个网页完全能实现以上功能,因此也不必使用 Cookie,这里只是为了说明 Cookie 的用法。请读者改为一个网页实现以上功能,如果不用 Cookie 又如何实现。 在浏览器中输入地址:http://Localhost/e11_3_2A.aspx,单击按钮选水果,单击 1 次,数 量增加 1 个。转到第二个网页 e11_3_2B.aspx,选花卉。在 Label 控件处显示所选择的商品。 当然,本例只是说明问题,有许多不尽合理之处。 11.4 Application 对象 当网站中的 ASP.Net 网页被第一次访问,网站 Application 对象被自动创建(网站只能有 一个 Application 对象),如果已没有浏览器访问网站中的 ASP.Net 网页,Application 对象被 自动撤销,这个期间是 Application 对象的生存期。在 Application 对象中的变量也有相同生 存期,并且这些变量可以被网站中的所有网页访问,因此这些变量是网站中所有网页的公用 变量。由于存储在 Application 对象中的变量可以被所有网页读取,所以 Application 对象的 变量也适合在网页之间传递信息。Application 对象主要有以下用途:  存储记录在线人数或访问网站总人数的变量。  存储网站共用最新消息,供所有网页更新。  记录网站中各网页同一条广告被点击的次数或时间。  存储供所有网页使用的数据库数据。  不同用户之间通讯,例如多用户聊天室,多用户游戏等 267 本节首先介绍 Application 对象的用法,然后介绍记录访问网站总人数的实现方法。 11.4.1 Application 对象方法和事件 这里只介绍 Application 对象常用的方法和事件。  方法 Add:加入一个变量到 Application 对象中,例如 Application.Add("string1","test"), 表示向 Application 中加入一个名为 string1 的变量,其值为字符串"test",其实它的效果 和 Application("string1")="test"以及 Application.item("string1")="test"是一样的。  方法 Remove:从 Application 对象中删除变量,例如 Application.Remove("string1")。  方法 RemoveAll 和 Clear:清除 Application 对象中所有变量。  方法 Get:使用名字变量名或者下标,来取得 Application 对象中变量值。例如 object tmp= Application.Get("string1") 或 object tmp=Application.Get(0) 。它等价于 object tmp= Application("string1")或 object tmp= Application(0)。  方法 Set:修改 Application 对象中指定变量的值。例如 Application.Set("string1","try")。 等价于 Application("string1")="try"。  方法GetKey:得到 Application 中指定下标的变量名。例如string s= Application.GetKey(0)。  方法 Lock :该方法是用来解决多个用户对存储在 Application 中的同一变量进行修改时 的同步问题。由于存储在 Application 对象中的变量可以被网站中的所有网页存取,为了 避免多个用户同时修改同一变量发生错误,当一个用户在修改这个变量时,不允许其它 用户修改。该方法阻止其他客户修改存储在 Application 对象中的变量,以确保在同一时 刻仅有一个客户在修改 Application 变量。如果用户没有明确调用 Unlock 方法,则 Web 服务器将在修改变量的网页关闭后或锁定超时后,解除对 Application 对象的锁定。  方法 Unlock:和 Lock 方法相反,Unlock 方法将允许其它网页修改 Application 对象的变 量。下例介绍一个修改计数器变量的方法。 Application.Lock; Application["counter"]=(Int32)Application["counter"]+1; Application.UnLock;  事件 Application_OnStart:当网站中的 ASP.Net 网页被第一次访问时,产生的事件。  事件 Application_OnEnd:没有浏览器访问网站中的 ASP.Net 网页后,产生的事件。这两 个事件的事件处理函数必须写在 global.asax 文件之中。 11.4.2 Global.asax 文件 Global.asax 文件位于 Web 应用程序项目所在的 Web 应用目录下,Web 应用目录概念见 9.3.7 节。每个解决方案中每个项目中都可以有一个 Global.asax 文件。使用 VS.Net 创建一个 项目,将在 Web 应用目录中自动建立 Global.asax 文件,读者可以查看该文件的具体内容, 这里就不列出了。用户使用浏览器不能下载或查看这个文件的内容。Global.asax 文件实际上 是一个可选文件,删除它不会出问题,当然是在没有使用它的情况下。在 Global.asax 文件 中定义了一个 HttpApplication 类的派生类 Global,在 Global 类中可以定义变量、方法、事 件处理函数。在类中已预先定义了若干事件的事件处理函数,包括以下事件: Application_Start、Application_End、Application_BeginRequest、Application_EndRequest、 Session_Start、Session_End 等,还可以增加其它事件处理函数。在 Global 类中也可以放置 一些组件,例如放置组件 OleDbAdapter 或 SqlDataAdapter,也支持可视化设计,例如可以 268 从"工具箱"的"数据"选项卡中,将 OleDbDataAdapter 对象拖到窗体上。"数据适配器配置向 导"启动,它将帮助您创建连接和适配器。具体步骤可参见例子 e8_12。这些组件可以供所 有网页使用。 11.4.3 显示访问网站总人数的例子 例子 e11_4_3:本例记录并显示建站以来访问网站的总人数。在 Application 对象中增加 一个变量 AllVister,记录访问网站的总人数。一个用户访问网站首先产生 Session_Start 事件, 在此事件函数中,AllVister 加 1。用 VS.Net 实现的具体步骤如下: (1) 创建一个 Web 应用项目,项目名为 e11_4_3。 (2) 在 global.asax 文件中的 Application_OnStart 事件处理函数中增加语句如下: Application.Add("AllVister",0); (3) 在 global.asax 文件中的 Session_Start 事件处理函数中增加语句如下: Application.Lock(); Application["AllVister"]=(int)Application["AllVister"]+1; Application.UnLock(); (4) 单击 VS.Net 菜单"文件"|"添加新项(w)…"菜单项,打开"添加新项"对话框,在右侧选中 "Web 窗体"模板,名称为:WebForm2.aspx,单击"打开"按钮,创建 WebForm2 窗体。 (5) 放 Label 和 HyperLink 控件到 WebForm1 窗体,HyperLink 控件 NavigateUrl 属性为 WebForm2.aspx, 属性 Text 为"转到 WebForm2 网页"。 (6) WebForm1 的 Page_Load 方法中增加如下语句: void Page_Load(Object src,EventArgs e) { if(!Page.IsPostBack)//如果网页响应事件后刷新,计数器不加 1 { Application.Lock(); int num=(int)Application["AllVister"]; Application.UnLock(); Label1.Text="您是第"+Convert.ToString(num)+ "位访问者"; } } (7) 放 Label 和 HyperLink 控件到 WebForm2 窗体,HyperLink 控件 NavigateUrl 属性为 WebForm1.aspx, 属性 Text 为"转到 WebForm1 网页"。 (8) WebForm2 的 Page_Load 方法中增加如下语句: void Page_Load(Object src,EventArgs e) { if(!Page.IsPostBack)//如果网页响应事件后刷新,计数器不加 1 { Application.Lock(); int num=(int)Application["AllVister"]; Application.UnLock(); Label1.Text="您是第"+Convert.ToString(num)+ "位访问者"; } } (9) 运行,在浏览器打开 WebForm1 网页,查看显示的计数器数值,单击刷新按钮,查看显 示的计数器数值是否改变,单击超级链接,转到 WebForm2 网页,查看显示的计数器数 值是否改变,单击超级链接,再转回 WebForm1 网页,查看显示的计数器数值是否改变。 关闭所有网页,等待一段时间,再打开 WebForm1.aspx 网页,显示的计数器值从 1 开始, 269 这是因为没有网页访问网站时,Web 应用程序关闭,Application 对象被自动撤销。在打 开新网页,产生 Application_OnStart 事件,将 counter 置为 0。为了解决此问题,可以建 立一个文件,记录访问网站总人数,初值为 0,Application_OnStart 事件函数中,从文件 取出已访问网站总人数,赋值给 counter,Application_OnEnd 事件函数中,将 counter 存 到文件中。 (10) 单击 VS.Net 菜单"文件"|"添加新项(w)…"菜单项,出现"添加新项"对话框,在右侧选 中"文本文件"模板,名称为:TextFile1.txt,单击"打开"按钮,创建新文件。打开该文件, 键入字符’0’,保存文件后,关闭该文件。 (11) 修改 global.asax 文件中的 Application_OnStart 事件处理函数语句如下: string s=Server.MapPath("TextFile1.txt"); Application.Add("counterFile",s);//保存变量在 Application_OnEnd 事件函数使用 System.IO.StreamReader r=new System.IO.StreamReader (s); s=r.ReadLine(); r.Close(); Application.Add("AllVister",Convert.ToInt32(s)); (12) 在 global.asax 文件中的 Application_ OnEnd 事件处理函数中增加语句如下: //此时 Server 对象已不存在,无法用 Server 对象得到 counter_File 文件绝对路径 string s=(string)Application["counterFile"];//取出保存的文件的全路径地址 System.IO.StreamWriter w=new System.IO.StreamWriter (s,false);//建立新文件 int num=(int)Application["AllVister"]; w.Write(num.ToString()); w.Close(); (13) 再一次访问 WebForm1.aspx 网页,看是否已解决以上提出的问题。 11.5 Session 对象 用浏览器访问一个网站,从网站的一个网页跳转到另一个网页,有时希望为这些被访问 的网页中的数据建立某种联系,例如一个网上商店的购物筐,要记录用户在各个网页中所选 的商品。前边用 Cookie 实现了购物筐。用 Session 对象也可以解决类似问题。 当用户使用浏览器进入网站访问网站中的第一个网页时,Web 服务器将自动为该用户 创建一个 Session 对象,在 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.1 Session 对象的属性、方法和事件 这里只介绍 Session 对象常用的方法和事件。  属性 Count:在 Session 对象中建立的变量的项数。只读属性。 270  属性 Keys:Session 对象中的变量名也叫键,该属性得到所有键的集合。只读属性。  属性 Mode:Session 对象运行模式(只读),有 4 种模式,InProc:默认值,Session 数据 被保存在 Web 服务器的内存中。Off:Session 对象被禁用。SQLServer:使用 SQL Server 数据库存储 Session 数据。StateServer:将 Session 数据存储在远程服务器上。  属性 TimeOut:Session 对象超时时限(分钟为单位)。如果用户在该超时时限之内不刷新 或请求网页,则该用户的 Session 对象将终止。默认值是 2 0 分 钟。  方法 Abandon:删除 Session 对象中所有的变量并释放 Session 对象的资源。如果未明确 地调用 Abandon 方法,一旦超过属性 TimeOut 指定时间,服务器将删除 Session 对象。  方法 Add:加入一个变量到 Session 对象中,例如 Session.Add("string1","test"),表示向 Session 中加入一个名为 string1 的变量,其值为字符串"test",其实它的效果和 Session("string1")="test"以及 Session.item("string1")="test"是一样的。  方法 RemoveAll 和 Clear:清除 Session 对象中所有变量。  事件 Session_OnStart:当用户使用浏览器进入网站访问网站中的第一个网页时,发生 Session_OnStart 事件。服务器在响应请求页之前先执行该事件处理函数。在该事件处理 函数中可以判断用户是否登录,或判断是否首先访问了主页,这可用判断是否定义了某 Session 变量来实现,如果答案为否,可用 ResPone.Redirect 转向登录网页或主页。  事件 Session_OnEnd:当浏览器离开网站,或超过属性 TimeOut 指定时间没有请求或刷 新网站中的任何网页,该事件在 Session 对象被撤销前发生。 11.5.2 用 Session 对象实现网上商店购物筐 本例用 Session 对象实现网上商店购物筐。由于数据库 DepotI 的 goods 表要被网站中所 有网页使用,因此在 Application_OnStart 事件处理函数中,建立表 goods 的两个视图,一个 是所有水果的视图,一个是所有花卉的视图,并把视图类引用变量存到 Application 中,这 样视图类引用变量在整个 Web 应用程序运行期间都有效,在内存中的视图类对象就不会被 垃圾收集器撤销,每个网页都可以直接使用这些视图类对象。购物筐也采用一个表,其字段 和表 goods 相同,其中数量记录用户购买的数量。这个表在 Session_Start 事件处理函数中建 立,表类的引用变量存到 Session 中,表生命周期从用户访问开始,到用户离开网站结束。 水果和花卉分别用两个网页显示,每个网页中都有两个 DataGrid 控件,一个显示水果或花 卉,另一个显示用户选择的商品的编号、货物名称、所选商品的数量,即显示购物筐中所选 商品。用 VS.Net 实现的具体步骤如下: (1) 创建一个 Web 应用项目,项目名为 e11_5_2。 (2) 双击 VS.Net 集成环境右侧的解决方案管理器中的 global.asax 文件,打开 global 窗体, 右击 global 窗体,在弹出快捷菜单中单击"查看代码"菜单项,打开 global.asax 源文件。 在 global.asax 文件头部增加语句:using System.Data;using System.Data.OleDb;在 global.asax 文件中的 Application_OnStart 事件处理函数中增加语句如下: string s="Provider=Microsoft.Jet.OLEDB.4.0;"; s+="Data Source=D:\\ASP\\DepotI.mdb";//Data Source两词之间有空格 OleDbConnection conn=new OleDbConnection(s); s="SELECT * FROM Goods WHERE gID<3"; OleDbDataAdapter da=new OleDbDataAdapter(s,conn); DataSet ds=new DataSet(); da.Fill(ds,"Table1");//Table1是只有水果的表 s="SELECT * FROM Goods WHERE gID>2"; 271 da=new OleDbDataAdapter(s,conn); da.Fill(ds,"Table2");//Table2是只有花卉的表 DataView dw1=new DataView(ds.Tables["Table1"]);//水果视图 DataView dw2=new DataView(ds.Tables["Table2"]);//花卉视图 Application["dw1"]=dw1;//保存两个视图类引用变量 Application["dw2"]=dw2; (3) 在 global.asax 文件中的 Session_Start 事件处理函数中增加语句如下: DataView dw1=(DataView)Application["dw1"];//2 个 dw1 引用同一个视图类对象 DataTable dt=dw1.Table.Clone();//创建一个空表,字段和 goods 表相同 Session["dt"]=dt;//此表作为购物筐,在用户访问网站期间一直可用 (4) 在 WebForm1 类中定义 DataView 类和 DataTable 类变量: DataView dataView1;DataTable DataTable1; (5) 在 WebForm1 窗体中放置两个 DataGrid 控件,其属性 Name 分别为 DataGrid1、DataGrid2。 (6) 为 Page_Load 事件处理函数增加语句: private void Page_Load(object sender, System.EventArgs e) { dataView1=(DataView)Application["dw1"];//dataView1引用水果视图类对象 DataGrid1.DataSource=dataView1;//DataGrid1显示水果商品 DataGrid1.DataBind();//数据绑定 DataTable1=(DataTable)Session["dt"];//DataTable1引用购物筐 DataGrid2.DataSource=DataTable1.DefaultView;//DataGrid2显示购物筐中商品 DataGrid2.DataBind(); } (7) 右击控件 DataGrid1,在弹出快捷菜单中单击"属性生成器"菜单项,打开"DataGrid 属 性"对话框。在对话框左侧选中"常规",如图 10.9C,将 标题为"数据源(D):"的 ComboBox 控件置为空。选中"显示页眉"、"显示页脚"多选框,不选中"允许排序"多选框。在 "DataGrid 属性"对话框左侧选中"列",如图 10.9D,不选中"在运行时自动创建列"多选 框,在"可用列(A)"列表框中选中绑定列,单击标题为">"的按钮,将其移到"选定的列 (S)"列表框,增加一个绑定列,页眉为"编号",数据字段为 gID;用同样的办法增加另 外 2 个绑定列,页眉分别为"货物名称"、"货物数量",数据字段分别为 gName、gNum。 全部为只读列。在"可用列(A)"列表框中单击"按钮列"前的+号,展开树,可以看到选择 项,选中选择项,单击标题为">"的按钮,增加一个按钮列,页眉为"单击按钮购买", Text 属性为"购买",命令名为:BuyBtn,按钮类型为 LinkButton。 (8) 为按钮列增加事件 ItemCommand 的事件处理函数如下: private void DataGrid1_ItemCommand(object source, System.Web.UI.WebControls.DataGridCommandEventArgs e) { string s="gID="+e.Item.Cells[0].Text;//DataGrid1当前行第0列文本,即货物编号 DataRow[] foundRows=DataTable1.Select(s);//查找购物筐中是否有此编号商品 if(foundRows.Length==0)//购物筐中没有此编号商品,在记录购物的表中增加新记录 { DataRow dr=DataTable1.NewRow();//创建DataTable1表新记录 s=e.Item.Cells[0].Text;//DataGrid1当前行第0列文本,即货物编号 dr["gID"]=Convert.ToInt16(s);//新纪录的gID=货物编号 dr["gName"]=e.Item.Cells[1].Text;//DataGrid1当前行第1列文本,即货物名称 dr["gNum"]=1;//购买数量为1 DataTable1.Rows.Add(dr);//DataTable1表增加新记录 272 } else//购物筐中已有所选编号商品,在相应记录中数量字段加1 { object o=foundRows[0]["gNum"];//原购买商品数量 s=o.ToString();//用int n=foundRows[0]["gNum"]不能通过 int n=Convert.ToInt16(s); n++;//购买商品数量加1 foundRows[0]["gNum"]=n; } DataGrid2.DataBind(); }//还应该将所选货物的存量减 1,如付款购买,源数据库也应修改,如不买要恢复原数据 (9) 右击 DataGrid2,在弹出快捷菜单中单击"属性生成器"菜单项,打开"DataGrid 属性"对 话框。在对话框左侧选中"常规",如图 10.9C,将 "数据源(D):"的 ComboBox 控件置为空。 选中"显示页眉"、"显示页脚"多选框,不选中"允许排序"多选框。在"DataGrid 属性"对 话框左侧选中"列",如图 10.9D,不选中"在运行时自动创建列"多选框,在"可用列(A)" 列表框中选中绑定列,单击标题为">"的按钮,将其移到"选定的列(S)"列表框,增加一 个绑定列,页眉为"编号",数据字段为 gID;用同样的办法增加另外 2 个绑定列,页眉 分别为"货物名称"、"购买数量",数据字段分别为 gName、gNum。 (10) 放 HyperLink 控件到窗体,属性 Text="选择花卉",NavigateUrl 属性为 WebForm2.aspx。 (11) 单击 VS.Net 菜单"文件"|"添加新项(w)…"菜单项,出现"添加新项"对话框,在右侧选 中"Web 窗体"模板,窗体名为:WebForm2.aspx,单击"打开"按钮,创建新窗体 WebForm2。 (12) 按照为 WebForm1 窗体增加控件、变量和方法的步骤,为 WebForm2 窗体增加控件和变 量,只是在第 6 步中,Page_Load 事件处理函数增加语句 dataView1=(DataView1) Application["dw1"]修改为 dataView1=(DataView1)Application["dw2"]。在第 10 步中,控 件 HyperLink 的 NavigateUrl 属性为 WebForm1.aspx,属性 Text="选择水果"。 (13) 在浏览器中输入地址:http://Localhost/WebForm1.aspx,选中某种水果,转到第二个网页 WebForm2.aspx,选中某种花卉,购物筐中应显示所选的所有商品。本例不尽合理,读 者可以以此为基础修改,创建网上商店。 11.6 Server 对象 Server 对象提供对 Web 服务器资源进行访问的方法,主要包括:得到服务器的计算机 名称,设置脚本程序失效时间,将 HTML 的特殊标记转变为 ASCII 字符,得到文件的真实 路径等,本节将逐一介绍这些方法。使用 Server 对象也可以从一个网页传递数据到另一个 网页。 11.6.1 Server 对象属性和方法 这里只介绍 Server 对象常用的方法和事件。  属性 MachineName:该属性用来获取当前运行 Web 应用程序的 Web 服务器的计算机名 称,使用方法如下:string s=Server.MachineName;这个计算机名称可以用如下办法查到: 打开"控制面板",选中"系统"中的"计算机名",应和用 Server 对象的属性 MachineName 获得计算机名称一致。  属性 ScriptTimeout:Web 应用程序由于运行在计算机网络中,由于网络的原因,一些代 273 码可能无法完成,一直在等待,这将极大消耗 Web 服务器的资源,为了避免这种情况, 可以设置程序运行的最长时间,即设置属性 ScriptTimeout,在脚本程序运行超过属性 ScriptTimeout 指定时间之后即作超时处理,也就停止程序运行。如以下代码指定服务器 处理脚本程序在 100 秒后超时:Server.ScriptTimeout=100,其默认值为 90 秒。  方法 HtmlEncode 和 HtmlDecode:HTML 标记语言中,有些 ASCII 字符被作为标记,例 如字符串:
中的<和>都是标记,如需要显示这些字符,必须作特殊处理,例如为了 在浏览器中正确显示如下字符串:"
是换行标记",字符串必须写为如下形式: ; 也可以用 Server 对象的属性 HtmlEncode 方法,用法如下: Server.HtmlEncode(‖
是换行标记‖)
; 方法 HtmlDecode 对被 HtmlEncode 方法编码的字符串进行解码。例如: string s=Server.HtmlDecode(label1.Text);  方法 URLEncode 和 UrlDecode:在 URL 中,像?、&、/ 和空格这样的字符有特殊意义, 因此这些字符在 URL 中不能作为普通字符使用,用 HttpUtility.UrlEncode 方法将对具有 特殊含义字符做特殊处理。确保所有浏览器均正确地传输 URL 字符串中的文本,见例 子 e11_2_1C。方法 UrlDecode 对字符串进行 URL 解码并返回已解码的字符串,例如 String s= Server.UrlDecode(已编码字符串);  方法 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  方法 Transfer:终止当前网页,转向参数指定的 URL 路径的一个新页。例子见下节。 11.6.2 使用 Transfer 在网页之间传递数据 11.2.1 节介绍了用 Request 和 Response 对象在网页之间传递数据,本节介绍使用 Transfer 在网页之间传递数据。 例子 e11_6_2A:创建一个 Web 网页,将数据发送到另一个 Web 网页,网页文件如下: <%@ Page Language="C#" ClassName="FirstPageClass" %>
274 输入数据:
在网页的顶部的@Page 指令中,ClassName 属性设置本网页有效的类名,类名由程序员 定义。然后为要传递到另一个网页的每个值都定义一个具有 get 访问器的属性,get 访问器 返回要传递的值,本例是 Web 窗体的文本框输入的值。必须在服务器端脚本中定义这些属性。 当单击了按钮后,要将数据传递到另一个网页时,在按钮的事件处理行数中使用 Server.Transfer("e11_6_2B.aspx")语句,转向 e11_6_2B.aspx,同时传递数据。 例子 e11_6_2B:创建一个 Web 网页,接受另一个 Web 网页传递的数据,网页文件如下: <%@ Page Language="C#" %> <%@ Reference Page="e11.6.2A.aspx" %>
你好: 在网页的顶部增加指令:<%@ Reference Page="e11.6.2A.aspx" %>,其中 Page 属性值 为 e11.6.2A.aspx 网页。在服务器端脚本中声明变量:FirstPageClass fp,fp 将引用发送信息 的网页中定义的类的实例。Page_Load 事件处理程序中,用语句 fp=(FirstPageClass) Context.Handler 引用这个对象。用 fp.Data1 得到对象中的属性值。 例子 e11_6_2C:用 VS.Net 实现上述功能,具体步骤如下: (1) 创建一个 Web 应用项目,项目名为 e11_6_2C。 (2) 在 WebForm1 窗体中放置 TextBox 控件,Name 属性 textBox1。 (3) 在 WebForm1 类中增加属性 Data1 public string Data1 { get { return textBox1.Text; } } (4) 在 WebForm1 窗体中放置 Button 控件,单击事件处理函数如下: void button1_Click(object sender, EventArgs e) { Server.Transfer("WebForm2.aspx"); } 275 (5) 单击 VS.Net 菜单"文件"|"添加新项(w)…"菜单项,出现"添加新项"对话框,在右侧选 中"Web 窗体"模板,窗体名为:WebForm2.aspx,单击"打开"按钮,创建新窗体 WebForm2。 (6) 在 WebForm2 窗体中放置 Label 控件,Name 属性 Label。 (7) 为 WebForm2 类增加变量 public WebForm1 fp;这里 WebForm1 是在文件 WebForm1.aspx 定 义的类名。 (8) 在 Page_Load()增加语句如下: void Page_Load() { if (!IsPostBack) { fp = (WebForm1)Context.Handler; label1.Text=fp.Data1; } } (9) 在 WebForm2.aspx 文件 Page语句后增加语句:<%@ Reference Page="WebForm1.aspx" %> (10) 运行,打开 WebForm1.aspx,在 textBox1 输入数据,单击按钮,打开 WebForm2.aspx, 在其 label1 中显示输入的数据。 11.7 Cache 对象 Cache 对象生存期和 Application 对象生存期一样长,因此,也可以在 Cache 对象中建立 一些网站中所有网页都可使用的公用变量。例如,在 Cache 对象中增加一个 DataSet 类变量 用语句:Cache["myDataSet"]=DataSet1; 取出 DataSet 类变量用语句:DataSet dataSet1=(DataSet)Cache["myDataSet"]; 和 Application 对象不同,在 Web 服务器内存比较紧张时,为了提高 Web 服务器的性能, Cache 对象采用最近最少使用(LRU)方法自动清除不常用的变量。因此每次取出 Cache 对象 中的变量,要检查一下是否为 NULL,如果是 NULL,则要重新建立 DataSet 对象。 11.8 Config.web 配置文件 ASP.Net 的配置文件是基于 XML 格式的纯文本文件,保存在 Web 应用目录下,统一命 名为"config.web"。它决定了所在目录及其子目录的配置信息。在子目录中可以增加 Config.web 配置文件,并且子目录下的配置覆盖其父目录的配置。在操作系统安装目录 \Microsoft.Net\Framework\版本号\下的 config.web 为整个机器的根配置文件,它定义了整个 环境下的缺省配置。缺省情况下,浏览器是不能够直接访问目录下的 config.web 文件。在运 行状态下,ASP.Net 会根据远程 URL 请求,把访问路径下的各个 config.web 配置文件叠加, 产生一个唯一的配置集合。举例来说,一个对 URL: http://localhost\webapp\owndir\test.aspx 的访问,ASP.Net 会根据以下顺序来决定最终的配置情况: 1..\Microsoft.Net\Framework\v.1.00\config.web (缺省配置文件) 2..\webapp\config.web (应用的配置) 3..\webapp\owndir\config.web (自己的配置) ASP.Net 提供了一个丰富而可行的配置系统,以帮助管理人员轻松快速的建立自己的 WEB 应用环境。ASP.Net 提供的是一个层次配置架构,可以帮助 WEB 应用、站点、机器分 别配置自己的扩展配置数据。ASP.Net 的配置系统具有以下优点:  ASP.Net 允许配置内容可以和静态内容、动态页面和商业对象放置在同一应用的目录结 276 构下。当管理人员需要安装新的 ASP.Net 应用时,只需要将应用目录拷贝到新的机器上 即可。  ASP.Net 的配置内容以纯文本方式保存,可以以任意标准的文本编辑器、XML 解析器 和脚本语言解释、修改配置内容。  ASP.Net 提供了扩展配置内容的架构,以支持第三方开发者配置自己的内容。  ASP.Net 配置文件的更新被系统自动监控,无须管理人员手工干预。 VS.Net 为每一个 Web 应用项目自动建立了一个 config.web 文件如下,它是一个 XML 文件,这里只对 XML 文件的注解做了修改,请读者仔细研究该文件,理解其意义。 在config.web文件中除了以上设置外,还可以增加自定义标记,用来存储一些在运行中 不必修改的数据,例如数据库连接字符串,当把数据库位置移动时,只需修改config.web 文件中相关设置。在网页文件中可以用第12章介绍的读写XML文件的方法将有关的设置读出。 下边是一个例子: 习题 (1) 如何实现记录访问网站的在线人数。(提示:在 Session_End 事件函数中计数器减 1。) (2) 用 Application 对象建立一个 2 人聊天室。如果是多人聊天室,又如何实现。 (3) 如何防止用户不经过主页或企图不经过登录直接访问其它网页。 (4) 例子 e11_3_2 中从一个网页转到另一网页时,购物筐显示不正确,请修改。 (5) 例子 e11_3_2 和 e11_5_2 完全可使用一个网页,请问如何实现。 (6) 例子 e11_5_2 中,也可以把 DataSet 类变量存到 Application 中,请问如何实现。 (7) 例子 e11_3_1 中,单击刷新按钮,访问次数也加 1,这不合理,如何禁止。 (8) 修改例子 e11_5_2,不使用 Application 对象,改用 Cache 对象。 (9) 总结一下,从一个网页向另一个网页传递数据的方法。 (10) 创建一个网上书店,具有登录、注册功能,能查询指定书籍,用网页返回,具有购物筐 功能,选中商品放入后,显示的商品数量减少,购物后,原数据商品数量也要减少。 278 第十二章 可扩展标记语言 本章介绍 XML 可扩展标记语言的基本概念和使用,包括使用 XML 的必要性、XML 定 义,以及如何建立、显示和处理 XML 文档数据,XML 数据和数据库数据之间的转换等。 12.1 XML 可扩展标记语言的基本概念 XML 是基于文本的标记语言,它通过有意义的标签以结构化的格式存储数据,这种格 式可以被任何一种计算机系统所解释。本节介绍 XML 的基本概念。 12.1.1 HTML 及其缺点 Internet 提供了全球范围的网络互连与通信功能,Web 技术的发展更是一日千里,其丰 富的信息资源给人们的学习和生活带来了极大的便利。特别是应运而生的 HTML(超文本标 记语言),以简单易学、灵活通用的特性,使人们发布、检索、交流信息都变得非常简单, 从而使 Web 成了最大的环球信息资源库。然而,电子商务、电子出版、远程教育等基于 Web 的新兴领域的全面兴起,使得传统的 Web 资源更加复杂化、多样化,人们对 Web 服务功能 的需求也达到更高的标准。而 传统的 HTML 由于自身特点的限制,不能满足这些要求。HTML 主要有如下不足:  HTML 的标记都是预先定义的,用户不能自定义有意义的标记,可扩展性差。  HTML 的显示方式内嵌在数据中,这样在创建文本时,要同时考虑显示格式,如果因为 需求不同而需要对同样的内容进行不同风格的显示时,要从头创建一个全新的文档,重 复工作量很大。不能对数据按照不同的需求进行多样化显示。  HTML 缺乏对数据结构的描述,对于用程序理解文档内容、抽取语义信息都有诸多不便。 不能进行智能化的语义搜索。不能对不同平台、不同格式的数据源进行数据集成和数据 转化等。  HTML 语言不能描述矢量图形、数学公式、化学符号等特殊对象。 12.1.2 SGML(标准通用标记语言) SGML(Standard Generalized Markup Language)是一种通用的文档结构描述标记语言,为 文档数据的标记提供了异常强大的工具,同时具有极好的扩展性,因此在数据分类和索引中 非常有用。但 SGML 复杂度太高,不适合网络的日常应用,加上开发成本高、不被主流浏 览器所支持等原因,使得 SGML 在 Web 上的推广受到阻碍。 12.1.3 XML(可扩展标记语言) XML(eXtensible Markup Language)是由W3C于1998年2月发布的一种标准。它是SGML 279 的一个简化子集,它将 SGML 的丰富功能与 HTML 的易用性结合到 Web 的应用中。XML 的优点如下:  XML 简单易用,功能强大。  XML 允许各个组织、个人建立适合自己需要的标记集合,并且这些标记可以用通用的 工具显示。例如定义数学、化学、音乐等专用标记。  XML 的最大优点在于它的数据存储格式不受显示格式的制约。一般来说,一篇文档包 括三个要素:数据、结构以及显示方式。XML 把文档的显示格式从数据内容中独立出 来,保存在样式表文件(Style Sheet)中,这样如果需要改变文档的显示方式,只要修改样 式表文件就行了。  通过有意义的标签以结构化的格式存储数据,用一种开放的自我描述方式定义数据结 构,在描述数据内容的同时突出对结构的描述,从而体现出数据之间的关系,XML 的 自我描述性质能够很好地表现许多复杂的数据关系,使得基于 XML 的应用程序可以在 XML 文件中准确高效地搜索相关的数据内容,忽略其它不相关部分。  XML 还有其他许多优点,比如它有利于不同系统之间的信息交流,完全可以充当网际 语言,并有希望成为数据和文档交换的标准机制。 由于以上优点,XML 必将在商务的自动化处理,信息发布,智能化的 Web 应用程序和 数据集成等领域被广泛使用。 12.1.4 XML 的文档格式 首先介绍 XML 文档内容的基本单元——元素,它的语法格式如下: 〈标签〉文本内容〈/标签〉 元素是由起始标签、元素内容和结束标签组成。用户把要描述的数据对象放在起始标签 和结束标签之间。例如:<姓名>王平。无论文本内容有多长或者多么复杂,XML 元素中可以再嵌套别的元素,这样使相关信息构成等级结构。用这样的方法定义 XML 文档 和数据结构。 例子 e12_1_4:下面的例子是一个描述学生情况的 XML 文档,在<学生>元素中包括了 所有学生的信息,每个学生都由<学生>元素来描述,而<学生>元素中又嵌套了<编号>、< 姓名>、<性别>和<年龄>元素。完整 XML 文件 e12_1_4.xml 内容如下: <学生> <编号>001 <姓名>张三 <性别>男 <年龄>20 除了元素,XML 文档中出现的有效对象是:声明、注释、根元素、子元素和属性。  声明:声明给 XML 解析器提供信息,使其能够正确解释文档内容,它的起始标识是""。例如 XML 声明:,该声明 指明使用的 XML 版本号和文档使用的字符集是中文字符集"GB2312"。又如显示样式表 文件声明:,指明使用 e12_2_1.xsl 样式表文件显示本 XML 文档。  注释:注释是 XML 文件中用作解释的字符数据,XML 处理器不对它们进行任何处理。 280 注释文本被""标记,注释可以出现在 XML 元素间的任何地方,但是不可以 嵌套。下边是一个注释的例子:。  根元素和子元素:如果一个元素从文件头的序言部分之后开始,一直到文件尾,包含了 文件中所有的数据信息,我们称之为根元素。XML 元素是可以嵌套的,那么被嵌套在 内的元素称为子元素。在前面的例子中,<学生>就是根元素,<编号>就是<学生>的子 元素。一个 XML 文档中有且仅有一个根元素,其他所有的元素都是它的子元素。  属性:属性给元素提供进一步的说明信息,它必须出现在起始标签中。属性以名称/值成 对出现,属性名不能重复,名称与取值之间用等号分隔,取值用引号括起来。例如:< 工资 currency="US$"> 25000 ,上例中的属性说明了薪水的货币单位是美元。  XML 文档的基本结构:XML 文档的基本结构由序言部分和一个根元素组成。序言包括 了 XML 声明和 DTD 或 XSD 声明,DTD(Document Type Define,文档定义类型)和 XSD(XML Schema,XML 架构)都是用来描述 XML 文档的数据结构的。例如,在例 子 e12_1_4 的文档前面加上如下的序言部分,就构成了一个完整的 XML 文档:  格式良好的(Well-Formed)XML 文档:一个 XML 文档首先应当是格式良好的,格式良好 XML 文档的正式定义位于:http://www.w3.org/TR/REC-xml。格式良好的 XML 文档除 了要满足根元素唯一的特性之外,还包括: (1) 起始标签和结束标签应当匹配,结束标签是必不可少的。 (2) 大小写应一致,XML 对字母的大小写是敏感的,是完全不 同的两个标签,所以结束标签在匹配时一定要注意大小写一致。 (3) 元素应当正确嵌套,子元素应当完全包括在父辈元素中,下面的例子就是错误嵌套:
,正确的嵌套方式如下: 。 (4) 属性值必须包括在引号中,元素中的属性名是不允许重复的。 12.1.5 用 DTD 和 XML Schema 定义 XML 架构 DTD(Document Type Definition 文档类型定义)是 SGML 语言的组成部分,可以用来定 义 XML 文档的数据结构和组成结构的元素类型,可以看作一个或多个 XML 文档的模板。 使用 DTD 可以对一个 XML 文档的结构进行校验。它可以是一个独立文件,也可以直接放 在 XML 文档中。例如,例子 e12_1_4 的 DTD 文件如下: ]> 由于 DTD 采用了非 XML 的语法规则,不支持多种多样的数据类型,扩展性较差等原 因,W3C 提出了 XML Schema(XML 架构,XSD),在保留了并扩充了 DTD 原有的文档结构 说明能力的同时,克服了 DTD 的缺点。XML Schema 使用的例子见 12.3.5 节。 281 12.1.6 较复杂的 XML 文档 例子 e12_1_6:为了说明属性的用法,以及为显示 XML 文档提供一个例子,这里建立 一个较复杂的 XML 文档,有较多的数据。本例是一个描述书店中所有书籍的 XML 文档, 显示了 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 以上版本)浏览 e12_1_6.xml 文件,效果如上图。单击标记前的减号(或 加号),看一下效果。上图显示的数据,已用单击减号方法,将最后几本书的数据隐藏。 282 12.2 XML 文档显示 由于 XML 文档只是定义数据及其数据结构,并不包含显示的格式。如要按指定格式显 示这些数据,必须采用其它方法定义显示格式。本节介绍显示 XML 文档的一些方法。 12.2.1 用 XSL 文件显示 XML 文档 使用 CSS 文件或 XSL 文件可以定义 XML 文档的显示格式。这里使用两个 XSL 文件按 不同显示格式显示同一个 XML 文件。 例子 e12_2_1:首先定义第一个 xsl 文件 e12_2_1.xsl 显示 e12_1_4.xml 内容。文件如下: 将文件 e12_2_1.xsl 和 e12_1_4.xml 存到同一文件夹中。请注意 e12_1_4.xml 文件中的语 句,表示使用 e12_2_1.xsl 样式表文件显 示 e12_1_4.xml 文件。用 IE 打开 e12_1_4.xml 文件,显示效果如上图。 例子 e12_2_1B:定义第二个 xsl 文件 e12_2_1B.xsl,以不同的显示方式显示 e12_1_4.xml 文件。文件如下: 283
编号
姓名
性别
年龄
将文件 e12_2_1B.xsl 和 e12_1_4.xml 存到同一文件夹,修改 e12_1_4.xml 文件的第 2 条语句为:。用 IE 打开 e12_1_4.xml 文件,显示效果如上图。 12.2.2 使用 XML 控件显示 XML 文档 例子 e12_2_2:用 XML 控件也可以显示 XML 文档,XML 控件属性 DocumentSource 是要显示的 XML 文件,属性 TransformSource 是指定显示格式的 XSL 文件。下边是使用 XML 控件显示 e12_1_4.xml 文件的例子。显示效果和例子 e12_2_1B 相同。 <%@ Page Language="C#" %>

使用 Xml 控件示例

12.2.3 使用数据绑定方法显示 XML 文档 DataSet 类提供了若干方法处理 XML 文件,主要有:  GetXml():将 DataSet 中数据转换为 XML 格式,以字符串类型返回。  GetXmlSchema():将 DataSet 中数据转换为 XML 格式,以字符串类型返回其 XSD 架构。  ReadXml():将包括 XML 架构和数据的 XML 文件读入 DataSet。  ReadXmlSchema():将 XML 架构(XSD)文件读入 DataSet。  WriteXml():将 DataSet 中数据转换为 XML 格式写入 XML 文件,可包含或不包含架构。  WriteXmlSchema():将 DataSet 中数据转换为 XML 格式,将 XML 架构写入 XML 文件。 本节仅介绍将 XML 文档读入 DataSet,其它例子见 12.4 节。 284 例子 e12_2_3:XML 文档也可以作为控件的数据源,本例使用 e12_1_6.xml 作为 DataGrid 控件的数据源,用 DataGrid 控件把 XML 文档显示出来。网页文件如下: <%@ Import Namespace="System.Xml" %> <%@ Import Namespace="System.Data" %>

用数据绑定方法显示 XML 文档

网页的显示效果如上图。将 XML 文件读入 DataSet 后,就可以将此文件的内容看作一 个数据库表,例如本例为"book" , 该 表 也 可 以 用 ds.Tables[0] 表 示 , 表 名 为 : ds.Tables[0].TableName。 12.2.4 使用 VS.Net 建立和显示 XML 文档 例子 e12_2_4:本例使用 VS.Net 建立网页文件,用来显示 e12_1_6.xml 文档 (1) 创建一个 Web 应用程序框架,项目名为 e12_2_4。 (2) 在窗体中放置控件 DataGrid,其属性 Name=DataGrid1。 285 (3) 单击 VS.Net 菜单"项目"|"添加新项"菜单项,弹出标题为"添加新项"的窗口,在窗口中 选中 XML 文件,文件名为 MyXMLFile.xml,单击"打开"按钮,增加一个 XML 文件。 (4) 将 XML 文件 e12_1_6 内容拷贝到 MyXMLFile.xml 中。使用 VS.Net 创建 XML 文档,其 XML 文档编辑器为编写 XML 文档提供了一些支持,例如,当加入 XML 开始标记,将 自动增加 XML 结束标记,并能以网格的形式显示数据。 (5) 单击 MyXMLFile.xml 窗口下的"数据"标签,可以看到用表格显示的 XML 文件。 (6) 为 Page_Load 事件函数增加语句: private void Page_Load(object sender, System.EventArgs e) { DataSet ds = new DataSet(); ds.ReadXml(Server.MapPath("MyXMLFile.xml")); DataGrid1.DataSource=ds; DataGrid1.DataMember="book"; DataGrid1.DataBind(); } (7) 运行,可以看到用表格显示的 XML 文件,显示效果和例子 e12_2_3 相同。 12.2.5 将 XML 文件转换为 HTML 文件 例子 e12_2_5:本例将 e12_1_4.xml 文件,按照 e12_2_1B.xsl 定义的显示格式生成 Html 文件 e12_2_5.htm。用浏览器 IE 显示效果和例子 e12_2_1B 相同。网页文件如下: <%@ import namespace="System.Xml" %> <%@ import namespace="System.Xml.XPath" %> <%@ import namespace="System.Xml.Xsl" %>
可用浏览器 IE 显示这个 Html 文件。转换后的 Html 文件,有时不能正确显示中文,可 在标记后加入如下语句,表示使用中文。 286 12.3 对 XML 文档的处理 对 XML 文档的处理是指读取或查找 XML 文档中指定数据或标记、用程序生成 XML 文档,修改 XML 文档等。当前处理 XML 文档的方法主要有两种:DOM (Document Object Model)和 SAX(Simple API for XML)。在 .Net 框架的 System.XML 命名空间为处理 XML 文档 提供了若干类。.Net 框架支持 DOM,提供了 XmlDocument 类可以按节点读出或查找数据及 元素,还可以增加节点,修改数据。.Net 框架不支持 SAX,但可以使用 XmlTextReader 类 对 XML 数据流进行快速顺序只读访问,按节点读出或查找指定数据或标记。提供了 XmlTextWriter 类可用语句快速顺序生成 XML 文件。本节介绍这些方法。 12.3.1 使用 XmlTextReader 类读 XML 文件 XmlTextReader 类可以读取 XML 文件,但只提供非缓存的只进、只读访问。这意味着 使用 XmlTextReader 无法编辑属性值或元素内容,也无法插入和移除节点。 例子 e12_3_1:本例用来读出 e12_1_6.xml 文件中每本书的书名、作者、出版日期、价 格等数据。使用 XmlTextReader 类读 XML 文档各种元素只能顺序读出。运行效果如下图。 <%@ Import Namespace="System.Xml" %>
读 XML 文件数据
在 XML 文档结构中,把 XML 文档的基本组成单元叫做节点,例如例子 e12_1_4 中的 XML 文档中,<学生>、<编号>、001、、<姓名>…都是节点。XmlTextReader 类的 方法 Read()读 Xml 文档时,按节点在 XML 文档中的顺序逐一读出每一个节点。 XML 文档的节点分为两大类,第一类是文本节点,即 XML 文档的数据。在两个标记 之间的文本被称为一个文本节点,例如,<书名>SQL 实用全书中的"SQL 实用全书" 是一个文本节点。文本节点的类型是 XmlNodeType.Text,dr.Value 为数据(dr 意义见上例)。 第二类是非数据节点,它又可以分为以下几大类:注释节点、声明节点、开始标记节点, 结束标记节点,统称为 Xml 文档的非数据节点。例如,是注释节点, 287 节点类型为:XmlNodeType.Comment,dr.Value 为注释的内容,这里为"这是一个注释"。 是声明节点,声明节点的节点类型为: XmlNodeType.XmlDeclartion。本声明节点包括两个声明:xml version="1.0"和 encoding= "GB2312"。dr.Name 为声明的名称,这里为 xml version 和 encoding;dr.Value 为声明的值, 这里为"1.0"和"GB2312"。是开始标记节点,其节点类型 为:XmlNodeType.Element。dr.Name 为标记名称,这里为 book。出版社被称为属性名字 (Name),"电子工业出版社"被称为属性的值(Value),可以有多个属性,dr.AttributeCount 表 示属性的个数,用方法 dr.GetAttribute(i)得到第 i 个属性的值,如果要同时得到属性名字和 属性的值,可以使用方法 dr.MoveToFirstAttribute() 移到第 1 个属性,方法 dr.MoveToNextAttribute()移到下 1 个属性,然后用 dr.Name 得到属性名字,用 dr.Value 得到 属性的值。是结束标记,节点类型为:XmlNodeType.EndElement。dr.Name 得到标 记名称,这里为 book。 本网页的 Page_Load 方法中,用 dr.Read()读 Xml 文档,每次读出一个节点的数据,用 语句 if(dr.NodeType==XmlNodeType.Text)判断是否是文本节点,如果是文本节点,则把文本 内容加到 ListBox1。 12.3.2 使用 XmlTextReader 类读 XML 文档标记 例子 e12_3_2A:本例用来读出 e12_1_6.xml 文件 book 标记的属性。具体内容如下: <%@ Import Namespace="System.Xml" %>
读 XML 文件开始标记的属性
例子 e12_3_2B:如果显示 e12_1_6.xml 文档注释,修改上例 Page_Load 方法如下: public void Page_Load(Object sender, EventArgs e) { XmlTextReader dr= new XmlTextReader(Server.MapPath("e12_1_6.xml")); while(dr.Read()) if(dr.NodeType==XmlNodeType.Comment) ListBox1.Items.Add(dr.Value); } 288 例子 e12_3_2C:如果显示 e12_1_6.xml 文档声明,修改上例 Page_Load 方法如下: public void Page_Load(Object sender, EventArgs e) { XmlTextReader dr= new XmlTextReader(Server.MapPath("e12_1_6.xml")); while(dr.Read()) if(dr.NodeType==XmlNodeType.XmlDeclaration) ListBox1.Items.Add(dr.Name+" "+dr.Value); } 12.3.3 使用 XmlTextWriter 类写 XML 文档 XmlTextWriter 类提供了快速、非缓存、只进方法生成 XML 文档的方法,可以生成包 含 XML 数据的流或文件。该类属性 Formatting 为 Formatting.None,表示不使用特殊的格式 设置 XML 文档,这是默认选项;如果为 Formatting.Indented,表示使子元素根据 Indentation 和 IndentChar 设置缩进。 <%@ Import Namespace="System.Xml" %>
用程序写出的 XML 文档的书局格式如下: <学生 编号="001"> <姓名>张三 <性别>男 <年龄>20 289 12.3.4 文档对象模型(DOM)使用 文档对象模型(DOM)类是 XML 文档在内存中表示形式。DOM 使程序员能够以编程方 式读取、操作和修改 XML 文档。DOM 的节点的概念和 12.3.1 节中叙述的概念完全相同, 因此也可以使用类似 XmlTextReader 类的方法读出 XML 文档的数据和非数据节点。下边仅 给出读出 XML 文档的数据的例子,读 XML 文档中的非数据节点请读者完成。 例子 e12_3_4A:使用 XML 文档对象模型(DOM)读出 e12_1_6.xml 文件中每本书的书名、 作者、出版日期、价格等数据。 <%@ Import Namespace="System.Xml" %>

使用 Xml 文档对象模型

例子 e12_3_4B:用文档对象模型(DOM)创建一个 XML 文档。 <%@ Import Namespace="System.Xml" %> <%@ Import Namespace="System.IO" %>

运行此网页创建一个 XML 文档,请用 IE 察看

290 例子 e12_3_4C:为例子 e12_3_4B 中创建的 XML 文件 e12_3_4B.xml 增加一个新节点。 <%@ Import Namespace="System.Xml" %> <%@ Import Namespace="System.IO" %>

运行此网页增加一个新节点,请用 IE 察看

也可以用方法 InsertAfter 在参数 2 指定的节点后插入参数 1 指定的节点,用方法 InsertBefore 在参数 2 指定的节点前插入参数 1 指定的节点,方法的参数 1 是要插入的节点, 例如上例的 elem, 参数 2 指定 1 个节点,是插入的参考位置,例如:root.FirstChild。 例子 e12_3_4D:为例子 e12_3_4B 中创建的 XML 文件 e12_3_4B.xml 增加一个新属性。 <%@ Import Namespace="System.Xml" %> <%@ Import Namespace="System.IO" %>

运行此网页增加一个属性,请用 IE 察看

例子中的语句 doc.DocumentElement 为 XML 文档的根节点,本例为 book 节点。 291 doc.DocumentElement.Attributes 语句得到根节点的所有属性。任何 XML 文档节点都可用 XmlNode 节点类对象来代表,XmlNode 类属性 Attributes 表示开始标记节点中的所有属性, 该属性是 XmlAttributeCollection 类对象,可以象操作一个普通数组那样修改该节点的属性。 例中用 Append 方法为根节点增加了一个属性。attrColl.InsertAfter(newAttr, attrColl.ItemOf(0)) 则表示在第 1 个属性之后增加新属性,attrColl.InsertBefore(newAttr, attrColl[0]) 则表示在第 1 个属性之前增加新属性。其它方法,例如 Remove、RemoveAll 等方法意义可以察看 XmlAttributeCollection 类帮助文档。请注意对这个数组的操作,就是对节点属性的操作。 例子 e12_3_4E:查找 e12_1_6.xml 文档指定节点,修改该节点数据。网页文件如下,第 1 条语句中的 Debug="true"表示允许调试,当发现错误时,在 IE 浏览器中显示错误信息。 <%@ Page Language="C#" Debug="true" %> <%@ Import Namespace="System.Xml" %> <%@ Import Namespace="System.IO" %>

运行此网页修改指定节点数据,请用 IE 察看

例子 e12_3_4F:本例显示 XML 文件 e12_1_6.xml 的所有书名。 <%@ Page Language="C#" Debug="true" %> <%@ Import Namespace="System.Xml" %> <%@ Import Namespace="System.IO" %>
使用 Xml 文档对象模型要把整个 XML 文件到内存,形成 DOM 结构,但可以修改 XML 文档。例子 e12_2_5 也是使用 Xml 文档对象模型的例子。 12.3.5 用 XML Schema 验证 XML 架构 一个 XML 文档首先应当是格式良好的,为了验证格式的正确性,可以使用 XML Schema (XML 架构,XSD)对一个 XML 文档进行验证,下例介绍验证的具体方法。 例子 e12_3_5:首先按 12.4.3 节方法为文件 e12_1_4.xml 建立 XSD 文件 e12_1_4.xsd, 然后用下边的网页文件用 e12_1_4.xsd 对 e12_1_4.xml 的架构进行验证。 <%@ import namespace="System.Xml" %> <%@ import namespace="System.IO" %> <%@ import namespace="System.Xml.Schema" %>
293 12.4 数据库和 XML XML 提供了异构数据库之间交换数据的一种方法。本节介绍这种方法。 12.4.1 数据库数据存为 XML 文档 察看 e12_1_6.xml 文件和数据库的表的对应关系,标记之间的内容可以看作 一个数据库的表,标记之间的内容可以看作一个数据库的表的一个记录,标记<书 名>、<作者>、<出版日期>、<价格>可以看作一个数据库的表的字段,这些标记之间的文本 可以看作这些字段的数据。因此,可以用 XML 文档来表示一个数据库表。由于 XML 文档 可以被任何一种计算机系统所解释,因此 XML 提供了异构数据库之间交换数据的一种方法。 数据库表的字段还有一些其它属性,例如,字段的数据类型,为了表示这些属性及其数 据库表结构,可以使用 DTD(Document Type Define,文档定义类型)或 XML Schema 来描 述 XML 文档的数据结构和组成结构的元素类型。微软的.Net 系统支持用 XML Schema 来描 述 XML 文档的数据结构。VS.Net 提供了将数据库表存为带 XML 架构和不带 XML 架构 XML 文件的方法,下边例子介绍实现的具体步骤。 例子 e12_4_1:将数据库 studentI.mdb 中的 studnt 表存为带 XML 架构或不带 XML 架构 的 XML 文件。使用 VS.Net 建立这个 ASP.Net 网页的具体步骤如下: (1) 创建 Web 应用程序项目,项目名为 e12_4_1。 (2) 按照 8.12 节的例子 e8_12 中的第(4)步到第(8)步创建 OleDbConnection 对象、 OleDbDataAdapter 对象和数据集 DataSet 对象。 (3) 在窗体中放置控件 DataGrid,其属性 Name=dataGrid1,属性 DataSource 为 dataSet11, 属性 DataMember 为 Student。 (4) 在 Page_Load 函数中增加语句如下: oleDbDataAdapter1.Fill(dataSet11); DataGrid1.DataBind(); (5) 运行,应能在 DataGrid 控件中看到数据库 studentI.mdb 中的 studnt 表的数据。 (6) 增加一个按钮,属性 ID 为 Button1,属性 Text 为"将数据库表存为带 XML 架构 XML 文 件",为其增加事件处理函数如下: private void Button1_Click(object sender, System.EventArgs e) { string s="D:\\asp\\e12_4_1\\e12_4_1A.xml"; dataSet11.WriteXml(s,XmlWriteMode.WriteSchema); } (7) 增加一个按钮,属性 ID 为 Button2,属性 Text 为"将数据库表存为不带 XML 架构 XML 文件",为其增加事件处理函数如下: private void Button1_Click(object sender, System.EventArgs e) { string s="D:\\asp\\e12_4_1\\e12_4_1B.xml"; dataSet11.WriteXml(s,XmlWriteMode.IgnoreSchema); } (8) 运行,单击两个按钮,可以创建带 XML 架构和不带 XML 架构 XML 文件,文件名为 "e12_4_1A.xml"和"e12_4_1B.xml"。用浏览器察看这两个 XML 文件,可以看到它们的 区别。不使用 VS.Net,使用记事本建立这个 ASP.Net 网页不能完成此功能。 294 12.4.2 读 XML 文档到 DataSet 对象 例子 e12_4_2:把 12.4.1 节生成的带 XML 架构或不带 XML 架构 XML 文件用控件 DataGrid 显示。用记事本生成的网页文件如下: <%@ Import Namespace="System.Xml" %> <%@ Import Namespace="System.Data" %>

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

12.4.3 为自建的 XML 文档建立 XSD 文件 自己创建的 XML 文档,如果用手工创建 XML Schema 是比较困难的,下例介绍如何使 用 VS.Net 为一个 XML 文档创建 XML Schema。 例子 e12_4_3:为自建的 XML 文档 e12_1_4.xml 建立 XSD 文件。 (1) 运行 VS.Net,打开 e12_1_4.xml 文件。 (2) 单击 VS.Net 菜单"XML"|"创建架构"菜单项,将创建 e12_1_4.xsd 文件,打开此文件,可 以修改每个字段的数据类型。然后存盘。 (3) e12_1_4.xsd 也是一个 XML 文档,请用 IE 浏览器打开 e12_1_4.xsd,察看 e12_1_4.xsd 的内容。 习题 (1) 创建一个记录某专业学生学习科目的 XML 文档。 (2) 读出 e12_1_6.xml 文件中所有开始和结束标记,用 ListBox 控件显示。 (3) 用 Xml 文档对象模型(DOM)读 e12_1_6.xml 文件开始和结束标记,用 ListBox 控件显示。 (4) 编写一个程序,读 e12_1_4.xml 文件,在网页中按照如下格式输出: <学生> 295 <编号>001 <姓名>张三 <性别>男 <年龄>20 (5) 编写一个程序,读 e12_1_6.xml 文件到 DataSet 中,用 TextBox 控件显示每一个记录。(提 示:将 XML 文件读入 DataSet 后,就可以将此文件的内容看作一个数据库表。) (6) 例子 e12_3_1 中如果希望只显示书名,如何实现?(提示:先找开始标记为‖书名‖的节点, 如果找到,读下一节点,用 dr.Value 得到书名。) (7) 修改例子 e12_3_2A,使其能够读出节点属性的名称和和属性的值。 (8) 修改例子 e12_3_4E,查找例子 e12_1_6.xml 中作者名字为张三的书的价格为 20.00 元。 (9) 对 e12_1_6.xml 的架构进行验证。 (10) 用记事本程序编制的网页文件,可以将 SQL2000 数据库系统的例子数据库 NorthWind 的表 Employees 存为带 XML 架构和不带 XML 架构 XML 文件。请写出网页文件。 (11) 在 8.13 节,用 xsd 文件记录了两个表的主从关系,如果要把两个表以及两个表的主从关 系转换为 XML 文档,则 xsd 文件要单独存为一个文件,可以使用方法 WriteXmlSchema() 将 DataSet 中架构写入 XML 文档。请把 8.13 节例子中数据库数据用 XML 文档保存。 296 第十三章 Web 服务 Micosoft.Net 平台架构中的分布式系统主要包括两部分:用 ASP.Net 技术构建服务器端 动态网页,以及 Web 服务(Web Service 或 XML Web Service)。前边章节已详细介绍了构建服 务器端动态网页的方法,本章将介绍 Web 服务的基本概念和构建方法。 13.1 Web 服务的概念和用途 Web 中无论是静态网页还是动态网页,数据都被嵌入到网页中,网页的服务对象都是 人,用户可以很容易阅读这些网页。但如果一个程序使用这种方式获得数据,会是十分困难 的,程序必须从网页中把数据分离,才能加以利用。而用一个程序在 Web 中获得数据有时 又是十分必要的,例如:一个气象台总站希望通过 Internet 获得各个基层气象台的各种资料, 在网上以统一的网页对外发布。气象台总站希望各个基层气象台提供一个 Internet 的服务, 能根据总站的要求,自动提供相应的资料。类似的例子很多,例如一个大单位的总部和下属 单位之间信息系统的整合,一个综合网站希望自动获得其它网站提供的信息等等。这种需求 实际上就是 Web 服务。 为实现这种功能有很多困难,各个基层气象台使用的系统可能完全不同,即使使用相同 的操作系统,也可能使用不同数据库系统,数据库中定义的字段可能不同,数据库应用程序 可能使用不同的语言编制,即使这些完全相同,还可能数据的表示方式不相同,例如数据格 式,数据的位数等等。为解决这些问题,已提出了许多方案,例如:微软的分布式控件对象 模型(DCOM)、对象管理组织(OMG)的公用对象请求代理程序体系结构(CORBA)、SUN 公司的远程方法调用(RMI)等等,但这些方法都不能很好的解决以上问题。 Micosoft.Net 的 Web 服务为实现这种功能提供了完整的解决方案。Web 服务使用 XML 描述数据,保证程序可以理解数据;用 SOAP 协议把 XML 数据封装为消息,SOAP(Simple Object Access Protocol,简单对象访问协议)是基于 XML 的消息传递协议,由于是基于 XML,保证了不同系统的不同程序之间,只要支持 SOAP,都可以互相通信;用 Http 协议 在 Internet 上传输 SOAP 消息。用 Micosoft.Net 的 Web 服务实现气象台总站所需功能的大概 思路是这样的,每个基层气象台在自己的系统中提供一个 Internet 远程调用函数,该函数用 HTTP 协议接受用 SOAP 表示的调用,并把函数的返回值用 XML 扩展标记语言描述,用 SOAP 表示后,用 HTTP 协议返回给调用者。气象台总站只要使用 HTTP 和 SOAP 协议逐一 调用这些 Web 远程函数,就可以获得各个基层气象台返回的用 XML 表示的资料了,用程序 很容易分析、提取 XML 表示的资料中的数据。由于这些协议都是被不同系统广泛接受的协 议,XML 数据可以被不同系统的不同程序所理解,也就解决了以上所提出的问题。 由以上叙述可知,Web 服务不追求代码的可移植性,而是提供一个可行的解决方案来 实现不同系统程序之间的数据通讯和理解。有许多 Web 服务的定义,比较简单又比较容易 理解的描述是:Web 服务是一个独立于系统的,可通过 HTTP、SOAP 和 XML 协议进行访 问的 Web 远程函数库。 刚才讨论的问题只是 Web 服务的几个应用,还有许多其它用途,例如:  应用程序集成 可以使用 Web 服务以一种集成的方式整合表面上看上去完全不同的现有应用程序。例 297 如许多公司的每个部门都有定制的软件,产生一系列有用但是孤立的数据和业务逻辑。为了 管理上的方便,非常有必要把这些应用程序功能集合到一起。利用 Web 服务,就有可能把 现有的应用程序中的数据和功能以 Web 服务方式提供给其它部门。然后可以创建一个集成 的应用程序,增强各部门之间的互操作性。  代码复用 在软件开发行业,大部分软件开发者都依赖代码复用。过去软件开发者为了利用他人已 经实现的代码,或者将代码段复制到自己的代码中,做一些改动以适应自己得需要,或者在 服务器或个人计算机上安装一个库,让应用程序来访问这个库。这将使得代码有很多个版本, 而这些版本间可能只有细微差别,却分散在各个地方。当代码最初的开发者决定对代码更新 一下或者改正一下错误,要把这些改变告诉所有使用这些代码的开发者的时候,将是非常困 难的。如果我们把代码放在一个中心位置存储,让所有人都访问这儿,这不是很好吗?这样 原创者可以在做了一些增补或者修正之后,能够立即提供给所有使用它的人。用 Web 服务 可以实现以上设想,远程调用 Web 服务中的方法,就象调用本地函数一样方便。  工作流程解决方案 有些工作是非常复杂的,例如货物的运输,可能要使用多种交通工具,火车、汽车、轮 船等,是一个非常复杂的流程,流程的每一个环节都由不同部门的不同的程序进行控制,如 何建立这些控制程序之间的联系,是十分重要的。使用 Web 服务是一个很好的解决方案。 通过 Web 服务,使各个流程控制程序建立联系,完全实现货物运输的自动控制和管理。  新的销售方式 现在软件的销售方式一般是用户把软件买回去,安装在自己的计算机中。有了 Web 服 务,就可以提供软件的使用服务,用户可以远程使用应用软件,按次收费。  由 Web 服务组成的自动化系统 不远的将来,信息家电将要联接到 Internet 网上,PDA、手机,甚至各种嵌入式设备也 要上网,这些设备和其它设备之间通过 Web 服务建立联系也是一种可行的方案。 13.2 建立 Web 服务 Web 服务仍采用客户/服务器模式(Client/Server)。本节介绍在服务器端应做的工作, 包括建立供客户端调用的 Web 服务方法,以及为了客户端使用 Web 服务方法,提供给客户端 描述该 Web 服务的 WSDL 文档。 13.2.1 用记事本建立 Web 服务 例子 e13_2_1A:建立一个 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)//注意形参的名称将在图 13.2.1C 中出现 { return a+b; 298 } //其它 WebMethod } 在文件中,第一行的语句表示这是一个 Web 服务文件,使用 C#语言,Web 服务的类名 是 MyClass。第 4 条语句创建了一个以 WebService 类为基类的派生类 MyClass,该类必须是 一个公有类。建立的 Web 服务类不必以 WebService 类为基类,但从 WebService 类派生,可 获得对 ASP.Net 对象(例如 Application 和 Session)的访问权限。如以 WebService 类为基类 必须引入命名空间 System.Web.Services。可供网上其它程序访问的方法叫 Web 服务方法, 在其头部必须增加关键字[WebMethod],表示这个方法是一个 Web 服务方法,这个方法必须 是一个公有方法。 建立文件后,以 asmx 为扩展名存盘,存到网站的宿主目录中或其任意子目录中,本例 以 e13_2_1.asmx 存到网站的宿主目录下的 WriteBook 目录中。现在使用浏览器测试这个 Web 服务方法,使用 URL 定位这个 Web 服务方法,在浏览器中 URL 地址栏中输入如下地址: http://localhost/ WriteBook/e13_2_1.asmx,浏览器中显示如图 13.2.1A。 图 13.2.1A 在网页中可以看到建议:"此 Web 服务使用 http://tempuri.org/作为它的默认命名空 间。建议:在使 XML Web service 公开之前,请更改默认命名空间"。这段话的意思是 说为了和其它 Web 服务相区别,在发布 Web 服务时,必须有唯一的命名空间,可以使用 自己单位的网址或 IP 地址作为此命名空间。修改 e13_3_1A.asmx 为 e13_3_1.asmx 如下: <%@ WebService Language="C#" Class="MyClass"%> using System; using System.Web.Services; [WebService(Namespace="http://202.206.96.204/")] public class MyClass:WebService { [WebMethod] public int MyWebMethod (int a,int b) 299 { return a+b; } //其它 WebMethod } 再一次用网址http://localhost/e13_2_1.asmx 运行,将不出现建议部分,如图13.2.1B 所示。 图 13.2.1B 点击 MyWebMethod,浏览器中显示如图 13.2.1C。 图 13.2.1C 在编辑框中输入两个加数分别为 10 和 20,然后点击"调用"按钮,打开另一浏览器 窗口如图 13.2.1D,这是用 XML 标记表示的调用 Web 服务方法 MyWebMethod 返回的结 果。请注意,这里运行只是一种测试,实际上应在其它计算机上生成一个调用此 Web 300 服务的客户端程序,可以是 Windows 应用程序,也可以是控制台程序,或者是 ASP.Net 应用程序,即可以是 Micosoft.Net 系统程序,也可以是其它系统程序,例如 Java 程序, Linux 程序等等,下节将介绍这方面的知识。 图 13.2.1D 13.2.2 用 VS.Net 建立 Web 服务 如果使用 VS.Net 建立这个 Web 服务文件,具体步骤如下: (1) 新建 asp.Net web 服务项目,在"位置(L)"编辑框中键入:http://localhost/e13_2_2,其 中 e13_2_2 就是项目的名字。单击"确定"按钮,创建项目。 (2) 可以看到标题为"Service1.asmx.cs(设计) "窗口。右击该文件,在弹出快捷菜单中单 击"查看代码(C)" 菜单项,打开 Service1.asmx.cs 源文件如图 13.2.2。 图 13.2.2 301 (3)为了在访问时不出现建议提示,在 Service1 类定义的前边加入如下语句: [WebService(Namespace="http://202.206.96.204/")]。 (4) 文件中已有一个 Web 服务方法 HelloWorld,但被注释掉。下面增加一个 Web 服务方 法 MyWebMethod 如下: [WebMethod] public int MyWebMethod(int a,int b) { return a+b; } (5) 现在用 IE 浏览器测试这个 Web 服务,按 F5 运行,将显示类似图 13.2.1B 的界面(类 名不同),点击 MyWebMethod,浏览器中显示如图 13.2.1C。在编辑框中输入两个加数 分别为 10 和 20,然后点击"调用"按钮,打开另一浏览器窗口如图 13.2.1D,这是用 XML 表示的调用 Web 服务方法 MyWebMethod 返回的结果。 13.2.3 服务描述语言(WSDL) Web 服务提供了一种服务,允许 Internet 上的计算机使用 HTTP 和 SOAP 协议远程调用 Web 服务方法。大家都知道,为了使用一个函数,首先要看一下函数的使用说明。Web 服 务方法也存在同样的问题,特别是 SOAP 协议,它采用 XML 标记语言描述 Web 服务中传 递的消息,而 XML 标记语言是可以定义自己的标记的,但 SOAP 并没有提供一种通用的 XML 标记供 Web 服务使用,不同的 Web 服务中 SOAP 的 XML 标记定义可能不同。因此, 为了使不同系统调用其它系统中的 Web 服务,必须对调用 Web 服务的方法及 Web 服务返回 的数据的格式做详细说明即服务描述,而且这种描述也应采用被广泛接受的协议。 WSDL(Web Services Description Language)中文名称为 Web 服务描述语言,用来对 Web 服务进行说明。WSDL 是基于 XML 的,用 WSDL 生成一个 XML 文档,可以提供关于 Web 服务的操作信息,例如,抽象形式的服务接口信息、数据传输的具体访问协议和格式、供客 户端使用该 Web 服务的细节等等。服务描述是一个使用 WSDL 语言的 XML 文档,定义了 Web 服务能理解的 Web 服务消息格式。服务描述起一个协定的作用,用来定义一个 Web 服务的行为并且指示潜在的客户如何与之交互。WSDL 文档最重要的的用途就是在客户端 生成代理类,详情见 13.3.3 节。 由于在 VS.Net 可以自动生成 WSDL 文档,这里就不介绍 WSDL 了。可以通过下边方 法看到 VS.Net 自动生成的 WSDL 文档,例如查看上节生成的 Web 服务,在浏览器中 URL 地址中输入 http://localhost/ e13_2_2/Service1.asmx?WSDL,浏览器中显示该 Web 服务 WSDL 文档。或者单击图 13.2.1B 中的服务说明,也可看到这个 WSDL 文档。 13.3 基于.Net 的 Web 服务客户端程序 Web 服务客户端程序是用来调用服务器端的 Web 服务方法,前边使用浏览器调用 Web 服 务方法,只能算做一种测试,通过这种测试,可以验证 Web 服务方法的正确性,发现错误。 作为客户端程序,无论在何处,采用那种操作系统,希望只要知道 Web 服务的所在网址,就 可以调用其相关 Web 服务方法。Web 服务客户端程序一般应在 Web 网上的另一台计算机中, 仅仅做实验或学习,也可以和 Web 服务在同一台计算机中。本节介绍如何实现基于.Net 的 Web 服务客户端程序。 302 13.3.1 调用 Web 服务方法采用的协议 客户端可采用 3种协议调用 Web 服务方法: HTTP-GET、HTTP-POST 和SOAP 协议。HTTP-GET 和 HTTP-POST 协议是 Web 网页传递参数的标准协议。使用 HTTP-GET 调用 e13_2_1.asmx 中 的 Web 服务方法,可在 IE 浏览器的地址栏中输入如下内容,将返回图 13.2.1D 网页,注意 a 和 b 是 Web 服务方法的形参名称。 http://localhost/WriteBook/e13_2_1.asmx/MyWebMethod?a=10&b=20 如果使用 HTTP-POST 调用 e13_2_1.asmx 中的 Web 服务方法,可采用如下网页:
输入第 1 个加数 a:
输入第 2 个加数 b:
用记事本程序输入此网页,注意 a 和 b 是 Web 服务方法的形参名称,以 e13_3_1.htm 为文件名存入 e13_2_1.asmx 所在的文件夹。用浏览器打开此网页,浏览器显示效果如下。 按图所示输入两个加数,单击调用 Web 服务按钮,将返回图 13.2.1D 网页。 图 13.3.1A 使用 HTTP-GET 和 HTTP-POST 协议传递 Web 网页参数只能采用变量名/变量值对,无法传 递象 DataSet、二进制文件等数据类型,为了传递复杂数据类型,可采用 SOAP 协议。 SOAP(Simple Object Access Protocol,简单对象访问协议)是基于 XML 的消息传递协议, 由于是基于 XML,保证了不同系统的不同程序或组件之间,只要支持 SOAP,都可以互相通信, 更多 SOAP 规格的信息,请看 W3C Web 站点(http://www.w3.org/TR/soap)。如使用 SOAP 协议调用 e13_2_1.asmx 中的 Web 服务方法,必须对调用的 Web 服务方法和方法参数用 SOAP 协议封装,实现比较复杂,.Net 系统一般采用创建代理类的方法,因此不再介绍 用 SOAP 封装的方法了,以后章节将详细介绍创建代理类的知识。 13.3.2 VS.Net 建立 Web 服务客户端程序 Web 服务客户端程序用 SOAP 协议调用远程的 Web 服务方法,Web 服务客户端程序 303 必须把程序的调用及其参数转化为 SOAP 协议,传送到 Web 服务。调用 Web 服务方法后, Web 服务方法返回以 XML 表示的结果,还需要对 XML 文档进行解析,得到指定类型的数 据。这些工作比较繁琐。程序员希望采用象普通编程语言调用一个方法那样调用 Web 服务 方法,返回指定类型的数据。.Net 系统采用创建代理类的方法实现这一目的。所谓代理, 就是在客户端生成本地对象,作为远程 Web 服务方法的前端,该代理的功能是,Web 服务 客户端程序用一般程序语言那样调用 Web 服务方法,代理程序负责转换为 SOAP 协议,用 SOAP 协议调用 Web 服务方法,由代理程序负责获得 Web 服务方法返回的数据,由于这 些数据也用 SOPA 协议表示,也要由代理程序转换为一般程序语言能够理解的数据类型, 传送给 Web 服务客户端程序。 VS.Net 工具提供了在 Web 服务客户端创建代理类的方法,客户端程序不必一定和 Web 服务在同一台计算机中,可以在任意一台 Internet 网中的计算机中。客户端程序可以是 windows 应用程序、ASP 应用程序,也可以是另一个 Web 服务程序。下边以创建 windows 应用程序为例,介绍在 Web 服务客户端创建代理类的方法,具体步骤如下: (1) 打开 VS.Net,新建 C#语言 windows 应用程序项目,项目命为 e13_3_2,在窗体中增 加一个按钮,标题为"调用 Web 服务",单击按钮将调用 Web 服务的 Web 方法,在 窗体中增加 2 个文本框,用来输入两个加数。用 3 个 Label 控件作为提示信息,属性 Text 分别为:加数 a、加数 b、和。用一个 Label 控件显示调用 Web 服务的 Web 方 法后返回的结果。设计界面如图 13.3.2D。 (2) 建立 Web 服务客户端代理。单击 VS.Net 菜单"项目"|"添加 Web 引用"菜单项,弹出标 题为"添加 Web 引用"的对话框如图 13.3.2A。 图 13.3.2A (3)在对话框中的"地址(A) "栏中输入 Web 服务的 URL,这个 Web 服务一般应在远程计算机中。 本例 Web 服务和客户端在同一台计算机中,即是在 13.2.2 节中用 VS.Net 建立的 Web 服 务,其 URL 为 http://localhost/e13_2_2/Service1.asmx。不能使用例子 e13_2_1 建立 的 Web 服务,因其不能自动创建 WSDL 文件。然后单击"地址(A)"栏右侧箭头图标,V S.Net 304 自动查找指定 URL 的 WSDL 文件。见图 13.3.2B。 图 13.3.2B (4) 单击标题为"添加引用"的按钮,在解决方案资源管理器中,可以看到一个新的"Web References"。见图 13.3.2C。引用 Service1 采用如下格式:localhost.Service1。 (5) 为按钮增加事件函数如下,注意引用 Service1 的方法。 private void button1_Click(object sender, System.EventArgs e) { int x,y; x=Convert.ToInt16(textBox1.Text); y=Convert.ToInt16(textBox2.Text); localhost.Service1 ws= new localhost.Service1(); label4.Text=ws.MyWebMethod(x,y).ToString(); } (6) 按 F5,运行,输入 2 个加数,单击标题为"调用 Web 服务"按钮,显示和为 30。 图 13.3.2D 图 13.3.2C 13.3.3 WSDL 程序 .Net Framework 的 SDK 提供了一个命令行程序 WSDL.EXE,可以自动为 Web 服务客户端 程序生成一个代理程序。下边介绍生成代理程序的具体方法。WSDL.EXE 必须在控制台界面 下使用,以生成例子 e13_2_2 中的 Web 服务的客户端代理类为例,使用的格式如下: 305 wsdl /l:C# /OUT:Service1.cs /protocol:soap http://localhost/e13_2_2/Service1.asmx?WSDL 其中,/l 参数指定编制 Web 服务客户端程序使用的语言,可以是 vb、C#和 Jscript,默认 值为 C#;/OUT 参数指定生成的代理类文件的路径和文件名,默认值和 Web 服务 ASMX 文件同 名,扩展名为参数/l 指定的语言的扩展名;参数/protocol 指定调用 Web 服务方法使用的协 议,可以是 HTTP-GET、HTTP-POST 和 SOAP 协议;http://后边是 Web 服务 ASMX 文件的 URL。WSDL.exe 是一个绿色软件,可以拷贝到任意位置运行。WSDL 运行的结果是生成一 个 Web 服务客户端程序代理类的源程序,可以用记事本程序查看其源代码。有了源程序, 还要编译源程序生成 dll 文件,格式如下:csc /t:library Service1.cs,使用方法可参见 1.2 节。 最终生成一个 Service1.dll 文件,使用它可以生成客户端代理类。 VS.Net 也是使用 WSDL.EXE 为客户端程序增加代理程序。如果不使用 VS.Net,只能直接 使用 WSDL.EXE 生成客户端程序增加代理程序。 例子 e13_3_3:控制台应用程序调用 Web 服务方法,源程序如下: using System; using System.Web.Services; class UseService1 { public static void Main() { string s; int x,y,z; Console.WriteLine("输入第1个加数a:"); s=Console.ReadLine(); x=Convert.ToInt(s); Console.WriteLine("输入第2个加数b:"); s=Console.ReadLine(); y=Convert.ToInt(s); Service1 ws=new Service1();//代理类对象 z=ws.MyWebMethod(x,y);//调用Web服务方法 Console.WriteLine("sum:={0}",z); } } 以文件名UseService1.cs存盘,和Service1.dll在同一文件夹,用下面的命令编译这段代 码:csc /r:Service1.dll UseService1.cs,生成UseService1.exe,运行生成的UseService1.exe程 序,输入两个加数后,在CRT上输出运算结果。 如用记事本生成调用Web服务的ASP.Net网页文件,要把生成的Service1.dll文件存到这个 网页文件所在目录的子目录bin下,这个代理类就可以被这个ASP.Net网页使用了。请读者完 成这个例子。 13.3.4 同步和异步 Web 服务 以上所介绍的客户端调用 Web 服务方法,要一直等待 Web 服务方法运行结束后,返回 结果,才能执行其它程序,这种调用方法叫做同步模式 Web 服务调用。如果 Web 服务方法 访问远程数据库、执行网络输入/输出和读写大文件,将花费大量时间执行这些操作,采用 同步 Web 服务模式将会降低程序的运行效率,此时可使用 Web 服务异步调用模式。所谓异 步模式 Web 服务调用是程序执行 Web 服务调用后,并不等待返回的结果,而是执行其它程 306 序,一直到希望取回 Web 服务方法返回数据时,才取回数据分析。13.3.3 节中生成的 Service1.cs 中有两个方法 BeginMyWebMethod 和 EndMyWebMethod 就是为了实现 Web 服务异步 调用模式的,有兴趣的读者可参考有关文献深入学习。 13.3.5 不同系统 Web 服务的互操作 前面仅仅介绍了.Net 系统 Web 服务的建立及使用,那么不同系统 Web 服务是否可以实 现互操作呢?换句话讲,一个系统的客户端程序是否可以使用另一个系统的 Web 服务呢? 答案应该是肯定的,但实现起来却很麻烦。这主要是各个系统实现的 WSDL、Soap 协议等 细节还不完全相同,很多系统并不能自动生成代理程序,很多工作还必须由程序员自己实现。 程序员首先要做的是把程序的调用及其参数转化为 SOAP 协议,用 Http-Post 协议发送给 Web 服务程序。其次调用 Web 服务方法后,Web 服务方法返回以 XML 表示的结果,还需 要对 XML 文档进行解析,得到指定类型的数据。很多公司提供了 SOAP 工具包来创建和分 析 SOAP 消息。这些工具包通常将函数调用从某种语言转换为 SOAP 消息。例如,Microsoft SOAP Toolkit 2.0 将 COM 函数调用转换为 SOAP,而 Apache Toolkit 将 JAVA 函数调用 转换为 SOAP。函数调用的类型和支持的参数的数据类型随每个 SOAP 实现方案的不同而 不同,因此适用于一个工具包的函数可能并不适用于另一个工具包。这并不是 SOAP 的限 制,而是所使用的特定实现方案的限制。本课程就不介绍这方面的知识了,有兴趣的读者可 参考有关文献,继续深入学习。 13.4 发布和发现 Web 服务 在 Web 服务中应包括三个角色:Web 服务提供者、Web 服务调用者、Web 服务注册查询 中心。他们之间的关系如下图。 完成 Web 服务开发,Web 服务提供者为了让 Web 服务调用者找到自己的 Web 服务,必须 到 Web 服务注册查询中心注册自己的 Web 服务。Web 服务调用者只有到 Web 服务注册查询中 心查询才能找到需要的 Web 服务。当然,并不是所有的 Web 服务应用都包括以上 3 部分,例 如,上节所介绍的气象台、应用程序集成、工作流程解决方案等并不需要 Web 服务注册,而 代码复用、新的销售方式则需要 Web 服务注册。作为程序员,学习的重点应在学习创建 Web 服务服务器端和客户端程序。本节简单介绍 Web 服务发布和查找问题。 图 13.4 13.4.1 Web 服务发布 和使用因特网上任何其他的资源一样,如果没有查找的方法,是不可能够找到一个特定 的 Web 服务的。为了实现 Web 服务的注册和查找,微软、IBM 和 Ariba 合作建立了一个带 307 有 UDDI(Universal Description 、Discovery and Integration,统一描述发现和集成规范)的网 站:http://www.uddi.org/,可以让 Web 服务供应者在其上发布他们提供的 Web 服务的信息, 而 Web 服务使用者可以在此网站查询所需的 Web 服务。 UDDI 是一个独立于平台的、开放式的架构,可描述 Web 服务、发现服务,并提供一 个可操作的注册表,允许 Web 服务提供者注册。现在已有几百家公司支持 UDDI 协议。 13.4.2 Web 服务发现 程序设计者可以通过以下步骤发现所需的 Web 服务: (1) 首先,访问 Web 服务目录网址,例如 http://www.uddi.org/,查找所需 Web 服务,将返 回一个所需 Web 服务 URL。 (2) 按照返回 URL,用如下格式访问这个网址:http:// 返回 Web 服务 URL?discro。disco 文件是包含连接到其它描述 XML Web 服务资源的 XML 文件。.Net 的 Web 服务自动创建这 个 XML Web 服务 disco 文件,读者可用如下语句访问 web 服务例子 e13_2_2 的 disco 文 件:http://localhost/e13_2_2/Service1.asmx? disco。 (3) 使用 Web 服务的 WSDL 建立一个 Web 服务客户端程序代理类。 (4) 建立 Web 服务客户端程序,使用代理类访问 Web 服务方法。 13.5 使用 Web 服务的例子 本节介绍 2 个使用 Web 服务的实际例子,进一步介绍 Web 服务的使用。 13.5.1 .Net 系统 Web 服务支持的数据类型 并不是所有数据类型都可以作为 Web 服务方法参数和返回值的数据类型,这是因为一 种数据类型在不同的系统中,定义的格式可能不同,例如常见的 int 类型,在不同系统中位 数可能不一致。为了解决这个问题,XML Schema Definition 草案规定了 Web 服务应支持的 数据类型及定义。主要包括基本类型:String、Char、Boolen、Int16、Int32、Int64、UInt16、 UInt32、UInt64、Single、Double、Decimal 等,以及 class、struct、DataSet、XmlNode、String[]、 DataSet[]、XmlNode[]等等。.Net 系统的 Web 服务支持以上数据类型。 13.5.2 使用 Web 服务返回数据库表 使用 Web 服务,经常要查询数据库中指定数据,一般这些数据为一个数据库表。由于 Web 服务不支持 DataTable 类对象作为参数或返回值数据类型,因此必须把 DataTable 类对 象放到 DataSet 类对象中,作为返回值数据类型,下边是实现的例子。 例子 e13_5_2A:本例创建 Web 服务器端的程序,其 Web 服务方法返回第 8 章创建的数 据库 StudentI 中的 Student 表。使用 VS.Net 建立这个 Web 服务的具体步骤如下: (1)创建 asp.Net web 服务项目,项目名为 e13_5_2。 (2)按照 8.12 节的例子 e8_12 中的第(4)步到第(8)步创建 OleDbConnection 对象、 OleDbDataAdapter 对象和数据集 DataSet 对象。 (3)在构造函数 Service1 中增加语句如下: 308 dataSet11.Clear(); oleDbDataAdapter1.Fill(dataSet11); (4) 为了在访问时不出现建议提示,在 Service1 类定义的前边加入如下语句: [WebService(Namespace="http://202.206.96.204/")]。 (5) 增加一个 Web 服务方法 MyWebMethod 如下: [WebMethod] public DataSet MyWebMethod () { return dataSet11; } 例子 e13_5_2B:本例创建 Web 服务客户端的程序,单击按钮,调用例子 e13_5_2 中 Web 服务方法,将返回的使用数据库 StudentI 中的 Student 表在 DataGrid 控件中显示。用 VS.Net 建立 Windows 应用程序的具体步骤如下: (1) 打开 VS.Net,新建 Windows 应用程序项目,项目命为 e13_5_2B,在窗体中增加一个 按钮,标题为"调用 Web 服务",单击按钮将调用例子 e13_5_2 中的 Web 服务方法。 增加一个 DataGrid 控件,用来显示调用 Web 服务方法后返回的数据库 StudentI 中的 Student 表。 (2) 按照例子 e13_3_2 的方法添加 Web 引用。 (3) 为按钮增加事件函数如下: private void button1_Click(object sender, System.EventArgs e) { localhost.Service1 ws=new localhost.Service1(); DataSet ds=ws.MyWebMethod(); dataGrid1.DataSource=ds.Tables[0].DefaultView; } (4) 编译运行,单击标题为"调用 Web 服务"的按钮,将在 DataGrid 控件中显示数据库 StudentI 中的 Student 表。 13.5.3 用 Web 服务传送图形文件 本节介绍用 Web 服务传送图形文件的方法。基本思想是在服务器端把图形文件转换为 一个字节数组,传递到客户端,再转换为图形文件,并显示,步骤如下: 例子 e13_5_3:在服务器端的 Web 服务方法如下: [WebMethod] public byte[] GetImage() { string s=Server.MapPath("p1.GIF"); if(File.Exists(s)) { FileStream fs=File.OpenRead(s); int n; MemoryStream fst=new MemoryStream(); while((n=fs.ReadByte())!=-1) { fst.WriteByte(((byte)n)); } fs.Close(); return fst.ToArray(); } 309 else { return new byte[0]; } } 例子 e13_5_3B:在客户端建立 Windows 应用程序,按照例子 e13_3_2 的方法添加 Web 引用。增加一个按钮,标题为"调用 Web 服务",增加一个 PictureBox 控件用来显示所传 递的图形文件。按钮单击事件函数如下: private void button1_Click(object sender, System.EventArgs e) { localhost.Service1 ws=new localhost.Service1(); byte[] B1=ws.GetImage(); MemoryStream fst=new MemoryStream(B1,0,B1.Length); Bitmap image=new Bitmap(fst); pictureBox1.Image=image; } 习题 (1) 请说出其它使用 Web 服务的例子。 (2) 修改例子 e13_3_2 为 ASP.Net 应用程序,此时,网页提供了一个界面,允许访问此网页 的用户输入两个加数,单击计算按钮,调用另一台计算机中的 Web 服务方法计算两个加 数的和。这时应在网页所在的计算机中生成代理类。(提示:要在两台计算机中完成) (3) 修改例子 e13_3_2,客户端程序也是一个 Web 服务,调用例子 e13_2_2 中的求和 Web 服务方法,请问如何实现。(提示:要在两台计算机中完成) (4) 在 Web 服务中,也可以使用 Application 和 Session 对象,和 ASP.Net 中使用方法相同。 修改例子 e15_5_2,将 DataSet 对象放到 Application 中,供所有的 Web 服务使用。 (5) 修改例子 e15_5_2,客户端程序提供一个 Sql 语句,Web 服务方法根据此 Sql 语句查询 指定数据,返回客户端。 (6) 完成例子 e13_5_3。(提示:使用文件必须引入的命名空间 using System.IO;) (7) 例子 e13_5_3 读文件可能出错,如何处理。
还剩308页未读

继续阅读

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

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

需要 20 金币 [ 分享pdf获得金币 ] 5 人已下载

下载pdf

pdf贡献者

fbmw123

贡献于2011-07-02

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