| 长春 的个人资料长春的共享空间照片日志列表 | 帮助 |
|
|
3月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接口。用接口写的这个函数不用需改就可以才许多中情况下使用。 [XmlAttribute ("Month")] 简单通过属性就可以使你的所有数据元素私有化。 3. 在Producer/Consumer 的Idiom中使用Delegate 当你生成一个实现producer idiom类的时候,使用deletate来通知consumer。这种方法相对于用接口更加灵活。Delegate是多点传送的,所以不用加额外的代码你就何以支持多用户。相对于用接口这样做可使类之间的耦合性降低。 下面的类处理键盘输入并把它传给所有的registered listeners: public class KeyboardProcessor public OnGetLine OnGetLineCallback { public void Run (){ 任何数目的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#到底是何种关系。 9. 把Visual Studio .net的当前窗口切换到【MyControl.cs(设计)】窗口,并从【工具箱】中的【Windows窗体组件】选项卡中往设计窗体中按顺序拖入下列组件:一.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所示:
7.选择【解决方案资源管理器】窗口,并从中上传Class1.cs文件,因为此文件在本程序中已经没有用途了。 8.选择【项目】|【添加组件】后,弹出【添加新项】对话框,在此对话框中设定【模板】为"组件类",设定【名称】值为"MyControl.cs"后,单击【打开】按钮。则在项目文件中新增一个名称"MyControl.cs"的文件。具体如图02所示:
一个GrouPBox组件,并向此组件中再拖入, 一个TextBox组件和一个Lable组件。 10. 把Visual Studio .Net的当前窗口切换到【MyControl.cs】代码编辑窗口,并用下列代码替换MyControl.cs中的InitializeComponent过程,下列代码是初始化上述加入的组件:
至此【ActiveXDotNet】项目创建的Active X组件的界面就基本完成了,具体如图03所示:
11. 在MyControl.cs中的【MyControl 的摘要说明】代码区中添加下列代码,以下代码是定义一个公用的接口,此接口是告诉COM/COM+,这儿有一个公用的属性可以进行读写操作:
12. 在MyControl.cs的【MyControl】class代码区中添加下列代码,以下代码是首先定义一个私有的字符串,用此字符串来保存从Web测试页面中传递来的数值定义一个公用属性,在接下来的Web测试页面中,将通过这个属性来传递数值,此属性是可读写:
13. 保存上面的修改步骤,至此我们就利用Visual C#创建了一个名称为MyControl的class,这也就是用Visual C#封装的、酷似Active X组件的组件。 14. 单击快捷键【Ctrl+F5】,则Visual C#会自动完成编译,并在"C:\Class\ActiveXDotNet\bin\Debug"目录生成一个名称为"ActiveXDotNet.dll"文件,这就是产生的组件。 以下是经过上述步骤产生的MyControl.cs的全部代码:
四.Visual C#中使用刚封装的Active X组件: 以下步骤就是通过Web页面的方式来测试上面创建组件: 1. 创建一个名称为test.htm文件,MyControl就是放在此Web页面中加以测试的,此文件的内容如下:
2. 把产生的"test.htm"和"ActiveXDotNet.dll"文件全部都拷贝到机器的虚拟目录下,一般来说虚拟目录是"C:\Inetpub\wwwroot"。 3. 打开浏览器,在浏览器的地址栏中输入"http://localhost/test.htm"后,单击"转到"按钮,则会得到如下的运行界面:
至此Visual C#产生的Active X组件和测试这个组件的全部工作就完成了。 五.总结: 虽然本文介绍的方法的确能够方便的解决Web页面中很多棘手的问题,本文介绍用Visual C#产生的组件的在实用性上的确非常的类似Active X组件,但从本质上说,本文产生的组件并不是真正意义上的Active X组件。如要使用本文所创建的组件,必须在Web页面所在机器上安装.net框架,客户端访问Web页面时,也不会真正下载本文介绍的组件,从而也不需要设定计算机的安全级别就能够访问使用此组件的Web页面。可见本文产生的组件其实质也是一个托管的代码文件。它只是巧妙的用定义接口的方式来告诉COM/COM+对象,本组件有一个可供访问的公用属性,通过对此属性的读写操作,完成类似Active X组件的工作。 C#+ASP.NET开发基于Web的RSS阅读器 最近我一直在寻找如何在Web页面上显示RSS Feed的方法,我选择 C#和ASP.net作为工具。我创建了一个简单的处理函数来处理从一个URL获得的RSS Feed。你可以直接使用这个简单的函数,或者改造成你想要的功能。 显示结果如下:这个函数使用一个字符串rssURL作为它的参数。这个字符串包含了RSS的URL。它使用rssURL的值建立了一个WebRequest项:
这个请求的响应将会被放到一个WebResponse对象里:
然后这个WebResponse对象被用来建立一个流来取出XML的值:
然后可以使用一个XMLDocument对象来存储流中的XML内容。XmlDocument对象用来调入XML的内容:
因为RSS Feed不只是一个XML文件,我们可以假设里面包含了一些RSS标准的规定。这里,我们假设使用了RSS 2.0。你可以从http://blogs.law.harvard.edu/tech/rss里得到规范的详细内容。 具体的来说,每个项应该在rss/channel/里。使用XPath表达,一个项节点列表可以如下方式创建:
rssItems存储了从RSS里获得所有项节点的信息。这样就可取得内部所需要的信息了。这里,标题、链接和每个项的描述将会被显示。在rssItems中存储的每个项,每个标记(tag)元素都可以用SelectSingleNode方法提取出来。返回的值将被赋给一个XMLNode对象。以下代码获取了一个标题节点:
现在标记需要被提取出来,使用InnerText完成这项工作。在调用SelectSingleNode之后,可以用rssDetail来测试格式化的RSS XML是否包含某些标记:
这样,你就完成了从一个Feed里获取RSS内容的工作。剩下的工作就是调用这个方法来显示Feed的内容了。以下是一个使用ASP.NET完成的完整的例子:
对C#开发的两个基本原则的深入讨论 使用属性,避免将数据成员直接暴露给外界 学习研究.net的早期,经常碰到一些学习C#/.NET的朋友问,要属性这种华而不实的东西做什么?后来做项目时也时常接到team里的人的抱怨反馈,为什么不直接放一个public字段?如:
而要做一个private字段+public属性
我记得在早期的一个项目里,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将不再是一个沉重的负担,应付一些非常轻量级的场合,结构类型依然有自己的一席之地。 Visual C#中编写多线程程序之起步.net将关于多线程的功能定义在System.Threading名字空间中。因此,要使用多线程,必须先声明引用此名字空间(using System.Threading;)。 即使你没有编写多线程应用程序的经验,也可能听说过“启动线程”“杀死线程”这些词,其实除了这两个外,涉及多线程方面的还有诸如“暂停线程”“优先级”“挂起线程”“恢复线程”等等。下面将一个一个的解释。 a.启动线程 顾名思义,“启动线程”就是新建并启动一个线程的意思,如下代码可实现:
其中的 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窗体上点击右键,选“查看代码”,然后在最顶端输入:
括号之间输入下面的代码:
第四步:点“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实现以上的功能,代码如下:
输入其它的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”之后:
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 事件中:
6、从 toolbox 页中拖一个 Button 控件到 form 上。 7、将下面的代码添加到 Button 控件的 Click 事件中:
8、在 Debug 菜单中,点击 Start 来运行样例项目。 9、点击Button。注意观察那两个进度指示器。一个逐渐减小,另一个逐渐增加。 ASP.NET程序中常用的三十三种代码1. 打开新的窗口并传送参数:
1. 打开新的窗口并传送参数:
12.Panel 横向滚动,纵向自动扩展
18.日期格式化
24.datagrid选定比较底下的行时,为什么总是刷新一下,然后就滚动到了最上面,刚才选定的行因屏幕的关系就看不到了。
24.datagrid选定比较底下的行时,为什么总是刷新一下,然后就滚动到了最上面,刚才选定的行因屏幕的关系就看不到了。
ASP.NET结合存储过程写的通用搜索分页程序select.aspx
-------------------------------------------------------------------------------- <%@ Page Language="C#" %> protected void Page_Load(Object sender, EventArgs e) mySqlConnection.Open(); </script> <html> <HeaderTemplate> <table width="583" border="0" cellspacing="0" cellpadding="0"> <ItemTemplate> <tr align="center" bgcolor="#FFFFFF" class="small" onMouseOver='this.style.background="#CCCCCC"' onMouseOut='this.style.background="#FFFFFF"'> </ItemTemplate> <FooterTemplate> </table></td> </FooterTemplate> </asp:Repeater> </form> --------------------------------------------------------------------------------
-------------------------------------------------------------------------------- CREATE proc up_GetTopicList select @TmpSelect = 'set nocount on;select @SPintRootRecordCount = count(*) from '+@a_TableName+' '+@a_SelectWhere select @RecordCount = @intRootRecordCount if (@intRootRecordCount = 0) --如果没有贴子,则返回零 /*求开始rootID*/ select @TmpSelect = 'set nocount on;set rowcount @SPintRowCount;select @SPintBeginID = '+@a_SelectOrderId+' from '+@a_TableName+' '+@a_SelectWhere+' '+@a_SelectOrder
select @TmpSelect = 'set nocount on;set rowcount @SPintRowCount;select @SPintEndID = '+@a_SelectOrderId+' from '+@a_TableName+' '+@a_SelectWhere+' '+@a_SelectOrder
if @intEndID > @intBeginID execute sp_executesql return(@@rowcount) 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)); 3月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中实现AJAXAsynchronous JavaScript and XML(AJAX)最近掀起的高潮,要完全归功于Google在Google Suggest和Google 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(同样参见上述代码)是用于处理服务器响应的客户端函数。这个回调函数接收一个响应对象,该对象公开了三个主要性质
首先我们检查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; } } 在ASP.NET中从SQL Server检索图片和存储图片相比,读取图片就要简单多了。输出一副图片我们要做的就是使用Response对象的BinaryWrite方法。
同时设置图片的格式。在这篇文章中,我们将讨论如何从SqlServer中检索图片。并将学习以下几个方面的知识。 ·如何设置图片的格式? ·如何使用BinaryWrite方法。 我们已经在Person表中存储了数据,那么我们就写些代码来从表中读取数据。 下面的代码检索了所有的值从Person表中。 从sqlserver中读取图片的代码。 Public Sub Page_Load(sender As Object, e As EventArgs) Do While (myDataReader.Read()) myConnection.Close() 看看他是怎么工作的? 上面的例子很简单。我们所作的就是执行一个sql语句,再循环读取所有的记录(looping through all the records). 在显示图片之前,我们先设置了图片的contentType,然后我们使用BinaryWrite方法把图片输出到浏览器。 源代码: /// retriving.aspx <%@ Page Language="vb" %> Do While (myDataReader.Read()) myConnection.Close() </script> 在Asp.Net中使用SmtpMail发送邮件的方法在ASP中,就可以通过调用CDONTS组件发送简单邮件,在ASP.Net中,自然也可以。不同的是,.Net Framework中,将这一组件封装到了System.Web.Mail命名空间中。
The server rejected one or more recipient addresses. The server response was: 550 5.7.1 Unable to relay for brookes@brookes.com 产生这个错误的原因除了地址错误的可能外,还有一个重要原因。如上文提到的,IIS并不带有真正的邮件功能,只是借用一个“SMTP虚拟服务器”实现邮件的转发。在MSDN中,有如下提示: 如果本地 SMTP 服务器(包括在 Windows 2000 和 Windows Server 2003 中)位于阻塞任何直接 SMTP 通信量(通过端口 25)的防火墙之后,则需要查找网络上是否有可用的智能主机能用来中转发往 Internet 的 SMTP 消息。 打开默认SMTP虚拟服务器-属性-访问-中继限制,可以看到,这种转发或者中继功能受到了限制。在限制列表中,添加需要使用此服务器的主机的IP地址,就可以解决上文提到的问题。 如果不使用IIS自带的SMTP虚拟服务器而使用其他真正的邮件服务器,如IMail,Exchange等,常常遇到服务器需要寄送者身份验证的问题(ESMTP)。在使用需要验证寄送者身份的服务器时,会出现错误: The server rejected one or more recipient addresses. The server response was: 550 not local host ckocoo.com, not a gateway 以前在ASP中,遇到这种问题没有什么解决的可能,只能直接使用CDO组件(CDONTS的父级组件): 在.Net Framework 1.1中,显然对这一需求有了考虑,在MailMessage组件中增加了Fields集合易增加ESMTP邮件服务器中的寄送者身份验证的问题。不过,这一方法仅适用于.Net Framework 1.1,不适用于.Net Framework 1.0版本。带有寄送者身份验证的邮件发送程序如下:
|
|
|