长春's profile长春的共享空间PhotosBlogLists Tools Help

Blog


    March 15

    Autodesk官方最新的.NET教程(C#版)

     第 1章 Hello World: 访问 ObjectARX .NET 封装类
       在这一章中,我们将使用Visual Studio .NET来创建一个新的类库工程。通过这个工程,你可以创建一个能被AutoCAD装载的.NET dll文件。这个dll文件会向AutoCAD加入一个名为“HelloWorld”的新命令。当用户运行这个命令后,在AutoCAD 命令行上将显示“Hello World”文本。
      
      1) 启动Visual Studio.NET,选择”文件>新建>工程”(File> New> Project)。在新建工程对话框中选择工程类型为”Visual C#工程”,然后选择”类库”模板,在工程名字框中输入”Lab1”,然后选择工程存放的位置。点击确定按钮来创建工程。
      2) 在工程的Class1.cs文件中,一个公有类“Class1”已经被系统自动创建了。接下来向这个类加入命令。要加入命令,你必须使用AutoCAD .NET托管封装类。这些托管封装类包含在两个托管模块中。要加入对这两个托管模块的引用,请用鼠标右键单击”引用”然后选择”添加引用”。在弹出的”添加引用”对话框中选择”浏览”。在”选择组件”对话框中,选择AutoCAD 2006的安装目录(这里假定为C:\Program Files\AutoCAD 2006\),在这个目录下找到“acdbmgd.dll”然后选择并打开它。再一次选择”浏览”,在AutoCAD 2006的安装目录下找到“acmgd.dll”并打开它。当这两个组件被加入后,请单击”添加引用” 对话框中的”确定”按钮。正如它们的名字所表示的,acdbmgd.dll包含ObjectDBX托管类,而acmgd.dll包含AutoCAD托管类。
      3) 使用对象浏览器(Visual Studio.NET的”查看>其它窗口>对象浏览器”菜单项)来浏览加入的两个托管模块所提供的类。请展开“AutoCAD .NET Managed Wrapper”对象(在对象浏览器中显示为acmgd),在整个教程中我们将使用这个对象中的类。在本章中,我们将使用 “Autodesk.AutoCAD.EditorInput.Editor”类的一个实例来在AutoCAD命令行中显示文本。请再展开“ObjectDBX .NET Managed Wrapper” 对象(在对象浏览器中显示为acdbmgd),这个对象中的类将被用来访问和编辑AutoCAD图形中的实体(这部分内容将在以后的章节中介绍)。
      4) 引用了ObjectARX .NET 封装类后,我们就可以导入它们。在Class1类的声明语句(位于Class1.cs文件的顶部的)之前,导入ApplicationServices, EditorInput 和 Runtime命名空间。
      
      using Autodesk.AutoCAD.ApplicationServices;
      using Autodesk.AutoCAD.EditorInput;
      using Autodesk.AutoCAD.Runtime;
      
      5) 接下来在类Class1中加入命令。要加入能在AutoCAD 中调用的命令,你必须使用“CommandMethod”属性。这个属性由Runtime命名空间提供。在类Class1中加入下列属性和函数。
      
      [CommandMethod("HelloWorld")]
       public void HelloWorld()
      {
      
      }
      
      6) 当“HelloWorld”命令在AutoCAD中运行的时候,上面定义的HelloWorld函数就会被调用。在这个函数中,一个Editor类的实例将被创建。Editor类拥有访问AutoCAD命令行的相关方法,它还包括选择对象和其它一些重要的功能。AutoCAD当前活动文档的Editor对象可以使用Application类来访问。当Editor对象被创建后,你可以使用它的WriteMessage方法在命令行中显示“Hello World”文本。在HelloWorld函数中加入以下代码:
      
      Editor ed = Application.DocumentManager.MdiActiveDocument.Editor;
      ed.WriteMessage("Hello World");
      
      7) 要在AutoCAD中调试这个程序,你可以让Visual Studio.NET启动一个AutoCAD进程。在解决方案管理器中右键单击“Lab1”,然后选择”属性”。在Lab1的属性页对话框中,选择” 配置属性>调试”。在”启动”项中,选择”调试模式”为”程序”,在”启动程序”的右边单击省略号按钮然后选择AutoCAD 2006安装目录下的acad.exe。设置好以后,按F5来启动一个AutoCAD进程。这样就会编译你的程序然后自动启动AutoCAD,而当编译后有错误的时候就会停止。请修正你可能碰到的任何错误。
      8) “NETLOAD”命令被用来装载托管程序。在AutoCAD命令行中输入NETLOAD,会出现”选择.NET组件”的对话框。选择上面生成的“lab1.dll”然后打开它。
      9) 在命令行中输入“HellowWorld”。如果一切顺利的话,命令行中将显示“Hello World”文本。切换到Visual Studio.NET,在ed.WriteMessage(“Hello World”);语句处加入一个断点。在AutoCAD中再次运行HelloWorld命令,你会注意到你可以跟踪代码的运行。Visul Studio.NET的”调试”菜单有好几项可以用来跟踪程序的运行。
       如果有时间的话,请浏览一下CommandMethod属性。你会发现它有七种不同的形式。在上面的例子中,我们使用了最简单的形式,它只有一个输入参数(命令的名字)。你可以使用其它的形式来控制命令的工作方式,例如你可以确定命令组的名字、全局和局部名字、命令标识(命令如何来运行)等。 
    第2章 .NET AutoCAD 向导及Editor类
       在第一章中,我们使用的是类库模板,这样就不得不手工加入acdbmdg. dll 和acmgd.dll这两个引用。在这一章中,我们将使用AutoCAD托管C#应用程序向导来创建.NET工程,它会自动加入以上两个引用。在开始本章之前,你首先得安装ObjectARX向导(ObjectARX2006开发包的\utils\ObjARXWiz\ArxWizards.msi)。
      
      1) 启动Visual Studio .NET,选择”文件>新建>工程”(File> New> Project)。在新建工程对话框中选择工程类型为”Visual C#工程”,然后选择“AutoCAD Managed CS Project Application”模板。在工程名字框中输入”Lab2”,然后选择工程存放的位置。点击确定按钮,“AutoCAD Managed CSharp Application Wizard”对话框将会出现。因为我们不需要使用非托管代码,所以不要选择“Enable Unmanaged Debugging”项。“Registered Developer Symbol”将会使用你在安装ObjectARX向导时输入的值。单击”finish”按钮来创建工程。
      2) 下面来看一下向导生成的工程。在解决方案浏览器中,你会看到acdbmgd 和 acmgd已经被引用了。在Class.cs文件中,“Autodesk.AutoCAD.Runtime”命名空间已被导入,工程使用“Registered Developer Symbol”的名字来命名缺省的公有类。向导还为类加入了一个CommandMethod属性和一个函数,它们用于AutoCAD命令。
      3) 在前一章中,我们使用一个“Autodesk.AutoCAD.EditorInput.Editor”类的实例对象在AutoCAD命令行上输出文本。在这一章中,我们将使用这个类来提示用户在AutoCAD图形中选择一个点,然后将用户选择的点的x,y,z值显示出来。和前一章一样,请导入Autodesk.AutoCAD.ApplicationServices 和 Autodesk.AutoCAD.EditorInput命名空间。
      4) 把向导生成的CommandMethod属性的值改为有意义一些的名字如“selectPoint”(函数的名字可以不用修改)。PromptPointOptions类用来设置提示字符串和其它的一些控制提示的选项。这个类的一个实例作为参数被传入到Editor.GetPoint方法。在函数的开始,实例化这个类,设置字符串参数为“Select a point”。因为 Editor.GetPoint方法会返回一个PromptPointResult类的实例对象,所以我们也要把它实例化。
      
      PromptPointOptions prPointOptions =
      new PromptPointOptions("Select a point");
      PromptPointResult prPointRes;
      
      5) 接下来实例化一个Editor类的对象并使用参数为PromptPointOptions对象的GetPoint方法。用GetPoint方法的返回值来给上面声明的PromptPointResult对象赋值。赋值好以后,我们可以测试PromptPointResult对象的状态,如果不是OK就返回。
      
      prPointRes = ed.GetPoint(prPointOptions);
       if (prPointRes.Status != PromptStatus.OK)
       {
       ed.WriteMessage("Error");
      }
      
      6) 如果PromptPointResult对象返回了一个有效的点,我们就可以使用WriteMessage方法把结果输出到命令行。PromptPointResult.Value的ToString方法使输出非常容易:
      
      ed.WriteMessage("You selected point "
       prPointRes.Value.ToString)
      
      7) 按F5来运行一个调试AutoCAD的进程。(注意:向导已经设置好用acad.exe来调试)在AutoCAD命令行中输入NETLOAD,选择Lab2.dll并打开。在命令行中输入你起的命令名字(selectPoint)。在选择点的提示下,单击图形中的任一点。如果一切正常的话,你可以在命令行中看到你所选的点的坐标值。在Class.cs文件的“ed.WriteMessage("Error");”行加入断点,然后再次运行selectPoint命令。这一次,在选择点的提示下按ESC键而不是选择一个点。PromptPointResult对象的状态就不是OK了,所以上面代码中的if语句就会被执行,“ed.WriteMessage("Error")”;语句就会被调用。
      8) 接下来我们将加入另外一个命令,它可以获取两个点之间的距离。向导没有添加命令的功能,所以我们必须手工添加。在Class.cs文件的选择点的函数(getPoint)下面添加一个名为getDistance的新命令。加入命令的方法请参考上一章的内容或本章的源代码,这里就不列出了。使用CommandMethod属性并使字符串参数为“getdistance”或其它类似的名字。在命令的函数中使用PromptDistanceOptions代替PromptPointOptions。当然GetDistance方法的返回值是一个PromptDoubleResult类的实例对象,所以请用PromptDoubleResult来代替PromptPointResult:
      
      PromptDistanceOptions prDistOptions = new
       PromptDistanceOptions("Find distance, select first point:");
       PromptDoubleResult prDistRes;
      prDistRes = ed.GetDistance(prDistOptions);
      
      9) 和前面的命令一样,也可以测试PromptDoubleResult的状态,然后用WriteMessage方法在命令行中显示值。
      
      if (prDistRes.Status != PromptStatus.OK)
       {
       ed.WriteMessage("Error");
       }
       else
       {
      ed.WriteMessage("The distance is: " + prDistRes.Value.ToString());
      
       } 
    第 3 章 数据库基础: 创建我们自己的Employee 对象
      
      打开Lab3文件夹下的Lab3工程文件,或或接着Lab2的代码。
      
      在这一章中,我们将创建一个‘Employee 对象’(包括一个圆,一个椭圆和一个多行文本对象),这个对象属于一个自定义的EmployeeBlock’块(这个块驻留在‘EmployeeLayer’层,当在模型空间插入这个块的时候,‘EmployeeLayer’层就会拥有这个块的一个块索引)。本章的每一个步骤中的代码都可以运行,这样做的目的可以使你更清楚地知道每一部分代码完成的功能。第一步将简要说明一下如何在模型空间创建一个圆。
      
      
      
      
      这一章的重点是在AutoCAD中访问数据库的基础。主要内容包括事务处理(Transaction)、对象Id(ObjectId)、符号表(symbol tables,如块表BlockTable和层表LayerTable)以及对象引用。使用的其它一些对象如颜色Color、三维点Point3d和三维向量Vector3d,都和各自的步骤有关,但重点应该放在数据库基础上。
      
      1) 创建一个名为‘CREATE’的命令,它调用函数CreateEmployee()。这个函数用来在模型空间(MODELSPACE)的(10,10,0)点处创建一个半径为2.0的圆:
      
      [CommandMethod("test")]
      
      public void createCircle()
      
      {
      
      
      
      //首先声明我们要使用的对象
      
      Circle circle; //这个是我们要加入到模型空间的圆
      
      BlockTableRecord btr;//要加入圆,我们必须打开模型空间
      
      BlockTable bt; //要打开模型空间,我们必须通过块表(BlockTable)来访问它
      
      
      
      //我们使用一个名为‘Transaction’的对象,把函数中有关数据库的操作封装起来
      
      Transaction trans;
      
      
      
      //使用TransactionManager的StartTransaction()成员来开始事务处理
      
      trans = HostApplicationServices.WorkingDatabase.TransactionManager.StartTransaction();
      
      
      
      //现在创建圆……请仔细看这些参数——注意创建Point3d对象的‘New’和Vector3d的静态成员ZAxis
      
      circle = new Circle(new Point3d(10, 10, 0), Vector3d.ZAxis, 2);
      
      bt = (BlockTable)trans.GetObject(HostApplicationServices.WorkingDatabase.BlockTableId, OpenMode.ForRead);
      
      
      
      //使用当前的空间Id来获取块表记录——注意我们是打开它用来写入
      
      btr = (BlockTableRecord)trans.GetObject(HostApplicationServices.WorkingDatabase.CurrentSpaceId,OpenMode.ForWrite );
      
      
      
      //现在使用btr对象来加入圆
      
      btr.AppendEntity(circle);
      
      trans.AddNewlyCreatedDBObject(circle, true); //并确定事务处理知道要加入圆!
      
      
      
      //一旦完成以上操作,我们就提交事务处理,这样以上所做的改变就被保存了……
      
      trans.Commit();
      
      
      
      //…然后销毁事务处理,因为我们已经完成了相关的操作(事务处理不是数据库驻留对象,可以销毁)
      
      trans.Dispose();
      
      
      
      }
      
      
      
      
      
      请仔细阅读一下上面的代码块的结构,可以通过注释来了解相关的细节。
      
      注意:要编译代码,你必须导入Autodesk.AutoCAD.DatabaseServices 和Autodesk.AutoCAD.Geometry命名空间
      
      运行这个函数来看看它是否可行。应该会在图形中创建一个在(10,10,0)处的半径为2.0的白色的圆。
      
      
      
      2) 我们可以减少代码的输入量,这可以通过声明一个Database变量代替HostApplicationServices.WorkingDatabase来实现:
      
       Database db = HostApplicationServices.WorkingDatabase;
      
      
      
      使用这个变量来代替在代码中出现的HostApplicationServices.WorkingDatabase。
      
      
      
      3) 在上面的代码中,我们没有使用任何异常处理,而异常处理对一个正确的.NET应用程序来说是非常重要的。我们要养成使用异常处理的好习惯,所以让我们在这个函数中加入try-catch-finally。
      
      4) 为了使代码紧凑,我们可以把许多变量的声明和初始化放在同一个语句中。现在,你的代码看起来应该是这样的:
      
      
      
       [CommandMethod("CREATE")]
      
      public void CREATEEMPLOYEE()
      
      {
      
      
      
      Database db = HostApplicationServices.WorkingDatabase;
      
      Transaction trans = db.TransactionManager.StartTransaction();
      
       try
      
       {
      
      Circle circle = new Circle(new Point3d(10, 10, 0), Vector3d.ZAxis, 2);
      
      BlockTable bt = (BlockTable)trans.GetObject(db.BlockTableId, OpenMode.ForRead);
      
      BlockTableRecord btr = (BlockTableRecord)trans.GetObject(HostApplicationServices.WorkingDatabase.CurrentSpaceId,OpenMode.ForWrite);
      
      btr.AppendEntity(circle);
      
      trans.AddNewlyCreatedDBObject(circle, true);
      
      trans.Commit();
      
       }
      
      
      
      
      
       catch
      
       {
      
      ed.WriteMessage("Error ");
      
       }
      
      finally
      
      {
      
      trans.Dispose();
      
      }
      
      }
      
      End Function
      
      
      
      运行你的代码来进行测试……
      
      上面的catch块只显示一个错误信息。实际的清理工作是在finally块中进行的。这样做的理由是如果在事务处理被提交(Commit())之前,Dispose()被调用的话,事务处理会被 销毁。我们认为如果在trans.Commit()之前出现任何错误的话,你应该销毁事务处理(因为Commit将永远不会被调用)。如果在Dispose()之前调用了Commit(),也就是说没有任何错误发生,那么事务处理将会被提交给数据库。
      
      所以基于上面的分析,Catch块其实并不是必须的,因为它只用来通知用户程序出现了一个错误。它将在下面的代码中被去掉。
      
      5) 现在让我们在Employee加入剩下的部分:椭圆和多行文本的实例。
      
       多行文本实体:
      
       中心点应该与圆心的创建一样:
      
       (建议:创建一个名为‘center’而值为10,10,0的Point3d变量来表示中心点)
      
       多行文本的内容可以是你的名字。
      
       椭圆(提示:你可以先看一下Ellipse的构造函数)
      
       法向量应该沿着Z轴(请查看Vector3d类型)
      
       主轴设为Vector3d(3,0,0)(提示:不要忘了用new)
      
       半径比例设为0.5
      
       椭圆还必须闭合(也就是说,开始和结束点必须相同)
      
      运行你的代码来进行测试……应该可以生成一个圆、一个椭圆和一个中心点在10,10,0的多行文本。
      
      注意:和事务处理对象有关的.NET API中的Try-Catch-Finally块结构,应该是异常观察者。实际上我们是在try块中实例化对象的,但没有显式地销毁它们。当产生异常的时候可能会产生问题,特别是当观察者注意到我们实际上用的是封装的非托管对象!记住,当资源不再使用的时候,垃圾收集机制就会回收内存。垃圾收集机制会不时的调用封装类的Dispose()方法,删除非托管对象。
      
      这里还要注意的是Dispose()作用于封装的非托管类对象的方式取决于对象是否是数据库驻留对象。由非数据库驻留对象调用的Dispose()会删除非托管对象,而由数据库驻留对象调用的Dispose()只是关闭它们。
      <!--[if !supportLineBreakNewLine]-->
      <!--[endif]-->
      
      6) 接下来让我们来创建一个新的函数,它用来新建一个颜色为黄色,名字为“EmployeeLayer” 的AutoCAD层。
      
      这个函数应该检查是否这个层已经存在,但不管这个层是否存在,函数都应该返回“EmployeeLayer”的ObjectId。下面是这个函数的代码:
      
      public ObjectId CreateLayer()
      
      {
      
      ObjectId layerId; //它返回函数的值
      
      Database db = HostApplicationServices.WorkingDatabase;
      
      Transaction trans = db.TransactionManager.StartTransaction();
      
      //首先取得层表……
      
      LayerTable lt = (LayerTable)trans.GetObject(db.LayerTableId, OpenMode.ForWrite);
      
      //检查EmployeeLayer层是否存在……
      
      if (lt.Has("EmployeeLayer"))
      
      {
      
       layerId = lt["EmployeeLayer"];
      
      }
      
      else
      
      {
      
      //如果EmployeeLayer层不存在,就创建它
      
      LayerTableRecord ltr = new LayerTableRecord();
      
      ltr.Name = "EmployeeLayer"; //设置层的名字
      
      ltr.Color = Color.FromColorIndex(ColorMethod.ByAci, 2);
      
      layerId = lt.Add(ltr);
      
      trans.AddNewlyCreatedDBObject(ltr, true);
      
      }
      
      
      
      trans.Commit();
      
      trans.Dispose();
      
      return layerId;
      
      }
      
      
      
      是不是觉得这个函数的基本结构与在模型空间加入实体的代码比较类似?访问数据库的方法都是这样的:使用事务处理来获取数据库对象,在符号表(模型空间所在的块表也是符号表之一)中加入实体,然后让事务处理知道。
      
      7) 在这个函数中加入异常处理,就像在CreateEmployee函数中的一样。
      
      8) 接下来,改变新建层的颜色。下面是实现的代码片断,请把它加入到你的代码中:
      
      ltr.Color = Color.FromColorIndex(ColorMethod.ByAci, 2)
      
      
      
      注意:ColorMethod.ByAci可以让我们使用AutoCAD ACI颜色索引……这里为2(表示黄色)。
      
      <!--[if !supportLists]-->9) <!--[endif]-->回到CreateEmployee()函数,加入把上面创建的几个实体设置到EmployeeLayer层的代码。声明一个类型为ObjectId的变量,用CreateLayer函数的返回值给它赋值。使用每个实体(文本、圆和椭圆)的LayerId属性设置它们所在的层。
      
      
      
      例如: text.LayerId = empId
      
      
      
      运行代码来查看“EmployeeLayer”层是否已被创建,所有已创建的实体是否都在这一层上(应该显示为黄色)
      
      10) 现在为各个实体设置不同的颜色,可以使用ColorIndex属性(ColorIndex属性表示AutoCAD的颜色)
      
       圆为红色-1
      
       椭圆为绿色-3
      
       文本为黄色-2
      
      
      
      运行代码,看看实体的颜色是否为设置的值,即使这些实体是在“EmployeeLayer”层上。
      
      11) 接下来,我们要在AutoCAD数据库中创建一个独立的块,然后把它插入到块表而不是模型空间中。
      
      首先把CreateEmployee函数的名字改为CreateEmployeeDefinition()。
      
      加入以下代码来创建一个独立的块:
      
      
      
      BlockTableRecord newBtr = new BlockTableRecord();
      
      newBtr.Name = "EmployeeBlock";
      
      newBtrId = bt.Add(newBtr);
      
      trans.AddNewlyCreatedDBObject(newBtr, true);
      
      
      
      
      
      12) 现在,请稍微改动一下加入实体到模型空间的代码(改为加入块到块表中,记得加入前要打开块表)。
      
      现在运行代码,然后使用INSERT命令来检查是否可以正确插入这个块。
      
      
      
      13) 最后,我们要创建一个位于模型空间的块索引,它表示上面创建的块的一个实例。这一步留给大家练习。
      
       下面是你要遵循的最基本的步骤:
      
      <!--[if !supportLists]-->A) <!--[endif]-->创建一个名为CreateEmployee新的函数
      
      <!--[if !supportLists]-->B) <!--[endif]-->把命令属性“CREATE”移动到CreateEmployee()
      
      <!--[if !supportLists]-->C) <!--[endif]-->修改CreateEmployeeDefintion()来返回新创建的块“EmployeeBlock”的ObjectId,操作的步骤请参考CreateLayer()的作法。
      
      <!--[if !supportLists]-->D) <!--[endif]-->你需要修改CreateEmployeeDefintion()来查看块表中是否已包含“EmployeeBlock”块,如果包含这个块,则返回它的ObjectId(做法与CreateLayer()一样)。
      
      提示:把‘bt’的声明语句移动到try块的顶部,使用BlockTable.Has()方法,把其它的代码移动到else语句:
      
      try
      
       {
      
       //获取BlockTable 对象
      
      BlockTable bt = (BlockTable)trans.GetObject(db.BlockTableId, OpenMode.ForWrite);
      
       if ((bt.Has("EmployeeBlock")))
      
       {
      
       newBtrId =bt["EmployeeBlock"];
      
       }
      
       else
      
       {
      
       …
      
      
      
      <!--[if !supportLists]-->E) <!--[endif]-->在新创建的CreateEmployee()函数中创建一个新的BlockReference对象,并把它加入到模型空间。提示:我们可以使用CreateEmployeeDefinition()中引用模型空间的代码,这些代码在这里不需要了
      
      <!--[if !supportLists]-->F) <!--[endif]-->在CreateEmployee中调用CreateEmployeeDefinition()函数,使上面生成的BlockReference对象的BlockTableRecord()指向CreateEmployeeDefinition()函数。提示:请参考BlockReference的构造函数。
      
      
      
      附加的问题:
      
      让我们来看一下代码的运行情况,执行命令会生成一个EmployeeBlock的块索引,你会看到它被插入到20,20,0而不是10,10,0。为什么?
      
      如果你知道原因,那么怎样才能使块索引插入到正确的点?
      
      当你用List命令查看块索引时,它会告诉你它位于0层(或者当命令运行时位于当前层)。为什么?
      
      怎样才能让块索引总是位于EmployeeLayer层? 
    第 4 章 数据库基础2: 添加自定义数据
      
      在这一章中,我们将创建一个新的字典对象,它用来表示我们雇员就职的 ‘Acme 公司‘(呵呵,当然是虚构的一家公司)的部门。这个“部门”字典对象将包含一个表示部门经理的记录。我们还会加入代码到雇员创建过程,这个过程会加入一个索引到雇员工作的部门。
      
      我们要说明的是如何在DWG文件中创建自定义数据,包括“每个图形”的自定义数据和“每个实体”的自定义数据。“每个图形”的自定义数据是指只在整个图形中加入一次的数据,它表示对象可以引用的单一类型或特性。“每个实体”的自定义数据是指是为特定的对象或数据库中的实体加入的数据。
      
      在下面的示例中,我们将加入“每个图形”的自定义数据到命名对象字典(简称NOD)。NOD存在于每一个DWG文件中。“每个实体”的自定义数据加入到一个名为“扩展字典”的字典(可选)中,它表示每一个雇员。每一个由DBObject派生的对象都拥有存储自定义数据的扩展字典。而在我们的示例中将包含这种自定义数据如名字、薪水和部门。
      
      因此这一章的重点是字典对象和扩展记录(XRecord),它们是我们用来表示自定义数据的容器。
      
      首先让我们来创建表示公司的条目。在本章的前几个步骤中,我们将创建如下所示的部门层次结构:
      
       NOD-命名对象字典
      
       ACME_DIVISION-自定义公司字典
      
       销售(Sales) -部门字典
      
       部门经理-部门条目
      
      
      
      请打开Lab4文件夹下的Lab4工程,或接着Lab3的代码。
      
      <!--[if !supportLists]-->1) <!--[endif]-->我们首先要做的是定义一个新的函数,它用来在命名对象字典(NOD)中创建公司字典对象。为这个函数取名为CreateDivision(),,并使用命令属性来定义CREATEDIVISION命令。
      
      下面是这个函数的代码,它的形式非常简单,只是用来在NOD中创建一个ACME_DIVISION(用来表示公司)
      
       [CommandMethod("CreateDivision")]
      
       public void CreateDivision()
      
       {
      
       Database db = HostApplicationServices.WorkingDatabase;
      
       Transaction trans = db.TransactionManager.StartTransaction();
      
      
      
       try
      
       {
      
       //首先,获取NOD……
      
       DBDictionary NOD = (DBDictionary)trans.GetObject(db.NamedObjectsDictionaryId, OpenMode.ForWrite);
      
       //定义一个公司级别的字典
      
       DBDictionary acmeDict;
      
       try
      
       {
      
       //如果ACME_DIVISION不存在,则转到catch块,这里什么也不做
      
       acmeDict = (DBDictionary)trans.GetObject(NOD.GetAt("ACME_DIVISION"), OpenMode.ForRead);
      
       }
      
       catch
      
       {
      
       //如果ACME_DIVISION不存在,则创建它并把它加入到NOD中……
      
       acmeDict = new DBDictionary();
      
       NOD.SetAt("ACME_DIVISION", acmeDict);
      
       trans.AddNewlyCreatedDBObject(acmeDict, true);
      
       }
      
       trans.Commit();
      
       }
      
       finally
      
       {
      
       trans.Dispose();
      
       }
      
      }
      
      
      
      请仔细阅读一下上面的代码块的结构,可以通过注释来了解相关的细节。特别要注意的是我们是如何用一个try-catch块来处理ACME_DIVISION是否存在?如果ACME_DIVISION字典不存在,GetObject()将会抛出异常,catch块被执行,它会创建一个新的字典。
      
      运行这个函数来看它是否可行。可以使用数据库查看工具来检查字典是否已被加入(建议使用ARX SDK的ArxDbg工具)
      
      <!--[if !supportLists]-->2) <!--[endif]-->接下来,我们要在ACME_DIVISION字典中加入销售(Sales)条目。销售(Sales)条目同样也是一个字典。由于销售(Sales)字典与ACME_DIVISION字典的关系如同ACME_DIVISION字典与NOD,所以代码是类似的。定义下面的代码部分在ACME_DIVISION字典中创建一个名为’Sales’的字典。
      
      代码提示:
      
      
      
       DBDictionary divDict;
      
       try
      
       {
      
       divDict = (DBDictionary)trans.GetObject(acmeDict.GetAt("Sales"), OpenMode.ForWrite);
      
       }
      
       catch
      
       …
      
      
      
      运行函数来看‘Sales’条目是否已加入到ACME_DIVISION字典。
      
      <!--[if !supportLists]-->3) <!--[endif]-->现在我们要在这个字典中加入一个特殊的记录,它可以包含任意的自定义数据。我们要加入的数据类型为扩展记录(XRecord),它可以包含任何东西,因此我们可以让它包含ResultBuffer类的对象(就是有些人可能非常熟悉的‘resbuf’)。ResultBuffer可以存储不同类型的预定义数据。扩展记录存储任意数目的ResultBuffer关系列表,因此可能会很大。下表是可以包含在ResultBuffer中一些数据类型(位于Database类的DxfCode枚举中):
      
      
      
      Start
       0
      
      
      Text
       1
      
      
      XRefPath
       1
      
      
      ShapeName
       2
      
      
      BlockName
       2
      
      
      AttributeTag
       2
      
      
      SymbolTableName
       2
      
      
      MstyleName
       2
      
      
      SymTableRecName
       2
      
      
      AttributePrompt
       3
      
      
      DimStyleName
       3
      
      
      LinetypeProse
       3
      
      
      TextFontFile
       3
      
      
      
      
      
      
      
      在下面的代码部分,我们将创建只包含一个ResultBuffer的扩展记录。这个ResultBuffer包含一个单一的的字符串值,它表示’Sales’部门的部门经理的名字。我们使用和加入字典一样的方法加入扩展记录。唯一的区别是扩展记录与字典的不同:
      
       mgrXRec = new Xrecord();
      
       mgrXRec.Data = new ResultBuffer(new TypedValue((int)DxfCode.Text, "Randolph P. Brokwell"));
      
      
      
      请看一下我们是怎样使用new来创建一个新的扩展记录。但我们也使用了new来创建一个ResultBuffer,传入的参数是一个名为‘TypedValue’的对象。‘TypedValue’对象和C++中resbuf的成员‘restype’是类似的。这个对象一般表示一个特定类型的DXF值,我们使用它来组装诸如扩展数据或扩展记录之类的通用数据容器。在这里,我们简单地使用DxfCode.Text键值和“Randolph P. Brokwell”数据值来定义一个TypedValue,然后把它作为单一的参数传入ResultBuffer构造函数(由new来调用)中。
      
      XRecord的’Data’属性实际上正是扩展记录链的第一个ResultBuffer,我们使用它来表示扩展记录链是从什么地方开始的。
      
      所以接下来的代码块看起来和前面两个非常相似:
      
      Xrecord mgrXRec;
      
       try
      
       {
      
       mgrXRec = (Xrecord)trans.GetObject(divDict.GetAt("Department Manager"), OpenMode.ForWrite);
      
       }
      
       catch
      
       {
      
       mgrXRec = new Xrecord();
      
       mgrXRec.Data = new ResultBuffer(new TypedValue((int)DxfCode.Text, "Randolph P. Brokwell"));
      
       divDict.SetAt("Department Manager", mgrXRec);
      
       trans.AddNewlyCreatedDBObject(mgrXRec, true);
      
       }
      
      
      
      运行函数并使用数据库查看工具来确定部门经理已被加入到’Sales’字典。
      
      
      
      4) 我们已经定义了公司字典,现在我们要把每个雇员的数据加入到前一章定义的块索引中。我们要加入的数据是:名字、薪水和雇员所属的部门。要加入这些数据,我们要同前几个步骤一样使用扩展记录。因为我们要加入三个条目,所以我们要使扩展记录可以把这些数据联系在一起。
      
      一般来说,扩展记录只能存在于字典中。而我们要为每个雇员加入这些数据(就是本章开头所讲的“每个图形”的自定义数据和“每个实体”的自定义数据),那应该怎么做呢?答案就是:每一个对象或AutoCAD中的实体实际上都有一个名为’扩展字典(Extension Dictionary)’的可选字典。我们可以把扩展记录直接加入到这个字典中。
      
      请回到我们在上一章创建的CreateEmployee()函数。这个函数是我们创建块索引的地方。
      
      让我们像前面的步骤一样来创建一个新的扩展记录。因为我们要加入3个条目,因此我们既可以使用ResultBuffer的Add方法(它会在扩展记录链中加入一个链接),也可以利用ResultBuffer的构造函数(它的一种构造函数可以输入可变数量的参数)。
      
      无论用哪一种方法,请在CreateEmployee()函数中使用ResultBuffer来创建一个新的XRecord,ResultBuffer包括以下的类型和值:
      
       Text – “Earnest Shackleton” (或是你选择的其它雇员的名字)
      
       Real – 72000 或者更多的薪水J
      
       Text – “Sales” 雇员所在的部门
      
      
      
      5) 要把上面的扩展记录加入到块索引,我们必须把它加入到扩展字典。通常这个字典是不存在的,除非它被明确地创建,块索引就是这种情况。要给一个对象创建扩展字典,你要调用它的成员‘CreateExtensionDictionary()’。这个函数不返回任何值,所以要访问它创建的扩展字典,你还得使用对象的‘ExtensionDictionary’属性。你可以使用类似于以下的代码来创建并访问扩展字典:
      
      
      
       br.CreateExtensionDictionary();
      
       DBDictionary brExtDict = (DBDictionary)trans.GetObject(br.ExtensionDictionary, OpenMode.ForWrite, false);
      
      
      
      由于扩展字典也是字典,我们可以和第3步一样在扩展字典中加入扩展记录。请完成有关的代码来创建和访问块索引的扩展字典,加入你在第4步中创建的扩展记录,然后把扩展记录加入到事务处理。
      
      6) 返回到NOD……因为在NOD中创建公司字典只需要一次(就像创建Employee块一样),因此我们应该把CreateDivision函数的命令属性去掉,而在CreateEmployeeDefinition()中调用这个函数。请自己完成这些改变。当所有这些都做完后,当CREATE命令第一次运行的时候,所有的函数都会被调用。
      
      7) 下面的步骤和上面的无关。我们将创建一个函数来遍历模型空间,以用来查找加入的Employee对象(这里其实是块索引)的数目。在VB.NET 或C#中,我们可以把模型空间块表记录(ModelSpace BlockTableRecord)当作一个集合,这样就可以使用For Each(C#是foreach)来遍历它。请仔细研究一下下面的代码片断:
      
      
      
      VB.NET:
      
       Dim id As ObjectId ‘ 首先,定义一个For循环要使用的ObjectId变量。
      
       For Each id In btr
      
       Dim ent As Entity = trans.GetObject(id, OpenMode.ForRead, False) '打开当前的对象!
      
      
      
      C#:
      
       foreach (ObjectId id in btr)
      
       {
      
       Entity ent = (Entity)trans.GetObject(id, OpenMode.ForRead, false); //打开当前的对象!
      
      
      
      一旦我们获得模型空间对象,你们就可以定义一个ObjectId变量,然后把它用于For Each循环(C#是foreach)。
      
      现在,我们需要使用一些方法来筛选雇员。我们知道模型空间中的对象都是实体,但不全是雇员。我们需要使用一些方法来加以区分。在这里,我们可以使用VB.NET的TypeOf关键字并用CType进行类型转换(C#是GetType函数和typeof):
      
      VB.NET:
      
       If TypeOf ent Is BlockReference Then
      
       Dim br As BlockReference = CType(ent, BlockReference)
      
       …
      
      
      
      C#:
      
       If(ent.GetType() == typeof(BlockReference))
      
       BlockReference br = (BlockReference)ent;
      
      
      
      上面讲的概念对于AutoCAD编程是很重要的,因为容器对象经常包含不同类型的对象。你会在AutoCAD程序的开发中经常碰到这种类型转化。
      
      请定义一个名为EmployeeCount()的函数,函数的结构如上所示,它用来统计模型空间中的块索引的数目。这个函数不会输出任何东西,但你可以使用逐步调试程序来查看整数变量的增加(每发现一个块索引对象)。
      
      8) 接下来,为了把结果输出到命令行,我们需要使用Application.DocumentManager.MdiActiveDocument.Editor对象的服务。要使用它,请加入下面的代码:
      
      
      
      Imports Autodesk.AutoCAD.EditorInput
      
      Imports Autodesk.AutoCAD.ApplicationServices
      
      
      
      在函数的内部:
      
      Editor ed = Application.DocumentManager.MdiActiveDocument.Editor;
      
      
      最后,在循环的后面确定找到了多少个块索引:
      
      ed.WriteMessage("Employees Found: " + nEmployeeCount.ToString());
      
      
      
      第四章结束
      
      下面的代码片断演示了怎样获取Employee对象的所有内容,包括ACME_DIVISION字典中的部门经理的名字。这部分要在后面的章节中使用,但因为它和本章有关,因此我们把它放在本章作介绍。如果有时间的话,请阅读一下其中的代码来看看它是怎么使用的。它可以被直接放到你的类中并可以运行。命令的名字是PRINTOUTEMPLOYEE。ListEmployee()函数接收一个ObjectId参数,它通过一个ref类型的字符串数组返回值(包含相应的雇员数据)。调用它的PrintoutEmployee()函数只是用来在命令行中输出这些数据。
      
      我们需要一个遍历并显示所有雇员数据的命令。
      
      public static void ListEmployee(ObjectId employeeId, ref string[] saEmployeeList)
      
      {
      
       int nEmployeeDataCount = 0;
      
       Database db = HostApplicationServices.WorkingDatabase;
      
       Transaction trans = db.TransactionManager.StartTransaction(); //开始事务处理。
      
       try
      
       {
      
       Entity ent = (Entity)trans.GetObject(employeeId, OpenMode.ForRead, false); //打开当前对象!
      
       if (ent.GetType() == typeof(BlockReference))
      
       {
      
      //不是所有的块索引都有雇员数据,所以我们要处理错误
      
       bool bHasOurDict = true;
      
       Xrecord EmployeeXRec = null;
      
       try
      
       {
      
       BlockReference br = (BlockReference)ent;
      
       DBDictionary extDict = (DBDictionary)trans.GetObject(br.ExtensionDictionary, OpenMode.ForRead, false);
      
       EmployeeXRec = (Xrecord)trans.GetObject(extDict.GetAt("EmployeeData"), OpenMode.ForRead, false);
      
       }
      
       catch
      
       {
      
      bHasOurDict = false; //出现了错误……字典或扩展记录不能访问
      
       }
      
      
      
      if (bHasOurDict) //如果获得扩展字典,而又有扩展记录……
      
       {
      
      
      
       // 为雇员列表分配内存
      
       saEmployeeList = new String[4];
      
       //加入雇员的名字
      
       TypedValue resBuf = EmployeeXRec.Data.AsArray()[0];
      
       saEmployeeList.SetValue(string.Format("{0}\n", resBuf.Value), nEmployeeDataCount);
      
       nEmployeeDataCount += 1;
      
       //加入雇员的薪水
      
       resBuf = EmployeeXRec.Data.AsArray()[1];
      
       saEmployeeList.SetValue(string.Format("{0}\n", resBuf.Value), nEmployeeDataCount);
      
       nEmployeeDataCount += 1;
      
       //加入雇员所在的部门
      
       resBuf = EmployeeXRec.Data.AsArray()[2];
      
       string str = (string)resBuf.Value;
      
       saEmployeeList.SetValue(string.Format("{0}\n", resBuf.Value), nEmployeeDataCount);
      
       nEmployeeDataCount += 1;
      
       //现在,让我们从公司字典中获取老板的名字
      
       //在NOD中找到.
      
       DBDictionary NOD = (DBDictionary)trans.GetObject(db.NamedObjectsDictionaryId, OpenMode.ForRead, false);
      
       DBDictionary acmeDict = (DBDictionary)trans.GetObject(NOD.GetAt("ACME_DIVISION"), OpenMode.ForRead);
      
       //注意我们直接使用扩展数据...
      
       DBDictionary salesDict = (DBDictionary)trans.GetObject(acmeDict.GetAt((string)EmployeeXRec.Data.AsArray()[2].Value), OpenMode.ForRead);
      
       Xrecord salesXRec = (Xrecord)trans.GetObject(salesDict.GetAt("Department Manager"), OpenMode.ForRead);
      
       //最后,把雇员的数据输出到命令行
      
       resBuf = salesXRec.Data.AsArray()[0];
      
       saEmployeeList.SetValue(string.Format("{0}\n", resBuf.Value), nEmployeeDataCount);
      
       nEmployeeDataCount += 1;
      
       }
      
       }
      
       trans.Commit();
      
       }
      
       finally
      
       {
      
       trans.Dispose();
      
       }
      
      }
      
      
      
      
      
      [CommandMethod("PRINTOUTEMPLOYEE")]
      
      public static void PrintoutEmployee()
      
      {
      
       Editor ed = Application.DocumentManager.MdiActiveDocument.Editor;
      
       //声明我们将在下面使用的工具...
      
       Database db = HostApplicationServices.WorkingDatabase;
      
       Transaction trans = db.TransactionManager.StartTransaction();
      
       try
      
       {
      
       //首先,获取块表和模型空间块表记录
      
      BlockTable bt = (BlockTable)trans.GetObject(HostApplicationServices.WorkingDatabase.BlockTableId, OpenMode.ForRead);
      
       BlockTableRecord btr = (BlockTableRecord)trans.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForRead);
      
       //现在,我们需要把内容输出到命令行。这里可以有一个对象帮助我们:
      
       //下面的部分,我们将遍历模型空间:
      
      
      
       foreach (ObjectId id in btr)
      
       {
      
       Entity ent = (Entity)trans.GetObject(id, OpenMode.ForRead, false); //打开当前对象!
      
       if (ent is BlockReference)
      
       {
      
       string[] saEmployeeList = null;// 这是正确的...定义新的列表。
      
      
      
       ListEmployee(id, ref saEmployeeList);
      
       if ((saEmployeeList.Length == 4))
      
       {
      
       ed.WriteMessage("Employee Name: {0}", saEmployeeList[0]);
      
       ed.WriteMessage("Employee Salary: {0}", saEmployeeList[1]);
      
       ed.WriteMessage("Employee Division: {0}", saEmployeeList[2]);
      
       ed.WriteMessage("Division Manager: {0}", saEmployeeList[3]);
      
       }
      
       }
      
       }
      
       }
      
       finally
      
       {
      
       }
      
      }
    背景
      提示通常包含一个描述性信息,伴随一个停止以让用户理解所给的信息并输入数据。数据可以通过多种方式被输入,如通过命令行、对话框或AutoCAD编辑窗口。给出的提示要遵循一定的格式,格式要与一般的AutoCAD提示相一致,这一点是非常重要的。例如,关键字要用“/”号分隔并放在方括号“[]”中,缺省值要放在“<>”内。对于一个AutoCAD用户来说,坚持统一的格式将会减少信息理解错误的产生。
      当用户在AutoCAD命令行中选择一个实体时,实体是使用选择机制被选择的。这种机制包括一个提示,用来让用户知道选择什么并怎样选择(如,窗口或单一实体),然后是一个停顿。
      试一下诸如PINE这种命令来看一下提示的显示,PEDIT来看一下使用单一实体或多线来进行选择。
      练习
      Prompts:
      提示:
      在本章中,我们将提示输入雇员名字、职位、薪水和部门来创建一个雇员块索引对象。如果输入的部门不存在,我们将提示输入部门经理的名字来创建一个新的部门。在我们继续之前,让我们试着重用以前的代码。
      为了进行选择,我们将提示用户在一个窗口中进行选择或选择一个实体,而我们只显示选择集中的雇员对象。
      在前面的章节中,我们创建了一个名叫“Earnest Shackleton”的雇员,名字被存储为“EmployeeBlock”块定义(块表记录)中的MText。如果我们多次插入这个块,那么我们看到的都是同一个雇员的名字。我们怎样才能自定义这个块以使每次插入这个块的时候显示不同雇员的名字?这就要使用块属性的功能了。属性是存储在每一个块索引实例中的文本,并被作为实例的一部分来被显示。属性从存储在块表记录中的属性定义中继承相关的属性。
      属性:
      让我们来把MText实体类型改变为属性定义。在CreateEmployeeDefinition()函数中,把下面的代码替换
      
      //文本:
      MText text = new MText();
      text.Contents = "Earnest Shackleton";
      text.Location = center;
      
      为
      
      //属性定义
      AttributeDefinition text = new AttributeDefinition(center, "NoName", "Name:", "Enter Name", db.Textstyle);
      text.ColorIndex = 2;
      
      试着使用TEST命令来测试一下CreateEmployeeDefinition()函数:
       [CommandMethod("Test")]
       public void Test()
       {
       CreateEmployeeDefinition();
       }
      
      你现在应该可以使用INSERT命令来插入EmployeeBlock块并对每一个实例确定一个雇员名。
      当你插入Employee块时,请注意一下块插入的位置。它是正好被放置在所选点还是有些偏移?试试怎样修复它。(提示:检查块定义中的圆心)
      修改CreateEmployee ()以重用
      
      1)让我们来修改CreateEmployee()函数,以让它可以接收名字、薪水、部门和职位并返回创建的雇员块索引的ObjectId。函数的形式如下(你可以改变参数顺序)
      public ObjectId CreateEmployee(string name, string division, double salary, Point3d pos)
      
      2) 移除上面函数中的CommandMethod属性”CREATE”,这样它就不再是用来创建雇员的命令。
      3) 修改函数的代码,这样就可以正确地设置块索引的名字、职位、部门和薪水和它的扩展字典。
      · 替换
      
      BlockReference br = new BlockReference(new Point3d(10, 10, 0), CreateEmployeeDefinition());
      
      为
      
      BlockReference br = new BlockReference(pos, CreateEmployeeDefinition());
      
      · 替换
      
       xRec.Data = new ResultBuffer(
       new TypedValue((int)DxfCode.Text, "Earnest Shackleton"),
       new TypedValue((int)DxfCode.Real, 72000),
       new TypedValue((int)DxfCode.Text, "Sales"));
      
      为
      
       xRec.Data = new ResultBuffer(
       new TypedValue((int)DxfCode.Text, name),
       new TypedValue((int)DxfCode.Real, salary),
       new TypedValue((int)DxfCode.Text, division));
      
      4) 因为我们把雇员的名字从MText替换成块的属性定义,因此我们要创建一个相应的属性索引来显示雇员的名字。属性索引将使用属性定义的属性。
      · 替换:
      
       btr.AppendEntity(br); //加入索引到模型空间
       trans.AddNewlyCreatedDBObject(br, true); //让事务处理知道
      
      为
      
       AttributeReference attRef = new AttributeReference();
       //遍历雇员块来查找属性定义
       BlockTableRecord empBtr = (BlockTableRecord)trans.GetObject(bt["EmployeeBlock"], OpenMode.ForRead);
       foreach (ObjectId id in empBtr)
       {
       Entity ent = (Entity)trans.GetObject(id, OpenMode.ForRead, false);
       //打开当前的对象!
       if (ent is AttributeDefinition)
       {
       //设置属性为属性索引中的属性定义
       AttributeDefinition attDef = ((AttributeDefinition)(ent));
       attRef.SetPropertiesFrom(attDef);
       attRef.Position = new Point3d(attDef.Position.X + br.Position.X, attDef.Position.Y + br.Position.Y, attDef.Position.Z + br.Position.Z);
       attRef.Height = attDef.Height;
       attRef.Rotation = attDef.Rotation;
       attRef.Tag = attDef.Tag;
       attRef.TextString = name;
       }
       }
       //把索引加入模型空间
       btr.AppendEntity(br);
       //把属性索引加入到块索引
       br.AttributeCollection.AppendAttribute(attRef);
       //让事务处理知道
       trans.AddNewlyCreatedDBObject(attRef, true);
       trans.AddNewlyCreatedDBObject(br, true);
      
      
      研究一下上面的代码,看看是怎样把属性定义中除显示用的文本字符串外的属性复制到属性索引的。属性被加入到块索引的属性集合中。这就是你怎样来为每一个实例自定义雇员名字。
      5)不要忘记返回雇员块索引的ObjectId,但要在提交事务处理之后才能返回:
      trans.Commit();
      return br.ObjectId;
      
      6) 测试CreateEmployee。
      加入一个Test命令来测试CreateEmployee:
      
       [CommandMethod("Test")]
       public void Test()
       {
       CreateEmployee("Earnest Shackleton", "Sales", 10000, new Point3d(10, 10, 0));
       }
      
      
      修改CreateDivision()以重用:
      让我们来修改CreateDivision ()函数,以让它可以接收部门名字、经理名字并返回创建的部门经理扩展记录的ObjectId。如果部门经理已经存在,则不改变经理的名字。
      1) 如果你先前在CreateEmployeeDefinition()中调用了CreateDivision(),请把它注释掉,因为我们在这里不需要创建一个部门
      2)   改变CreateDivision()的形式让它接收部门和经理的名字并返回一个ObjectId。
      public ObjectId CreateDivision(string division, string manager)
      3) 修改上面函数的代码创建部门的名字和经理:
      · 替换:
      
      divDict = (DBDictionary)trans.GetObject(acmeDict.GetAt("Sales"), OpenMode.ForWrite);
      
      为:
      
      divDict = (DBDictionary)trans.GetObject(acmeDict.GetAt(division), OpenMode.ForWrite);
      
      · 替换:
      
      acmeDict.SetAt("Sales", divDict);
      
      为:
      
      acmeDict.SetAt(division, divDict);
      
      · 替换:
      
      mgrXRec.Data = new ResultBuffer(new TypedValue((int)DxfCode.Text, "Randolph P. Brokwell"));
      
      为:
      
      mgrXRec.Data = new ResultBuffer(new TypedValue((int)DxfCode.Text, manager));
      
      
      不要忘了返回部门经理这个扩展记录的ObjectId,但要在提交事务处理后才返回。
      trans.Commit();
      //返回部门经理这个扩展记录的ObjectId
      return mgrXRec.ObjectId;
      
      现在把在中CreateEmployeeDefinition调用的CreateDivision函数给注释掉。
      4) 现在通过使用TEST命令来测试调用CreateDivision函数。使用ArxDbg工具来检查条目是否已被加入到“ACME_DIVISION”下的命名对象字典。
      CreateDivision("Sales", "Randolph P. Brokwell")
      
      使用CREATE命令来创建雇员:
      
      我们将加入一个名为CREATE的新命令,此命令用来提示输入雇员的详细资料来创建雇员块索引。让我们来看一下这个命令是怎样使用的。
      1) 让我们加入一个名为CREATE的新命令,并声明几个常用的变量和一个try-finally块。
      
      [CommandMethod("CREATE")]
      public void CreateEmployee()
      {
       Database db = HostApplicationServices.WorkingDatabase;
       Editor ed = Application.DocumentManager.MdiActiveDocument.Editor;
       Transaction trans = db.TransactionManager.StartTransaction();
       try
       {
       trans.Commit();
       }
       finally
       {
       trans.Dispose();
       }
      }
      
      
      2) 让我们来为雇员定义可以用作为提示缺省值的常数。注意,布尔值gotPosition是用来判断用户是否已输入职位。
      . 雇员名 - 类型 :String -缺省值 “Earnest Shackleton”
      . 雇员所在部门名   - 类型:String     -缺省值“Sales”
      . 薪水 -类型:Double (non-negative and not zero) -缺省值10000
      . 职位 -类型:Point3d -缺省值(0,0,0)
      
      把这些常数加入到try语句后面:
      string empName = "Earnest Shackleton";
      string divName = "Sales";
      double salary = new double();
      salary = 10000;
      Point3d position = new Point3d(0, 0, 0);
      bool gotPosition = new bool();
      //布尔值用来判断用户是否已输入职位
      gotPosition = false;
      
      3) 现在让我们提示用户输入值。我们先使用PromptXXXOptions类来初始化要显示的提示字符串。
      //提示输入每个雇员的详细资料
      PromptStringOptions prName = new PromptStringOptions("Enter Employee Name <" + empName + ">");
      PromptStringOptions prDiv = new PromptStringOptions("Enter Employee Division <" + divName + ">");
      PromptDoubleOptions prSal = new PromptDoubleOptions("Enter Employee Salary <" + salary + ">");
      PromptPointOptions prPos = new PromptPointOptions("Enter Employee Position or");
      
      注意,提示字符串用尖括号来显示变量的值。这是AutoCAD用来提示用户这个值为缺省值。
      4) 当提示用户输入职位时,我们也提供了一个关键字列表选项,如名字、部门和薪水。如果用户想要在选择一个点的时候改变为其它值,他可以选择那个关键字。
      一个命令提示的例子如下:
      Command: CREATE
      Enter Employee Position or [Name/Division/Salary]:
      
      要创建一个雇员,用户会选择一个点而其它的值被设置为缺省值。如果用户要改变其它的值,如名字,他可以输入”N”或全名”Name”,然后输入名字:
      Command: CREATE
      Enter Employee Position or [Name/Division/Salary]:N
      Enter Employee Name <Earnest Shackleton>:
      
      如果用户想要再次选择缺省的名字,他可以按回车键。
      让我们创建用于职位提示的关键字列表:
      
      //加入用于职位提示的关键字
      prPos.Keywords.Add("Name");
      prPos.Keywords.Add("Division");
      prPos.Keywords.Add("Salary");
      //设置提示的限制条件
      prPos.AllowNone = false; //不允许没有值
      
      
      5) 现在让我们声明PromptXXXResult变量来获取提示的结果:
      //prompt results
      PromptResult prNameRes;
      PromptResult prDivRes;
      PromptDoubleResult prSalRes;
      PromptPointResult prPosRes;
      
      6) 直到用户成功输入一个点后,循环才结束。如果输入错误的话,我们会提示用户并退出函数:
      判断用户是否输入了关键字,我们通过检查promptresult的状态来进行:
      //循环用来获取雇员的详细资料。当职位被输入后,循环终止。
      while (!gotPosition)
      {
       //提示输入职位
       prPosRes = ed.GetPoint(prPos);
       //取得一个点
       if (prPosRes.Status == PromptStatus.OK)
       {
       gotPosition = true;
       position = prPosRes.Value;
       }
       else if (prPosRes.Status == PromptStatus.Keyword) //获取一个关键字
       {
       //输入了Name关键字
       if (prPosRes.StringResult == "Name")
       {
       //获取雇员名字
       prName.AllowSpaces = true;
       prNameRes = ed.GetString(prName);
       if (prNameRes.Status != PromptStatus.OK)
       {
       return;
       }
       //如果获取雇员名字成功
       if (prNameRes.StringResult != "")
       {
       empName = prNameRes.StringResult;
       }
       }
       }
       else
       {
       // 获取职位时发生错误
       ed.WriteMessage("***Error in getting a point, exiting!!***" + "\r\n");
       return;
       } // 如果获取一个点
      
      }
      
      7) 上面的代码只提示输入名字,请加入提示输入薪水和部门的代码。
      8) 完成提示输入后,我们将使用获得的值来创建雇员。
      //创建雇员
      CreateEmployee(empName, divName, salary, position);
      
      9) 现在来检查部门经理是否已存在。我们通过检查NOD中部门的扩展记录中的经理名字来进行。如果检查到的是一个空字符串,那么我们会提示用户输入经理的名字。注意,通过修改CreateDivision()函数,获取经理的名字变得简单了。
      string manager = "";
      //创建部门
      //给经理传入一个空字符串来检查它是否已存在
      Xrecord depMgrXRec;
      ObjectId xRecId;
      xRecId = CreateDivision(divName, manager);
      //打开部门经理扩展记录
      depMgrXRec = (Xrecord)trans.GetObject(xRecId, OpenMode.ForRead);
      TypedValue[] typedVal = depMgrXRec.Data.AsArray();
      foreach (TypedValue val in typedVal)
      {
       string str;
       str = (string)val.Value;
       if (str == "")
       {
       //经理没有被设置,现在设置它
       // 先提示输入经理的名字
       ed.WriteMessage("\r\n");
       PromptStringOptions prManagerName = new PromptStringOptions("No manager set for the division! Enter Manager Name");
       prManagerName.AllowSpaces = true;
       PromptResult prManagerNameRes = ed.GetString(prManagerName);
       if (prManagerNameRes.Status != PromptStatus.OK)
       {
       return;
       }
       //设置经理的名字
       depMgrXRec.Data = new ResultBuffer(new TypedValue((int)DxfCode.Text, prManagerNameRes.StringResult));
       }
      }
      
      
      
      10) 测试CREATE命令
      
      选择集:
      现在让我们来创建一个命令,当用户在图形中选择一个雇员对象时,它会显示雇员的详细资料。
      我们会使用上一章中创建的ListEmployee()函数在命令行中输出雇员的详细资料。
      下面是你必须遵循的步骤:
      调用“LISTEMPLOYEES”命令
      调用Editor的GetSelection()函数来选择实体
       PromptSelectionResult res = ed.GetSelection(Opts, filter);
      
      上面的filter用来过滤选择集中的块索引。你可以创建如下的过滤列表:
      TypedValue[] filList = new TypedValue[1];
      filList[0] = new TypedValue((int)DxfCode.Start, "INSERT");
      SelectionFilter filter = new SelectionFilter(filList);
      
      从选择集中获取ObjectId数组:
      
       //如果选择失败则什么也不做
       if (res.Status != PromptStatus.OK)
       return;
       Autodesk.AutoCAD.EditorInput.SelectionSet SS = res.Value;
       ObjectId[] idArray;
       idArray = SS.GetObjectIds();
      
      5. 最后,把选择集中的每个ObjectId输入到ListEmployee()函数来获取一个雇员详细资料的字符串数组。把雇员的详细资料输出到命令行。例如:
      
      //获取saEmployeeList 数组中的所有雇员
      foreach (ObjectId employeeId in idArray)
      {
       ListEmployee(employeeId, ref saEmployeeList);
       //把雇员的详细资料输出到命令行
       foreach (string employeeDetail in saEmployeeList)
       {
       ed.WriteMessage(employeeDetail);
       }
      
       ed.WriteMessage("----------------------" + "\r\n");
      }

    用C#生成随机中文汉字验证码的基本原理

    下面介绍一下使用C#生成随机的中文汉字的原理。
      
      
      1、汉字编码原理
      到底怎么办到随机生成汉字的呢?汉字从哪里来的呢?是不是有个后台数据表,其中存放了所需要的所有汉字,使用程序随机取出几个汉字组合就行了呢?使用后台数据库先将所有汉字存起来使用时随机取出,这也是一种办法,但是中文汉字有这么多,怎么来制作呢?其实可以不使用任何后台数据库,使用程序就能做到这一切。要知道如何生成汉字,就得先了解中文汉字的编码原理。
      1980年,为了使每一个汉字有一个全国统一的代码,我国颁布了第一个汉字编码的国家标准: GB2312-80《信息交换用汉字编码字符集》基本集,简称GB2312,这个字符集是我国中文信息处理技术的发展基础,也是国内所有汉字系统的统一标准。到了后来又公布了国家标准GB18030-2000《信息交换用汉字编码字符集基本集的扩充》,简称GB18030,编程时如果涉及到编码和本地化的朋友应该对GB18030很熟悉。这是是我国继GB2312-1980和GB13000-1993之后最重要的汉字编码标准,同时也是未来我国计算机系统必须遵循的基础性标准之一。
      目前在中文WINDOWS操作系统中,.NET编程中默认的的代码页就是GB18030简体中文。但是事实上如果生成中文汉字验证码只须要使用GB2312字符集就已经足够了。字符集中除了我们平时大家都认识的汉字外,也包含了很多我们不认识平时也很少见到的汉字。如果生成中文汉字验证码中有很多我们不认识的汉字让我们输入,对于使用拼音输入法的朋友来说可不是好事,五笔使用者还能勉强根据汉字的长相打出来,呵呵!所以对于GB2312字符集中的汉字我们也不是全都要用。
      中文汉字字符可以使用区位码来表示,见
      
      汉字区位码表 http://navicy2005.home4u.china.com/resource/gb2312tbl.htm
      汉字区位码代码表 http://navicy2005.home4u.china.com/resource/gb2312tbm.htm
      
      其实这两个表是同一回事,只不过一个使用十六进制分区表示,一个使用区位所在的数字位置表示。 例如“好”字的十六进制区位码是ba c3,前两位是区域,后两位代表位置,ba处在第26区,“好”处在此区汉字的第35位也就是c3位置,所以数字代码就是2635。这就是GB2312汉字区位原理。根据《汉字区位码表 》我们可以发现第15区也就是AF区以前都没有汉字,只有少量符号,汉字都从第16区B0开始,这就是为什么GB2312字符集都是从16区开始的。
      
      2、.Net程序处理汉字编码原理分析
      在.Net中可以使用System.Text来处理所有语言的编码。在System.Text命名空间中包含众多编码的类,可供进行操作及转换。其中的Encoding类就是重点处理汉字编码的类。通过在.NET文档中查询Encoding类的方法我们可以发现所有和文字编码有关的都是字节数组,其中有两个很好用的方法:
      
      
      
      Encoding.GetBytes ()方法将指定的 String 或字符数组的全部或部分内容编码为字节数组
      Encoding.GetString ()方法将指定字节数组解码为字符串。
      
      
      没错我们可以通过这两个方法将汉字字符编码为字节数组,同样知道了汉字GB2312的字节数组编码也就可以将字节数组解码为汉字字符。通过对“好”字进行编码为字节数组后
      
      
      
      Encoding gb=System.Text.Encoding.GetEncoding("gb2312");
      object[] bytes=gb.Encoding.GetBytes ("好");
      
      
      发现得到了一个长度为2的字节数组bytes,使用
      
      
      
      string lowCode = System.Convert.ToString(bytes[0], 16); //取出元素1编码内容(两位16进制)
      string hightCode = System.Convert.ToString(bytes[1], 16);//取出元素2编码内容(两位16进制)
      
      
      之后发现字节数组bytes16进制变码后内容竟然是{ba,c3},刚好是“好”字的十六进制区位码(见区位码表)。
      因此我们就可以随机生成一个长度为2的十六进制字节数组,使用GetString ()方法对其进行解码就可以得到汉字字符了。不过对于生成中文汉字验证码来说,因为第15区也就是AF区以前都没有汉字,只有少量符号,汉字都从第16区B0开始,并且从区位D7开始以后的汉字都是和很难见到的繁杂汉字,所以这些都要排出掉。所以随机生成的汉字十六进制区位码第1位范围在B、C、D之间,如果第1位是D的话,第2位区位码就不能是7以后的十六进制数。在来看看区位码表发现每区的第一个位置和最后一个位置都是空的,没有汉字,因此随机生成的区位码第3位如果是A的话,第4位就不能是0;第3位如果是F的话,第4位就不能是F。
      好了,知道了原理,随机生成中文汉字的程序也就出来了,以下就是生成4个随机汉字的C#控制台代码:
      
      
      3、程序代码:
      
      
      
      
      using System;
      using System.Text;
      
      namespace ConsoleApplication
      {
       class ChineseCode
        {
       public static void Main()
       {
       //获取GB2312编码页(表)
       Encoding gb=Encoding.GetEncoding("gb2312");
      
       //调用函数产生4个随机中文汉字编码
       object[] bytes=CreateRegionCode(4);
      
       //根据汉字编码的字节数组解码出中文汉字
       string str1=gb.GetString((byte[])Convert.ChangeType(bytes[0], typeof(byte[])));
       string str2=gb.GetString((byte[])Convert.ChangeType(bytes[1], typeof(byte[])));
       string str3=gb.GetString((byte[])Convert.ChangeType(bytes[2], typeof(byte[])));
       string str4=gb.GetString((byte[])Convert.ChangeType(bytes[3], typeof(byte[])));
      
       //输出的控制台
         Console.WriteLine(str1 + str2 +str3 +str4);
         }
      
      
       /**//*
       此函数在汉字编码范围内随机创建含两个元素的十六进制字节数组,每个字节数组代表一个汉字,并将
       四个字节数组存储在object数组中。
       参数:strlength,代表需要产生的汉字个数
       */
       public static object[] CreateRegionCode(int strlength)
       {
       //定义一个字符串数组储存汉字编码的组成元素
       string[] rBase=new String [16]{"0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f"};
      
       Random rnd=new Random();
      
       //定义一个object数组用来
       object[] bytes=new object[strlength];
      
       /**//*每循环一次产生一个含两个元素的十六进制字节数组,并将其放入bject数组中
       每个汉字有四个区位码组成
       区位码第1位和区位码第2位作为字节数组第一个元素
       区位码第3位和区位码第4位作为字节数组第二个元素
       */
       for(int i=0;i<strlength;i++)
       {
       //区位码第1位
       int r1=rnd.Next(11,14);
       string str_r1=rBase[r1].Trim();
      
       //区位码第2位
       rnd=new Random(r1*unchecked((int)DateTime.Now.Ticks)+i);//更换随机数发生器的
      
      种子避免产生重复值
       int r2;
       if (r1==13)
       {
       r2=rnd.Next(0,7);
       }
       else
       {
       r2=rnd.Next(0,16);
       }
       string str_r2=rBase[r2].Trim();
      
       //区位码第3位
       rnd=new Random(r2*unchecked((int)DateTime.Now.Ticks)+i);
       int r3=rnd.Next(10,16);
       string str_r3=rBase[r3].Trim();
      
       //区位码第4位
       rnd=new Random(r3*unchecked((int)DateTime.Now.Ticks)+i);
       int r4;
       if (r3==10)
       {
       r4=rnd.Next(1,16);
       }
       else if (r3==15)
       {
       r4=rnd.Next(0,15);
       }
       else
       {
       r4=rnd.Next(0,16);
       }
       string str_r4=rBase[r4].Trim();
      
       //定义两个字节变量存储产生的随机汉字区位码
       byte byte1=Convert.ToByte(str_r1 + str_r2,16);
       byte byte2=Convert.ToByte(str_r3 + str_r4,16);
       //将两个字节变量存储在字节数组中
       byte[] str_r=new byte[]{byte1,byte2};
      
       //将产生的一个汉字的字节数组放入object数组中
       bytes.SetValue(str_r,i);
      
       }
      
       return bytes;
      
       }
        }
      
      }
      
      
      
      实现了随机生成汉字后,就可以使用.NET GDI来绘制自己需要的验证码图形了。具体的怎样生成验证码图片,以及改变其中字符的长和宽等效果网上已经有很多相关的文章,这里由于篇幅就不再介绍了。不过有一点要说明的是以上代码在中文版的Windows下才能运行,因为它带有GB的字符集,如果你是其他语言的操作系统,就需要安装GB字符集了。

    ASP.net 验证码(C#)

        public class ValidateCode : System.Web.UI.Page
       {
       private void Page_Load(object sender, System.EventArgs e)
       {
       this.CreateCheckCodeImage(GenerateCheckCode());
       }
      
       #region web 窗体设计器生成的代码
       override protected void OnInit(EventArgs e)
       {
       //
       // CODEGEN: 该调用是 asp.NET web 窗体设计器所必需的。
       //
       InitializeComponent();
       base.OnInit(e);
       }
      
       /// <summary>
       /// 设计器支持所需的方法 - 不要使用代码编辑器修改
       /// 此方法的内容。
       /// </summary>
       private void InitializeComponent()
       {
       this.Load += new System.EventHandler(this.Page_Load);
       }
       #endregion
      
       private string GenerateCheckCode()
       {
       int number;
       char code;
       string checkCode = String.Empty;
      
       System.Random random = new Random();
      
       for(int i=0; i<5; i++)
       {
       number = random.Next();
      
       if(number % 2 == 0)
       code = (char)('0' + (char)(number % 10));
       else
       code = (char)('A' + (char)(number % 26));
      
       checkCode += code.ToString();
       }
      
       Response.Cookies.Add(new HttpCookie("CheckCode", checkCode));
      
       return checkCode;
       }
      
       private void CreateCheckCodeImage(string checkCode)
       {
       if(checkCode == null || checkCode.Trim() == String.Empty)
       return;
      
       System.Drawing.Bitmap image = new System.Drawing.Bitmap((int)Math.Ceiling((checkCode.Length * 12.5)), 22);
       Graphics g = Graphics.FromImage(image);
      
       try
       {
       //生成随机生成器
       Random random = new Random();
      
       //清空图片背景色
       g.Clear(Color.White);
      
       //画图片的背景噪音线
       for(int i=0; i<25; i++)
       {
       int x1 = random.Next(image.Width);
       int x2 = random.Next(image.Width);
       int y1 = random.Next(image.Height);
       int y2 = random.Next(image.Height);
      
       g.DrawLine(new Pen(Color.Silver), x1, y1, x2, y2);
       }
      
       Font font = new System.Drawing.Font("Arial", 12, (System.Drawing.FontStyle.Bold | System.Drawing.FontStyle.Italic));
       System.Drawing.Drawing2D.LinearGradientBrush brush = new System.Drawing.Drawing2D.LinearGradientBrush(new Rectangle(0, 0, image.Width, image.Height), Color.Blue, Color.DarkRed, 1.2f, true);
       g.DrawString(checkCode, font, brush, 2, 2);
      
       //画图片的前景噪音点
       for(int i=0; i<100; i++)
       {
       int x = random.Next(image.Width);
       int y = random.Next(image.Height);
      
       image.SetPixel(x, y, Color.FromArgb(random.Next()));
       }
      
       //画图片的边框线
       g.DrawRectangle(new Pen(Color.Silver), 0, 0, image.Width - 1, image.Height - 1);
      
       System.IO.MemoryStream ms = new System.IO.MemoryStream();
       image.Save(ms, System.Drawing.Imaging.ImageFormat.Gif);
       Response.ClearContent();
       Response.ContentType = "image/Gif";
       Response.BinaryWrite(ms.ToArray());
       }
       finally
       {
       g.Dispose();
       image.Dispose();
       }
       }
       }

    C#的四个基本技巧

    .如果可能尽量使用接口来编程
      .NET框架包括类和接口,在编写程序的时候,你可能知道正在用.NET的哪个类。然而,在这种情况下如果你用.NET支持的接口而不是它的类来编程时,代码会变得更加稳定、可用性会更高。请分析下面的代码:
      这个函数从一个可为任何对象的数组中加载ListBox,这段代码被限定为只能使用数组。假想过些时候你发现那些对象存在数据库中,或别的集合中。那么你需要修改程序来使用不同的集合类型。如果你用ICollection接口来写那段程序,你就不用修改那段程序了,对于任何实现ICollection接口的类型它都能很好的工作:

      ICollection被数组和所有System.Collection中的集合实现。此外,多维数组也支持ICollection接口。如果那还不够的话,数据库.NET类同样支持ICollection接口。用接口写的这个函数不用需改就可以才许多中情况下使用。
      2. 使用属性代替原始数据
      因为属性已经成为语言本身的元素,所以声明数据元素时它的作用域等级没有必要大于private。因为代码本身会把属性看成数据元素,你并没有失去使用简单数据类型的便利性 。相反它会使你的代码更加灵活功能更加强大。属性使你的数据元素封装性更好。属性可以让你使用lazy evaluation来返回数据。lazy evaluation的意思是当用户请求时才计算它的值,而不是一直保留着它。
      最后,属性可以是virtual也可以是abstract。你也可以在接口中定义属性。
      这里还有维护方面的因素应当注意:尽管操作两者的方法是一样的,但是你把一个数据元素变成属性,那么原先客户端的程序便不能访问服务端的新版本程序了。实际上对于在Web service中你想实现序列化的值你可以把它们变成属性来使用:
    private int TheMonth = 0;

    [XmlAttribute ("Month")]
    public int Month
    {
     get {
      return TheMonth;
     }
     set {
      TheMonth = value;
     }
    }

      简单通过属性就可以使你的所有数据元素私有化。

      3. 在Producer/Consumer 的Idiom中使用Delegate

      当你生成一个实现producer idiom类的时候,使用deletate来通知consumer。这种方法相对于用接口更加灵活。Delegate是多点传送的,所以不用加额外的代码你就何以支持多用户。相对于用接口这样做可使类之间的耦合性降低。

      下面的类处理键盘输入并把它传给所有的registered listeners:

    public class KeyboardProcessor
    {
    private OnGetLine theFunc = null;

    public OnGetLine OnGetLineCallback {
     get {
      return theFunc;
     }
     set {
      theFunc = value;
     }
    }

    public void Run (){
    // Read input.
    // If there is any listeners, publish:
    string s;
    do {
     s = Console.ReadLine ();
     if (s.Length == 0)
      break;
     if (theFunc != null){
      System.Delegate [] funcs =theFunc.GetInvocationList();
      foreach (OnGetLine f in funcs) {
       try {
        f (s);
       } catch (Exception e) {
        Console.WriteLine
        ("Caught Exception: {0}", e.Message);
       }
      }
     }
    } while (true);
    }

      任何数目的listeners都可注册到producer,它们所要做的只是提供一个特定的函数:deletate。

    Visual C#创建和使用ActiveX组件

      开发基于.net平台上的程序员是很难从本质上把Visual C#和ActiveX组件联起来,虽然在使用Visual C#开发应用程序时,有时为了快速开发或者由于.Net Framework SDK的不完整,还需要借助ActiveX。但即使如此,也很难把二者联系起来。其中的原因就是能够被Visual C#直接使用文件和通过Visual C#生成的可执行程序只可能是托管的文件。而Active X组件却都是非托管文件。这种文件的差异决定了二者本质"对立"。于是这就引出了本文第一个问题,ActiveX和Visual C#到底是何种关系。
     
      一.Visual C#和Active X组件

      此时可能有些朋友会说,既然能够被Visual C#直接使用只能是托管代码文件,那在Visual C#中提供的可直接通过引用调用ActiveX又是怎么回事?的确Visual C#提供了引用ActiveX组件的操作,这种操作有效的利用了很多以前资源,使得这些资源并没有随着微软推出.Net平台而由于平台的差异被"抛弃",但这种在Visual C#中引入ActiveX组件的操作其实并不被微软公司所倡导,也不符合微软推出.Net的最终目的。这是因为微软之所以推出.Net是为了实现跨平台,为了实现"Write Once and Run Anywhere",写一遍代码,可以在任何平台上运行的目的。如果程序中使用了Active X组件,这也就从另一方面决定了此程序只能在Windows平台上使用,也就无法实现微软的"Write Once and Run Anywhere"最终目标了。

      再者Visual C#提供的引用ActiveX组件的操作,其实Active X组件被加入Visual C#的"工具箱"时,Visual Stuio .Net其实对ActiveX组件进行了很多操作,而这些操作又都被Visual C#隐藏了,使用者往往并不完全清楚。这些操作的作用就是把非托管的ActiveX组件转换成托管的组件,这些操作统称"互操作",细心的程序员可能就会发现,当往程序窗体中拖入ActiveX组件后,源程序所在目录的"Bin"目录中就会新增若干个"Dll"文件,这些文件就是Active X组件进行互操作转换后生成的。此时在Visual C#使用的并不是ActiveX组件,而是由ActiveX组件进行互操作得到可供.Net平台使用的、功能和原先ActiveX组件相同的类库了。

      既然在Visual C#中不能直接使用ActiveX组件,那种看似在Visual C#中使用的ActiveX组件其实使用的是经过了互操作后转换的类库。那么Visual C#是否能够生成Active X组件?本文就来探讨一下Visual C#中生成ActiveX组件的实现方法。制作的方法就是首先通过Visual C#创建一个Windows组件,然后把其接口以COM形式发布即可。

      二.本文中介绍的程序设计及运行环境

      (1).微软视窗2000 服务器版。
     
      (2).Visual Studio .Net 2003企业结构版,.Net Framework SDK 4322。

      三.使用Visual C#创建Windows组件

      以下是使用Visual C#创建一个Windows组件的实现步骤:

      1.启动Visual Studio .Net。

      2.选择菜单【文件】|【新建】|【项目】后,弹出【新建项目】对话框。

      3.将【项目类型】设置为【Visual C#项目】。

      4.将【模板】设置为【类库】。

      5.在【名称】文本框中输入【ActiveXDotNet】。

      6.在【位置】的文本框中输入【C:\Class】,然后单击【确定】按钮,则Visual C#则在"C:\Class"目录中创建"ActiveXDotNet"文件夹,里面存放的是ActiveXDotNet项目文件,具体如图01所示:


    图01:创建类库的【新建项目】对话框

      7.选择【解决方案资源管理器】窗口,并从中上传Class1.cs文件,因为此文件在本程序中已经没有用途了。

      8.选择【项目】|【添加组件】后,弹出【添加新项】对话框,在此对话框中设定【模板】为"组件类",设定【名称】值为"MyControl.cs"后,单击【打开】按钮。则在项目文件中新增一个名称"MyControl.cs"的文件。具体如图02所示:


    图02:在项目中【添加新项】对话框
     9. 把Visual Studio .net的当前窗口切换到【MyControl.cs(设计)】窗口,并从【工具箱】中的【Windows窗体组件】选项卡中往设计窗体中按顺序拖入下列组件:

      一个GrouPBox组件,并向此组件中再拖入,

      一个TextBox组件和一个Lable组件。

      10. 把Visual Studio .Net的当前窗口切换到【MyControl.cs】代码编辑窗口,并用下列代码替换MyControl.cs中的InitializeComponent过程,下列代码是初始化上述加入的组件:

    private void InitializeComponent ( )
    {
     this.groupBox1 = new System.Windows.Forms.GroupBox ( ) ;
     this.txtUserText = new System.Windows.Forms.TextBox ( ) ;
     this.label1 = new System.Windows.Forms.Label ( ) ;
     this.groupBox1.SuspendLayout ( ) ;
     this.SuspendLayout ( ) ;
     this.groupBox1.Controls.Add ( this.txtUserText ) ;
     this.groupBox1.Controls.Add ( this.label1 ) ;
     this.groupBox1.Location = new System.Drawing.Point ( 8 , 8 ) ;
     this.groupBox1.Name = "groupBox1" ;
     this.groupBox1.Size = new System.Drawing.Size ( 272 , 56 ) ;
     this.groupBox1.TabIndex = 0 ;
     this.groupBox1.TabStop = false ;
     this.groupBox1.Text = "Visual Studio .Net创建的Active X组件" ;
     this.txtUserText.Enabled = false ;
     this.txtUserText.Location = new System.Drawing.Point ( 84 , 20 ) ;
     this.txtUserText.Name = "txtUserText" ;
     this.txtUserText.Size = new System.Drawing.Size ( 180 , 21 ) ;
     this.txtUserText.TabIndex = 1 ;
     this.txtUserText.Text = "" ;
     this.label1.Location = new System.Drawing.Point ( 8 , 24 ) ;
     this.label1.Name = "label1" ;
     this.label1.Size = new System.Drawing.Size ( 66 , 16 ) ;
     this.label1.TabIndex = 0 ;
     this.label1.Text = "输入信息:" ;
     this.Controls.Add ( this.groupBox1 ) ;
     this.Name = "MyControl" ;
     this.Size = new System.Drawing.Size ( 288 , 72 ) ;
     this.groupBox1.ResumeLayout ( false ) ;
     this.ResumeLayout ( false ) ;
    }

      至此【ActiveXDotNet】项目创建的Active X组件的界面就基本完成了,具体如图03所示:


    图03:【ActiveXDotNet】项目创建的Active X组件的设计界面

      11. 在MyControl.cs中的【MyControl 的摘要说明】代码区中添加下列代码,以下代码是定义一个公用的接口,此接口是告诉COM/COM+,这儿有一个公用的属性可以进行读写操作:

    public interface AxMyControl
    {
     String UserText { set ; get ; }
    }

      12. 在MyControl.cs的【MyControl】class代码区中添加下列代码,以下代码是首先定义一个私有的字符串,用此字符串来保存从Web测试页面中传递来的数值定义一个公用属性,在接下来的Web测试页面中,将通过这个属性来传递数值,此属性是可读写:

    private String mStr_UserText ;
    public String UserText
    {
     get { return mStr_UserText ; }
     set
     {
      mStr_UserText = value ;
      //修改组件的数值
      txtUserText.Text = value ;
     }
    }

      13. 保存上面的修改步骤,至此我们就利用Visual C#创建了一个名称为MyControl的class,这也就是用Visual C#封装的、酷似Active X组件的组件。

      14. 单击快捷键【Ctrl+F5】,则Visual C#会自动完成编译,并在"C:\Class\ActiveXDotNet\bin\Debug"目录生成一个名称为"ActiveXDotNet.dll"文件,这就是产生的组件。

      以下是经过上述步骤产生的MyControl.cs的全部代码:

    using System ;
    using System.Collections ;
    using System.ComponentModel ;
    using System.Drawing ;
    using System.Data ;
    using System.Windows.Forms ;
    namespace ActiveXDotNet
    {
     public interface AxMyControl
     {
      String UserText { set ; get ; }
     }
     /// <summary>
     /// MyControl 的摘要说明。
     /// </summary>
     public class MyControl : System.Windows.Forms.UserControl , AxMyControl
     {
      /// <summary>
      /// 必需的设计器变量。
      /// </summary>
      private System.ComponentModel.Container components = null ;
      private System.Windows.Forms.GroupBox groupBox1 ;
      private System.Windows.Forms.Label label1 ;
      private System.Windows.Forms.TextBox txtUserText ;
      private String mStr_UserText ;
      public String UserText
      {
       get { return mStr_UserText ; }
       set
       {
        mStr_UserText = value ;
        //修改组件的数值
        txtUserText.Text = value ;
       }
      }
      public MyControl ( )
      {
       // 该调用是 Windows.Forms 窗体设计器所必需的。
       InitializeComponent ( ) ;

       // TODO: 在 InitializeComponent 调用后添加任何初始化
      }
      /// <summary>
      /// 清理所有正在使用的资源。
      /// </summary>
      protected override void Dispose ( bool disposing )
      {
       if ( disposing )
       {
        if ( components != null )
        {
         components.Dispose ( ) ;
        }
       }
       base.Dispose ( disposing ) ;
      }
      #region 组件设计器生成的代码
      /// <summary>
      /// 设计器支持所需的方法 - 不要使用代码编辑器
      /// 修改此方法的内容。
      /// </summary>
      private void InitializeComponent ( )
      {
       this.groupBox1 = new System.Windows.Forms.GroupBox ( ) ;
       this.txtUserText = new System.Windows.Forms.TextBox ( ) ;
       this.label1 = new System.Windows.Forms.Label ( ) ;
       this.groupBox1.SuspendLayout ( ) ;
       this.SuspendLayout ( ) ;
       this.groupBox1.Controls.Add ( this.txtUserText ) ;
       this.groupBox1.Controls.Add ( this.label1 ) ;
       this.groupBox1.Location = new System.Drawing.Point ( 8 , 8 ) ;
       this.groupBox1.Name = "groupBox1" ;
       this.groupBox1.Size = new System.Drawing.Size ( 272 , 56 ) ;
       this.groupBox1.TabIndex = 0 ;
       this.groupBox1.TabStop = false ;
       this.groupBox1.Text = "Visual C#创建的Active X组件" ;
       this.txtUserText.Enabled = false ;
       this.txtUserText.Location = new System.Drawing.Point ( 84 , 20 ) ;
       this.txtUserText.Name = "txtUserText" ;
       this.txtUserText.Size = new System.Drawing.Size ( 180 , 21 ) ;
       this.txtUserText.TabIndex = 1 ;
       this.txtUserText.Text = "" ;
       this.label1.Location = new System.Drawing.Point ( 8 , 24 ) ;
       this.label1.Name = "label1" ;
       this.label1.Size = new System.Drawing.Size ( 66 , 16 ) ;
       this.label1.TabIndex = 0 ;
       this.label1.Text = "输入信息:" ;
       this.Controls.Add ( this.groupBox1 ) ;
       this.Name = "MyControl" ;
       this.Size = new System.Drawing.Size ( 288 , 72 ) ;
       this.groupBox1.ResumeLayout ( false ) ;
       this.ResumeLayout ( false ) ;
      }
      #endregion
     }
    }

    四.Visual C#中使用刚封装的Active X组件:

      以下步骤就是通过Web页面的方式来测试上面创建组件:

      1. 创建一个名称为test.htm文件,MyControl就是放在此Web页面中加以测试的,此文件的内容如下:

    <html>
    <body color = white>
    <hr>

    <font face = arial size = 1>
    <OBJECT id = "MyControl1" name = "MyControl1" classid = "ActiveXDotNet.dll#ActiveXDotNet.MyControl" width = 288 height = 72 >
    </OBJECT>
    </font>

    <form name = "frm" id = "frm" >
    <input type = "text" name = "txt" value = "请输入数据:" ><input type = button value = "确定" onClick = "doScript ( ) ; ">
    </form>
    <hr>
    </body>

    <script language = "JavaScript">
    function doScript ( )
    {
     MyControl1.UserText = frm.txt.value ;
    }
    </script>
    </html>

      2. 把产生的"test.htm"和"ActiveXDotNet.dll"文件全部都拷贝到机器的虚拟目录下,一般来说虚拟目录是"C:\Inetpub\wwwroot"。

      3. 打开浏览器,在浏览器的地址栏中输入"http://localhost/test.htm"后,单击"转到"按钮,则会得到如下的运行界面:


    图04:测试用Visual C#产生的Active X组件的运行界面

      至此Visual C#产生的Active X组件和测试这个组件的全部工作就完成了。

      五.总结:

      虽然本文介绍的方法的确能够方便的解决Web页面中很多棘手的问题,本文介绍用Visual C#产生的组件的在实用性上的确非常的类似Active X组件,但从本质上说,本文产生的组件并不是真正意义上的Active X组件。如要使用本文所创建的组件,必须在Web页面所在机器上安装.net框架,客户端访问Web页面时,也不会真正下载本文介绍的组件,从而也不需要设定计算机的安全级别就能够访问使用此组件的Web页面。可见本文产生的组件其实质也是一个托管的代码文件。它只是巧妙的用定义接口的方式来告诉COM/COM+对象,本组件有一个可供访问的公用属性,通过对此属性的读写操作,完成类似Active X组件的工作。

    《Effective C# 精髓》摘选

    昨天买了一本《Effective C#》,看了几个Item,虽然没有当初读《Effective C++》时的那般震撼,但是也收获不少。把其中的要点记录于下,有些条款加上了自己的理解,权当作读书笔记吧 :-)

      Item 1: Always Use Properties Instead of Accessible Data Members

      这个是地球人都知道的条款了。你需要记住,属性是类的外部接口部分,而(公共)成员却是内部实现。如果把内部实现暴露给外部,对于以后类的实现变更是非常不利的。

      Item 2: Prefer readonly to const

      这个条款需要注意一下几点:

      (1)const在编译期发生作用,即编译器会将所有的const成员置换成对应的常量“值”。

      (2)即使引用其他程序集中的const成员,本程序集中也是硬编码了const成员的值。

      (3)readonly在运行期被评估,所以其性能比const稍差,但是灵活性更高。

      (4)const的值必须在编译期决定,所以不能使用new为其赋值。

      (5)更新一个公有的const成员的值应被视为接口改变,而更新一个readonly变量的值可视为内部实现的改变。

      Item 3: Prefer the is or as Operators to Casts

      (1)is或as称为“动态转换”,是尝试性的,如果失败,不会抛出异常。尽可能使用as操作符。该机制使用元数据完成功能。

      (2)Cast称为“强制转换”,如果失败,则抛出异常--代价高昂。

      (3)is、as、Cast转换都不会调用自定义的转换操作符。

      (4)is可以判断一个object是否为值类型,而as不行。

      (5)请注意Type.IsAssignableFrom()和Type.IsSubclassOf()方法,他们也是常用的“类型检测”手段。注意,Type.IsSubclassOf()方法不支持接口检测,而Type.IsAssignableFrom()支持。

      Item 4: Use Conditional Attributes Instead of #if

      使用#if常(可能)导致性能问题(如空方法调用)和程序对#if/#endif块代码的依赖问题。

      (1)使用Conditional Attributes修饰的方法总是会被编译到目标程序集中,无论是Release或Debug。

      (2)如果条件不满足该Conditional Attributes指定的条件,则编译器会忽略所有对其修饰的方法的调用。

      (3)被Conditional Attributes修饰的方法必须返回void,这是有道理的。因为我们的程序运行不能依赖被Conditional Attributes修饰的方法的返回值。否则,在不同的条件下,我们的程序将表现出非我们期望的不用行为。

      Item 5: Always Provide ToString()

      关于这一点,我在以往的项目中早有体会。举个例子,曾经我们需要把从数据库中取出的Customer列表绑定到ComboBox,开始时我们设计Customer时并没有重写ToString()方法,所以我们要这样做:

                //从数据库中挑出所有有效用户
                string whereStr = string.Format("where {0} = '1'" ,Customer._IsValid) ;
                Customer[] customers 
    = (Customer[])DataEntrance.GetObjects(typeof(Customer) ,whereStr) ;
                ArrayList cusNameList 
    = new ArrayList() ;
                
    foreach(Customer cus in customers)
                {
                    cusNameList.Add(
    string.Format("{0} {1}" ,cus.ID ,cus.Name)) ;
                }
                
    //绑定
                this.comboBox1.DataSource = cusNameList ;

      如果为Customer重写ToString()方法,

            #region ToString 
            
    public override string ToString()
            {
                
    return this.ID.ToString() + " " + this.Name.ToString() ;
            }
            
    #endregion

      则只需要这样:

                string whereStr = string.Format("where {0} = '1'" ,Customer._IsValid) ;
                Customer[] customers 
    = (Customer[])DataEntrance.GetObjects(typeof(Customer) ,whereStr) ;
                
    this.comboBox1.DataSource = customers ;

      这样就简便了好多,而且这样做还有一个好处,比如,从ComboBox从选取一个客户时,以前需要这样:

                string cusID = this.comboBox1.SelectedItem.ToString().Split(' ')[0] ;
                Customer desCus 
    = null ;
                
    foreach(Customer cus in customers)
                {
                    
    if(cus.ID = cusID)
                    {
                        desCus 
    = cus ;
                        
    break ;
                    }
                }

      现在,简单多了,一行代码搞定。

    Customer desCus = this.comboBox1.SelectedItem as Customer ;

    C#+ASP.NET开发基于Web的RSS阅读器

     最近我一直在寻找如何在Web页面上显示RSS Feed的方法,我选择 C#ASP.net作为工具。我创建了一个简单的处理函数来处理从一个URL获得的RSS Feed。你可以直接使用这个简单的函数,或者改造成你想要的功能。

      这个函数使用一个字符串rssURL作为它的参数。这个字符串包含了RSS的URL。它使用rssURL的值建立了一个WebRequest项:

    System.Net.WebRequest myRequest = System.Net.WebRequest.Create(rssURL);

      这个请求的响应将会被放到一个WebResponse对象里:

    System.Net.WebResponse myResponse = myRequest.GetResponse();

      然后这个WebResponse对象被用来建立一个流来取出XML的值:

    System.IO.Stream rssStream = myResponse.GetResponseStream();

      然后可以使用一个XMLDocument对象来存储流中的XML内容。XmlDocument对象用来调入XML的内容:

    System.Xml.XmlDocument rssDoc = new System.Xml.XmlDocument();
    rssDoc.Load(rssStream);

      因为RSS Feed不只是一个XML文件,我们可以假设里面包含了一些RSS标准的规定。这里,我们假设使用了RSS 2.0。你可以从http://blogs.law.harvard.edu/tech/rss里得到规范的详细内容。

      具体的来说,每个项应该在rss/channel/里。使用XPath表达,一个项节点列表可以如下方式创建:

    System.Xml.XmlNodeList rssItems = rssDoc.SelectNodes("rss/channel/item");

      rssItems存储了从RSS里获得所有项节点的信息。这样就可取得内部所需要的信息了。这里,标题、链接和每个项的描述将会被显示。在rssItems中存储的每个项,每个标记(tag)元素都可以用SelectSingleNode方法提取出来。返回的值将被赋给一个XMLNode对象。以下代码获取了一个标题节点:

    System.Xml.XmlNode rssDetail;
    rssDetail = rssItems.Item(i).SelectSingleNode("title");

      现在标记需要被提取出来,使用InnerText完成这项工作。在调用SelectSingleNode之后,可以用rssDetail来测试格式化的RSS XML是否包含某些标记:

    if (rssDetail != null) { title = rssDetail.InnerText; } else { title = ""; }

      这样,你就完成了从一个Feed里获取RSS内容的工作。剩下的工作就是调用这个方法来显示Feed的内容了。以下是一个使用ASP.NET完成的完整的例子:

    <%@ Page Language="C#" %>

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

    <script runat="server">
    public void ProcessRSSItem(string rssURL)
    {
     System.Net.WebRequest myRequest = System.Net.WebRequest.Create(rssURL);
     System.Net.WebResponse myResponse = myRequest.GetResponse();

     System.IO.Stream rssStream = myResponse.GetResponseStream();
     System.Xml.XmlDocument rssDoc = new System.Xml.XmlDocument();
     rssDoc.Load(rssStream);

     System.Xml.XmlNodeList rssItems = rssDoc.SelectNodes("rss/channel/item");

     string title = "";
     string link = "";
     string description = "";

     for (int i = 0; i < rssItems.Count; i++)
     {
      System.Xml.XmlNode rssDetail;

      rssDetail = rssItems.Item(i).SelectSingleNode("title");
      if (rssDetail != null)
      {
       title = rssDetail.InnerText;
      }
      else
      {
       title = "";
      }

      rssDetail = rssItems.Item(i).SelectSingleNode("link");
      if (rssDetail != null)
      {
       link = rssDetail.InnerText;
      }
      else
      {
       link = "";
      }

      rssDetail = rssItems.Item(i).SelectSingleNode("description");
      if (rssDetail != null)
      {
       description = rssDetail.InnerText;
      }
      else
      {
       description = "";
      }

      Response.Write("<p><b><a href='" + link + "' target='new'>" + title + "</a></b><br/>");
      Response.Write(description + "</p>");
     }
    }
    </script>

    <html xmlns="http://www.w3.org/1999/xhtml">
    <head runat="server">
    <title>Untitled Page</title>
    </head>
    <body>
    <form id="form1" runat="server">
    <div>
    <%
     string rssURL = "http://www.codeguru.com/icom_includes/feeds/codeguru/rss-all.xml";
     Response.Write("<font size=5><b>Site: " + rssURL + "</b></font><Br />");
     ProcessRSSItem(rssURL);
     Response.Write("<hr />");

     rssURL = "http://www.developer.com/icom_includes/feeds/special/dev-5.xml";
     Response.Write("<font size=5><b>Site: " + rssURL + "</b></font><Br />");
     ProcessRSSItem(rssURL);
    %>
    </div>
    </form>
    </body>
    </html>

    显示结果如下:

    对C#开发的两个基本原则的深入讨论

     使用属性,避免将数据成员直接暴露给外界

      学习研究.net的早期,经常碰到一些学习C#/.NET的朋友问,要属性这种华而不实的东西做什么?后来做项目时也时常接到team里的人的抱怨反馈,为什么不直接放一个public字段?如:

    class Card
    { 
     public string Name;
    }

      而要做一个private字段+public属性

    class Card
    {
     private string name;
     public string Name
     {
      get { return this.name;}
      set { this.name=value;}
     }
    }

      我记得在早期的一个项目里,team中的一个朋友甚至厌烦了写private字段+public属性,尤其是碰到一大堆臃肿的data object class的时候,索性自己写了一个小工具,来提供一个类的字段名和类型,然后自动为该类生成相应的private字段+public属性。

      我在编程的时候是个彻底的实用主义者,用稍微高雅一点的话说叫“不喜欢过度的设计”。如果真的像上面那样写Card,而且在将来没有什么改变的需求,我也不喜欢像上面第2段程序那样把事情故意搞得复杂。但如果从component的角度来讲,总有一些class是要供外部长久地使用,也潜在地在将来有被改变的需求。这时候,提供属性就很有必要了。

      这就是这个Item试图要归纳的使用属性的理由:

      1.可以对赋值做校验、或者额外的处理

      2.可以做线程同步

      3.可以使用虚属性、或者抽象属性

      4.可以将属性置于interface中

      5.可以提供get-only或者set-only版本,甚至可以给读、写以不同的访问权限(C# 2.0支持)

      个人感觉3、4条是属性最大的优点,可以填补没有“虚字段”或“抽象字段”的缺憾,在设计组件的时候非常有用,也体现了C#这样的component-oriented语言的精神内涵。

      但如果没有上述理由,而且日后对程序做大的改动可能性比较小时,我想也大可不必非要把每个public字段都要变成属性。比如在设计一些轻型的struct,用于互操作的时候,直接使用public字段没什么不好。所以,感觉本条目Bill Wagner先生使用“Always Use Properties Instead of Accessible Data Members”显得太过强硬。

      其实,这里的讨论也表明阅读《Effective C#》一书时需要注意的地方,即Effective原则并不是放之四海而皆准的。不同的项目(组件化、复用程度较高的项目?还是“一次编写、N年都run”的项目),不同的角色(类库/组件开发人员?还是应用程序开发人员?),有着不同的Effective准则。事实上,书中很多Items都是从类库/组件开发人员的角度来考虑的。

      关于属性的性能问题需要谈一点,如果仅仅是简单地以存取模式来使用属性,在相当程度上是没有性能损失的。因为在JIT编译过程中已经做了inline的处理。不过inline处理还是有一些基本的条件,有些情况下JIT编译器不会inline,比如虚调用,方法的IL代码长度过长(目前CLR的规定是超过32bytes为代码长度过长),有复杂的控制流逻辑,有异常处理等。这些条件都是要么根本不能使用inline(比如虚属性),要么inline的代价太大,容易导致代码的bloat,要么是inline起来很费时间——已经丧失了inline的意义,因为.NET的inline机制发生在JIT过程中。使用属性有个别让人感觉不舒服的地方,比如它影响开发人员的开发效率,但对代码运行的效率不产生影响。

      明辨值类型和引用类型的使用场合

      这个条款讨论的是类型设计时候的tradeoff——是将类型设计为结构还是类。Bill Wagner先生给出了一个原则“值类型用于存储数据,引用类型用于定义行为(value types store values and reference types define behavior)”。

      如何判断这个原则的适用性,Bill Wagner也给出了一个方法,那就是首先回答下面几个问题:

      1.该类型的主要职责是否用于数据存储?

      2.该类型的公有接口是否都是一些存取属性?

      3.是否确信该类型永远不可能有子类?

      4.是否确信该类型永远不可能具有多态行为?

      如果所有问题的答案都是yes,那么就应该采用值类型。这样的判断确实有很好的理由支撑,但是我个人认为“将这4个问题回答为yes”还不足以构成采用值类型的全部理由。因为在很多项目实践中,我发现值类型带来的性能问题不可小视。值类型带来的性能问题主要有两个:

      1.由于值类型实例在栈和托管堆之间的转换而导致的box/unbox,以及由此带来的托管堆上的垃圾。

      2.值类型默认情况下采用的是值拷贝语义,如果是比较大的值类型,在传递参数和函数返回值时,同样会带来性能问题。

      关于第1条,Bill Wagner在本条款中提到了“引用类型会给垃圾收集器带来负担”这个表面看似正确的判断。但是由于box/unbox的效应,有些情况下,反倒是值类型给垃圾收集器带来了更多的负担。比如将一些值类型放到一个集合中,然后又频繁地对其进行读写操作。如果碰到这种情况,我想“放弃结构而采用类”未尝不是一种更好的做法。事实上,将一个用作数据存储的值类型(比如System.Drawing.Point)添加到一个集合(System.Collections.ArrayList)中是一个太常见不过的操作。不过,C# 2.0中新引入的泛型技术对box/unbox的问题有极大的改善。

      关于第2条,Scott Meyers先生在Effective C++的第22条“尽量使用pass-by-reference(传址),少用pass-by-value(传值)”中讲的比较清楚。虽然由于C#中的结构类型具有默认的深拷贝语义,没有拷贝构造器的调用。而且结构类型也没有子类,因此在某种程度上来讲不具有多态性,也就没有C++对象传值时可能出现的切割(slicing)效应。但是值拷贝的成本仍然不小。尤其是在这个值类型比较大的情况下,问题就比较严重。实际上,在.NET框架的Design Guidelines for Class Library Developers文档中,在说明什么时候应该使用结构类型的时候,其中提到了一项原则(还有其他一些并行原则)——类型实例数据的大小要小于16个字节。该文档主要是从类型的运行效率层面来考虑的,而Bill Wagner先生这里的条款主要是从类型的设计层面来考虑的。

      从上述两条讨论来看,我个人倾向于对结构类型采取更为保守的设计策略。而对于类则可以积极大胆地使用。因为“将结构类型不适当地设计为类”带来的不良后果要远远小于“将类不适当地设计为结构类型”所带来的不良后果。就目前的经验来看,我甚至认为只有和非托管互操作打交道的情况才是使用结构类型最充足的理由,其他情况都要“三思而后行”。当然,在C# 2.0中引入泛型技术之后,box/unbox将不再是一个沉重的负担,应付一些非常轻量级的场合,结构类型依然有自己的一席之地。

    C# 2.0中泛型编程初级入门教程

      在2005年底微软公司正式发布了C# 2.0,与C# 1.x相比,新版本增加了很多新特性,其中最重要的是对泛型的支持。通过泛型,我们可以定义类型安全的数据结构,而无需使用实际的数据类型。这能显著提高性能并得到更高质量的代码。泛型并不是什么新鲜的东西,他在功能上类似于C++的模板,模板多年前就已存在C++上了,并且在C++上有大量成熟应用。

      本文讨论泛型使用的一般问题,比如为什么要使用泛型、泛型的编写方法、泛型中数据类型的约束、泛型中静态成员使用要注意的问题、泛型中方法重载的问、泛型方法等,通过这些使我们可以大致了解泛型并掌握泛型的一般应用,编写出更简单、通用、高效的应用系统。

      什么是泛型

      我们在编写程序时,经常遇到两个模块的功能非常相似,只是一个是处理int数据,另一个是处理string数据,或者其他自定义的数据类型,但我们没有办法,只能分别写多个方法处理每个数据类型,因为方法的参数类型不同。有没有一种办法,在方法中传入通用的数据类型,这样不就可以合并代码了吗?泛型的出现就是专门解决这个问题的。读完本篇文章,你会对泛型有更深的了解。

      为什么要使用泛型

      为了了解这个问题,我们先看下面的代码,代码省略了一些内容,但功能是实现一个栈,这个栈只能处理int数据类型:

    public class Stack
    {
     private int[] m_item;
     public int Pop(){...}
     public void Push(int item){...}
     public Stack(int i)
     {
      this.m_item = new int[i];
     }
    }

      上面代码运行的很好,但是,当我们需要一个栈来保存string类型时,该怎么办呢?很多人都会想到把上面的代码复制一份,把int改成string不就行了。当然,这样做本身是没有任何问题的,但一个优秀的程序是不会这样做的,因为他想到若以后再需要long、Node类型的栈该怎样做呢?还要再复制吗?优秀的程序员会想到用一个通用的数据类型object来实现这个栈:

    public class Stack
    {
     private object[] m_item;
     public object Pop(){...}
     public void Push(object item){...}

     public Stack(int i)
     {
      this.m_item = new[i];
     }

    }

      这个栈写的不错,他非常灵活,可以接收任何数据类型,可以说是一劳永逸。但全面地讲,也不是没有缺陷的,主要表现在:

      当Stack处理值类型时,会出现装箱、折箱操作,这将在托管堆上分配和回收大量的变量,若数据量大,则性能损失非常严重。
    在处理引用类型时,虽然没有装箱和折箱操作,但将用到数据类型的强制转换操作,增加处理器的负担。

      在数据类型的强制转换上还有更严重的问题(假设stack是Stack的一个实例):

    Node1 x = new Node1();
    stack.Push(x);
    Node2 y = (Node2)stack.Pop();

      上面的代码在编译时是完全没问题的,但由于Push了一个Node1类型的数据,但在Pop时却要求转换为Node2类型,这将出现程序运行时的类型转换异常,但却逃离了编译器的检查。

      针对object类型栈的问题,我们引入泛型,他可以优雅地解决这些问题。泛型用用一个通过的数据类型T来代替object,在类实例化时指定T的类型,运行时(Runtime)自动编译为本地代码,运行效率和代码质量都有很大提高,并且保证数据类型安全。

      使用泛型


      下面是用泛型来重写上面的栈,用一个通用的数据类型T来作为一个占位符,等待在实例化时用一个实际的类型来代替。让我们来看看泛型的威力:

    public class Stack<T>
    {
     private T[] m_item;
     public T Pop(){...}
     public void Push(T item){...}

     public Stack(int i)
     {
      this.m_item = new T[i];
     }
    }

      类的写法不变,只是引入了通用数据类型T就可以适用于任何数据类型,并且类型安全的。这个类的调用方法:

    //实例化只能保存int类型的类

    Stack<int> a = new Stack<int>(100);
    a.Push(10);
    a.Push("8888"); //这一行编译不通过,因为类a只接收int类型的数据
    int x = a.Pop();

    //实例化只能保存string类型的类

    Stack<string> b = new Stack<string>(100);
    b.Push(10); //这一行编译不通过,因为类b只接收string类型的数据
    b.Push("8888");
    string y = b.Pop();

      这个类和object实现的类有截然不同的区别:

      1. 他是类型安全的。实例化了int类型的栈,就不能处理string类型的数据,其他数据类型也一样。

      2. 无需装箱和折箱。这个类在实例化时,按照所传入的数据类型生成本地代码,本地代码数据类型已确定,所以无需装箱和折箱。

      3. 无需类型转换。
      在2005年底微软公司正式发布了C# 2.0,与C# 1.x相比,新版本增加了很多新特性,其中最重要的是对泛型的支持。通过泛型,我们可以定义类型安全的数据结构,而无需使用实际的数据类型。这能显著提高性能并得到更高质量的代码。泛型并不是什么新鲜的东西,他在功能上类似于C++的模板,模板多年前就已存在C++上了,并且在C++上有大量成熟应用。

      本文讨论泛型使用的一般问题,比如为什么要使用泛型、泛型的编写方法、泛型中数据类型的约束、泛型中静态成员使用要注意的问题、泛型中方法重载的问、泛型方法等,通过这些使我们可以大致了解泛型并掌握泛型的一般应用,编写出更简单、通用、高效的应用系统。

      什么是泛型

      我们在编写程序时,经常遇到两个模块的功能非常相似,只是一个是处理int数据,另一个是处理string数据,或者其他自定义的数据类型,但我们没有办法,只能分别写多个方法处理每个数据类型,因为方法的参数类型不同。有没有一种办法,在方法中传入通用的数据类型,这样不就可以合并代码了吗?泛型的出现就是专门解决这个问题的。读完本篇文章,你会对泛型有更深的了解。

      为什么要使用泛型

      为了了解这个问题,我们先看下面的代码,代码省略了一些内容,但功能是实现一个栈,这个栈只能处理int数据类型:

    public class Stack
    {
     private int[] m_item;
     public int Pop(){...}
     public void Push(int item){...}
     public Stack(int i)
     {
      this.m_item = new int[i];
     }
    }

      上面代码运行的很好,但是,当我们需要一个栈来保存string类型时,该怎么办呢?很多人都会想到把上面的代码复制一份,把int改成string不就行了。当然,这样做本身是没有任何问题的,但一个优秀的程序是不会这样做的,因为他想到若以后再需要long、Node类型的栈该怎样做呢?还要再复制吗?优秀的程序员会想到用一个通用的数据类型object来实现这个栈:

    public class Stack
    {
     private object[] m_item;
     public object Pop(){...}
     public void Push(object item){...}

     public Stack(int i)
     {
      this.m_item = new[i];
     }

    }

      这个栈写的不错,他非常灵活,可以接收任何数据类型,可以说是一劳永逸。但全面地讲,也不是没有缺陷的,主要表现在:

      当Stack处理值类型时,会出现装箱、折箱操作,这将在托管堆上分配和回收大量的变量,若数据量大,则性能损失非常严重。
    在处理引用类型时,虽然没有装箱和折箱操作,但将用到数据类型的强制转换操作,增加处理器的负担。

      在数据类型的强制转换上还有更严重的问题(假设stack是Stack的一个实例):

    Node1 x = new Node1();
    stack.Push(x);
    Node2 y = (Node2)stack.Pop();

      上面的代码在编译时是完全没问题的,但由于Push了一个Node1类型的数据,但在Pop时却要求转换为Node2类型,这将出现程序运行时的类型转换异常,但却逃离了编译器的检查。

      针对object类型栈的问题,我们引入泛型,他可以优雅地解决这些问题。泛型用用一个通过的数据类型T来代替object,在类实例化时指定T的类型,运行时(Runtime)自动编译为本地代码,运行效率和代码质量都有很大提高,并且保证数据类型安全。

      使用泛型


      下面是用泛型来重写上面的栈,用一个通用的数据类型T来作为一个占位符,等待在实例化时用一个实际的类型来代替。让我们来看看泛型的威力:

    public class Stack<T>
    {
     private T[] m_item;
     public T Pop(){...}
     public void Push(T item){...}

     public Stack(int i)
     {
      this.m_item = new T[i];
     }
    }

      类的写法不变,只是引入了通用数据类型T就可以适用于任何数据类型,并且类型安全的。这个类的调用方法:

    //实例化只能保存int类型的类

    Stack<int> a = new Stack<int>(100);
    a.Push(10);
    a.Push("8888"); //这一行编译不通过,因为类a只接收int类型的数据
    int x = a.Pop();

    //实例化只能保存string类型的类

    Stack<string> b = new Stack<string>(100);
    b.Push(10); //这一行编译不通过,因为类b只接收string类型的数据
    b.Push("8888");
    string y = b.Pop();

      这个类和object实现的类有截然不同的区别:

      1. 他是类型安全的。实例化了int类型的栈,就不能处理string类型的数据,其他数据类型也一样。

      2. 无需装箱和折箱。这个类在实例化时,按照所传入的数据类型生成本地代码,本地代码数据类型已确定,所以无需装箱和折箱。

      3. 无需类型转换。
    泛型类实例化的理论

      C#泛型类在编译时,先生成中间代码IL,通用类型T只是一个占位符。在实例化类时,根据用户指定的数据类型代替T并由即时编译器(JIT)生成本地代码,这个本地代码中已经使用了实际的数据类型,等同于用实际类型写的类,所以不同的封闭类的本地代码是不一样的。按照这个原理,我们可以这样认为:

      泛型类的不同的封闭类是分别不同的数据类型。

      例:Stack<int>和Stack<string>是两个完全没有任何关系的类,你可以把他看成类A和类B,这个解释对泛型类的静态成员的理解有很大帮助。

      泛型类中数据类型的约束

      程序员在编写泛型类时,总是会对通用数据类型T进行有意或无意地有假想,也就是说这个T一般来说是不能适应所有类型,但怎样限制调用者传入的数据类型呢?这就需要对传入的数据类型进行约束,约束的方式是指定T的祖先,即继承的接口或类。因为C#的单根继承性,所以约束可以有多个接口,但最多只能有一个类,并且类必须在接口之前。这时就用到了C#2.0的新增关键字:

    public class Node<T, V> where T : Stack, IComparable
    where V: Stack
    {...}

      以上的泛型类的约束表明,T必须是从Stack和IComparable继承,V必须是Stack或从Stack继承,否则将无法通过编译器的类型检查,编译失败。

      通用类型T没有特指,但因为C#中所有的类都是从object继承来,所以他在类Node的编写中只能调用object类的方法,这给程序的编写造成了困难。比如你的类设计只需要支持两种数据类型int和string,并且在类中需要对T类型的变量比较大小,但这些却无法实现,因为object是没有比较大小的方法的。 了解决这个问题,只需对T进行IComparable约束,这时在类Node里就可以对T的实例执行CompareTo方法了。这个问题可以扩展到其他用户自定义的数据类型。

      如果在类Node里需要对T重新进行实例化该怎么办呢?因为类Node中不知道类T到底有哪些构造函数。为了解决这个问题,需要用到new约束:

    public class Node<T, V> where T : Stack, new()

    where V: IComparable

      需要注意的是,new约束只能是无参数的,所以也要求相应的类Stack必须有一个无参构造函数,否则编译失败。

      C#中数据类型有两大类:引用类型和值类型。引用类型如所有的类,值类型一般是语言的最基本类型,如int, long, struct等,在泛型的约束中,我们也可以大范围地限制类型T必须是引用类型或必须是值类型,分别对应的关键字是class和struct:

    public class Node<T, V> where T : class

    where V: struct

      泛型方法

      泛型不仅能作用在类上,也可单独用在类的方法上,他可根据方法参数的类型自动适应各种参数,这样的方法叫泛型方法。看下面的类:

    public class Stack2
    {
     public void Push<T>(Stack<T> s, params T[] p)
     {
      foreach (T t in p)
      {
       s.Push(t);
      }
     }
    }

      原来的类Stack一次只能Push一个数据,这个类Stack2扩展了Stack的功能(当然也可以直接写在Stack中),他可以一次把多个数据压入Stack中。其中Push是一个泛型方法,这个方法的调用示例如下:

    Stack<int> x = new Stack<int>(100);
    Stack2 x2 = new Stack2();
    x2.Push(x, 1, 2, 3, 4, 6);

    string s = "";

    for (int i = 0; i < 5; i++)
    {
     s += x.Pop().ToString();
    } //至此,s的值为64321

      泛型中的静态成员变量

      在C#1.x中,我们知道类的静态成员变量在不同的类实例间是共享的,并且他是通过类名访问的。C#2.0中由于引进了泛型,导致静态成员变量的机制出现了一些变化:静态成员变量在相同封闭类间共享,不同的封闭类间不共享。

      这也非常容易理解,因为不同的封闭类虽然有相同的类名称,但由于分别传入了不同的数据类型,他们是完全不同的类,比如:

    Stack<int> a = new Stack<int>();
    Stack<int> b = new Stack<int>();
    Stack<long> c = new Stack<long>();


      类实例a和b是同一类型,他们之间共享静态成员变量,但类实例c却是和a、b完全不同的类型,所以不能和a、b共享静态成员变量。

      泛型中的静态构造函数

      静态构造函数的规则:只能有一个,且不能有参数,他只能被.net运行时自动调用,而不能人工调用。

      泛型中的静态构造函数的原理和非泛型类是一样的,只需把泛型中的不同的封闭类理解为不同的类即可。以下两种情况可激发静态的构造函数:

      1. 特定的封闭类第一次被实例化。

      2. 特定封闭类中任一静态成员变量被调用。

    泛型类中的方法重载

      方法的重载在.net Framework中被大量应用,他要求重载具有不同的签名。在泛型类中,由于通用类型T在类编写时并不确定,所以在重载时有些注意事项,这些事项我们通过以下的例子说明:

    public class Node<T, V>
    {
     public T add(T a, V b) //第一个add
     {
      return a;
     }
     public T add(V a, T b) //第二个add
     {
      return b;
     }
     public int add(int a, int b) //第三个add
     {
      return a + b;
     }
    }

      上面的类很明显,如果T和V都传入int的话,三个add方法将具有同样的签名,但这个类仍然能通过编译,是否会引起调用混淆将在这个类实例化和调用add方法时判断。请看下面调用代码:

    Node<int, int> node = new Node<int, int>();
    object x = node.add(2, 11);

      这个Node的实例化引起了三个add具有同样的签名,但却能调用成功,因为他优先匹配了第三个add。但如果删除了第三个add,上面的调用代码则无法编译通过,提示方法产生的混淆,因为运行时无法在第一个add和第二个add之间选择。

    Node<string, int> node = new Node<string, int>();
    object x = node.add(2, "11");

      这两行调用代码可正确编译,因为传入的string和int,使三个add具有不同的签名,当然能找到唯一匹配的add方法。

      由以上示例可知,C#的泛型是在实例的方法被调用时检查重载是否产生混淆,而不是在泛型类本身编译时检查。同时还得出一个重要原则:

      当一般方法与泛型方法具有相同的签名时,会覆盖泛型方法。

      泛型类的方法重写

      方法重写(override)的主要问题是方法签名的识别规则,在这一点上他与方法重载一样,请参考泛型类的方法重载。

      泛型的使用范围

      本文主要是在类中讲述泛型,实际上,泛型还可以用在类方法、接口、结构(struct)、委托等上面使用,使用方法大致相同,就不再讲述。

      小结

      C# 泛型是开发工具库中的一个无价之宝。它们可以提高性能、类型安全和质量,减少重复性的编程任务,简化总体编程模型,而这一切都是通过优雅的、可读性强的语法完成的。尽管 C# 泛型的根基是 C++ 模板,但 C# 通过提供编译时安全和支持将泛型提高到了一个新水平。C# 利用了两阶段编译、元数据以及诸如约束和一般方法之类的创新性的概念。毫无疑问,C# 的将来版本将继续发展泛型,以便添加新的功能,并且将泛型扩展到诸如数据访问或本地化之类的其他 .NET Framework 领域。

    Visual C#中编写多线程程序之起步

    .net将关于多线程的功能定义在System.Threading名字空间中。因此,要使用多线程,必须先声明引用此名字空间(using System.Threading;)。

      即使你没有编写多线程应用程序的经验,也可能听说过“启动线程”“杀死线程”这些词,其实除了这两个外,涉及多线程方面的还有诸如“暂停线程”“优先级”“挂起线程”“恢复线程”等等。下面将一个一个的解释。

      a.启动线程

      顾名思义,“启动线程”就是新建并启动一个线程的意思,如下代码可实现:

    Thread thread1 = new Thread(new ThreadStart( Count));

      其中的 Count 是将要被新线程执行的函数。

      b.杀死线程

      “杀死线程”就是将一线程斩草除根,为了不白费力气,在杀死一个线程前最好先判断它是否还活着(通过 IsAlive 属性),然后就可以调用 Abort 方法来杀死此线程。

      c.暂停线程

      它的意思就是让一个正在运行的线程休眠一段时间。如 thread.Sleep(1000); 就是让线程休眠1秒钟。

    利用C#编写一个简单的抓网页应用程序

     本文利用C#和.NET提供的类来轻松创建一个抓取网页内容源代码的程序 。HTTP是WWW进行数据访问最基本的协议之一,在.NET的基本类型库类中提供了两个对象类:HTTPWebRequest和HTTPWebResponse,分别用来向某资源发送请求和获得响应。为了得到一个资源的内容,我们先指定一个想要抓取的URL地址,用HTTPWebRequest对象进行请求,用HTTPWebResponse对象接收响应的结果,最后用TextStream对象来提取我们想要的信息,并在控制台打印出来。

      下面就是看看如何实现这样的功能:

      第一步:打开VS.NET,点“文件”-“新建”-“项目”,项目类型选择“Visual C#项目”,模板选“Windows应用程序”,

      第二步:在Form1里加入Label1,Button1,TextBox1,TextBox2四个控件,TextBox2的Multiline属性改为True,

      第三步:在Form1窗体上点击右键,选“查看代码”,然后在最顶端输入:

    using System.IO;
    using System.Net;
    using System.Text;

    private void button1_Click(object sender, System.EventArgs e)
    {

    }

      括号之间输入下面的代码:

    byte[] buf = new byte[38192];
    HttpWebRequest request = (HttpWebRequest)
    WebRequest.Create(textBox1.Text);
    HttpWebResponse response = (HttpWebResponse)
    request.GetResponse();
    Stream resStream = response.GetResponseStream();
    int count = resStream.Read(buf, 0, buf.Length);
    textBox2.Text = Encoding.Default.GetString(buf, 0,
    count);
    resStream.Close();

      第四步:点“Save all”按钮,按“F5”运行应用程序,在“请输入URL地址:”后面的单行文本框里输入http://lucky.myrice.com/down.htm,点击“得到 HTML 代码”按钮,就可以看到该地址的代码了!

      下面,我们就对上面的程序做一个分析:

      上面的这个程序的功能是抓取网页http://lucky.myrice.com/down.htm的内容,并在多行文本框里显示出HTML代码,由于返回的数据是字节类型的,因此,我们创建一个名为buf的字节类型的数组变量来存储请求返回来的结果,其中数组的大小与我们要请求返回的数据大小有关系。首先,我们实例化HttpWebRequest对象,使用WebRequest类的静态方法Create(),该方法的字符串参数就是我们要请求页面的URL地址,由于Create()方法返回的是WebRequest类型的,我们必须对它进行造型(即类型转换)成HttpWebRequest类型,再赋给request变量。一旦我们建立了HttpWebRequest对象,就可以使用它的GetResponse()方法来返回一个WebResponse对象,然后再造型成HttpWebResponse对象赋给response变量。现在,就可以使用response对象的GetResponseStream()方法来得到响应的文本流了,最后用Stream对象的Read()方法把返回的响应信息放到我们最初创建的字节数组buf中,Read()有3个参数,分别是:要放入的字节数组,字节数组的开始位置,字节数组的长度。最后把字节转换成字符串,注意:这里采用的采用的是Default编码,它使用默认的编码方式,我们就不用再进行字符编码之间的转换了。也可以利用WebRequest和WebResponse实现以上的功能,代码如下:

    WebRequest request = WebRequest.Create(textBox1.Text);
    WebResponse response =request.GetResponse();

      输入其它的URL看看是不是很方便!

    利用Visual C#打造一个平滑的进度条

    概述

      本文描述了如何建立一个简单的、自定义的用户控件——一个平滑的进度条。

      在早先的进度条控件版本中,例如在 Microsoft Windows Common Controls ActiveX 控件中提供的版本,您可以看到进度条有两种不同的视图。您可以通过设定 Scrolling 属性来设定 Standard 视图或是 Smooth 视图。 Smooth 视图提供了一个区域来平滑的显示进度, Standard 试图则看上去是由一个一个方块来表示进度的。

      在 Visual C# .NET 中提供的进度条控件只支持 Standard 视图。

      本文的代码样例揭示了如何建立一个有如下属性的控件:

       Minimum。该属性表示了进度条的最小值。默认情况下是 0 ;您不能将该属性设为负值。

       Maximum。该属性表示了进度条的最大值。默认情况下是 100 。

       Value。该属性表示了进度条的当前值。该值必须介于 Minimum 和 Maximum 之间。

       ProgressBarColor。该属性表示了进度条的颜色。

      建立一个自定义的进度条控件

      1、按着下面的步骤,在 Visual C# .NET 中建立一个 Windows Control Library 项目:

      a、打开 Microsoft Visual Studio .NET。

      b、点击 File 菜单,点击 New ,再点击 Project 。

      c、在 New Project 对话框中,在 Project Types 中选择 Visual C# Projects,然后在 Templates 中选择 Windows Control Library 。

      d、在 Name 框中,填上 SmoothProgressBar ,并点击 OK 。

      e、在 Project Explorer 中,重命名缺省的 class module ,将 UserControl1.cs 改为 SmoothProgressBar.cs 。

      f、在该 UserControl 对象的 Property 窗口中,将其 Name 属性从 UserControl1 改为 SmoothProgressBar 。

      2、此时,您已经从 control 类继承了一个新类,并可以添加新的功能。但是,ProgressBar累是密封(sealed)的,不能再被继承。因此,您必须从头开始建立这个控件。

      将下面的代码添加到UserControl模块中,就在“Windows Form Designer generated code”之后:

    int min = 0; // Minimum value for progress range
    int max = 100; // Maximum value for progress range
    int val = 0; // Current progress
    Color BarColor = Color.Blue; // Color of progress meter

    protected override void OnResize(EventArgs e)
    {
     // Invalidate the control to get a repaint.
     this.Invalidate();
    }

    protected override void OnPaint(PaintEventArgs e)
    {
     Graphics g = e.Graphics;
     SolidBrush brush = new SolidBrush(BarColor);
     float percent = (float)(val - min) / (float)(max - min);
     Rectangle rect = this.ClientRectangle;

     // Calculate area for drawing the progress.
     rect.Width = (int)((float)rect.Width * percent);

     // Draw the progress meter.
     g.FillRectangle(brush, rect);

     // Draw a three-dimensional border around the control.
     Draw3DBorder(g);

     // Clean up.
     brush.Dispose();
     g.Dispose();
    }

    public int Minimum
    {
     get
     {
      return min;
     }

     set
     {
      // Prevent a negative value.
      if (value < 0)
      {
       min = 0;
      }

      // Make sure that the minimum value is never set higher than the maximum value.
      if (value > max)
      {
       min = value;
       min = value;
      }

      // Ensure value is still in range
      if (val < min)
      {
       val = min;
      }

      // Invalidate the control to get a repaint.
      this.Invalidate();
     }
    }

    public int Maximum
    {
     get
     {
      return max;
     }

     set
     {
      // Make sure that the maximum value is never set lower than the minimum value.
      if (value < min)
      {
       min = value;
      }

      max = value;

      // Make sure that value is still in range.
      if (val > max)
      {
       val = max;
      }

      // Invalidate the control to get a repaint.
      this.Invalidate();
     }
    }

    public int Value
    {
     get
     {
      return val;
     }

     set
     {
      int oldValue = val;

      // Make sure that the value does not stray outside the valid range.
      if (value < min)
      {
       val = min;
      }
      else if (value > max)
      {
       val = max;
      }
      else
      {
       val = value;
      }

      // Invalidate only the changed area.
      float percent;

      Rectangle newValueRect = this.ClientRectangle;
      Rectangle oldValueRect = this.ClientRectangle;

      // Use a new value to calculate the rectangle for progress.
      percent = (float)(val - min) / (float)(max - min);
      newValueRect.Width = (int)((float)newValueRect.Width * percent);

      // Use an old value to calculate the rectangle for progress.
      percent = (float)(oldValue - min) / (float)(max - min);
      oldValueRect.Width = (int)((float)oldValueRect.Width * percent);

      Rectangle updateRect = new Rectangle();

      // Find only the part of the screen that must be updated.
      if (newValueRect.Width > oldValueRect.Width)
      {
       updateRect.X = oldValueRect.Size.Width;
       updateRect.Width = newValueRect.Width - oldValueRect.Width;
      }
      else
      {
       updateRect.X = newValueRect.Size.Width;
       updateRect.Width = oldValueRect.Width - newValueRect.Width;
      }

      updateRect.Height = this.Height;

      // Invalidate the intersection region only.
      this.Invalidate(updateRect);
     }
    }

    public Color ProgressBarColor
    {
     get
     {
      return BarColor;
     }

     set
     {
      BarColor = value;

      // Invalidate the control to get a repaint.
      this.Invalidate();
     }
    }

    private void Draw3DBorder(Graphics g)
    {
     int PenWidth = (int)Pens.White.Width;

     g.DrawLine(Pens.DarkGray, new Point(this.ClientRectangle.Left, this.ClientRectangle.Top),
    new Point(this.ClientRectangle.Width - PenWidth, this.ClientRectangle.Top));
     g.DrawLine(Pens.DarkGray, new Point(this.ClientRectangle.Left, this.ClientRectangle.Top), new Point(this.ClientRectangle.Left, this.ClientRectangle.Height - PenWidth));
     g.DrawLine(Pens.White, new Point(this.ClientRectangle.Left, this.ClientRectangle.Height - PenWidth),
    new Point(this.ClientRectangle.Width - PenWidth, this.ClientRectangle.Height - PenWidth));
    g.DrawLine(Pens.White, new Point(this.ClientRectangle.Width - PenWidth, this.ClientRectangle.Top),
    new Point(this.ClientRectangle.Width - PenWidth, this.ClientRectangle.Height - PenWidth));
    }

      3、在 Build 菜单中,点击 Build Solution 来编译整个项目。

      建立一个简单的客户端应用

      1、在 File 菜单中,点击 New ,再点击Project。

      2、在 Add New Project 对话框中,在 Project Types 中点击 Visual C# Projects,在 Templates 中点击 Windows Application,并点击 OK。

      3、按照下面的步骤,在 Form 上添加两个 SmoothProgressBar 实例:

      a、在 Tools 菜单上,点击 Customize Toolbox。

      b、点击 .NET Framework Components 页。

      c、点击 Browse,然后选中你在 Create a Custom ProgressBar Control 段中建立的 SmoothProgressBar.dll 文件。

      d、点击 OK。您可以看到在 toolbox 中已经有 SmoothProgressBar 控件了。

      e、从 toolbox 中拖两个 SmoothProgressBar 控件的实例到该 Windows Application 项目中的默认 form 上。

      4、从 toolbox 页中拖一个 Timer 控件到 form 上。

      5、将下面的代码添加到 Timer 控件的 Tick 事件中:

    if (this.smoothProgressBar1.Value > 0)
    {
     this.smoothProgressBar1.Value--;
     this.smoothProgressBar2.Value++;
    }
    else
    {
     this.timer1.Enabled = false;
    }

      6、从 toolbox 页中拖一个 Button 控件到 form 上。

      7、将下面的代码添加到 Button 控件的 Click 事件中:

    this.smoothProgressBar1.Value = 100;
    this.smoothProgressBar2.Value = 0;

    this.timer1.Interval = 1;
    this.timer1.Enabled = true;

      8、在 Debug 菜单中,点击 Start 来运行样例项目。

      9、点击Button。注意观察那两个进度指示器。一个逐渐减小,另一个逐渐增加。

    ASP.NET程序中常用的三十三种代码

    1. 打开新的窗口并传送参数:

      传送参数:

    response.write("<script>window.open(’*.aspx?id="+this.DropDownList1.SelectIndex+"&id1="+...+"’)</script>")


      接收参数:

    string a = Request.QueryString("id");
    string b = Request.QueryString("id1");


      2.为按钮添加对话框

    Button1.Attributes.Add("onclick","return confirm(’确认?’)");
    button.attributes.add("onclick","if(confirm(’are you sure...?’)){return true;}else{return false;}")


      3.删除表格选定记录

    int intEmpID = (int)MyDataGrid.DataKeys[e.Item.ItemIndex];
    string deleteCmd = "DELETE from Employee where emp_id = " + intEmpID.ToString()


      4.删除表格记录警告

    private void DataGrid_ItemCreated(Object sender,DataGridItemEventArgs e)
    {
    switch(e.Item.ItemType)
    {
    case ListItemType.Item :
    case ListItemType.AlternatingItem :
    case ListItemType.EditItem:
    TableCell myTableCell;
    myTableCell = e.Item.Cells[14];
    LinkButton myDeleteButton ;
    myDeleteButton = (LinkButton)myTableCell.Controls[0];
    myDeleteButton.Attributes.Add("onclick","return confirm(’您是否确定要删除这条信息’);");
    break;
    default:
    break;
    }

    }


      5.点击表格行链接另一页

    private void grdCustomer_ItemDataBound(object sender, System.Web.UI.WebControls.DataGridItemEventArgs e)
    {
    //点击表格打开
     if (e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem)
    e.Item.Attributes.Add("onclick","window.open(’Default.aspx?id=" + e.Item.Cells[0].Text + "’);");
    }


      双击表格连接到另一页

      在itemDataBind事件中

    if(e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem)
    {
    string OrderItemID =e.item.cells[1].Text;
    ...
    e.item.Attributes.Add("ondblclick", "location.href=’../ShippedGrid.aspx?id=" + OrderItemID + "’");
    }


      双击表格打开新一页

    if(e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem)
    {
    string OrderItemID =e.item.cells[1].Text;
    ...
    e.item.Attributes.Add("ondblclick", "open(’../ShippedGrid.aspx?id=" + OrderItemID + "’)");
    }


      ★特别注意:【?id=】 处不能为【?id =

    1. 打开新的窗口并传送参数:

      传送参数:

    response.write("<script>window.open(’*.aspx?id="+this.DropDownList1.SelectIndex+"&id1="+...+"’)</script>")


      接收参数:

    string a = Request.QueryString("id");
    string b = Request.QueryString("id1");


      2.为按钮添加对话框

    Button1.Attributes.Add("onclick","return confirm(’确认?’)");
    button.attributes.add("onclick","if(confirm(’are you sure...?’)){return true;}else{return false;}")


      3.删除表格选定记录

    int intEmpID = (int)MyDataGrid.DataKeys[e.Item.ItemIndex];
    string deleteCmd = "DELETE from Employee where emp_id = " + intEmpID.ToString()


      4.删除表格记录警告

    private void DataGrid_ItemCreated(Object sender,DataGridItemEventArgs e)
    {
    switch(e.Item.ItemType)
    {
    case ListItemType.Item :
    case ListItemType.AlternatingItem :
    case ListItemType.EditItem:
    TableCell myTableCell;
    myTableCell = e.Item.Cells[14];
    LinkButton myDeleteButton ;
    myDeleteButton = (LinkButton)myTableCell.Controls[0];
    myDeleteButton.Attributes.Add("onclick","return confirm(’您是否确定要删除这条信息’);");
    break;
    default:
    break;
    }

    }


      5.点击表格行链接另一页

    private void grdCustomer_ItemDataBound(object sender, System.Web.UI.WebControls.DataGridItemEventArgs e)
    {
    //点击表格打开
     if (e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem)
    e.Item.Attributes.Add("onclick","window.open(’Default.aspx?id=" + e.Item.Cells[0].Text + "’);");
    }


      双击表格连接到另一页

      在itemDataBind事件中

    if(e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem)
    {
    string OrderItemID =e.item.cells[1].Text;
    ...
    e.item.Attributes.Add("ondblclick", "location.href=’../ShippedGrid.aspx?id=" + OrderItemID + "’");
    }


      双击表格打开新一页

    if(e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem)
    {
    string OrderItemID =e.item.cells[1].Text;
    ...
    e.item.Attributes.Add("ondblclick", "open(’../ShippedGrid.aspx?id=" + OrderItemID + "’)");
    }


      ★特别注意:【?id=】 处不能为【?id =

    12.Panel 横向滚动,纵向自动扩展

    <asp:panel style="overflow-x:scroll;overflow-y:auto;"></asp:panel>


      13.回车转换成Tab

    <script language="javascript" for="document" event="onkeydown">
     if(event.keyCode==13 && event.srcElement.type!=’button’ && event.srcElement.type!=’submit’ && event.srcElement.type!=’reset’ && event.srcElement.type!=’’&& event.srcElement.type!=’textarea’);
    event.keyCode=9;
    </script>

    onkeydown="if(event.keyCode==13) event.keyCode=9"


      14.DataGrid超级连接列

    DataNavigateUrlField="字段名" DataNavigateUrlFormatString="http://xx/inc/delete.aspx?ID={0}"


      15.DataGrid行随鼠标变色

    private void DGzf_ItemDataBound(object sender, System.Web.UI.WebControls.DataGridItemEventArgs e)
    {
    if (e.Item.ItemType!=ListItemType.Header)
    {
    e.Item.Attributes.Add( "onmouseout","this.style.backgroundColor=\""+e.Item.Style["BACKGROUND-COLOR"]+"\"");
    e.Item.Attributes.Add( "onmouseover","this.style.backgroundColor=\""+ "#EFF3F7"+"\"");
    }
    }


      16.模板列

    <ASP:TEMPLATECOLUMN visible="False" sortexpression="demo" headertext="ID">
    <ITEMTEMPLATE>
    <ASP:LABEL text=’<%# DataBinder.Eval(Container.DataItem, "ArticleID")%>’ runat="server" width="80%" id="lblColumn" />
    </ITEMTEMPLATE>
    </ASP:TEMPLATECOLUMN>

    <ASP:TEMPLATECOLUMN headertext="选中">
    <HEADERSTYLE wrap="False" horizontalalign="Center"></HEADERSTYLE>
    <ITEMTEMPLATE>
    <ASP:CHECKBOX id="chkExport" runat="server" />
    </ITEMTEMPLATE>
    <EDITITEMTEMPLATE>
    <ASP:CHECKBOX id="chkExportON" runat="server" enabled="true" />
    </EDITITEMTEMPLATE>
    </ASP:TEMPLATECOLUMN>


      后台代码

    protected void CheckAll_CheckedChanged(object sender, System.EventArgs e)
    {
    //改变列的选定,实现全选或全不选。
     CheckBox chkExport ;
    if( CheckAll.Checked)
    {
    foreach(DataGridItem oDataGridItem in MyDataGrid.Items)
    {
    chkExport = (CheckBox)oDataGridItem.FindControl("chkExport");
    chkExport.Checked = true;
    }
    }
    else
    {
    foreach(DataGridItem oDataGridItem in MyDataGrid.Items)
    {
    chkExport = (CheckBox)oDataGridItem.FindControl("chkExport");
    chkExport.Checked = false;
    }
    }
    }


      17.数字格式化

      【<%#Container.DataItem("price")%>的结果是500.0000,怎样格式化为500.00?

    <%#Container.DataItem("price","{0:¥#,##0.00}")%>

    int i=123456;
    string s=i.ToString("###,###.00");

    18.日期格式化

      【aspx页面内:<%# DataBinder.Eval(Container.DataItem,"Company_Ureg_Date")%

      显示为: 2004-8-11 19:44:28

      我只想要:2004-8-11

    <%# DataBinder.Eval(Container.DataItem,"Company_Ureg_Date","{0:yyyy-M-d}")%>


      应该如何改?

      【格式化日期】

      取出来,一般是object((DateTime)objectFromDB).ToString("yyyy-MM-dd");

      【日期的验证表达式】

      A.以下正确的输入格式: [2004-2-29], [2004-02-29 10:29:39 pm], [2004/12/31]

    ^((\d{2}(([02468][048])|([13579][26]))[\-\/\s]?((((0?[13578])|(1[02]))[\-\/\s]?((0?[1-9])|([1-2][0-9])|(3[01])))|(((0?[469])|(11))[\-\/\s]?((0?[1-9])|([1-2][0-9])|(30)))|(0?2[\-\/\s]?((0?[1-9])|([1-2][0-9])))))|(\d{2}(([02468][1235679])|([13579][01345789]))[\-\/\s]?((((0?[13578])|(1[02]))[\-\/\s]?((0?[1-9])|([1-2][0-9])|(3[01])))|(((0?[469])|(11))[\-\/\s]?((0?[1-9])|([1-2][0-9])|(30)))|(0?2[\-\/\s]?((0?[1-9])|(1[0-9])|(2[0-8]))))))(\s(((0?[1-9])|(1[0-2]))\:([0-5][0-9])((\s)|(\:([0-5][0-9])\s))([AM|PM|am|pm]{2,2})))?$


      B.以下正确的输入格式:[0001-12-31], [9999 09 30], [2002/03/03]

    ^\d{4}[\-\/\s]?((((0[13578])|(1[02]))[\-\/\s]?(([0-2][0-9])|(3[01])))|(((0[469])|(11))[\-\/\s]?(([0-2][0-9])|(30)))|(02[\-\/\s]?[0-2][0-9]))$


      【大小写转换】

    HttpUtility.HtmlEncode(string);
    HttpUtility.HtmlDecode(string)


      19.如何设定全局变量

      Global.asax

      Application_Start()事件中

      添加Application[属性名] xxx;

      就是你的全局变量

      20.怎样作到HyperLinkColumn生成的连接后,点击连接,打开新窗口?

      HyperLinkColumn有个属性Target,将器值设置成"_blank"即可.(Target="_blank")

      【ASPNETMENU】点击菜单项弹出新窗口

      在你的menuData.xml文件的菜单项中加入URLTarget="_blank",如:

    <?xml version="1.0" encoding="GB2312"?>
    <MenuData ImagesBaseURL="images/">
    <MenuGroup>
    <MenuItem Label="内参信息" URL="Infomation.aspx" >
    <MenuGroup ID="BBC">
    <MenuItem Label="公告信息" URL="Infomation.aspx" URLTarget="_blank" LeftIcon="file.gif"/>
    <MenuItem Label="编制信息简报" URL="NewInfo.aspx" LeftIcon="file.gif" />
    ......


      最好将你的aspnetmenu升级到1.2

      21.读取DataGrid控件TextBox

    foreach(DataGrid dgi in yourDataGrid.Items)
    {
    TextBox tb = (TextBox)dgi.FindControl("yourTextBoxId");
    tb.Text....
    }


      23.DataGrid中有3个模板列包含Textbox分别为 DG_ShuLiang (数量) DG_DanJian(单价) DG_JinE(金额)分别在5.6.7列,要求在录入数量及单价的时候自动算出金额即:数量*单价=金额还要求录入时限制为数值型.我如何用客户端脚本实现这个功能?

      〖思归〗

    <asp:TemplateColumn HeaderText="数量">
    <ItemTemplate>
    <asp:TextBox id="ShuLiang" runat=’server’ Text=’<%# DataBinder.Eval(Container.DataItem,"DG_ShuLiang")%>’
    onkeyup="javascript:DoCal()"
    />

    <asp:RegularExpressionValidator id="revS" runat="server" ControlToValidate="ShuLiang" ErrorMessage="must be integer" ValidationExpression="^\d+$" />
    </ItemTemplate>
    </asp:TemplateColumn>

    <asp:TemplateColumn HeaderText="单价">
    <ItemTemplate>
    <asp:TextBox id="DanJian" runat=’server’ Text=’<%# DataBinder.Eval(Container.DataItem,"DG_DanJian")%>’
    onkeyup="javascript:DoCal()"
    />

    <asp:RegularExpressionValidator id="revS2" runat="server" ControlToValidate="DanJian" ErrorMessage="must be numeric" ValidationExpression="^\d+(\.\d*)?$" />

    </ItemTemplate>
    </asp:TemplateColumn>

    <asp:TemplateColumn HeaderText="金额">
    <ItemTemplate>
    <asp:TextBox id="JinE" runat=’server’ Text=’<%# DataBinder.Eval(Container.DataItem,"DG_JinE")%>’ />
    </ItemTemplate>
    </asp:TemplateColumn><script language="javascript">
    function DoCal()
    {
    var e = event.srcElement;
    var row = e.parentNode.parentNode;
    var txts = row.all.tags("INPUT");
    if (!txts.length || txts.length < 3)
    return;

    var q = txts[txts.length-3].value;
    var p = txts[txts.length-2].value;

    if (isNaN(q) || isNaN(p))
    return;

    q = parseInt(q);
    p = parseFloat(p);

    txts[txts.length-1].value = (q * p).toFixed(2);
    }
    </script>

      24.datagrid选定比较底下的行时,为什么总是刷新一下,然后就滚动到了最上面,刚才选定的行因屏幕的关系就看不到了。

    page_load
    page.smartNavigation=true


      25.Datagrid中修改数据,当点击编辑键时,数据出现在文本框中,怎么控制文本框的大小 ?

    private void DataGrid1_ItemDataBound(obj sender,DataGridItemEventArgs e)
    {
    for(int i=0;i<e.Item.Cells.Count-1;i++)
    if(e.Item.ItemType==ListItemType.EditType)
    {
    e.Item.Cells[i].Attributes.Add("Width", "80px")
    }
    }


      26.对话框

    private static string ScriptBegin = "<script language=\"JavaScript\">";
    private static string ScriptEnd = "</script>";

    public static void ConfirmMessageBox(string PageTarget,string Content)
    {
    string ConfirmContent="var retValue=window.confirm(’"+Content+"’);"+"if(retValue){window.location=’"+PageTarget+"’;}";

    ConfirmContent=ScriptBegin + ConfirmContent + ScriptEnd;

    Page ParameterPage = (Page)System.Web.HttpContext.Current.Handler;
    ParameterPage.RegisterStartupScript("confirm",ConfirmContent);
    //Response.Write(strScript);
    }


      27. 将时间格式化:string aa=DateTime.Now.ToString("yyyyMMdd");

      1.1 取当前年月日时分秒

    currentTime=System.DateTime.Now;


      1.2 取当前年

    int 年= DateTime.Now.Year;


      1.3 取当前月

    int 月= DateTime.Now.Month;


      1.4 取当前日

    int 日= DateTime.Now.Day;


      1.5 取当前时

    int 时= DateTime.Now.Hour;


      1.6 取当前分

    int 分= DateTime.Now.Minute;


      1.7 取当前秒

    int 秒= DateTime.Now.Second;


      1.8 取当前毫秒

    int 毫秒= DateTime.Now.Millisecond;


      28.自定义分页代码:

      先定义变量 :

    public static int pageCount; //总页面数
    public static int curPageIndex=1; //当前页面


      下一页:

    if(DataGrid1.CurrentPageIndex < (DataGrid1.PageCount - 1))
    {
    DataGrid1.CurrentPageIndex += 1;
    curPageIndex+=1;
    }

    bind(); // DataGrid1数据绑定函数


      上一页:

    if(DataGrid1.CurrentPageIndex >0)
    {
    DataGrid1.CurrentPageIndex += 1;
    curPageIndex-=1;
    }

    bind(); // DataGrid1数据绑定函数


      直接页面跳转:

    int a=int.Parse(JumpPage.Value.Trim());//JumpPage.Value.Trim()为跳转值

    if(a<DataGrid1.PageCount)
    {
    this.DataGrid1.CurrentPageIndex=a;
    }

    bind();

      24.datagrid选定比较底下的行时,为什么总是刷新一下,然后就滚动到了最上面,刚才选定的行因屏幕的关系就看不到了。

    page_load
    page.smartNavigation=true


      25.Datagrid中修改数据,当点击编辑键时,数据出现在文本框中,怎么控制文本框的大小 ?

    private void DataGrid1_ItemDataBound(obj sender,DataGridItemEventArgs e)
    {
    for(int i=0;i<e.Item.Cells.Count-1;i++)
    if(e.Item.ItemType==ListItemType.EditType)
    {
    e.Item.Cells[i].Attributes.Add("Width", "80px")
    }
    }


      26.对话框

    private static string ScriptBegin = "<script language=\"JavaScript\">";
    private static string ScriptEnd = "</script>";

    public static void ConfirmMessageBox(string PageTarget,string Content)
    {
    string ConfirmContent="var retValue=window.confirm(’"+Content+"’);"+"if(retValue){window.location=’"+PageTarget+"’;}";

    ConfirmContent=ScriptBegin + ConfirmContent + ScriptEnd;

    Page ParameterPage = (Page)System.Web.HttpContext.Current.Handler;
    ParameterPage.RegisterStartupScript("confirm",ConfirmContent);
    //Response.Write(strScript);
    }


      27. 将时间格式化:string aa=DateTime.Now.ToString("yyyyMMdd");

      1.1 取当前年月日时分秒

    currentTime=System.DateTime.Now;


      1.2 取当前年

    int 年= DateTime.Now.Year;


      1.3 取当前月

    int 月= DateTime.Now.Month;


      1.4 取当前日

    int 日= DateTime.Now.Day;


      1.5 取当前时

    int 时= DateTime.Now.Hour;


      1.6 取当前分

    int 分= DateTime.Now.Minute;


      1.7 取当前秒

    int 秒= DateTime.Now.Second;


      1.8 取当前毫秒

    int 毫秒= DateTime.Now.Millisecond;


      28.自定义分页代码:

      先定义变量 :

    public static int pageCount; //总页面数
    public static int curPageIndex=1; //当前页面


      下一页:

    if(DataGrid1.CurrentPageIndex < (DataGrid1.PageCount - 1))
    {
    DataGrid1.CurrentPageIndex += 1;
    curPageIndex+=1;
    }

    bind(); // DataGrid1数据绑定函数


      上一页:

    if(DataGrid1.CurrentPageIndex >0)
    {
    DataGrid1.CurrentPageIndex += 1;
    curPageIndex-=1;
    }

    bind(); // DataGrid1数据绑定函数


      直接页面跳转:

    int a=int.Parse(JumpPage.Value.Trim());//JumpPage.Value.Trim()为跳转值

    if(a<DataGrid1.PageCount)
    {
    this.DataGrid1.CurrentPageIndex=a;
    }

    bind();


    ASP.NET结合存储过程写的通用搜索分页程序

    select.aspx

    --------------------------------------------------------------------------------

    <%@ Page Language="C#" %>
    <%@ import Namespace="System.Data" %>
    <%@ import Namespace="System.Data.SqlClient" %>
    <script runat="server">

        protected void Page_Load(Object sender, EventArgs e)
             {
                 int intPageNo,intPageSize,intPageCount;
                 intPageSize = 25;
                 if (Request["CurrentPage"]==null)
                     {
                         intPageNo = 1;
                     }
                 else
                     {
                         intPageNo = Int32.Parse(Request["CurrentPage"]);
                     }
                
                
                 SqlConnection mySqlConnection = new SqlConnection("server=(local);Database=test;user id=sa;password=");
                 SqlCommand mySqlCommand = new SqlCommand("up_GetTopicList", mySqlConnection);
                 mySqlCommand.CommandType = CommandType.StoredProcedure;
                
                 SqlParameter workParm;
                
                 //搜索表字段,以","号分隔
                 workParm = mySqlCommand.Parameters.Add("@a_TableList", SqlDbType.VarChar, 200);
                 mySqlCommand.Parameters["@a_TableList"].Value = "OFFERID,type,offertime";
                
                 //搜索表名
                 workParm = mySqlCommand.Parameters.Add("@a_TableName", SqlDbType.VarChar, 30);
                 mySqlCommand.Parameters["@a_TableName"].Value = "offer";
                
                 //搜索条件,如"select * from aa where a=1 and b=2 and c=3"则条件为"where a=1 and b=2 and c=3"
                 workParm = mySqlCommand.Parameters.Add("@a_SelectWhere", SqlDbType.VarChar, 500);
                 mySqlCommand.Parameters["@a_SelectWhere"].Value = "where type='idl'";
                
                 //表主键字段名,必须为INT类型
                 workParm = mySqlCommand.Parameters.Add("@a_SelectOrderId", SqlDbType.VarChar, 50);
                 mySqlCommand.Parameters["@a_SelectOrderId"].Value = "offerid";      
                
                 //排序,可以使用多字段排序但主键字段必需在最前面
                 workParm = mySqlCommand.Parameters.Add("@a_SelectOrder", SqlDbType.VarChar, 50);
                 mySqlCommand.Parameters["@a_SelectOrder"].Value = "order by offerid desc";
                
                 //页号
                 workParm = mySqlCommand.Parameters.Add("@a_intPageNo", SqlDbType.Int);
                 mySqlCommand.Parameters["@a_intPageNo"].Value = intPageNo;
                
                 //每页显示数
                 workParm = mySqlCommand.Parameters.Add("@a_intPageSize", SqlDbType.Int);
                 mySqlCommand.Parameters["@a_intPageSize"].Value = intPageSize;
                
                 //总记录数(存储过程输出参数)
                 workParm = mySqlCommand.Parameters.Add("@RecordCount", SqlDbType.Int);
                 workParm.Direction = ParameterDirection.Output;            
                
                 //当前页记录数(存储过程返回值)
                 workParm = mySqlCommand.Parameters.Add("RowCount", SqlDbType.Int);
                 workParm.Direction = ParameterDirection.ReturnValue;

                 mySqlConnection.Open();
                 Repeater.DataSource = mySqlCommand.ExecuteReader();                                  
                
                 Repeater.DataBind();
                
                 mySqlConnection.Close();
                
                 Int32 RecordCount = (Int32)mySqlCommand.Parameters["@RecordCount"].Value;
                 Int32 RowCount = (Int32)mySqlCommand.Parameters["RowCount"].Value;
                
                 LabelRecord.Text = RecordCount.ToString();
                 LabelRow.Text = intPageNo.ToString();
                 intPageCount = RecordCount/intPageSize;
                 if ((RecordCount%intPageSize)>0)
                     intPageCount += 1;
                 LabelPage.Text = intPageCount.ToString();
                
                 if (intPageNo>1)
                     {
                         HLFistPage.NavigateUrl = "select.aspx?CurrentPage=1";
                         HLPrevPage.NavigateUrl = String.Concat("select.aspx?CurrentPage=","",intPageNo-1);
                     }
                 else
                     {
                         HLFistPage.NavigateUrl = "";
                         HLPrevPage.NavigateUrl = "";
                         //HLFistPage.Enabled = false;
                         //HLPrevPage.Enabled = false;
                     }
                    
                 if (intPageNo<intPageCount)
                     {
                         HLNextPage.NavigateUrl = String.Concat("select.aspx?CurrentPage=","",intPageNo+1);
                         HLEndPage.NavigateUrl = String.Concat("select.aspx?CurrentPage=","",intPageCount);
                     }
                 else
                     {
                         HLNextPage.NavigateUrl = "";
                         HLEndPage.NavigateUrl = "";
                         //HLNextPage.Enabled=false;
                         //HLEndPage.Enabled=false;
                     }
                
             }

    </script>

    <html>
    <meta http-equiv="Content-Type" content="text/html; charset=gb2312">
    <head>
        <link href="/style.css" rel="stylesheet" />
    <style type="text/css">
    .high {  font-family: "宋体"; font-size: 9pt; line-height: 140%}
    .mid {  font-size: 9pt; line-height: 12pt}
    .small {  font-size: 9pt; line-height: normal}
    .TP10_5 {
        font-size: 14px;
        line-height: 140%;
    }
    </style>
        <style type="text/css">A:link {
        COLOR: #cc6666
    }
    </style>
    </head>
    <body>
        <form runat="server">
    <span class="high">              第<font color="#CC0000"><asp:Label id="LabelRow" runat="server"/></font>页 | 共有<asp:Label id="LabelPage" runat="server"/>页
                  | <asp:Label id="LabelRecord" runat="server"/>条信息 |
                  <asp:HyperLink id="HLFistPage" Text="首页" runat="server"/>
                  | <asp:HyperLink id="HLPrevPage" Text="上一页" runat="server"/>
                  | <asp:HyperLink id="HLNextPage" Text="下一页" runat="server"/>
                  | <asp:HyperLink id="HLEndPage" Text="尾页" runat="server"/></span><br>
       
            <asp:Repeater id=Repeater runat="server">

                <HeaderTemplate>

          <table width="583" border="0" cellspacing="0" cellpadding="0">
            <tr>
              <td bgcolor="#000000"><table width="100%" border="0" cellpadding="4" cellspacing="1" class="TP10_5">
                  <tr bgcolor="#999999">
                    <td align="center"> <strong><font color="#FFFFFF">订单号</font></strong></td>
                    <td align="center"> <strong><font color="#FFFFFF">服务项目</font></strong></td>
                    <td align="center"> <strong><font color="#FFFFFF">预订日期</font></strong></td>
                    <td align="center"> <strong><font color="#FFFFFF">操作人员</font></strong></td>
                    <td align="center"> <strong><font color="#FFFFFF">分配状态</font></strong></td>
                    <td> <div align="center"></div></td>
                  </tr>
                </HeaderTemplate>

                <ItemTemplate>

                  <tr align="center" bgcolor="#FFFFFF" class="small" onMouseOver='this.style.background="#CCCCCC"' onMouseOut='this.style.background="#FFFFFF"'>
                    <td><%# DataBinder.Eval(Container.DataItem, "offerid") %></td>
                    <td><%# DataBinder.Eval(Container.DataItem, "type") %></td>
                    <td><%# DataBinder.Eval(Container.DataItem, "offertime") %></td>
                    <td> </td>
                    <td> </td>
                    <td><a href="javascript:void(window.open('info.asp?id=<%# DataBinder.Eval(Container.DataItem, "offerid") %>','订单分配','height=600,width=1000'))">订单详情</a></td>
                  </tr>

    </ItemTemplate>

                <FooterTemplate>

                </table></td>
            </tr>
          </table>

                </FooterTemplate>

            </asp:Repeater>

        </form>
    </body>
    </html>

    --------------------------------------------------------------------------------


    up_GetTopicList.sql

    --------------------------------------------------------------------------------

    CREATE proc up_GetTopicList
           @a_TableList Varchar(200),
           @a_TableName Varchar(30),
           @a_SelectWhere Varchar(500),
           @a_SelectOrderId Varchar(20),
           @a_SelectOrder Varchar(50),
           @a_intPageNo int,
           @a_intPageSize int,
           @RecordCount int OUTPUT
    as
       /*定义局部变量*/
       declare @intBeginID         int
       declare @intEndID           int
       declare @intRootRecordCount int
       declare @intRowCount        int
       declare @TmpSelect          NVarchar(600)
       /*关闭计数*/
       set nocount on
      
       /*求总共根贴数*/

       select @TmpSelect = 'set nocount on;select @SPintRootRecordCount = count(*) from '+@a_TableName+' '+@a_SelectWhere
       execute sp_executesql
                 @TmpSelect,
                 N'@SPintRootRecordCount int OUTPUT',
                 @SPintRootRecordCount=@intRootRecordCount OUTPUT

    select @RecordCount = @intRootRecordCount

       if (@intRootRecordCount = 0)    --如果没有贴子,则返回零
           return 0
          
       /*判断页数是否正确*/
       if (@a_intPageNo - 1) * @a_intPageSize > @intRootRecordCount
          return (-1)

       /*求开始rootID*/
       set @intRowCount = (@a_intPageNo - 1) * @a_intPageSize + 1
       /*限制条数*/

       select @TmpSelect = 'set nocount on;set rowcount @SPintRowCount;select @SPintBeginID = '+@a_SelectOrderId+' from '+@a_TableName+' '+@a_SelectWhere+' '+@a_SelectOrder
       execute sp_executesql
                 @TmpSelect,
                 N'@SPintRowCount int,@SPintBeginID int OUTPUT',
                 @SPintRowCount=@intRowCount,@SPintBeginID=@intBeginID OUTPUT


       /*结束rootID*/
       set @intRowCount = @a_intPageNo * @a_intPageSize
       /*限制条数*/

       select @TmpSelect = 'set nocount on;set rowcount @SPintRowCount;select @SPintEndID = '+@a_SelectOrderId+' from '+@a_TableName+' '+@a_SelectWhere+' '+@a_SelectOrder
       execute sp_executesql
                 @TmpSelect,
                 N'@SPintRowCount int,@SPintEndID int OUTPUT',
                 @SPintRowCount=@intRowCount,@SPintEndID=@intEndID OUTPUT


    if @a_SelectWhere='' or @a_SelectWhere IS NULL
       select @TmpSelect = 'set nocount off;set rowcount 0;select '+@a_TableList+' from '+@a_TableName+' where '+@a_SelectOrderId+' between '
    else
       select @TmpSelect = 'set nocount off;set rowcount 0;select '+@a_TableList+' from '+@a_TableName+' '+@a_SelectWhere+' and '+@a_SelectOrderId+' between '

    if @intEndID > @intBeginID
       select @TmpSelect = @TmpSelect+'@SPintBeginID and @SPintEndID'+' '+@a_SelectOrder
    else
       select @TmpSelect = @TmpSelect+'@SPintEndID and @SPintBeginID'+' '+@a_SelectOrder

       execute sp_executesql
                 @TmpSelect,
                 N'@SPintEndID int,@SPintBeginID int',
                 @SPintEndID=@intEndID,@SPintBeginID=@intBeginID

     return(@@rowcount)
       --select @@rowcount
    GO

    c#中分割字符串的几种方法(split)

    第一种方法:打开vs.net新建一个控制台项目。然后在Main()方法下输入下面的程序。

     

          string s="abcdeabcdeabcde";

           string[] sArray=s.Split('c');

           foreach(string i in sArray)

           Console.WriteLine(i.ToString());

     

            输出下面的结果:ab

                           deab

                           deab

                           de

                             

     

        我们看到了结果是以一个指定的字符进行的分割。如果我们希望使用多个字符进行分割如c,d,e如何做呢?好,我们使用另一种构造方法:

     

           更改为     string s="abcdeabcdeabcde

         string[] sArray1=s.Split(new char[3]{'c','d','e'});

            foreach(string i in sArray1)

            Console.WriteLine(i.ToString());

     

        可以输出下面的结果:ab

                           ab

                           ab

     

      除了以上的这两种方法以外,第三种方法是使用正则表达式。新建一个控制台项目。然后先添加 using System.Text.RegularExpressions;

    Main() :中更改为

     

           System.Text.RegularExpressions

          string content="agcsmallmacsmallgggsmallytx";

          string[]resultString=Regex.Split(content,"small",RegexOptions.IgnoreCase) 
           foreach(string i in resultString)
            Console.WriteLine(i.ToString());
     
            输出下面的结果:agc
                           mac
                           ggg
                           ytx
    使用正则表达式有什么好处呢? 别着急,后面我们会看到它的独特之处。

    下面介绍第4种方法。比如

        string str1="我*****是*****一*****个*****教*****师";

        如果我希望显示为:我是一个教师,  ,如何作呢? 我们可以使用下面代码:

     

     

          string str1="我*****是*****一*****个*****教*****师;

           string[] str2;

           str1=str1.Replace("*****","*");

           str2=str1.Split('*');

          foreach(string i in str2)

           Console.WriteLine(i.ToString()); 

     

     

    这样也可以得到正确结果。但是比如

        string str1="我**是*****一*****个*****教*****师";

          我希望显示的结果为:我是一个教师。

          我如果采用上面的第四种方法来做就会产生下面的错误:我   是一个教师

          中间有空格输出,所以输出结果并不是我希望的结果,如何解决呢?这就又回到了正则表达式了(这里可以看到它的功能强大之处),这时可以采用下面的第五种方法:

     

          string str1="我**是*****一*****个*****教*****师";

        string[] str2 = System.Text.RegularExpressions.Regex.Split(str1,@"[*]+");                                                        

    foreach(string i in str2)

    Console.WriteLine(i.ToString());

     

    这里通过"[*]+" 巧妙的完成了我们的目标。

    ASP.NET性能优化

    DataReader vs.DataSets

    l         DataReader

    1.        对查询的结果提供了单向读取的操作

    2.        轻量快速-但在Reader关闭之前数据库始终处于连接状态

     

    l         DataSet

    1.        非连接的数据访问方式

    2.        内部使用DataReader用户获取数据

    3.        在完成DataSet的获取后会自动关闭DataReader

     

    l         Which is better?

    1.        依赖于你的应用

    2.        原则上在Middle_Tier设计到大量数据处理或进行离线的数据访问时建议使用DataSet

     

     

    连接池

    l         ADO.NET拥有内置的连接迟

    1.        自动缓寸/重新使用连接

    2.        不必为此编写任何代码

     

    l         代码建议

    1.        “在后期打开代码中的连接,然后在早期将其关闭”

    2.        切无长时间保持连接状态 – 切无尝试构建你自己的“智能”连接池逻辑

    3.        完成后应立即显示地关闭数据库连接,以将其返回致池中

     

    l         优化提示:

    1.        不同的连接字符串可以生成多个不同的连接池

    2.        Web.Config中存储单个连接字符串

    3.        使用ConfigurationSettings.AppSettings以在运行时采用编程形式对其进行访问

    4.        观察”.NET CLR数据“性能计数器,以变对由ADP.NET维护的连接池数量保持

     

     

    使用存储过程

    l         建议将SPROC用于数据存取

    1.        通过DBA进行更轻松的性能调试

    2.        通过使用数据库事务处理避免出现分布事务成本

    3.        有助于防止SQL注入攻击

    4.        有助与消除应用与数据库反复调用的成本

     

     

     

    服务器控件

    l         对性能优化而言有两点需要注意:

    1.        ViewState

    2.        Number of controls generated(especially for lists)

    ViewState管理

    l         ASP.NET controls能够维护页面Control元素的状态:

    1.        状态以”viewstate hidden field进行传递

     

    l         负面影响:

    1.        增加网络负荷(both on render and postback

    2.        额外的服务器性能消耗(serialize values to/from viewstate

     

    l         ViewState灵活性:

    1.        页面级(Can disable viewstate entirely for a page

    2.        控件级(Can disable viewstate usage on a per control basis

     

    l         建议

    1.        认真审核该功能的使用

    2.        若不使用PostBack功能,在页面级屏蔽ViewState

    3.        PostBack时没次都重新生成控件,请对控件级的ViewState屏蔽

    4.        使用<%@ Page Trace=true%>跟踪ViewState的大小

    C#常用函数和方法集汇总

    1、DateTime 数字型

    System.DateTime currentTime=new System.DateTime();
      1.1 取当前年月日时分秒

    currentTime=System.DateTime.Now;
      1.2 取当前年

    int 年=currentTime.Year;
      1.3 取当前月

    int 月=currentTime.Month;
      1.4 取当前日

    int 日=currentTime.Day;
      1.5 取当前时

    int 时=currentTime.Hour;
      1.6 取当前分

    int 分=currentTime.Minute;
      1.7 取当前秒

    int 秒=currentTime.Second;
      1.8 取当前毫秒

    int 毫秒=currentTime.Millisecond;
    (变量可用中文)
      1.9 取中文日期显示——年月日时分

    string strY=currentTime.ToString("f"); //不显示秒
      1.10 取中文日期显示_年月

    string strYM=currentTime.ToString("y");
      1.11 取中文日期显示_月日

    string strMD=currentTime.ToString("m");
      1.12 取当前年月日,格式为:2003-9-23

    string strYMD=currentTime.ToString("d");
      1.13 取当前时分,格式为:14:24

    string strT=currentTime.ToString("t");
      2、字符型转换 转为32位数字型

      Int32.Parse(变量) Int32.Parse("常量")

      3、 变量.ToString()

      字符型转换 转为字符串
      12345.ToString("n"); //生成 12,345.00
      12345.ToString("C"); //生成 ¥12,345.00
      12345.ToString("e"); //生成 1.234500e+004
      12345.ToString("f4"); //生成 12345.0000
      12345.ToString("x"); //生成 3039 (16进制)
      12345.ToString("p"); //生成 1,234,500.00%

      4、变量.Length 数字型

      取字串长度:

      如: string str="中国";

    int Len = str.Length ; //Len是自定义变量, str是求测的字串的变量名
    5、字码转换 转为比特码

      System.Text.Encoding.Default.GetBytes(变量)

      如:byte[] bytStr = System.Text.Encoding.Default.GetBytes(str);

      然后可得到比特长度:

      len = bytStr.Length;

      6、System.Text.StringBuilder("")

      字符串相加,(+号是不是也一样?)

      如:

    System.Text.StringBuilder sb = new System.Text.StringBuilder("");
    sb.Append("中华");
    sb.Append("人民");
    sb.Append("共和国");
      7、变量.Substring(参数1,参数2);

      截取字串的一部分,参数1为左起始位数,参数2为截取几位。

      如:string s1 = str.Substring(0,2);

      8、取远程用户IP地址

    String user_IP=Request.ServerVariables["REMOTE_ADDR"].ToString();
      9、穿过代理服务器取远程用户真实IP地址:

    if(Request.ServerVariables["HTTP_VIA"]!=null){
    string user_IP=Request.ServerVariables["HTTP_X_FORWARDED_FOR"].ToString();
    }else{
    string user_IP=Request.ServerVariables["REMOTE_ADDR"].ToString();
    }
      10、存取Session值

    Session["变量"];
      如,赋值:

    Session["username"]="小布什";
      取值:

    Object objName=Session["username"];
    String strName=objName.ToString();
      清空:

    Session.RemoveAll();
      11、用超链接传送变量

    String str=Request.QueryString["变量"];
      如在任一页中建超链接:<a href=Edit.aspx?fbid=23>点击</a>

      在Edit.aspx页中取值:String str=Request.QueryString["fdid"];

      12、创建XML文档新节点

      DOC对象.CreateElement("新建节点名");

      13、将新建的子节点加到XML文档父节点下

      父节点.AppendChild(子节点);

      14、 删除节点

      父节点.RemoveChild(节点);

      15、向页面输出:Response

    Response.Write("字串");
    Response.Write(变量);
      跳转到URL指定的页面:

    Response.Redirect("URL地址");
    16、查指定位置是否空字符

    char.IsWhiteSpce(字串变量,位数)——逻辑型;   
      如:

    string str="中国 人民";
    Response.Write(char.IsWhiteSpace(str,2)); //结果为:True, 第一个字符是0位,2是第三个字符。
      17、查字符是否是标点符号

    char.IsPunctuation(''''字符'''') --逻辑型
      如:

    Response.Write(char.IsPunctuation(''''A'''')); //返回:False
      18、把字符转为数字,查代码点,注意是单引号。

      (int)''''字符''''

      如:

    Response.Write((int)''''中''''); //结果为中字的代码:20013
      19、把数字转为字符,查代码代表的字符:(char)代码

      如:

    Response.Write((char)22269); //返回“国”字。
      20、 清除字串前后空格: Trim()

      21、字串替换

      字串变量.Replace("子字串","替换为")

      如:

    string str="中国";
    str=str.Replace("国","央"); //将国字换为央字
    Response.Write(str); //输出结果为“中央”
      再如:(这个非常实用)

    string str="这是<script>脚本";
    str=str.Replace("<","<font><</font>"); //将左尖括号替换为<font> 与 < 与 </font> (或换为<,但估计经XML存诸后,再提出仍会还原)
    Response.Write(str); //显示为:“这是<script>脚本”
      如果不替换,<script>将不显示,如果是一段脚本,将运行;而替换后,脚本将不运行。

      这段代码的价值在于:你可以让一个文本中的所有HTML标签失效,全部显示出来,保护你的具有交互性的站点。

      具体实现:将你的表单提交按钮脚本加上下面代码:

    string strSubmit=label1.Text; //label1是你让用户提交数据的控件ID。
    strSubmit=strSubmit.Replace("<","<font><</font>");
      然后保存或输出strSubmit。

      用此方法还可以简单实现UBB代码。

      22、取i与j中的最大值:Math.Max(i,j)

      如 int x=Math.Max(5,10); // x将取值 10

      加一点吧 23、字串对比......

      23、字串对比一般都用: if(str1==str2){ } , 但还有别的方法:

      (1)、

    string str1; str2
    //语法: str1.EndsWith(str2); __检测字串str1是否以字串str2结尾,返回布尔值.如:
    if(str1.EndsWith(str2)){ Response.Write("字串str1是以"+str2+"结束的"); }
      (2)、

    //语法:str1.Equals(str2); __检测字串str1是否与字串str2相等,返回布尔值,用法同上.
      (3)、

    //语法 Equals(str1,str2); __检测字串str1是否与字串str2相等,返回布尔值,用法同上.
      24、查找字串中指定字符或字串首次(最后一次)出现的位置,返回索引值:IndexOf() 、LastIndexOf(), 如:

    str1.IndexOf("字"); //查找“字”在str1中的索引值(位置)
    str1.IndexOf("字串");//查找“字串”的第一个字符在str1中的索引值(位置)
    str1.IndexOf("字串",3,2);//从str1第4个字符起,查找2个字符,查找“字串”的第一个字符在str1中的索引值(位置)
      25、在字串中指定索引位插入指定字符:Insert() ,如:

    str1.Insert(1,"字");在str1的第二个字符处插入“字”,如果str1="中国",插入后为“中字国”;
    26、在字串左(或右)加空格或指定char字符,使字串达到指定长度:PadLeft()、PadRight() ,如:

    <%
    string str1="中国人";
    str1=str1.PadLeft(10,''''1''''); //无第二参数为加空格
    Response.Write(str1); //结果为“1111111中国人” , 字串长为10
    %>
      27、从指定位置开始删除指定数的字符:Remove()

      28.反转整个一维Array中元素的顺序。

    har[] charArray = "abcde".ToCharArray();
    Array.Reverse(charArray);
    Console.WriteLine(new string(charArray));
      29.判断一个字符串中的第n个字符是否是大写

    string str="abcEEDddd";
    Response.Write(Char.IsUpper(str,3));
    March 14

    用c#实现类似QQ的简单通讯程序

    本文介绍了用c#实现的一个类似QQ的局域网通讯程序,当点击最小化程序跑到系统托盘里,双击托盘可以可以显示主页面。
    程序运行界面如下:



    托盘里的菜单如下:



    c#作为微软.Net战略的重要棋子,对网络编程提供了很好的支持和优化。实现起来特别方便,还是看代码吧,已经注释的很清楚了。工程文件放在后面了,需要的可以下载
    代码如下:
    using System;
    using System.Drawing;
    using System.Collections;
    using System.ComponentModel;
    using System.Windows.Forms;
    using System.Data;
    using System.IO;
    using System.Net.Sockets;
    using System.Threading;
    namespace p2pChat
    {
        /// <summary>
        /// MainForm 的摘要说明。
        /// </summary>
        public class MainForm : System.Windows.Forms.Form
        {
            private System.Windows.Forms.Label label1;
            private System.Windows.Forms.Label label2;
            private System.Windows.Forms.TextBox txtIp;
            private System.Windows.Forms.TextBox txtRecord;
            private System.Windows.Forms.TextBox txtName;
            private System.Windows.Forms.Button btnSend;
            private System.Windows.Forms.TextBox txtContent;
            private TcpListener tcpLister = new TcpListener(5566);    
            System.Threading.ThreadStart listenPort;
            System.Threading.Thread lister;
            private System.Windows.Forms.Label label3;    
            System.Windows.Forms.NotifyIcon NotifyIcon1;
            //托盘里显示的图标,我用的是QQ里的宠物狗的图标
            private Icon img = new Icon(@"C:\OpenPet.ico");
            System.Windows.Forms.ContextMenu nMenu;
            /// <summary>
            /// 必需的设计器变量。
            /// </summary>
            private System.ComponentModel.Container components = null;
            public MainForm()
            {
                  //
                  // Windows 窗体设计器支持所必需的
                  //
                  InitializeComponent();

                  //不显示最大化按钮
                  this.MaximizeBox = false;    
                  //最小化时不显示在任务栏
                  this.ShowInTaskbar = false;

                  listenPort += new ThreadStart(this.Listen);
                  lister = new Thread(listenPort);
                  this.Closing += new System.ComponentModel.CancelEventHandler(abortLister);
                  Initializenotifyicon();
                  //
                  // TODO: 在 InitializeComponent 调用后添加任何构造函数代码
                  //
            }
            #region 初始化托盘组件
            private void Initializenotifyicon()
            {
                  NotifyIcon1 = new NotifyIcon();
                  NotifyIcon1.Icon = img;
                  NotifyIcon1.Text = "局域网聊天程序";
                  NotifyIcon1.Visible = true;
                  NotifyIcon1.DoubleClick += new EventHandler(this.showMainForm);

                  MenuItem [] menuArray = new MenuItem[3];
                  menuArray[0] = new MenuItem();
                  menuArray[0].Text = "显示主窗口";
                  menuArray[0].Click += new EventHandler(this.showMainForm);
                  menuArray[0].DefaultItem = true;

                  menuArray[1] = new MenuItem("-");

                  menuArray[2] = new MenuItem();
                  menuArray[2].Text = "退出";
                  menuArray[2].Click += new EventHandler(this.exitSystem);

                  nMenu = new ContextMenu(menuArray);
                  NotifyIcon1.ContextMenu = nMenu;
            }        
            //显示主窗口
            private void showMainForm(object sender,System.EventArgs e)
            {
                  this.WindowState = System.Windows.Forms.FormWindowState.Normal;              
            }
            //退出程序
            private void exitSystem(object sender,System.EventArgs e)
            {
                  NotifyIcon1.Visible = false;
                  this.Close();
            }
            #endregion
            /// <summary>
            /// 清理所有正在使用的资源。
            /// </summary>
            protected override void Dispose( bool disposing )
            {
                  if( disposing )
                  {
                      if (components != null)
                      {
                          components.Dispose();
                      }
                  }
                  base.Dispose( disposing );                      
            }
            #region Windows 窗体设计器生成的代码
            /// <summary>
            /// 设计器支持所需的方法 - 不要使用代码编辑器修改
            /// 此方法的内容。
            /// </summary>
            private void InitializeComponent()
            {
                  this.label1 = new System.Windows.Forms.Label();
                  this.txtIp = new System.Windows.Forms.TextBox();
                  this.txtRecord = new System.Windows.Forms.TextBox();
                  this.label2 = new System.Windows.Forms.Label();
                  this.txtName = new System.Windows.Forms.TextBox();
                  this.btnSend = new System.Windows.Forms.Button();
                  this.txtContent = new System.Windows.Forms.TextBox();
                  this.label3 = new System.Windows.Forms.Label();
                  this.SuspendLayout();
                  //
                  // label1
                  //
                  this.label1.Location = new System.Drawing.Point(16, 232);
                  this.label1.Name = "label1";
                  this.label1.Size = new System.Drawing.Size(72, 23);
                  this.label1.TabIndex = 0;
                  this.label1.Text = "目标地址:";
                  this.label1.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
                  //
                  // txtIp
                  //
                  this.txtIp.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right)));
                  this.txtIp.Location = new System.Drawing.Point(80, 232);
                  this.txtIp.Name = "txtIp";
                  this.txtIp.Size = new System.Drawing.Size(200, 21);
                  this.txtIp.TabIndex = 1;
                  this.txtIp.Text = "";
                  //
                  // txtRecord
                  //
                  this.txtRecord.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right)));
                  this.txtRecord.Location = new System.Drawing.Point(16, 32);
                  this.txtRecord.Multiline = true;
                  this.txtRecord.Name = "txtRecord";
                  this.txtRecord.ReadOnly = true;
                  this.txtRecord.ScrollBars = System.Windows.Forms.ScrollBars.Vertical;
                  this.txtRecord.Size = new System.Drawing.Size(264, 176);
                  this.txtRecord.TabIndex = 4;
                  this.txtRecord.Text = "";
                  //
                  // label2
                  //
                  this.label2.Location = new System.Drawing.Point(24, 256);
                  this.label2.Name = "label2";
                  this.label2.Size = new System.Drawing.Size(48, 23);
                  this.label2.TabIndex = 5;
                  this.label2.Text = "呢 称:";
                  this.label2.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
                  //
                  // txtName
                  //
                  this.txtName.Location = new System.Drawing.Point(80, 256);
                  this.txtName.Name = "txtName";
                  this.txtName.Size = new System.Drawing.Size(88, 21);
                  this.txtName.TabIndex = 6;
                  this.txtName.Text = "";
                  //
                  // btnSend
                  //
                  this.btnSend.Location = new System.Drawing.Point(200, 256);
                  this.btnSend.Name = "btnSend";
                  this.btnSend.Size = new System.Drawing.Size(64, 23);
                  this.btnSend.TabIndex = 0;
                  this.btnSend.Text = "发 送";
                  this.btnSend.Click += new System.EventHandler(this.btnSend_Click);
                  //
                  // txtContent
                  //
                  this.txtContent.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)
                      | System.Windows.Forms.AnchorStyles.Right)));
                  this.txtContent.Location = new System.Drawing.Point(16, 288);
                  this.txtContent.Multiline = true;
                  this.txtContent.Name = "txtContent";
                  this.txtContent.Size = new System.Drawing.Size(264, 152);
                  this.txtContent.TabIndex = 8;
                  this.txtContent.Text = "";
                  //
                  // label3
                  //
                  this.label3.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
                      | System.Windows.Forms.AnchorStyles.Right)));
                  this.label3.Location = new System.Drawing.Point(16, 8);
                  this.label3.Name = "label3";
                  this.label3.Size = new System.Drawing.Size(100, 16);
                  this.label3.TabIndex = 9;
                  this.label3.Text = "聊天记录:";
                  this.label3.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
                  //
                  // MainForm
                  //
                  this.AutoScaleBaseSize = new System.Drawing.Size(6, 14);
                  this.ClientSize = new System.Drawing.Size(292, 453);
                  this.Controls.Add(this.label3);
                  this.Controls.Add(this.txtContent);
                  this.Controls.Add(this.btnSend);
                  this.Controls.Add(this.txtName);
                  this.Controls.Add(this.label2);
                  this.Controls.Add(this.txtRecord);
                  this.Controls.Add(this.txtIp);
                  this.Controls.Add(this.label1);
                  this.Name = "MainForm";              
                  this.Text = "局域网聊天程序";
                  this.Load += new System.EventHandler(this.MainForm_Load);
                  this.ResumeLayout(false);

            }
            #endregion
            /// <summary>
            /// 应用程序的主入口点。
            /// </summary>
            [STAThread]
            static void Main()
            {
                  Application.Run(new MainForm());
            }
            //开始监听
            private void Listen()
            {
                  try
                  {
                      tcpLister.Start();                  
                      while(true)
                      {
                          Socket s = tcpLister.AcceptSocket();
                          Byte[] stream = new Byte[80];
                          int i = s.Receive(stream);
                          string message = System.Text.Encoding.UTF8.GetString(stream);
                          this.txtRecord.AppendText(message);                      
                      }                  
                  }
                  catch(System.Security.SecurityException)
                  {
                      MessageBox.Show("防火墙安全错误!","错误",MessageBoxButtons.OK,MessageBoxIcon.Exclamation);
                  }
                  catch(System.Exception)
                  {
                      //this.txtRecord.AppendText("已停止监听!");
                  }
            }
           
            private void abortLister(object sender,System.ComponentModel.CancelEventArgs e)
            {
                  this.tcpLister.Stop();
            }
            //发送
            private void btnSend_Click(object sender, System.EventArgs e)
            {
                  if(this.txtContent.Text=="")
                  {
                      MessageBox.Show("不能发送空信息!","提示",MessageBoxButtons.OK,MessageBoxIcon.Exclamation);
                  }
                  else
                  {
                      this.Send();
                  }
            }
            //发送消息
            private void Send()
            {
                  try
                  {
                      string msg = this.txtName.Text+" ("+System.DateTime.Now.ToString()+")\r\n"+this.txtContent.Text+"\r\n";
                      TcpClient client = new TcpClient(this.txtIp.Text,5566);
                      NetworkStream sendStream = client.GetStream();
                      StreamWriter writer = new StreamWriter(sendStream);
                      writer.Write(msg);
                      writer.Flush();
                      sendStream.Close();
                      client.Close();
                      this.txtRecord.AppendText(msg);
                      this.txtContent.Clear();
                  }
                  catch(System.Exception)
                  {
                      this.txtRecord.AppendText("目标计算机拒绝连接请求!\r\n");
                  }
            }
            private void MainForm_Load(object sender, System.EventArgs e)
            {
                  this.txtRecord.AppendText("正在监听...\r\n");                  
                  lister.Name = "监听本地端口";
                  lister.Start();
            }
        }
    }
    工程下载:
    Chat.rar

    在ASP.NET中实现AJAX

    Asynchronous JavaScript and XML(AJAX)最近掀起的高潮,要完全归功于Google在Google SuggestGoogle Maps中的使用。对ASP.NET而言,AJAX不需要回传就能进行服务器端处理,从而使客户机(浏览器)具有丰富的服务器端能力。换句话说,它为异步指派和处理请求与服务器响应提供了一个框架。AJAX利用了一些不是很新颖的已有技术,但是对这些技术(加到一起就是AJAX)的爱好最近突然升温。
    请尝试Michael Schwarz的AJAX .NET包装器,通过它ASP.NET开发人员可以快速方便的部署很容易利用AJAX功能的页面。需要注意的是,这个包装器处于初期开发阶段,因此还没有完全成熟。
    然而,AJAX这样的技术很可能破坏分层体系结构(N-Tier)。我的看法是,AJAX增加了表示逻辑层(甚至更糟,业务层)渗透到表示层的可能性。像我这样严肃的架构师对这种想法可能畏步不前。我感到AJAX的使用即便稍微越过了层次边界,这种代价也是值得深思的。当然,这要视具体的项目和环境而定。
    起步
    它是如何工作的——概述
    AJAX依靠代理(broker)指派和处理往返服务器的请求。对此,.NET包装器依靠客户端XmlHttpRequest对象。多数浏览器都支持XmlHttpRequest对象,这就是选择它的原因。因为包装器的目的是隐藏XmlHttpRequest的实现,我们就不再详细讨论它了。
    包装器本身通过将.NET函数标记为AJAX方法来工作。标记之后,AJAX就创建对应的JavaScript函数,这些函数(和任何JavaScript函数一样)作为代理可以在客户端使用XmlHttpRequest调用。这些代理再映射回服务器端函数。
    复杂吗?并不复杂。我们来看一个例子。假设有一个.NET函数:
    ublic int Add(int firstNumber, int secondNumber)
    {
    return firstNumber + secondNumber;
    }
    AJAX .NET包装器将自动创建名为“Add”、带有两个参数的JavaScript函数。使用JavaScript(在客户机上)调用该函数时,请求将传递给服务器并把结果返回给客户机。
     
    初始设置
    我们首先介绍“安装”项目中使用的.dll的步骤。如果您很清楚如何添加.dll文件引用,可以跳过这一节。
    首先,如果还没有的话,请下载最新的AJAX版本。解压下载的文件并把ajax.dll放到项目的引用文件夹中。在Visual Studio.NET中有机Solution Explorer的“References(引用)”节点并选择Add Reference(添加引用)。在打开的对话框中,单击Browse(浏览)并找到ref/ajax.dll文件。依次单击Open(打开)和Ok(确认)。这样就可以用AJAX .NET包装器编程了。
    建立HttpHandler
    为了保证正常工作,第一步是在web.config中设置包装器的HttpHandler。不需要详细解释HttpHandlers是什么及其如何工作,只要知道它们用于处理ASP.NET请求就足够了。比如,所有*.aspx页面请求都由System.Web.UI.PageHandlerFactory类处理。类似的,我们让所有对ajax/*.ashx的请求由Ajax.PageHandlerFactory处理:
    <configuration>
      <system.web>
        <httpHandlers>
          <add verb="POST,GET" path="ajax/*.ashx"
              type="Ajax.PageHandlerFactory, Ajax" />
        </httpHandlers> 
        ...
      <system.web>
    </configuration>
    简言之,上面的代码告诉ASP.NET,和指定路径(ajax/*.ashx)匹配的任何请求都由Ajax.PageHandlerFactory而不是默认处理程序工厂来处理。不需要创建ajax子目录,使用这个神秘的目录只是为了让其他HttpHandlers能够在自己建立的子目录中使用.ashx扩展。
     
    建立页面
    现在我们可以开始编码了。创建一个新页面或者打开已有的页面,在file后的代码中,为Page_Load事件添加以下代码:
    public class Index : System.Web.UI.Page{
      private void Page_Load(object sender, EventArgs e){
          Ajax.Utility.RegisterTypeForAjax(typeof(Index));     
          //...   
      }
      //... 
    }
    调用RegisterTypeForAjax将在页面上引发后面的JavaScript(或者在页面中手工加入以下两行代码):
    <script language="javascript" src="ajax/common.ashx"></script>
    <script language="javascript"
    src="ajax/Namespace.PageClass,AssemblyName.ashx"></script>
    其中最后一行的含义是:
    下面是AjaxPlay项目中sample.aspx页面的结果例子:
    <%@ Page Inherits="AjaxPlay.Sample" Codebehind="sample.aspx.cs" ... %>
    <html>
    <head>
      <script language="javascript" src="ajax/common.ashx"></script>
      <script language="javascript"
              src="ajax/AjaxPlay.Sample,AjaxPlay.ashx"></script>
    </head>
      <body>   
        <form id="Form1" method="post" runat="server">
          ...
        </form>   
      </body>
    </html>
    可以在浏览器中手工导航到src路径(查看源代码,复制粘贴路径)检查是否一切正常。如果两个路径都输出一些(似乎)毫无意义的文本,就万事大吉了。如果什么也没输出或者出现ASP.NET错误,则表明有些地方出现问题。
    即便不知道HttpHandlers如何工作,上面的例子也很容易理解。通过web.config,我们已经保证所有对ajax/*.ashx的请求都由自定义的处理程序处理。显然,这里的两个脚本标签将由自定义的处理程序处理。
     
    创建服务器端函数
    现在来创建可从客户端调用中异步访问的服务器端函数。因为目前还不支持所有的返回类型(不用担心,将在目前的基础上开发新的版本),我们继续使用简单的ServerSideAdd函数吧。在file后的代码中,向页面添加下列代码:
    [Ajax.AjaxMethod()]
    public int ServerSideAdd(int firstNumber, int secondNumber)
    {
      return firstNumber + secondNumber;
    }
    要注意,这些函数具有Ajax.AjaxMethod属性集。该属性告诉包装器这些方法创建javaScript代理,以便在客户端调用。
     
    客户端调用
    最后一步是用JavaScript调用该函数。AJAX包装器负责创建带有两个参数的JavaScript函数Sample.ServerSideAdd。对这种最简单的函数,只需要调用该方法并传递两个数字:
    <%@ Page Inherits="AjaxPlay.Sample" Codebehind="sample.aspx.cs" ... %>
    <html>
    <head>
      <script language="javascript" src="ajax/common.ashx"></script>
      <script language="javascript"
              src="ajax/AjaxPlay.Sample,AjaxPlay.ashx"></script>
    </head>
      <body>   
        <form id="Form1" method="post" runat="server">
          <script language="javascript">
            var response = Sample.ServerSideAdd(100,99);
            alert(response.value);
          </script>
        </form>   
      </body>
    </html>
    当然,我们不希望仅仅用这种强大的能力来警告用户。这就是所有客户端代理(如JavaScript Sample.ServerSideAd函数)还接受其他特性的原因。这种特性就是为了处理响应而调用的回调函数:
    Sample.ServerSideAdd(100,99, ServerSideAdd_CallBack);

    function ServerSideAdd_CallBack(response){
     if (response.error != null){
      alert(response.error);
      return;
     }
     alert(response.value);
    }
    从上述代码中可以看到我们指定了另外一个参数。ServerSideAdd_CallBack(同样参见上述代码)是用于处理服务器响应的客户端函数。这个回调函数接收一个响应对象,该对象公开了三个主要性质
    • Value——服务器端函数实际返回的值(无论是字符串、自定义对象还是数据集)。
    • Error——错误消息,如果有的话。
    • Request——xml http请求的原始响应。
    • Context——上下文对象。
    首先我们检查error只看看是否出现了错误。通过在服务器端函数中抛出异常,可以很容易处理error特性。在这个简化的例子中,然后用这个值警告用户。Request特性可用于获得更多信息(参见下一节)。
     
    处理类型
     
    返回复杂类型
    Ajax包装器不仅能处理ServerSideAdd函数所返回的整数。它目前还支持integers、strings、double、booleans、DateTime、DataSets和DataTables,以及自定义类和数组等基本类型。其他所有类型都返回它们的ToString值。
    返回的DataSets和真正的.NET DataSet差不多。假设一个服务器端函数返回DataSet,我们可以通过下面的代码在客户端显示其中的内容:
    <script language="JavaScript">
    //Asynchronous call to the mythical "GetDataSet" server-side function
    function getDataSet(){
      AjaxFunctions.GetDataSet(GetDataSet_callback);   
    }
    function GetDataSet_callback(response){
      var ds = response.value;
      if(ds != null && typeof(ds) == "object" && ds.Tables != null){
        var s = new Array();
        s[s.length] = "<table border=1>";
        for(var i=0; i<ds.Tables[0].Rows.length; i++){
          s[s.length] = "<tr>";
          s[s.length] = "<td>" + ds.Tables[0].Rows[i].FirstName + "</td>";
          s[s.length] = "<td>" + ds.Tables[0].Rows[i].Birthday + "</td>";
          s[s.length] = "</tr>";
        }
        s[s.length] = "</table>";
        tableDisplay.innerHTML = s.join("");
      }
      else {
        alert("Error. [3001] " + response.request.responseText);
      }
    }
    </script>
    Ajax还可以返回自定义类,唯一的要求是必须用Serializable属性标记。假设有如下的类:
    [Serializable()]
    public class User{
      private int _userId;
      private string _firstName;
      private string _lastName;

      public int userId{
        get { return _userId; }
      }
      public string FirstName{
        get { return _firstName; }
      }
      public string LastName{
        get { return _lastName; }
      }
      public User(int _userId, string _firstName, string _lastName){
        this._userId = _userId;
        this._firstName = _firstName;
        this._lastName = _lastName;
      }
      public User(){}
      [AjaxMethod()]
      public static User GetUser(int userId){
        //Replace this with a DB hit or something :)
        return new User(userId,"Michael", "Schwarz");
      }
    }
    我们可以通过调用RegisterTypeForAjax注册GetUser代理:
    private void Page_Load(object sender, EventArgs e){
      Utility.RegisterTypeForAjax(typeof(User));
    }
    这样就可以在客户端异步调用GetUser:
    <script language="javascript">
    function getUser(userId){
      User.GetUser(GetUser_callback);
    }
    function GetUser_callback(response){
      if (response != null && response.value != null){
        var user = response.value;
        if (typeof(user) == "object"){         
          alert(user.FirstName + " " + user.LastName);
        }
      }
    }
    getUser(1);
    </script>
    响应中返回的值实际上是一个对象,公开了和服务器端对象相同的属性(FirstName、LastName和UserId)。
     
    自定义转换器
    我们已经看到,Ajax .NET包装器能够处理很多不同的.NET类型。但是除了大量.NET类和内建类型以外,包装器对不能正确返回的其他类型仅仅调用ToString()。为了避免这种情况,Ajax .NET包装器允许开发人员创建对象转换器,用于在服务器和客户机之间平滑传递复杂对象。
     
    其他事项
     
    在其他类中注册函数
     
    上面的例子中,我们的服务器端函数都放在执行页面背后的代码中。但是,没有理由不能把这些函数放在单独的类文件中。要记住,包装器的工作方式是在指定类中发现所有带Ajax.AjaxMethod的方法。需要的类通过第二个脚本标签指定。使用Ajax.Utility.RegisterTypeForAjax,我们可以指定需要的任何类。比如,将我们的服务器端函数作为单独的类是合情合理的:
    Public Class AjaxFunctions
      <Ajax.AjaxMethod()> _
      Public Function Validate(username As String, password As String) As Boolean
        'do something
        'Return something
      End Function
    End Class
    通过指定类的类型而不是页面就可以让Ajax包装器创建代理:
    private void Page_Load(object sender, EventArgs e){
      Ajax.Utility.RegisterTypeForAjax(typeof(AjaxFunctions));
      //...
    }
    要记住,客户端代理的名称是<ClassName>.<ServerSideFunctionName>。因此,如果ServerSideAdd函数放在上面虚构的AjaxFunctions类中,客户端调用就应该是: AjaxFunctions.ServerSideAdd(1,2)。
     
    代理到底是如何工作的
     
    Ajax工具生成的第二个脚本标签(也可以手工插入)传递了页面的名称空间、类名和程序集。根据这些信息,Ajax.PageHandlerFactory就能够使用反射得到具有特定属性的任何函数的详细信息。显然,处理函数查找具有AjaxMethod属性的函数并得到它们的签名(返回类型、名称和参数),从能够创建必要的客户端代理。具体而言,包装器创建一个和类同名的JavaScript对象,该对象提供代理。换句话说,给定一个带有Ajax ServerSideAdd方法的服务器端类AjaxFunctions,我们就会得到公开ServerSideAdd函数的AjaxFunction JavaScript对象。如果将浏览器指向第二个脚本标签的路径就会看到这种动作。
     
    返回Unicode字符
     
    Ajax .NET包装器能够从服务器向客户机返回Unicode字符。为此,数据在返回之前必须在服务器上用html编码。比如:
    [Ajax.AjaxMethod]
    public string Test1(string name, string email, string comment){
      string html = "";
      html += "Hello " + name + "<br>";
      html += "Thank you for your comment <b>";
      html += System.Web.HttpUtility.HtmlEncode(comment);
      html += "</b>.";
      return html;
    }
    SessionState
     
    服务器端函数中很可能需要访问会话信息。为此,只需要通过传递给Ajax.AjaxMethod属性的一个参数告诉Ajax启用这种功能。
    在考察包装器会话能力的同时,我们来看看其他几个特性。这个例子中我们有一个文档管理系统,用户编辑的时候会对文档加锁。其他用户可以请求在文档可用的时候得到通知。如果没有AJAX,我们就只能等待该用户再次返回来检查请求的文档是否可用。显然不够理想。使用支持会话状态的Ajax就非常简单了。
    首先来编写服务器端函数,目标是循环遍历用户希望编辑的documentId(保存在会话中)并返回所有已释放的文档。
    [Ajax.AjaxMethod(HttpSessionStateRequirement.Read)]
    public ArrayList DocumentReleased(){
      if (HttpContext.Current.Session["DocumentsWaiting"] == null){
        return null;
      }
      ArrayList readyDocuments = new ArrayList();
      int[] documents = (int[])HttpContext.Current.Session["DocumentsWaiting"];
      for (int i = 0; i < documents.Length; ++i){
        Document document = Document.GetDocumentById(documents[i]);
        if (document != null && document.Status == DocumentStatus.Ready){
          readyDocuments.Add(document);
        }       
      }
      return readyDocuments;
      }
    }
    要注意,我们指定了HttpSessionStateRequirement.Read值(还可以用Write和ReadWrite)。
    现在编写使用该方法的JavaScript:
    <script language="javascript">
    function DocumentsReady_CallBack(response){
      if (response.error != null){
        alert(response.error);
        return;
      }
      if (response.value != null && response.value.length > 0){
        var div = document.getElementById("status");
        div.innerHTML = "The following documents are ready!<br />";
        for (var i = 0; i < response.value.length; ++i){
          div.innerHTML += "<a href=\"edit.aspx?documentId=" + response.value[i].DocumentId + "\">" + response.value[i].Name + "</a><br />";
        }     
      }
      setTimeout('page.DocumentReleased(DocumentsReady_CallBack)', 10000);
    }
    </script> 
    <body onload="setTimeout('Document.DocumentReleased(DocumentsReady_CallBack)', 10000);">
    我们的服务器端函数在页面加载时调用一次,然后每隔10秒钟调用一次。回调函数检查响应看看是否有返回值,有的话则在div标签中显示该用户可使用的新文档。
    结束语
    AJAX技术已经催生了原来只有桌面开发才具备的健壮而丰富的Web界面。Ajax .NET包装器让您很容易就能利用这种新的强大技术。请注意,Ajax .NET包装器和文档仍在开发之中。

    在ASP.NET中实现多文件上传

    private Boolean SaveFiles()
      {
       //得到File表单元素
       HttpFileCollection files = HttpContext.Current.Request.Files;
       try
       {
        for(int intCount= 0; intCount< files.Count; intCount++)
        {
       
         HttpPostedFile postedFile = files[intCount];
         string fileName, fileExtension;
         //获得文件名字
         fileName = System.IO.Path.GetFileName(postedFile.FileName);
         if (fileName != "")
         {
          //获得文件名扩展
          fileExtension = System.IO.Path.GetExtension(fileName);
          //可根据不同的扩展名字,保存文件到不同的文件夹
          //注意:可能要修改你的文件夹的匿名写入权限。
          postedFile.SaveAs(System.Web.HttpContext.Current.Request.MapPath("upFiles/") + fileName);
         }
        }
        return true;
       }
       catch(System.Exception Ex)
       {
        return false;
       }
      }