点击“X”或“Alt F4”时,最小化窗口,如:
protected override void WndProc(ref Message m)
{
const int WM_SYSCOMMAND = 0x0112;
const int SC_CLOSE = 0xF060;
if (m.Msg == WM_SYSCOMMAND && (int) m.WParam == SC_CLOSE)
{
// User clicked close button
this.WindowState = FormWindowState.Minimized;
return;
}
base.WndProc(ref m);
}
二、如何让Foreach 循环运行的更快
foreach是一个对集合中的元素进行简单的枚举及处理的现成语句,用法如下例所示:
using System;
using System.Collections;
namespace LoopTest
{
class Class1
{
static void Main(string[] args)
{
// create an ArrayList of strings
ArrayList array = new ArrayList();
array.Add("Marty");
array.Add("Bill");
array.Add("George");
// print the value of every item
foreach (string item in array)
{
Console.WriteLine(item);
}
}
}
你可以将foreach语句用在每个实现了Ienumerable接口的集合里。如果想了解更多foreach的用法,你可以查看.NET Framework SDK文档中的C# Language Specification。
在编译的时候,C#编辑器会对每一个foreach 区域进行转换。
IEnumerator enumerator = array.GetEnumerator();
try
{
string item;
while (enumerator.MoveNext())
{
item = (string) enumerator.Current;
Console.WriteLine(item);
}
}
finally
{
IDisposable d = enumerator as IDisposable;
if (d != null) d.Dispose();
}
这说明在后台,foreach的管理会给你的程序带来一些增加系统开销的额外代码。
三、将图片保存到一个XML文件
WinForm的资源文件中,将PictureBox的Image属性等非文字内容都转变成文本保存,这是通过序列化(Serialization)实现的,
例子://
using System.Runtime.Serialization.Formatters.Soap;
Stream stream = new FileStream("E://Image.xml",FileMode.Create,FileAccess.Write,FileShare.None);
SoapFormatter f = new SoapFormatter();
Image img = Image.FromFile("E://Image.bmp");
f.Serialize(stream,img);
stream.Close();
四、屏蔽CTRL-V
在WinForm中的TextBox控件没有办法屏蔽CTRL-V的剪贴板粘贴动作,如果需要一个输入框,但是不希望用户粘贴剪贴板的内容,可以改用RichTextBox控件,并且在KeyDown中屏蔽掉CTRL-V键,例子:
private void richTextBox1_KeyDown(object sender, System.Windows.Forms.KeyEventArgs e)
{
if(e.Control && e.KeyCode==Keys.V)
e.Handled = true;
}
ps: 网上摘抄,看了以后感觉不错,以后碰见好的再陆续发布
在现在的网站设计中,更强调的是用户的个性化设置,让用户可以自由的设置符合自己喜好的页面成为网站开发人员的头号难题,不过现在看来这个难题微软帮我们解决了。在asp.net 2.0中新增加了一系列webpart控件,可以让用户很方便地对网页的各区域布局进行调整。在一些web应用程序中,如果用户想自定义页面布局,比如一个新闻发布系统,想让左,中,右三栏的位置进行调换的话,就可以使用webpart控件。
下面,我们来看下asp.net 2.0中webpart系列控件的一些基本用法。
首先,在vs.net 2005 中的工具箱中,可以找到如下图所示的webpart系列控件,有很多个,限于篇幅,本文介绍其中的一些重要的控件:
![ASP.NET 2.0中使用webpart系列控件](http://writeblog.csdn.net/attachment/Image/200841290191177801.gif)
在webpart系列控件中,其中的webpartmanager控件用于统一管理各webpart控件。而webpartzone控件,则是提供了各区域划分,在这些区域中,用户可以往里面放置各式各样的控件,而当运行的时候,用户可以移动的就是这些webpartzone控件所在的区域。
为增强认识,我们先做个简单的例子。
1、首先使用vs.net 2005 beta 2(或者RC1)新建一个web站点,
2、往窗体中拖拉一个webpartmanager控件,再建一个3列1行的表格,分别往每个单元格里拖拉一个webpartzone控件,如下图:
![ASP.NET 2.0中使用webpart系列控件](http://writeblog.csdn.net/attachment/Image/200841290194277802.gif)
3、往webpartzone1中拖拉放一个日历控件,并为这个日历控件选择一个合适的样式
4、切换到代码视图状态,将日历控件的title属性改为:today’s date。注意的是,日历控件本身没有title属性,但当一个控件加入到webpartzone区域中去后,则该控件被自动包装为GenericWebPart类型控件,这些类型的控件有title属性。
5、这时,我们可以按F5来运行该程序,运行如下图所示,可以看到,区域的右上角有最小化和关闭,恢复的按钮。
![ASP.NET 2.0中使用webpart系列控件](http://writeblog.csdn.net/attachment/Image/200841290197377803.gif)
接下来,我们介绍如何在webpart系列控件中,使用用户自定义的控件。
1、首先,我们为工程项目增加一个"google.ascx"的控件,并且在images目录下,添加google那张著名的logo图片。
接着,往窗体中添加一个2*2行的表格,再往其中的一个单元格添加一个image图象控件,指定其图象为google.gif,再添加一个文本框,一个按钮,如下图所示,其中,括号内的是该控件的名称:
![ASP.NET 2.0中使用webpart系列控件](http://writeblog.csdn.net/attachment/Image/2008412901912077804.gif)
3、在btnsearch按钮的click事件中写入如下代码:
Dim queryStr As String = HttpUtility.UrlEncode(txtSearch.Text)
Response.Redirect("http://www.google.com/search?q=" & queryStr)
End Sub
4、这时,将写好的google.ascx控件,整个拖拉到我们刚才建立好的表格中的中间那个单元格,如下图所示:
![ASP.NET 2.0中使用webpart系列控件](http://writeblog.csdn.net/attachment/Image/2008412901915177805.gif)
我们并且修改代码如下,修改其名称为google serach:
接下来,F5运行,可以看到,可以在googlesearch所在的webpart里进行google搜索了。
同时,如果觉得webpart的那些关闭,恢复,最小化的按钮不大好看,还可以自定义按钮,比如在images目录下,添加下面的图片:
![ASP.NET 2.0中使用webpart系列控件](http://writeblog.csdn.net/attachment/Image/2008412901919877806.gif)
然后,在webpartzone1的属性中,指定如下的属性就可以了。
EditVerb.ImageUrl="Images/EditVerb.gif"
MinimizeVerb.ImageUrl="Images/MinimizeVerb.gif"
RestoreVerb.ImageUrl="Images/RestoreVerb.gif"
使webpart动起来
上面设计的webpart还没能动起来,要让webpart动起来的话,必须要将webpar设置为design display 模式。先为webpart添加下面的radiobutton选择框
<asp:ListItem>Browse Display Mode</asp:ListItem>
<asp:ListItem>Design Display Mode</asp:ListItem>
</asp:RadioButtonList>
并且在code-behind的代码中,写入如下代码:
Select Case rblMode.SelectedIndex
Case 0 : WebPartManager1.DisplayMode =WebPartManager.BrowseDisplayMode
Case 1 : WebPartManager1.DisplayMode = WebPartManager.DesignDisplayMode
End Select
End Sub
运行上面代码,选择design display mode,则可以象下图那样,自由拖动webpart,
![ASP.NET 2.0中使用webpart系列控件](http://writeblog.csdn.net/attachment/Image/2008412901923077807.jpg)
要注意的是,当移动各webpart的位置后,即使关掉浏览器,下次重新打开时,依然可以看到各个控件保持原来的位置。其实,asp.net 2.0是使用在aspnetdb.mdf中的一个叫aspnet_PersonalizationPerUser的表来保存数据的,表的结构如下所示:
Field | Value |
Id | 928e121a-4042-4fb4-9520-21210b9b37c1 |
PathId | 7c3b5dc0-04d0-48a2-bbb2-2b70286f22fe |
UserId | 9bff14df-024f-4bda-9a0a-b4a19ab9e387 |
PageSettings | <Binary data> |
LastUpdatedDate | 10/06/2005 4:44:05 AM |
如果想恢复各控件的原来位置,只需要将该数据表中相应的行删除掉就可以了。但有个问题是,如果使用每一个webpart的关闭按钮,则很难再将其恢复(当然删除数据表中的行,但十分麻烦)。在asp.net 2.0中,提供了另一种webpart,叫做catlogzone控件,下面介绍其用法:
1、往窗体中拖拉一个catlogzone控件,如下图所视。
![ASP.NET 2.0中使用webpart系列控件](http://writeblog.csdn.net/attachment/Image/2008412901927677808.jpg)
2、往该catlogzone控件区域中,再拖放三个webpart系列的控件,分别是DeclarativeCatalogPart, PageCatalogPart, and ImportCatalogPart,如下图所示。其中,DeclarativeCatalogPart控件的作用是,显示目前页面上有哪些可以用的webpart控件;PageCatalogPart的作用是,可以让用户通过勾选的方式,选定将哪些控件添加转移到其他webpart区域中去。ImportCatalogPart则可以通过外部磁盘文件的方式,加载其他做好了的webpart部件。
![ASP.NET 2.0中使用webpart系列控件](http://writeblog.csdn.net/attachment/Image/2008412901932377809.jpg)
3、在radiobutton区域中,修改以下代码,增添一个catalog display的显示模式:
<asp:ListItem>Browse Display Mode</asp:ListItem>
<asp:ListItem>Design Display Mode</asp:ListItem>
<asp:ListItem>Catalog Display Mode</asp:ListItem>
</asp:RadioButtonList>
然后,在code-behind的代码中,将代码修改为如下:
ByVal sender As Object, _
ByVal e As System.EventArgs) _
Handles rblMode.SelectedIndexChanged
Select Case rblMode.SelectedIndex
Case 0 : WebPartManager1.DisplayMode = WebPartManager.BrowseDisplayMode
Case 1 : WebPartManager1.DisplayMode = WebPartManager.DesignDisplayMode
Case 2 : WebPartManager1.DisplayMode = WebPartManager.CatalogDisplayMode
End Select
End Sub
4、在DeclarativeCatalogPart任务菜单上,点击右上角的智能感知按钮,然后选"edit templates"的链接,进入模版编辑状态,如下图:
![ASP.NET 2.0中使用webpart系列控件](http://writeblog.csdn.net/attachment/Image/20084129019355778010.jpg)
再往其中的webpartstemplate区域中拖拉一个google.ascx控件,如下图,这将允许用户在运行时,可以自由地往页面增加这样的google搜索控件。
![ASP.NET 2.0中使用webpart系列控件](http://writeblog.csdn.net/attachment/Image/20084129019401778011.jpg)
5、然后修改代码如下:
<asp:DeclarativeCatalogPart ID="DeclarativeCatalogPart1" runat="server">
<WebPartsTemplate>
<uc1:Google title="Google Search" ID="Google2" runat="server" />
</WebPartsTemplate>
</asp:DeclarativeCatalogPart>
6、运行程序,可以看到,当选择catalog display mode时,会显示如下图所示的catalog zone,其中列出了当前可用的有哪些webpart控件,我们可以把这个google的控件加到其他的webpart区域,也可以尝试将已经存在的webpart控件关闭,然后在catalog zone区域中的控件列表中,把它们再加回到页面中去。
![ASP.NET 2.0中使用webpart系列控件](http://writeblog.csdn.net/attachment/Image/20084129019433778012.jpg)
此外,在运行期间,还可以动态地修改webpart控件的外观等属性,如下:
1) 往窗体中添加一个editor zone的区域控件,往其中再拖放一个appearanceEdiotrPart控件,该控件可以在运行时,让用户动态改变各webpart控件的属性。
2) 我们再修改radiobutton选择框的代码如下,则加一个编辑模式:
<asp:ListItem>Browse Display Mode</asp:ListItem>
<asp:ListItem>Design Display Mode</asp:ListItem>
<asp:ListItem>Catalog Display Mode</asp:ListItem>
<asp:ListItem>Edit Display Mode</asp:ListItem>
</asp:RadioButtonList>
3) 修改code-behind代码如下:
Handles rblMode.SelectedIndexChanged
Select Case rblMode.SelectedIndex
Case 0 : WebPartManager1.DisplayMode = WebPartManager.BrowseDisplayMode
Case 1 : WebPartManager1.DisplayMode = WebPartManager.DesignDisplayMode
Case 2 : WebPartManager1.DisplayMode = WebPartManager.CatalogDisplayMode
Case 3 : WebPartManager1.DisplayMode = WebPartManager.EditDisplayMode
End Select
End Sub
4) 运行程序,选择edit display mode模式,这时,会发现每个控件的右上角,会多了一个"edit"的按钮,点该按钮,弹出如下图的窗体,用户可以修改每个控件的外观等属性。
![ASP.NET 2.0中使用webpart系列控件](http://writeblog.csdn.net/attachment/Image/20084129019480778013.jpg)
最后,我们看下,webpart控件之间还可以进行相互之间的通信,下面的例子中,要实现的是,在一个日历控件中点选某一个日期,会在已经做好的googlesearch的webpart控件的文本框中显示其日期,达到通信的目的,下面介绍其实现步骤:
1、为了使两个webpart控件之间进行通信,必须先声明一个公共的接口。往工程项目里增加一个叫ISelectedDate.vb的类文件,放在app_code目录下,写入如下代码:
Public Interface ISelectedDate
ReadOnly Property SelectedDate( ) As Date
End Interface
这里,我们返回一个只读的日期属性selectedDate.
2、再创建一个日历控件CalendarUC.ascx,其中拖拉一个普通的日历控件即可。然后写入如下代码:
Inherits System.Web.UI.UserControl
Implements ISelectedDate
Public ReadOnly Property SelectedDate( ) As Date Implements ISelectedDate.SelectedDate
Get
Return Calendar1.SelectedDate.Date
End Get
End Property
<ConnectionProvider("SelectedDate", "SelectedDate")> _
Public Function GetSelectedDate( ) As ISelectedDate
Return Me
End Function
End Class
上面的代码,首先实现了已经声明了的IselectedDate接口,要留意的是<ConnectionProvider("SelectedDate", "SelectedDate")>中的写法。由于在这个例子中,日历控件要为其他的控件提供信息,因此,该日历控件是一个provider(提供者),而另外的接收信息的控件,是consumer(消费者)。而两者为了要通信,必须要提供一个通信接入点,就象一个电插头,要找到合适的电插板一样。因此,<ConnectionProvider("SelectedDate", "SelectedDate")>中的第一个参数,定义了两者的接口点,第二个参数,则是要传递给consumer的参数,本例是selectedDate。
3、接下来,我们在已经做好的google.ascx控件的代码中,编写如下代码:
<ConnectionConsumer("SelectedDate", "SelectedDate")> _
Sub setSearchText(ByVal SearchText As ISelectedDate)
Me._selectedDate = SearchText
End Sub
Protected Sub Page_PreRender(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.PreRender
If _selectedDate IsNot Nothing Then
txtSearch.Text = _selectedDate.SelectedDate.ToShortDateString
End If
End Sub
可以看到 <ConnectionConsumer("SelectedDate", "SelectedDate")>的定义必须和provider中的定义一样。
4、再修改如下代码,将两个控件的命名变得通俗易懂
<uc1:Google title="Google Search" runat="server" ID="Google1" />
<uc3:CalendarUC title="Calendar Web Part" runat="server" ID="CalendarUC1" />
</ZoneTemplate>
5、最后,为了使两者能互相通信,必须在default.aspx页中修改如下代码:
<StaticConnections>
<asp:WebPartConnection ID="Connection"
ProviderID="CalendarUC1"
ProviderConnectionPointID="SelectedDate"
ConsumerID="Google1"
ConsumerConnectionPointID="SelectedDate" />
</StaticConnections>
</asp:WebPartManager>
6、在页面代码中,增加一个radiobutton,用作显示connection模式,并写入如下代码:
7、运行程序,选择connect displaymode模式。再选择GOOGLE SEARCH的那个webpart控件,点右上角的"conenct"按钮,此时,会显示如下图所示,提示你要选择从那个控件中得到信息,这里选择日历控件,按确定。那么,当点选日历控件的某个日期值的时候,GOOGLE SEARCH的那个文本框里,就会显示相应的日期了。
![ASP.NET 2.0中使用webpart系列控件](http://writeblog.csdn.net/attachment/Image/20084129019511778014.jpg)
小结:
本文主要介绍了在asp.net 2.0中,如何使用基本的webpart系列控件,以达到改变页面布局以及如何使页面的各webpart控件相互之间通信。
Author:unknown From:Internet
从在复杂的B2B交易中的交换数据到为应用程序提供配置文件结构,XML在许多方面大显身手!由于XML不断地获得软件的支持,我们完全能够预见:XML的应用将不断增加。本文就介绍这样一种应用程序,它使用XML创建类似于Windows开始菜单的分层菜单系统,从而向终端用户提供更满意的Web体验。
这个应用程序将使用 C#、XML和服务器端Microsoft .Net框架,创建一个 DHTML 结构,IE4 或更高版本的浏览器可以操作它并在客户端动态显示。由于我们可以快速地访问服务器上的XML,并且XML能够描述层次之间的关系,因此它成为标记“父/子菜单”数据的一个最佳选择。
(一个三层菜单系统,可点击放大)
除了学习如何使用XML创建一个菜单应用程序外,我们还要介绍 .Net框架的主要 XML 类,它们位于 System.Xml 集合中。
什么是.Net集合(Assembly)
要在 C# 文件中使用XML,就必须引用一个特定的名称空间。 .Net平台中的一个名称空间是作为一个程序组件的组织系统使用的,它对于解决命名冲突很重要,这一点很象 XML中的 名称空间。这个基于 XML的菜单系统是用位于System.Xml集合中的System.Xml名称空间创建的。.Net SDK 是这样定义集合的:一个集合就是一个类型和资源的汇集信息,这些类和资源一起使用,形成一个功能的逻辑单元,即一个“逻辑”dll。
一个集合需要许多物理文件,如界面、类、资源文件等等,并且创建了关于文件如何一起工作的元数据。集合中还可能包含版本及安全信息。集合有许多好处,其中之一就是可以在ASP.NET应用程序中使用,而无须用regsvr32.exe向注册文件中增加一个类识别号(CLSID)。这样以来,集合的升级操作与将适当的集合复制到一个ASP.NET 应用程序的bin 目录中一样简单。现在我们就来仔细看一看那些建立在System.Xml名称空间和集合中的类。
如果你以前使用过Microsoft的 MSXML3分解器,就会发现使用System.Xml 集合中的类相当简单。这里的菜单应用程序只使用了这些主要集合类中的一部分:XmlNode、 XmlDocument、 XmlNodeList、 XmlNamedNodeMap、 DocumentNavigator、 XmlTextReader和 XmlTextWriter。
XmlDocument、 XmlNodeList和XmlNode类用来创建传递给客户端浏览器的菜单应用程序的结构。用XmlDocument类以一种安全线程(thread-safe)的方式从服务器上装载和分解本地或远程XML 文档。建立在一个字符串中的 XML标记也可以被装载或分解,从而在一个文档中创建、移动节点或取消节点的移动。XmlNodeList 类可以使我们列举一个节点的集合来访问一个特定属性,如名称、值或名称空间。最后,XmlNode类可以用来在XML文档中向一个用于检验的XmlNode 对象分配一个特定节点。
XmlNamedNodeMap类用来列举建立在一个元素类型选择中的属性集合。 DocumentNavigator、 XmlTextReader和XmlTextWriter类提供与XML一起使用所需要的额外功能。要特别说明的是,DocumentNavigator可以用来执行XPath查询,它被包含在XSLT转换中。XmlTextReader类提供对XML节点的只向前(forward-only)、无缓存的访问,从而使XML节点对大型XML文档也同样生效。XmlTextWriter类提供一个快速、只向前的指针模型,实现将 XML 内容写到一个流或一个文件中的目的。
在菜单应用程序中使用的XML 文档相对来说是较小的,因此我们可以在服务器端使用文档对象模式(DOM)来存取XML文档中的不同节点。当分解大型 XML文档时,就需要使用XmlTextReader 类所含有的只向前模型。
XML 代码
菜单应用程序使用了3个 XML 文档:menuItems.xml、menuItems2.xml 和menuItems3.xml,它们用于标记单独的菜单项目。XML的处理和操作由一个叫做xmlMenus.dll的集合完成,这个集合被服务器端的一个ASP.NET 文件createMenus.aspx所使用。在客户端的动态HTML (DHTML)内容使用了一个层叠格式表文件和一个JavaScript文件,这些文件联合在一起生成了本文开始部分的图示结果。
我们在List 1的代码显示了用来标记单独菜单项目的XML文档的一部分。文档中的主元素命名为menuItem,它可以包含一个名字和超级链接元素以及额外的menuItem 子元素。这种关系可以用来创建包含子菜单的菜单系统,就象Windows的开始菜单一样。
由于 XML已经标出了分层关系,因此就可以利用循环在不同元素之间行走:当使用XmlDocument、XmlNode和XmlNodeList类创建子节点时,我们可以反复调用WalkTree() 函数以颠倒父/子关系。其它的类如 ArrayList和 StreamWriter用于将相关菜单归类到数组中,然后在适当时间将所生成的菜单结构写入一个文件中。
XmlMenus集合的代码开始时要声明一个XmlHierMenu 名称空间,接下来要引用 System、 System.Xml、 System.Collections和System.IO 名称空间:
using System;
using System.Xml;
using System.Collections;
using System.IO;
在这部分代码之后要创建3个构造器。一个构造器不接收变量,只对变量进行初始化。下一个构造器接收一个图象文件的定制路径。最后一个构造器在不经常对XML菜单进行刷新的情况下,允许将由集合生成的输出存储到一个文件中。最后一个构造器所生成的文件可以被静态地包含在一个 ASP.NET文件中,而不是在各个Web页面请求时随时地生成。
在构造器之后,开始定义 CreateMenu()方法,具体请看List 2中的代码。
这个方法负责进行XML文档的装载和解析,找到根节点,然后在根的子节点之间循环。如果发现一个子节点本身还有子节点,就调用WalkTree() 方法,并且把这些子节点都传递到其中。如果这些子节点还有子节点,就再次调用 WalkTree()。这个过程循环进行直到不再发现增加的子节点。Walktree()方法的代码可以参看List 3。
在调用 WalkTree()方法并且对不同的节点进行分析的同时,要解析 menuItem节点,将来自其超级链接和名字节点的数据放在数组列表中。对整个 XML文档都进行解析之后,数组列表的内容就被传递回调用的ASP.NET的页面,然后使用Response对象写出信息。从这时起,客户端的 JavaScript代码就开始控制DHTML 菜单了。
在服务器端,我们使用一个 ASP.NET页面来开始菜单的创建过程。这个页面输入了与集合相关联的名称空间XmlHierMenu:
<%@ page language="C#" %>
<%@ Import Namespace="XmlHierMenu" %>
然后,将样式表文件和 JavaScript文件包含在文件的 代码区中。最后,在 ASP.NET 页面底部的代码用来引用上面提到的CreateMenu() 方法与WalkTree()方法,请看List 4。
在ASP.NET 页面中所找到的C# 代码只是建立了到不同的XML文档的路径,并且对 XmlMenu 类进行了例示。一旦对这个类进行了例示,就对 CreateMenu()方法进行调用。这个方法接收菜单名以及到这个菜单的XML文档的文件路径。在本例中,要创建3个名为menu1、 menu2和 menu3 的菜单,然后在一个ASP.NET页面中使用。这个应用程序在一个给定页面中可以支持无穷多的菜单,但是我们建议菜单数目不要过多,因为每增加一个菜单,发送到客户端的文件规模都会增加。
编译 C# 文件
现在我们已经看到了菜单应用程序的结构,接下来讨论一下使用哪些开关可以将C# 文件编译到一个集合中。在 .Net SDK 文档中,我们可以查询到一个所有编译开关的完整列表。
要想正确地编译 C# 文件以使它能用于一个ASP.NET 页面中,编译器就必须要知道包含了System.Xml 集合。这可以通过使用 /r 开关并在后面加上到集合的完整路径来完成。由于将要创建的 dll是一个库,因此就必须指定 /t开关,这样编译器就不再寻找一个静态的 Main() 方法。最后一个需要的开关是 /out ,它将告诉编译器输出的文件名是什么,以及将其放在哪里。
所有对 C# 编译器的调用都以 csc (C# 编译器)开始,然后指定适当的开关。编译器语法的最后一部分包括到已创建的 .cs 文件的路径。请看下面的编译文件的完整语法格式:
C:/>csc /r:System.Xml.dll /
t:library /out:d:/inetpub/wwwroot/
xml/bin/xmlMenus.dll d:/inetpub/
wwwroot/xml/menus/xmlMenus.cs
这行命令告诉编译器包含 System.Xml 集合、将文件作为一个库进行编译、将文件输出到 bin目录中、输入文件命名为xmlMenus.cs,输出文件名叫xmlMenus.dll。当输入回车键后,.cs文件就进行编译,生成的 dll将被放置在适当的文件夹中。
结束语
本文通过讲解一个应用程序的思路及实现代码,使我们对于使用Microsoft .Net平台中的一些集合和类有了一个很好的了解。随着进一步的学习,我们将看到,还会有其它一些集合和类可以以多种方式与本地和远程XML文档一起工作。
就像C语言众多的后世子孙一样,C#的函数参数是非常讲究的。首先,参数必须写在函数名后面的括号里,这里我们有必要称其为形参。参数必须有一个参数名称和明确的类型声明。该参数名称只在函数体内部可见。因此在该函数体以外的任何地方使用同样的变量名是不会引起冲突的。每当调用函数的时候,必须将一个实参传递给函数定义中的形参。默认情况下,C#的参数传递是值传递。这种方式的优点和缺点同样明显。另外,在传送引用类型的时候还时不时引起一些小误会。更加使人困惑的是,既然CLR不支持指针类型,那么我们以前在C/C 中的那些关于指针传递的妙用应该如何实现呢?不必发愁,本文将会逐一回答上述这些疑问。首先我们会讨论默认情况下的值传递以及这种方式的优缺点,解释默认情况下传递引用类型时容易产生的误解。然后,我们讨论如何利用ref关键字把一个值类型作为引用类型传递给参数。最后,我们尝试着让一个函数可以返回多个值,在C/C 中我们经常利用指针达到这一目的,这里我们将会利用out关键字重温这种美妙的感觉。
值传递
每当调用一个函数的时候,我们就必须为该函数的每一个形参传递一个实参。默认情况下,采用值传递的机制。也就是说,实参的值会被拷贝到形参里面,这样我们在函数内部得到一个本地变量,该变量的值和传递进来的那个实参的值相等,但是它们存放在不同的存储空间。因此,我们对函数参数所做的一切实际上都是对函数提内本地变量的操作,绝对不会影响到作为实际参数传递过来的那个函数体外的变量。看下面的例子,我就不再多费口舌了。
using System;
namespace CS语言函数参数的传递
{
/// <summary>
/// Class1 的摘要说明。
/// </summary>
class Example
{
static void Main(string[] args)
{
int argument = 5;
Example exp = new Example();
System.Console.WriteLine(argument);
exp.fun1(argument);
System.Console.WriteLine(argument);
}
public Example()
{
}
public void fun1(int parameter)
{
//对parameter的操作实际上是对本地变量的修改
//不会影响到函数体外作为实参传递过来的变量
parameter = 5;
System.Console.WriteLine(parameter);
}
}
}
但是值传递的机制有一个明显的缺点。主要表现在值类型的传递方面。我们对参数的修改会在函数体执行结束之际消失。如果我们希望将这种变化影响到作为实参传递过来的那个函数体以外的变量就必须把值类型作为引用类型传递。后边会具体讨论。值传递机制的另一个缺点,或许你会认为这是一个优点,表现在引用类型的传递方面。按照值传递的机制传递一个引用类型的变量,实际上只是完成了一次浅拷贝。请不要误认为对整个对象进行了深拷贝。函数参数得到的只是实参的handle的值。也就是说,本地的参数实际上只是一个引用类型的handle,和作为实参传递过来的那个变量的handle具有相同的值,指向同一个object(两个handle指向堆上的相同位置)。这样我们在函数内部对参数所做的修改会直接影响到堆上的object。当函数结束之后,本地的参数消失,而对于堆上的object的修改会成为持久的修改而继续保留下来。
把值类型作为引用类型传递
有一些时候,我们不惜望函数对于参数的修改随着函数的结束而消失。作为引用类型,作到这一点其实一点都不难,就像我们上面说的那样。但是,如果是值类型的参数,似乎就有一点麻烦了。从前在C/C 里面可以采取传递指针的方法来达到这个目的。但是CLR已经明确取消了指针。作为补偿,C#为我们提供了ref关键字。ref关键字通知编译器,参数的实参是作为引用类型而非值类型进行传递。下面的这段程序帮助我们说明问题。
using System;
namespace CS语言函数参数的传递
{
class Example
{
static void Main(string[] args)
{
int argument = 5;
Example exp = new Example();
//首先显示argument
System.Console.WriteLine(argument);
exp.fun2(ref argument);//传递参数时必须使用ref关键字
System.Console.WriteLine(argument);
System.Console.ReadLine();
}
public void fun1(int parameter)
{
//对parameter的操作实际上是对本地变量的修改
//不会影响到函数体外作为实参传递过来的变量
parameter = 5;
System.Console.WriteLine(parameter);
}
public void fun2(ref int parameter)
{
parameter = 5;
System.Console.WriteLine(parameter);
}
}
}
函数fun2要求一个int类型的参数,并且伴有关键字ref。在Main()函数内定义了一个整形变量argument,它将会作为实参传递给函数fun2()。在调用该函数之前,首先显示了变量argument,其值等于5。紧接着调用函数fun2(),并且传递argument给参数parameter。这时函数得到的是一个本地的,指向整形变量argument的handle。在函数内部,把parameter加5,然后显示它。这时其值为10。函数返回后再一次显示argument,其值同样为10。
让函数返回多个返回值
有些时候我们可能会希望一个函数可以返回多个返回值。事实上,这是不可能的因为一个函数只能返回一个返回值。但是我们确实办法达到这种效果。最简单的是下面这种方法。
public int fun3(ref int i, int j)
{
i = j;
return i j;
}
我们这样调用这个函数。
int i;
int sum = exp.fun3(ref i, 10);
System.Console.WriteLine(i);
System.Console.WriteLine(sum);
这样在执行过函数fun3()之后,我们实际上得到了i的值和i j的值。实际上起到了利用一个函数返回两个值的作用。另外有一个关键字也是非常重要的。那就是out关键字。该关键字允许向参数传递一个没有分配空间的引用类型。利用这个关键字同样可以达到返回多个值的目的。
public void fun4(ref int i, out object obj)
{
i =5;
obj = i.ToString();
System.Console.WriteLine(i);
System.Console.WriteLine(obj);
}
上面这个方法要求两个参数。第二个参数要求一个object类型的变量。该参数前面有一个out关键字。编译器会认为该参数的实参没有被分配存储空间。Out参数在未被赋值之前不能使用。可以这样调用该函数:
int i = 5;
object obj;
exp.fun4(ref i, out obj);
System.Console.WriteLine(i);
System.Console.WriteLine(obj);
输出为4个10。说明我们在调用该函数之后得到了变量i和obj两个变量的值。
访问数据库是大多数应用程序的一部分,而且随着C#和ADO.NET的发布,这个过程已经变得相当的简单.本文将展示下面四个基本的数据库操作:
1.读数据.这包括诸如整数,字符串和日期等不同的数据类型.
2.写数据.就象读数据一样我们会写这些通常的数据类型.这是通过SQL语句来实现的.
3.更新或是修改数据.我们还是使用简单SQL语句.
4.删除数据.使用SQL.
这些操作是对一个微软Access 2000数据库进行的,但是SQL或是其它ADO数据源可以通过简单的改变连接字符串来使用.
开始第一步
为了使用ADO类,我们需要包括进ADO.NET命名空间(namespace)和一些精巧的日期类.在你要进行数据库操作的地方加入下列几行代码.它应该被放置在命名空间引入代码行的下面而在类定义的上面.
using System.Data; // 申明变量
using System.Data.ADO; // 数据库
using System.Globalization; // 日期
根据你所参与的工程的类型,你可能需要增加对System.Data命名空间的引用.你可以根据在你添加上面的代码以后编译器是否产生错误来判断.要添加System.Data命名空间,你可以:
1.在Solution explorer-References 分支中右键单击.
2.选择"添加引用"
3.选择.NET Framework标签.
4.双击System.data.dll条目
5.单击OK
6.System.Data现在应该出现在了Solution explorer的引用列表中了.
因为连接字符串在大多数操作中都要使用,所以我建议你将它设置成你要编写的类的成员.注意:在你的程序中,数据库文件的路径有可能不同.
//属性
public const string DB_CONN_STRING =
"Driver={Microsoft Access Driver (*.mdb)}; "
"DBQ=D://CS//TestDbReadWrite//SimpleTest.mdb";
读数据
现在一切都变得有趣起来.读数据可以通过ADODataReader类来实现.(参看Chris Maunder的文章"ADO.NET ADODataReader类"来获取关于这个类的更多信息.)读数据的步骤如下:
1.我们用ADOConnection来打开一个数据库.
ADOConnection conn =
new ADOConnection(DB_CONN_STRING);
conn.Open();
2.我们编写一个SQL语句来定义将要取出的数据.这个数据执行的结果是返回一个ADODataReader 对象.注意Execute方法中的out关键字.这在C#中意味着通过引用传递参数.
ADODataReader dr;
ADOCommand cmd =
new ADOCommand( "SELECT * FROM Person", conn );
cmd.Execute( out dr);
3.我们循环遍历ADODataReader中的每一个记录直到我们完成要做的工作.注意:数据被直接作为一个字符串返回同时数据域名称用来指明读的数据域.
while( dr.Read() )
{
System.Console.WriteLine( dr["FirstName"] );
}
4.我们收工
但是,作为好的程序员我们还需要加进许多try/catch/finally语句来确保我们处理了所有的错误.
try
{
.... 数据库操作 ...
}
catch( Exception ex )
{
System.Console.WriteLine( "READING:" );
System.Console.WriteLine( " ERROR:" ex.Message );
System.Console.WriteLine( " SQL :" sSqlCmd );
System.Console.WriteLine( " Conn.:" DB_CONN_STRING );
}
finally
{
// 关闭连接
if( conn.State == DBObjectState.Open )
conn.Close();
}
读取不同的数据类型
dr["stuff"]这个语句通常能够返回一个数据.但是要返回一个int或者DateTime对象的话通常需要进行数据转换.这通常可以通过使用ADODataReader许多内建转换器中的一个来实现.也就是:
int nOrdinalAge = dr.GetOrdinal( "Age" );
int nAge = dr.GetInt32( nOrdinalAge );
DateTime tUpdated = (DateTime)dr["Updated"];
注意GetOrdinal定位数据域用名字来读取数据的用法.如果数据域是空白的(还没有填入任何数据),上面的代码会抛出一个异常.要捕捉这种情况,我们可以用IsNull方法检查是否有数据存在,如下所示:
int nOrdinalAge = dr.GetOrdinal( "Age" );
if( dr.IsNull( nOrdinalAge ) )
{
System.Console.WriteLine( " Age : Not given!" );
}
else
{
int nAge = dr.GetInt32( nOrdinalAge );
System.Console.WriteLine( " Age : " nAge );
}
插入,修改,删除和其他SQL命令
插入,修改,删除过程可以非常容易的通过SQL语句来实现.下面的代码执行一条SQL命令来插入一条记录.
// SQL 命令
String sSQLCommand =
"INSERT INTO Person (Age, FirstName, Description,
Updated) "
"VALUES( 55, 'Bob', 'Is a Penguin', '2001/12/25 20:30:15' );";
// 创建command对象
ADOCommand cmdAdder = new ADOCommand(
sSQLCommand,
DB_CONN_STRING);
cmdAdder.ActiveConnection.Open();
// Execute the SQL command
int nNoAdded = cmdAdder.ExecuteNonQuery();
System.Console.WriteLine(
"/nRow(s) Added = " nNoAdded "/n" );
注意:try/catch代码没有在上面的例子里出现,但是应该包括上面的代码.
插入
上面的代码通过建立一个SQL命令然后执行它来插入一条记录.一些在编写SQL命令时应注意的事项如下:
1.数值数据应该直接表示.没有单引号(').
2.字符串的表示应该包括在单引号中('blah').
3.确保字符串中不包含任何嵌入的单(双)引号.这会使事情混淆.
4.日期和时间数据用包裹在单引号中的国际通用形式来表示
('YYYY/MM/DD HH:MM:SS').
修改
UPDATE命令指明要修改的数据和修改的动作.ExecuteNonQuery()的返回值指出改变的记录的个数,所以如果有5个Peter's在表单中的话下面的代码将返回5.
String sSQLCommand =
"UPDATE Person SET Age = 27 WHERE FirstName = 'Peter'";
删除
DELETE命令指明要删除的记录.ExecuteNonQuery()的返回值指出改变的记录的个数,所以如果有2个Bobo在表单中的话下面的代码将返回2.两个Bobo都会被删除.
String sSQLCommand =
"DELETE FROM Person WHERE FirstName = 'Bobo'"";
//获得汉字的区位码
byte[] array = new byte[2];
array = System.Text.Encoding.Default.GetBytes("啊");
int i1 = (short)(array[0] - ''/0'');
int i2 = (short)(array[1] - ''/0'');
//unicode解码方式下的汉字码
array = System.Text.Encoding.Unicode.GetBytes("啊");
i1 = (short)(array[0] - ''/0'');
i2 = (short)(array[1] - ''/0'');
//unicode反解码为汉字
string str = "4a55";
string s1 = str.Substring(0,2);
string s2 = str.Substring(2,2);
int t1 = Convert.ToInt32(s1,16);
int t2 = Convert.ToInt32(s2,16);
array[0] = (byte)t1;
array[1] = (byte)t2;
string s = System.Text.Encoding.Unicode.GetString(array);
//default方式反解码为汉字
array[0] = (byte)196;
array[1] = (byte)207;
s = System.Text.Encoding.Default.GetString(array);
//取字符串长度
s = "iam方枪枪";
int len = s.Length;//will output as 6
byte[] sarr = System.Text.Encoding.Default.GetBytes(s);
len = sarr.Length;//will output as 3 3*2=9
//字符串相加
System.Text.StringBuilder sb = new System.Text.StringBuilder("");
sb.Append("i ");
sb.Append("am ");
sb.Append("方枪枪");
/
string --> byte array
byte[] data=Syste.Text.Encoding.ASCII.GetBytes(string);
string --> byte
byte data = Convert.ToByte(string);
byte[]-->string
string string = Encoding.ASCII.GetString( bytes, 0, nBytesSize );
(一).选择会话状态存储方式
在Webconfig文件配置:
<sessionState mode="???" stateConnectionString="tcpip=127.0.0.1:42424"
sqlConnectionString="data source=127.0.0.1;Trusted_Connection=yes"
cookieless="false" timeout="20"/>
Asp.net有三种方式存储会话状态信息:
1. 存储在进程中: 属性mode = InProc
特点: 具有最佳的性能,速度最快,但不能跨多台服务器存储共享.
2. 存储在状态服务器中: 属性mode = "StateServer"
特点: 当需要跨服务器维护用户会话信息时,使用此方法。但是信息存储在状态服务器上,一旦状态服务器出现故障,信息将丢失
3. 存储在Sql Server中: 属性mode="SqlServer"
特点: 工作负载会变大,但信息不会丢失.
补充一点:
I. 由于某些页面不需要会话状态,则可以将会话状态禁用:
代码如下: <%@ Page EnableSessionState="false" %>
II.如果页面需要访问会话变量但不允许修改它们,可以设置页面会话状态为只读:
代码如下: <%@ Page EnableSessionState="false" %>
使用时可以根据具体情况选择某种方式
(二).使用Page.IsPostBack
Page.IsPostBack表示是否是从客户端返回的. 初次运行时,不是从客户端返回,它的值为false,当触发页面上的事件或刷新页面时,Page.IsPostBack由于是回发的,值变为true;
一般在: Page_Load方法中用:
private void Page_Load(Object sender,EventArgs e)
{
if(!Page.IsPostBack)
{
....; //初始化页面的代码。这些代码第一次页面初始化时执行,当第二次回发时,
//不会再执行。提高效率。
}
}
往往很多时候不得不用IsPostBack, 因为有些控件初始化后,要保持它的状态.
例如: DropDownList,如果每次都初始化,则用户无论选择其选项,都会被初始化为默认值.
(三).避免使用服务器控件
1.一般的静态显示信息,尽量不要用服务端控件显示. 因为服务端控件需要回发服务端执行,会降低程序执行效率,一般用<DIV>显示即可. 如果用了服务端控件,将: runat="server"去掉,也会提高效率.
2.禁用服务端控件的状态视图,有些控件不需要维护其状态,可以设置其属性:
EnableViewState=false;
如果整个页面控件都不需要维持状态视图,则可以设置整个页面的状态视力为false:
代码如下: <%@ Page EnableViewState="false"%>
3.在Web.Config文件中配置:
ASP.NET Sessionss可以在Web.config或Machine.config中的Sessionsstate元素中配置。 下面是在 Web.config中的设置的例子:
<Sessionsstate timeout="10" cookieless="false" mode="Inproc" />
(四).避免使用DataGrid
大家都知道DataGrid功能强大。 但是功能强大的同时,增加了性能上的开销。 一般用其它控件: DataList或Repeater控件能实现的,尽量不用DataGrid.
(五).字符串操作
1.避免装箱操作. 装箱操作运行效率比较低.
例如运行两个代码段:
string test="";
for(for int i=0;i<10000;i )
{
test = test i;
}
和
string test="";
for(for int i=0;i<10000;i )
{
test = test i.ToString();
}
下面的代码段显然效率要高.因为i是整型的,系统要先把i进行装箱转换为string型的,再进行连接. 需要时间,读者可以Copy到自己机器上测试一下.
2.使用StringBulider类
在进行字符串连接时: string str = str1 str2 ....;
一般超过三项连接,最好用StringBuilder来代替String类. StringBuilder可以避免重新创建String 对象造成的性能损失.
一般用于组装Sql语句时用到: StringBulider.读者可以到自己机器上测试一下.
(六).ADO.Net使用方面优化
1.数据库连接打开和关闭。 在需要连接时打开,当访问完数据库要立刻关闭连接.
举例说明,还是看两个代码段:
I.
DataSet ds = new DataSet();
SqlConnection MyConnection = new SqlConnection("server=localhost; uid=sa; pwd=; database=NorthWind");
SqlCommand myCommand = new SqlCommand(strSql,MyConnection);
SqlDataAdapter myAdapter=new SqlDataAdapter(queryStr,connectionStr);
MyConnection.Open(); //打开连接
for(int i=0;i<1000;i ) //for循环模拟取得数据前的商业逻辑操作
{
Thread.Sleep(1000);
}
myAdapter.Fill(ds);
for(int i=0;i<1000;i ) //for循环模拟取得数据后的商业逻辑操作
{
Thread.Sleep(1000);
}
MyConnection.Close(); //关闭连接
II.
DataSet ds = new DataSet();
SqlConnection MyConnection = new SqlConnection("server=localhost; uid=sa; pwd=; database=NorthWind");
SqlCommand myCommand = new SqlCommand(strSql,MyConnection);
SqlDataAdapter myAdapter=new SqlDataAdapter(queryStr,connectionStr);
for(int i=0;i<1000;i ) //for循环模拟取得数据前的商业逻辑操作
{
Thread.Sleep(1000);
}
MyConnection.Open(); //打开连接
myAdapter.Fill(ds);
MyConnection.Close(); //关闭连接
for(int i=0;i<1000;i ) for循环模拟取得数据后的商业逻辑操作
{
Thread.Sleep(1000);
}
显示II代码比I代码好的多,I中早早占着连接不放,如果用户很多的话,容易出现连接池满情况。严重时出现死机现象.
2.数据库查询
I. 直接生成SQL语句。 Sql Server每次都要对其进行编译,在性能方面不会有很大的提高。 另外也不够安全。容易被攻击.
II. 使用带参数的SQL命令。这种方式Sql Server只对其编译一次,对于不同的参数可以重复使用编译后的命令。提高了性能.
III.使用Sql Server存储过程. 编译一次. 具有独立性,便于修改和维护. 一次能完成用语句发送多次的功能.减少了网络的流量。 并不一定存储过程一定比语句效率要高,如果商业逻辑很复杂的话,有时候用语句比存储过程效率要高.
(六).缓存优化
缓存分为两种: 页面缓存和API缓存.
1.使用页面缓存和片段缓存
<%@ OutputCache Duration="5" VaryByParam="None"%>
<%@ OutputCache Duration=60 VaryByParam=”TextBox1,TextBox2” %>
说明: Duration是设置Cache的过期时间;
VarByParam是设置是否根据参数而变化,None时所有参数使用同一Cache,设置TextBox1时则根据TextBox1的不同值分别缓存;当有多个参数时则要组合缓存;
2.API缓存。用于在应用程序中使用
I. 一个Cache使用的例子:
http://blog.csdn.net/chengking/archive/2005/10/03/494545.aspx
II.使用时注意Page.Cache和HttpContext.Current.Cache区别:
它们指的同一个对象,在Page里,用Page.Cache,如果在global.asax或自己的类里用:HttpContext.Current.Cache,在有些事件中,由于其没有HttpContext,就用HttpRuntime.Cache.
不对的地方请读者批评指正!
本文以Petshop为示例,详细介绍了如何利用Visutal Studio .Net中的数据库模版将该项目的数据库设计放到项目的解决方案中,并借助该方法将整个Petshop项目的数据库从SQL SERVER快速移植到MSDE上。
注:本文并没有就Petshop本身的技术进行任何的讨论,仅仅只是利用它的数据库做为一个普通示范。Petshop网上宠物商店范例的源码下载地址为:http://www.gotdotnet.com/team/compare。
引言:
SQL SERVER方便强大的查询,管理工具让它成为了我们开发.Net数据库应用的上上之选,甚至即使你并非开发基于SQL SERVER的应用,你也会喜欢利用它独有强大的查询分析器来帮助你完成项目。
但是使用SQL SERVER开发项目应用有一个不方便的地方,就是当需要将做了一半的项目转移到别的机器上的时候,假如你并不是很熟悉SQL SERVER你将会发现你几乎束手无策,因为SQL SERVER并不像ACCESS那样将整个数据库放在一个文件中,可以方便的放到你的项目文件夹中打包带走。你也许会说可以利用SQL SERVER企业管理器生成创建脚本,然后进行数据导出备份等等,虽然可行,但是总归是不太方便。
利用数据库模版项目,便能将你的数据库表创建脚本,数据文件以及存储过程同你的项目放在一起,这样,转移到任何地方都能很容易重新构建好调试程序所需要的数据库环境,并能在你的Visual Studio .Net集成调试环境中直接对脚本和存储过程进行修改。
下面将详细介绍如何为Petshop添加数据库模版项目,并将Petshop数据库的创建脚本,数据文件和存储过程导出到模版中同项目一起打包,然后导出到MSDE桌面数据引擎中。
创建数据库模版项目:
首先,我们需要先有一个安装好的Petshop项目(具体的安装事项和步骤请参考Petshop的说明文档),然后在Visual Studio .Net中打开它的项目文件,在"文件"菜单中选择"新建项目",在对话框中选择"其他项目"的"数据库项目",并在右侧的模版列表中选择"数据库项目",并选择"添入解决方案",名称和路径如下图所示:
点击确定后,出现数据连接对话框,选择Petshop数据库:
确定后,在解决方案中我们就可以看见为Petshop新创建的数据库模版项目,如下图所示:
生成创建脚本和存储过程:
选择左边的服务器资源管理器,右键点击Petshop的数据连接,选择"生成创建脚本",如下图所示:
在接下来的对话框中选择全部表和全部的存储过程:
保存路径选择数据库模版项目中的"Create Scripts":
如果没有什么意外情况发生的话,你的Create Scripts下已经生成了许多的SQL脚本文件。
然后在数据连接中按住Shift键一次选择所有的表,右键点击选择"导出数据":
数据同样放在"Create Scripts"下。
生成命令文件:
在项目管理器中选上数据库模版项目,然后在主菜单的"项目"中选择"生成命令文件",如下图所示:
如果需要导出数据则可以选择右下脚的"添加数据"按纽来选择那些表需要导出数据。点击确定后便可以看见在项目中生成了一个名为PetshopDB.cmd的命令文件。
到这里,我们就创建了一个完整的Petshop数据库模版项目,并能在Visual Studio .Net中方便的对脚本进行编辑修改。
接下来,我们将要把数据导入MSDE桌面数据库,然后将Petshop的数据库改为MSDE,模拟数据库移植过程。
将数据库移植到MSDE:
首先,使用SQL SERVER的查询分析器或者企业管理器在MSDE中创建一个名为petshop的数据库,在刚才生成的PetshopDB.cmd上点击右键,选择"运行":
在弹出的对话框中,填入MSDE的服务器及petshop数据库:
点击确定后,我们就可以看到数据库的创建和数据复制过程:
最后,我们打开MSDE中的petshop数据库,就能看见所有的数据表,存储过程和完整的数据。
好了,最后我们来测试一下成果,看看我们的Petshop是不是已经从SQL Server上移植到了MSDE上,我们修改一下Petshop的Web.Config文件中的数据连接字符串,如下图所示:
保存后运行程序,呵呵,我们又看见了那对熟悉的大鹦鹉,虽然这次它们是在MSDE上安的家,不过看上去还是那么可爱。
总结:
利用数据库模版项目,便能将你的数据库设计和数据从数据库独立出来,并能放在你的项目文件中,简化数据库的转移和分发过程,方便快速的移植和部署你的应用项目。
Q:新建项目时出错:Visual Studio .NET 已检测到指定Web服务器运行的不是ASP.NET 1.1版。您将无法运行ASP.NET Web应用程序或服务。
A:
对于这个错误有很多可能的原因,您可以参考下面几篇KB和文章的解决方案:
“prb: "The specified Web server is not running ASP.NET version 1.1" error message when you create an ASP.NET 1.1 application and you have both ASP.NET 1.1 and ASP.NET 1.0 installed”
http://support.microsoft.com/default.aspx?scid=kb;en-us;817267
“troubleshoot "Visual Studio .NET has detected Web server is not running ASP.NET 1.1"”
http://support.microsoft.com/default.aspx?scid=555132
“prb:创建 ASP.NET 1.1 应用程序时出现错误信息“The Specified Web Server Is Not Running ASP.NET Version 1.1”(指定的 Web 服务器未运行 ASP.NET 1.1 版本)”
http://support.microsoft.com/kb/817267/zh-cn
--------------------------------------------------------------------------
Q: 关于无法创建aps.web项目的解决办法
A: http://www.csdn.net/develop/Read_Article.asp?Id=19725
---------------------------------------------------------------------------
Q: aspx项目不能调试
A:
1、已经启动了一个调试进程(同时打开了两个项目,且有一个已经在调试):同时只能启动一个;
2、配置文件中debug="false":改成true;
3、虚拟目录没有建立应用程序(或者名称为空):在虚拟目录属性中“应用程序”点击创建
4、项目配置为Release:点菜单“生成”-配置,选择“debug”
5、“你没有调试服务器权限”:修改IE的安全设置,“自动使用当前用户名和密码登录”
如果还不行,参考ms的解决方法:
对照你的错误信息,应该在这个文档中能找到解决办法 http://www.gotdotnet.com/team/csharp/learn/whitepapers/howtosolvedebuggerproblems.doc
---------------------------------------------
q:把.net程序部署到没有安装.net Framwork的机器上
A: http://www.microsoft.com/China/Community/program/originalarticles/TechDoc/deployNETApp.mspx
-------------------------------------------------
Q:安装VS.NET 2003的时候,遇到 "无法访问windows 安装程序组件"的错误
A:
根据提示,应该windows installer出了问题。
需要重新安装windows installer。
执行如下操作:
一、先用msiexec /unregserver 停掉windows installer服务。
二、下载InstMsiW.exe,用winrar解压开。进入目录。
三、右击msi.inf ,点击安装。
四、安装vs.net 2003
-------------------------------------------------------------
q:vc软件包不可用或未注册
A:
症状:
当建立一个WinForms应用程序时,收到下述错误信息:"VC软件包不可用或未注册".这个错误甚至会在看上去一个成功的安装之后出现.
起因:
如果Visual Studio.NET安装程序发现一些TLB文件和DLL文件已经存在的话,就不会再次在计算机中注册这些文件,因此
就有可能发生"VC软件包不可用或未注册"的提示.
在多数情况下,旧版本的的Visual Studio .NET容易引起这个错误,尤其是旧版本安装在另一个操作系统下.
解决方案:
要解决这个问题,需要运行Visual Studio .NET修复程序.由于修复程序会强制注册一些安装程序中没有被注册的项目,因此能更有效地解决这个问题.
按照下列步骤运行修复程序:
在开始中,指向"设置",点击控制面板,然后点击"添加/删除程序";
在列出的已安装程序列表中,点击Visual Studio .NET,然后点击"更改/删除";
点击Visual Studio .NET安装对话框1 2 3项目中的第二项;
点击"修复/重装",然后按照屏幕提示操作.
更多相关信息:
重现这个问题的做法
要重现这个问题,最好有装在独立分区上的两个操作系统.两个Windows XP Professional会达到这个目的.
在一个操作系统下安装Visual Studio .NET;
在另一个操作系统下Visual Studio .NET,安装过程中改变安装的缺省路径,以匹配第一次安装的路径.
两次安装结果都会宣告成功.
然后启动第二次安装的Visual Studio .NET(你就会发现这个问题的重现).
这种安装形式是永远不推荐的,即使两次安装选项完全相同,而且你也会以此节省磁盘空间,但这种方法出现潜在问题的机会是很大的.如果一个visual Studio .NET改变了文件和注册选项,这些改变不会记录到另一个Visual Studio .NET中,以致造成出现不可预料结果的潜在危险.
引自:http://zhuonline.51.net/blogs/archives/000045.html http://support.microsoft.com/default.aspx?scid=http://support.microsoft.com:80/support/kb/articles/q320/4/27.asp&NoWebContent=1
----------------------------------------------------------------
q:不能启动调试,灾难性错误(在 Visual Studio .NET 中调试 ASP.NET 应用程序时出现的常见错误)
A: http://support.microsoft.com/?id=306172
----------------------------------------------------------------
q:在生成安装和部署项目时收到“unrecoverable Build Error”(不可恢复的生成错误)错误信息
A: http://support.microsoft.com/?id=329214
q:unable to start debugging on the web server
A:如果遇到这些错误,则需要考虑以下几个问题:
要检查的内容
远程服务器上的 Web 应用程序
存储在 Visual SourceSafe 中并使用 FrontPage 服务器扩展的 Web 应用程序
手动附加
要检查的内容
如果得到“无法在 Web 服务器上启动调试”错误,请尝试检查下列内容:
您是否正在运行一个允许 Visual Studio 调试器自动附加到 Web 应用程序的 Windows 版本?如果不是,则需要启动应用程序而不调试,然后手动附加到它。(有关更多信息,请参阅手动附加和 ASP.NET 调试:系统要求。)
您的 Web 应用程序是否具有 Web.config 文件?
Web.config 文件是否通过将 debug 属性设置为 true 而启用了调试模式?有关更多信息,请参阅 ASP.NET 应用程序中的调试模式。
Web.config 是否包含任何语法错误?您可以通过运行 Web 应用程序而不调试来检查是否存在语法错误。(从“调试”菜单中,选择“开始执行(不调试)”。)如果在 Web.config 中存在语法错误,则会显示详细信息。
您是否是“调试器用户”(Debugger Users) 组的成员?如果您作为管理员登录,则管理员是否在该组中?
您是否是通过指定特定的 IP 地址(如 100.20.300.400)而创建了项目?调试 Web 服务器要求 NTLM 身份验证。默认情况下,IP 地址被假定为 Internet 的一部分,而在 Internet 上不进行 NTLM 身份验证。若要更正这一问题:
创建项目时,指定 Intranet 上计算机的名称。
-或-
将 IP 地址 http://100.20.300.400) 添加到您的计算机上的受信任站点列表中。(从 Internet Explorer 的“工具”菜单中,选择“Internet 选项”,然后选择“安全”选项卡)。
运行 IIS 服务器的计算机是否已安装了 Visual Studio .NET 远程组件?
IIS 是否是在安装了 Visual Studio .NET 之后才被安装在本地计算机(即运行 Visual Studio .NET 的计算机)上的?IIS 应在安装 Visual Studio .NET 之前安装。如果它是后来安装的,则可能需要修复 .NET 框架。
修复 .NET 框架
插入 Visual Studio .NET 光盘并运行
<DVD Drive>:/wcu/dotNetFramework/dotnetfx.exe /t:c:/temp /c:"msiexec.exe /fvecms c:/temp/netfx.msi"
-或-
插入 Visual Studio .NET Windows 组件更新光盘并运行
<cd Drive>:/dotNetFramework/ dotnetfx.exe /t:c:/temp /c:"msiexec.exe /fvecms c:/temp/netfx.msi"
是否正确地指定了项目起始页的 URL?扩展名和项目目录是否正确?
是否正确地设置了 IIS 安全设置?若要验证这一点,请检查“默认 Web 站点”设置。
检查“默认 Web 站点”的 IIS 安全设置
从“开始”菜单中,依次选择“程序”和“管理工具”,然后单击“internet 服务管理器”(Windows 2000) 或“Internet 信息服务”(Windows XP)。
在“Internet 服务管理器”或“Internet 信息服务”对话框中,单击您的计算机的树控件 (Tree Control)。在“Web 站点”文件夹中,找到“默认 Web 站点”。
右击“默认 Web 站点”并选择“属性”。
在“默认 Web 站点属性”窗口中,选择“目录安全性”选项卡并单击“编辑”。
在“身份验证方法”对话框中,选择“匿名访问”和“集成的 Windows 身份验证”(如果尚未选择的话)。
单击“确定”以关闭“Internet 服务管理器”或“Internet 信息服务”对话框。
单击“确定”。
对于 ATL Server 应用程序,请验证 DEBUG 谓词是否与您的 ISAPI 扩展相关联。
对于 ASP.NET 应用程序,请确保应用程序的虚拟文件夹具有在“Internet 服务管理器”或“Internet 信息服务”中设置的“应用程序名称”。
为 Web 应用程序指定虚拟文件夹
从“开始”菜单中,依次选择“程序”和“管理工具”,然后单击“internet 服务管理器”(Windows 2000) 或“Internet 信息服务”(Windows XP)。
在“Internet 服务管理器”或“Internet 信息服务”对话框中,单击您的计算机的树控件 (Tree Control)。在“Web 站点”文件夹中,找到此 Web 应用程序。
右击“默认 Web 站点”并选择“属性”。
在“默认 Web 站点属性”窗口中,选择“目录”选项卡。
在“应用程序设置”下,单击“创建”。
应用程序名称即出现在此框中。
单击“确定”关闭“属性”对话框。
单击“确定”以关闭“Internet 服务管理器”或“Internet 信息服务”对话框。
远程服务器上的 Web 应用程序
如果 Web 应用程序位于远程服务器上,请检查以下问题:
是否运行了正确的安装程序以便在服务器上安装 ASP.NET/ATL Server 和远程调试器组件?
您是否是服务器上的“调试器用户”(Debugger Users) 组的成员?您是否具有调试在系统帐户下运行的进程所必需的访问特权?
根据安全设置的不同,ASP.NET 应用程序可能在 inetinfo.exe(IIS 进程)下运行,也可能在 ASP 辅助进程 aspnet_wp.exe 下运行。默认情况下,aspnet_wp.exe 进程作为 SYSTEM 运行。若要调试在 aspnet_wp.exe 下运行的应用程序,您需要具有管理员特权或为 aspnet_wp.exe 编辑 machine.config 文件,以便 aspnet_wp.exe 在用户帐户下运行。若要调试在 inetinfo.exe 下运行的应用程序,您必须是运行 inetinfo.exe 的计算机上的管理员。
根据安全设置的不同,atl Server 应用程序可能在 inetinfo.exe 下运行,也可能在 ATL 辅助进程 dllhost.exe 下运行。若要调试在 inetinfo.exe 下运行的应用程序,您必须是运行 inetinfo.exe 的计算机上的管理员,或者使用公共语言运行库应用程序设置将 dllhost 配置为作为特定用户运行。
您是否正在使用“终端服务器”尝试调试远程计算机上的 Web 应用程序?在 Windows XP 下,支持使用“终端服务器”对本机 Web 应用程序进行远程调试。而在 Windows 2000 或 Windows NT 下则不支持。
存储在 Visual SourceSafe 中并使用 FrontPage 服务器扩展的 Web 应用程序
如果 Web 应用程序存储在 Visual SourceSafe 中并且使用 FrontPage 服务器扩展作为它的 Web 访问模式,请检查以下问题:
visual SourceSafe 是否与 FrontPage 服务器/Web 服务器位于同一台计算机上?如果是,则可以使用“集成身份验证”进行调试。(若要检查“集成身份验证”设置,请参阅此过程以检查前面的“默认 Web 站点”的 IIS 安全设置。)
解决这一问题的另一种方法是将 Web 访问模式从 FrontPage 更改为文件共享 (File Share)。
将 Web 访问模式更改为文件共享 (File Share)
在解决方案资源管理器中,右击项目名称,然后从快捷菜单中选择“属性”。
在“<Project> 属性页”对话框中,打开“通用属性”文件夹,然后选择“Web 设置”。
在“Web 服务器连接”下,单击“Web 访问模式”,然后从列表框中选择“文件共享”。
单击“确定”以关闭“<Project> 属性页”对话框。
手动附加
如果按照这些疑难解答步骤执行了相应操作,而在开始调试时仍然收到错误信息,则可能需要尝试通过手动附加来调试应用程序。
手动附加
启动应用程序而不调试。(从“调试”菜单中,选择“开始执行(不调试)”。)
附加到适当的 IIS 进程或辅助进程。默认情况下,对于 ATL Server 应用程序为 inetinfo.exe;对于 ASP.NET 应用程序为 aspnet_wp.exe。使用下面的过程来确定 ASP.NET 或 ATL Server 应用程序在哪个进程下运行。
检查 ASP.NET 应用程序在哪个进程下运行
使用 Visual Studio .NET 或其他文本编辑器打开应用程序的 machine.config 文件。
找到下面的进程模型属性:
enable
如果 enable 设置为 TRUE,则应用程序在 aspnet_wp.exe 下运行(这也是默认设置。)
如果 enable 设置为 FALSE,则应用程序在 inetinfo.exe 下运行。
检查 ATL Server 应用程序在哪个进程下运行
在解决方案资源管理器中,右击项目名称,然后从快捷菜单中选择“属性”。
在“<Project> 属性页”对话框中,打开“Web 部署”文件夹,然后选择“常规”。
查看“应用程序保护”设置。
如果此设置为“低(IIS 进程)”,则应用程序在 inetinfo.exe 下运行。
如果此设置为“中等(池)”,则应用程序在 dllhost.exe 进程下运行(与其他放入池中的 ATL Server 应用程序相同)。
如果此设置为“高(独立)”,则应用程序在 dllhost.exe 进程下运行(与其他 ATL Server 应用程序不同)。
单击“确定”以关闭“<project> 属性页”对话框。
请参见
调试脚本和 Web:错误和疑难解答
q:web访问失败
此项目的默认web访问模式设置为文件共享,但无法从路径“D:/inetpub/bweb”打开http://localhost/bweb”处的
项目文件夹,返回的错误是:
无法打开Web项目“bweb”。文件路径“D:/inetpub/bweb”与URLhttp://localhost/bweb”不符。这两者需要映
射到不同的服务器位置。http错误404:not Found
a:1。打开iis管理器。
2。右键点击"default Web Site"并选择Properties.
3。 点击"Http Header".
4。 点击"MIME Types".
5。 点击“New”。
6。 在Extension中,输入".tmp". (不需要引号)
7。 在MIME Type中,输入 "Temp". (不需要引号)
Q:VS.NET调试问题
A:关于VisualStudio.NET里调试出现的一系列问题,例如没有权限调试Web服务器,不属于Debugger Users组、无法调试等情况,都可以在The VS7 Debugger doesn’t work. What can I dohttp://blogs.msdn.com/mkpark/articles/86872.aspx)该文上找到答案。 我就碰到过没有权限在Web服务器上调试的情况,后来选中了IE的Internet选项->安全->Intranet->自定义级别->用户验证的“自动使用当前用户和密码登录”才解决无法调试的问题。怎么会想到VS.NET无法调试还要修改IE选项
在.net平台下,部署 Web 解决方案是比较方便的。我们可以利用Visual Studio.NET 2003添加一个WEB安装项目,在部署的“文件系统编辑器”中添加项目的主输出和内容文件,非常简易地完成安装程序的制作。
但是,这样制作的安装程序,只是将web页和asp.net程序编译的dll文件安装到目标机器的iis目录,对于一般的应用程序是可以的(比如用access数据库,可以一起打包到安装程序中);如果数据库是sql SERVER,需要在部署的时候一并安装数据库,安装程序的制作就会复杂一些,需要我们自定义安装程序类。在安装程序类中执行SQL脚本并将连接字符串写入Web.config。
l 安装数据库
微软msdn上介绍过在部署应用程序的时候建立数据库。如:
/attachment/OtherFile/2008412902516777801.asp
这种方法是创建一个安装程序类,在安装程序类中调用ado.net执行sql 语句(SQL语句放在一个文本文件中)来创建数据库。
但是,这种方法有一个问题,如果用sql Server2000生成了所有建表、视图、存储过程的一个脚本文件,用ADO.NET来执行这个脚本文件,就会因为脚本中有许多“GO”语句而出现错误。当然,我们可以把“GO”替换成换行符,利用ADO.NET一条条执行SQL 语句。很显然,这样的效率比较低。
最好的办法是调用osql执行脚本。(或者创建一个数据库项目的cmd文件,而cmd文件建立数据库的时候也是调用的osql)。
首先,我们新建一个asp.net Web应用程序http://localhost/VbNetTest,并打开VbNetTest 项目
创建部署项目
1. 在“文件”菜单上指向“添加项目”,然后选择“新建项目”。
2. 在“添加新项目”对话框中,选择“项目类型”窗格中的“安装和部署项目”,然后选择“模板”窗格中的“Web 安装项目”。在“名称”框中键入 Test Installer。
3. 单击“确定”关闭对话框。
4. 项目被添加到解决方案资源管理器中,并且文件系统编辑器打开。
5. 在“属性”窗口中,选择 ProductName 属性,并键入 GCRM。
将 VbNetTest项目的输出添加到部署项目中
1. 在“文件系统编辑器”中,选择“Web 应用程序”文件夹。在“操作”菜单上,指向“添加”,然后选择“项目输出”。
2. 在“添加项目输出组”对话框中,选择“项目”下拉列表中的“VbNetTest”。
3. 单击“确定”关闭对话框。
4. 从列表中选择“主输出”和“内容文件”组,然后单击“确定”。
创建安装程序类
1. 在“文件”菜单上指向“新建”,然后选择“项目”。
2. 在“新建项目”对话框中,选择“项目类型”窗格中的“Visual Basic 项目”,然后选择“模板”窗格中的“类库”。在“名称”框中键入 DBCustomAction。
3. 单击“打开”关闭对话框。
4. 从“项目”菜单中选择“添加新项”。
5. 在“添加新项”对话框中选择“安装程序类”。在“名称”框中键入 DBCustomAction。
6. 单击“确定”关闭对话框。
创建自定义安装对话框
1. 在解决方案资源管理器中选择“Test Installer”项目。在“视图”菜单上指向“编辑器”,然后选择“用户界面”。
2. 在用户界面编辑器中,选择“安装”下的“启动”节点。在“操作”菜单上,选择“添加对话框”。
3. 在“添加对话框”对话框中,选择“许可协议”对话框,然后单击“确定”关闭对话框。
4. 在“添加对话框”对话框中,选择“文本框 (A)”对话框,然后单击“确定”关闭对话框。
5. 在“操作”菜单上,选择“上移”。重复此步骤,直到“文本框 (A)”对话框位于“安装文件夹”节点之上。
6. 在“属性”窗口中,选择 BannerText 属性并键入:安装数据库.。
7. 选择 BodyText 属性并键入:安装程序将在目标机器上安装数据库。
8. 选择 Edit1Label 属性并键入:数据库名称:。
9. 选择 Edit1Property 属性并键入 CUSTOMTEXTA1。
10. 选择 Edit1Value 属性并键入:GsCrm。
11. 选择 Edit2Label 属性并键入:服务器名:。
12. 选择 Edit2Property 属性并键入 CUSTOMTEXTA2。
13. 选择 Edit2Value 属性并键入:(local)。
14. 选择 Edit3Label 属性并键入:用户名:。
15. 选择 Edit3Value 属性并键入:sa。
16. 选择 Edit3Property 属性并键入 CUSTOMTEXTA3。
17. 选择 Edit4Label 属性并键入:密码:。
18. 选择 Edit4Property 属性并键入 CUSTOMTEXTA4。
19. 选择 Edit2Visible、Edit3Visible 和 Edit4Visible 属性,并将它们设置为 False。
创建自定义操作
1. 在解决方案资源管理器中选择“Test Installer”项目。在“视图”菜单上指向“编辑器”,然后选择“自定义操作”。
2. 在自定义操作编辑器中选择“安装”节点。在“操作”菜单上,选择“添加自定义操作”。
3. 在“选择项目中的项”对话框中,双击“应用程序文件夹”。
4. 选择“主输出来自 DBCustomAction(活动)”项,然后单击“确定”关闭对话框。
5. 在“属性”窗口中,选择 CustomActionData 属性并键入 /dbname=[CUSTOMTEXTA1] /server=[CUSTOMTEXTA2] /user=[CUSTOMTEXTA3] /pwd=[CUSTOMTEXTA4] /targetdir="[TARGETDIR]/"。
附/targetdir="[targetdir]/"是安装后的目标路径,为了在dbcustomaction类中获得安装后的路径,我们设置此参数。
另外,安装后的路径也可以通过Reflection得到:
Dim Asm As System.Reflection.Assembly = _
System.Reflection.Assembly.GetExecutingAssembly
MsgBox("Asm.Location")
添加文件
1. 将SQL Server生成的脚本文件DB.sql添加到“Test Installer”项目
2. 将安装文件LisenceFile.rtf添加到“Test Installer”项目
3. 在用户界面编辑器中,选择许可协议,设置LisenceFile属性为LisenceFile.rtf文件
将代码添加到安装程序类中,dbcustomaction.vb类
Imports System.ComponentModel
imports System.Configuration.Install
imports System.IO
imports System.Reflection
<runinstaller(true)> Public Class DBCustomAction
inherits System.Configuration.Install.Installer
#region "组件设计器生成的代码 "
public Sub New()
mybase.new()
'该调用是组件设计器所必需的
initializecomponent()
'在 InitializeComponent() 调用之后添加任何初始化
end Sub
' Installer 重写 dispose 以清理组件列表。
protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
if disposing Then
if Not (components Is Nothing) Then
components.dispose()
end If
end If
mybase.dispose(disposing)
end Sub
private components As System.ComponentModel.IContainer
<system.diagnostics.debuggerstepthrough()> Private Sub InitializeComponent()
end Sub
#end Region
'执行sql 语句
private Sub ExecuteSql(ByVal conn As String, ByVal DatabaseName As String, ByVal Sql As String)
dim mySqlConnection As New SqlClient.SqlConnection(conn)
dim Command As New SqlClient.SqlCommand(Sql, mySqlConnection)
command.connection.open()
command.connection.changedatabase(databasename)
try
command.executenonquery()
finally
'close Connection
command.connection.close()
end Try
end Sub
public Overrides Sub Install(ByVal stateSaver As System.Collections.IDictionary)
MyBase.Install(stateSaver)
' ------------------------建立数据库----------------------------
try
dim connStr As String = String.Format("data source={0};user id={1};password={2};persist security info=false;packet size=4096", Me.Context.Parameters.Item("server"), Me.Context.Parameters.Item("user"), Me.Context.Parameters.Item("pwd"))
'根据输入的数据库名称建立数据库
executesql(connstr, "master", "CREATE DATABASE " Me.Context.Parameters.Item("dbname"))
'调用osql执行脚本
dim sqlProcess As New System.Diagnostics.Process
sqlprocess.startinfo.filename = "osql.exe "
sqlprocess.startinfo.arguments = String.Format(" -U {0} -P {1} -d {2} -i {3}db.sql", Me.Context.Parameters.Item("user"), Me.Context.Parameters.Item("pwd"), Me.Context.Parameters.Item("dbname"), Me.Context.Parameters.Item("targetdir"))
sqlprocess.startinfo.windowstyle = ProcessWindowStyle.Hidden
sqlprocess.start()
sqlprocess.waitforexit() '等待执行
sqlprocess.close()
'删除脚本文件
dim sqlFileInfo As New System.IO.FileInfo(String.Format("{0}db.sql", Me.Context.Parameters.Item("targetdir")))
if sqlFileInfo.Exists Then
sqlfileinfo.delete()
end If
catch ex As Exception
throw ex
end Try
' -------------------将连接字符串写入Web.config---------------------
try
dim FileInfo As System.IO.FileInfo = New System.IO.FileInfo(Me.Context.Parameters.Item("targetdir") & "/web.config")
if Not FileInfo.Exists Then
throw New InstallException("没有找到配置文件")
end If
'实例化xml文档
dim XmlDocument As New System.Xml.XmlDocument
xmldocument.load(fileinfo.fullname)
'查找到appsettings中的节点
dim Node As System.Xml.XmlNode
dim FoundIt As Boolean = False
for Each Node In XmlDocument.Item("configuration").Item("appSettings")
if Node.Name = "add" Then
if Node.Attributes.GetNamedItem("key").Value = "connString" Then
'写入连接字符串
node.attributes.getnameditem("value").value = String.Format("Persist Security Info=False;Data Source={0};Initial Catalog={1};User ID={2};Password={3};Packet Size=4096;Pooling=true;Max Pool Size=100;Min Pool Size=1", _
me.context.parameters.item("server"), Me.Context.Parameters.Item("dbname"), Me.Context.Parameters.Item("user"), Me.Context.Parameters.Item("pwd"))
foundit = True
end If
end If
next Node
if Not FoundIt Then
throw New InstallException("web.Config 文件没有包含connString连接字符串设置")
end If
xmldocument.save(fileinfo.fullname)
catch ex As Exception
throw ex
end Try
end Sub
end Class
最后编译生成!
安装界面:
此功能适用于后台数据库功能不强的web站点,即大部分文本不是存放在数据库的记录中,而是放在html文件或者xml文件中,仅仅把索引放到数据库中,如文章标题、类别、查询关键字等。这样适合于后台没有诸如MS Sql Server这样的数据库支持的Web站点。
适用于新闻发布系统,比如sina、163等都是采用动态生成html页面的。
适用于需动态定制页面的程序。比如论坛、聊天室等。可以加载定制好的html页面,来加强美观。
思路
1. 利用如Dw-Mx这样的工具生成html格式的模板,在需要添加格式的地方加入特殊标记(如$htmlformat$),动态生成文件时利用代码读取此模板,然后获得前台输入的内容,添加到此模板的标记位置中,生成新文件名后写入磁盘,写入后再向数据库中写入相关数据。
2. 使用后台代码硬编码Html文件,可以使用HtmlTextWriter类来写html文件。
优点
1. 可以建立非常复杂的页面,利用包含js文件的方法,在js文件内加入document.write()方法可以在所有页面内加入如页面头,广告等内容。
2. 静态html文件利用MS Windows2000的Index Server可以建立全文搜索引擎,利用asp.net可以以DataTable的方式得到搜索结果。而Win2000的Index服务无法查找xml文件的内容。如果包括了数据库搜索与Index索引双重查找,那么此搜索功能将非常强大。
3. 节省服务器的负荷,请求一个静态的html文件比一个aspx文件服务器资源节省许多。
缺点
思路二: 如果用硬编码的方式,工作量非常大,需要非常多的html代码。调试困难。而且使用硬编码生成的html样式无法修改,如果网站更换样式,那么必须得重新编码,给后期带来巨大的工作量。
因此这里采用的是第一种思路
示列代码
1.定义(template.htm)html模板页面
<head>
<title></title>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
</head>
<body >
<table $htmlformat[0] height="100%" border="0" width="100%" cellpadding="10" cellspacing="0" bgcolor="#eeeeee" style="border:1px solid #000000">
<tr>
<td width="100%" valign="middle" align="left">
<span style="color: $htmlformat[1];font-size: $htmlformat[2]">$htmlformat[3]</span>
</td>
</tr>
</table>
</body>
</html>
string[] format=new string[4];//定义和htmlyem标记数目一致的数组
StringBuilder htmltext=new StringBuilder();
try
{
using (StreamReader sr = new StreamReader("存放模板页面的路径和页面名"))
{
String line;
while ((line = sr.ReadLine()) != null)
{
htmltext.Append(line);
}
sr.Close();
}
}
catch
{
Response.Write("<Script>alert('读取文件错误')</Script>");
}
//---------------------给标记数组赋值------------
format[0]="background=/"bg.jpg/"";//背景图片
format[1]= "#990099";//字体颜色
format[2]="150px";//字体大小
format[3]= "<marquee>生成的模板html页面</marquee>";//文字说明
//----------替换htm里的标记为你想加的内容
for(int i=0;i<4;i )
{
htmltext.Replace("$htmlformat[" i "]",format[i]);
}
//----------生成htm文件------------------――
try
{
using(StreamWriter sw=new StreamWriter("存放路径和页面名",false,System.Text.Encoding.GetEncoding("GB2312")))
{
sw.WriteLine(htmltext);
sw.Flush();
sw.Close();
}
}
catch
{
Response.Write ("The file could not be wirte:");
}
小结
用此方法可以方便的生成html文件。程序使用了是循环替换,因此对需替换大量元素的模板速度非常快。
请您检查作为 Microsoft ASP.NET 应用程序运行的示例(带有源代码)。或者仅在新窗口中查看源代码。请注意,程序员的注释在示例程序中是英文的,而在本文中被翻译成中文,以便更好地解释该程序。另外,使用了此新功能后(在此感谢 MSDN Web Publishing Team!),您可以将两个窗口都放在屏幕上,这样便可以方便地查看相应代码。
简介
在本文,我们将通过一个灵活的绘图应用程序提供一个有关继承、abstract (MustInherit) 基类和接口的更为完整的示例。这不是一个控制台应用程序;由于其图形化的特征,更适合作为一个 Microsoft Windows 窗体应用程序。(这就给了我们一个了解 Windows 窗体的机会。)
该 ASP.NET 版本将演示如何在 Web 页上使用自定义绘制的位图 -- 这在大多数 Web 编程系统中是非常难以实现的,但使用 ASP.NET 则很简单。Dr. GUI 相信您会喜欢这一点。而且您还可以运行该应用程序。
经典的多态示例
在教授编程时,有一些常用的、非常标准的示例程序。而我最初曾发誓不使用这些示例:我不会使用一个字符串类作为示例,也不会使用复杂的数字或绘图应用程序。毕竟,这样做就不是原创了。
然而随着事情的发展,使用这些示例显得很有必要(不仅仅是因为懒惰):这些示例非常丰富,易于解释和理解,并且可以非常清晰地揭示核心概念。
以下是该程序 Windows 窗体版本的屏幕快照:
图 1:经典多态示例的 Windows 窗体版本
![](http://writeblog.csdn.net/attachment/Image/200841290257377801.gif)
以下是 ASP.NET 版本在浏览器中的显示:
图 2:经典多态示例的 ASP.NET 版本
![](http://writeblog.csdn.net/attachment/Image/2008412902512077802.gif)
您可以运行上面显示的 ASP.NET 版本。
我们的任务
这个程序的基本思想如下:我们有一个 abstract 基类(在 Microsoft Visual Basic? 中是 MustInherit),其中包含公共数据(如边框)和一套虚拟方法,虚拟方法多数是抽象的(在 Visual Basic 中是 MustOverride),例如 Draw。请注意,Draw 的多态性很重要,因为每个可绘制对象类型(如点、线、矩形、圆等)都是用完全不同的代码绘制的。
虽然方法可以是多态的,但数据不能。因此,我们只将确实应用于所有可能的可绘制对象的数据放在程序中 -- 在本例中,放置了一个边框和颜色(在其中绘制对象的线)。
与特定类型的可绘制对象相关的数据(例如圆的中心和半径、矩形相对点的坐标,或者一条线的端点)都应该在与该类型的可绘制对象对应的特定类(从抽象基类中派生)中声明。请注意,可以使用二次派生合并相似的对象。例如,可以从椭圆中派生出圆,因为所有的圆都是椭圆。与此类似,也可以从矩形中派生出方形,因为所有的方形都是矩形(也都是四边形、多边形)。所选择的派生树会反映类之间的关系,以及常用的预期使用模式,这样您经常执行的操作便会非常快速、方便。
因为构造函数(在 Visual Basic 中为 New)存在的主要原因是用于初始化数据,因此构造函数不是(实际上也不能是)多态的。这意味着初始创建操作不能是多态的,因为数据要求随类型的不同而不同。但是,一个好的设计在对象创建后,可在之后的使用中将对象作为多态处理,这里我们就是这样做的。
让我们看看这个类集中包含什么,从根抽象基类开始:
抽象 (MustInherit) 基类
以下是 C# 中抽象基类的代码。单击此处在新窗口中查看全部源文件。
C#
public abstract class DShape {
public abstract void Draw(Graphics g);
protected Rectangle bounding;
protected Color penColor; // 还应具有属性
// 还应具有移动、调整大小等方法。
}
以下是等同的 Visual Basic .NET 代码。单击此处在新窗口中查看全部源文件。
Visual Basic .NET
Public MustInherit Class DShape
Public MustOverride Sub Draw(ByVal g As Graphics)
Protected bounding As Rectangle
Protected penColor As Color ' 还应具有属性
' 还应具有移动、调整大小等方法。
End Class
语法虽然不同,但很明显这是相同的类。
请注意,Draw 方法被暗示为 virtual (Overridable),因为它被声明为 abstract (MustOverride)。还要注意在这个类中我们并没有提供一个实现。因为我们尚不知道在这个类中执行的对象,因此不可能写出绘图代码。
包含哪些数据?
另请注意,这里并没有很多数据 -- 但我们已经为这样一个抽象类声明了所有数据。
每一个可绘制对象(无论其形状如何)都有一个边框 -- 即可以完全包含该对象的最小可能矩形。边框用于绘制点(作为很小的矩形)、长方形和圆 -- 并且对于其他形状,可以作为第一个用于点击或碰撞测试的快速估计。
适用于所有对象的其他共同点并没有很多;中心对于某些对象有用,例如圆和长方形,对于其他对象(如三角形)则没有意义。并且通常都是使用角来表示矩形,而不是使用中心。但您不能使用角来指定圆,因为圆没有角。Dr. GUI 确信您已经看到了为一个普通可绘制对象指定其他数据的困难之处。
每个可绘制对象还有一个与绘制它的线相关联的颜色,这里我们也做了声明。
某些派生类
如上所述,我们不能真正创建一个抽象基类类型的对象,虽然我们可以将从抽象基类(或任何基类)中派生的任何对象作为基类对象处理。
所以,为创建一个绘图对象,我们必须从抽象基类中派生一个新类 -- 并确保覆盖所有 abstract/MustOverride 方法。
在本例中我们将使用 DHollowCircle 类。DHollowRectangle 类和 DPoint 类非常相似。
以下是 C# 中的 DHollowCircle。单击此处在新窗口中查看其他类。
C#
public class DHollowCircle : DShape
{
public DHollowCircle(Point p, int radius, Color penColor) {
p.Offset(-radius, -radius); // 需要转换到左上角
int diameter = radius * 2;
bounding = new Rectangle(p, new Size(diameter, diameter));
this.penColor = penColor;
}
public override void Draw(Graphics g) {
using (Pen p = new Pen(penColor)) {
g.DrawEllipse(p, bounding);
}
}
}
以下是等同的 Visual Basic .NET 类。单击此处在新窗口中查看其他类。
Visual Basic .NET
Public Class DHollowCircle
Inherits DShape
Public Sub New(ByVal p As Point, ByVal radius As Integer, _
ByVal penColor As Color)
p.Offset(-radius, -radius) ' 需要转换到左上角
Dim diameter As Integer = radius * 2
bounding = New Rectangle(p, New Size(diameter, diameter))
Me.penColor = penColor
End Sub
Public Overrides Sub Draw(ByVal g As Graphics)
Dim p = New Pen(penColor)
Try
g.DrawEllipse(p, bounding)
Finally
p.Dispose()
End Try
End Sub
End Class
请注意,我们没有为这个类声明其他数据 -- 它给出的边框和笔已经足够了。(对于点和矩形是这样,但对于三角形和其他多边形就不够了。)我们的应用程序不需要在设置圆后知道圆的中心或半径,因此将它们忽略掉。(如果需要中心和半径,我们可以存储这些数据,或者根据边框计算得出。)
但我们确实需要边框,因为它是用于绘制圆的 Graphics.DrawEllipse 方法的一个参数。因此我们根据在构造函数中传递的中心点和半径计算边框。
下面我们深入了解每一个方法。
构造函数
构造函数传递三个参数:包含圆的中心坐标的点、圆的半径以及一个 System.Drawing.Color 结构(包含用于绘制圆轮廓的颜色)。
然后我们根据中心和半径计算边框,并将笔颜色项设置为我们传递的颜色对象。
绘图代码
Draw 方法重载实际上非常简单:它根据我们保存在构造函数中的颜色对象创建一个笔对象,然后使用该笔,调用 Graphics.DrawEllipse 方法绘制圆,同时传递了我们早先创建的边框。
图形、笔和画笔
这里我们需要解释一下 Graphics、Pen 和 Brush 对象。(在开始填充我们的可填充对象时,就会看到 Brush 对象。)
Graphics 对象代表一个与某个真实绘图空间相关联的虚拟化的绘图空间。虚拟化是指,通过在 Graphics 对象上绘图,我们可以使用相同的 Graphics 方法在与该对象相关联的任何类型的实际表面上绘图。对于那些习惯于使用 MFC 或 Microsoft Windows? SDK 编程的用户,Graphics 对象相当于 Windows 中称为“设备上下文”(或 DC)的 .NET 版本。
在此 Windows 窗体应用程序中,传递到 Draw 方法的 Graphics 对象将与屏幕上的一个窗口相关联 -- 这里是 PictureBox。在我们的 Microsoft ASP.NET 应用程序中使用这一代码时,传递给 Draw 方法的 Graphics 对象将与一个位图图像相关联。它也可以和打印机或其他设备相关联。
这个方案的优点是我们可以使用相同的绘图代码在不同的表面上绘图。在我们的绘图代码中,我们不需要知道任何有关屏幕、位图、打印机等等之间的不同 -- .NET Framework(以及底层的操作系统)可以为我们处理所有细节。
利用相同的标记,笔和画笔成为虚拟化的绘图工具。笔代表线条属性 -- 颜色、宽度、样式,甚至可以是用来绘制线的位图。画笔代表一个填充区域的属性 -- 颜色、样式,甚至可以是用来填充区域的位图。
在使用 Using 后清除(或至少 Dispose)
Graphics、Pen 和 Brush 对象都与相似类型的 Windows 对象相关联。这些 Windows 对象分配在操作系统的内存中 -- 这些内存尚未被 .NET 运行时管理。长时间将这些对象驻留在内存中会导致性能问题,并且在 Microsoft Windows 98 下,当图形堆填满时会导致绘图问题。因此,我们应尽快释放这些 Windows 对象。
当相应的 .NET Framework 对象完成操作并回收内存后,.NET 运行时会自动释放 Windows 对象。但回收内存的时间会很长 -- 如果我们不迅速释放这些对象,所有不幸的事情(包括填满 Windows 堆)都可能发生。在该应用程序的 ASP.NET 版本中,由于很多用户在同一台服务器上访问该应用程序,所以这种现象会更加严重。
因为这些对象与未管理的资源相关联,所以它们实现 IDisposable 接口。该接口有一个方法,即 Dispose,它将 .NET Framework 对象从 Windows 对象中分离出来,并释放 Windows 对象,从而使计算机处于良好的状态。
这时您只需完成一项任务:确保在使用完该对象后,调用 Dispose 方法。
Visual Basic .NET 代码中显示了这一内容的经典形式:首先创建对象,然后在一个 Try 块中使用该对象,最后在 Finally 块中清理该对象。Try/Finally 能够确保即使出现异常也会清理对象。(在本例中,我们调用的绘图方法可能不会引发异常,所以可能并不需要 Try/Finally 块。但掌握这些技巧很有用,因此 Dr. GUI 也希望向您演示这一正确方法。)
这种形式很常见,因此 C# 为其提供了一个私有语句:using。C# 代码中的 using 语句等同于 Visual Basic .NET 代码中的声明和 Try/Finally -- 但更为简洁、方便,并减少了发生错误的可能性。(Dr. GUI 不清楚为什么 Visual Basic .NET 不包含一些诸如 using 的语句。)
接口
有些(但不是全部)可绘制对象可以被填充。某些对象(如点和线)不能被填充,因为它们不是封闭的区域,而矩形和圆等对象可以是中空的,或者被填充。
另一方面,就象将所有可绘制对象作为多态处理一样,我们也可以将所有可填充对象作为多态处理,这会很方便。例如,如同我们将所有可绘制对象放到一个集合中,通过遍历集合并在每个对象上调用 Draw 来绘制对象一样,我们可以将所有可填充对象放到一个集合中,而不考虑这些可填充对象的实际类型。因此,我们使用某种机制(如继承)来获得真正的多态。
因为不是所有的可绘制对象都可以被填充,因此不能将 Fill 方法的声明放在抽象基类中。.NET Framework 不允许类的多重继承,所以也不能将其放在另一个抽象基类中。并且如果我们从不是非可填充类的其他基类中派生出可填充对象,则不能将所有可绘制对象作为多态处理。
但 .NET Framework 支持接口 -- 并提供了一个可实现任意数量的接口的类。接口不具有任何实现 -- 没有代码,也没有任何数据。因此,实现接口的类必须提供所有内容。
接口所能包含的只有声明。以下是我们在 C# 中的接口 IFillable。单击此处在新窗口中查看全部源文件。
C#
public interface IFillable {
void Fill(Graphics g);
Color FillBrushColor { get; set; }
}
以下是等同的 Visual Basic .NET 代码。单击此处在新窗口中查看全部源文件。
Visual Basic .NET
Public Interface IFillable
Sub Fill(ByVal g As Graphics)
Property FillBrushColor() As Color
End Interface
我们不需要声明方法或者 virtual/Overridable 或 abstract/MustOverride 属性以及任何其他项,因为接口中的所有方法和属性都自动设置为公开的和 abstract/MustOverride。
使用一个属性:不能在接口中包含数据
请注意,虽然我们不能在接口中声明字段,但可以声明一个属性,因为属性实际上是作为方法实现的。
但这样做会给接口的实现者带来负担,下面就会看到。实现者必须实现 get 和 set 方法,以及实现该属性所必需的任何数据。如果实现非常复杂,则可以编写一个 helper 类以封装某些部分。在本文稍后我们将就一个略微不同的上下文环境显示如何使用 helper 类。
实现接口
我们已经定义了接口,现在可以在类中实现它了。请注意,我们必须提供所实现接口的完整实现:不能只从中选取一部分。
下面我们看看 C# 中 DFilledRectangle 类的代码。
C#
public class DFilledCircle : DHollowCircle, IFillable
{
public DFilledCircle(Point center, int radius, Color penColor,
Color brushColor) : base(center, radius, penColor) {
this.brushColor = brushColor;
}
public void Fill(Graphics g) {
using (Brush b = new SolidBrush(brushColor)) {
g.FillEllipse(b, bounding);
}
}
protected Color brushColor;
public Color FillBrushColor {
get {
return brushColor;
}
set {
brushColor = value;
}
}
public override void Draw(Graphics g) {
Fill(g);
base.Draw(g);
}
}
以下是 Visual Basic .NET 中 DFilledRectangle 类的代码。
Visual Basic .NET
Public Class DFilledRectangle
Inherits DHollowRectangle
Implements IFillable
Public Sub New(ByVal rect As Rectangle, _
ByVal penColor As Color, ByVal brushColor As Color)
MyBase.New(rect, penColor)
Me.brushColor = brushColor
End Sub
Public Sub Fill(ByVal g As Graphics) Implements IFillable.Fill
Dim b = New SolidBrush(FillBrushColor)
Try
g.FillRectangle(b, bounding)
Finally
b.dispose()
End Try
End Sub
Protected brushColor As Color
Public Property FillBrushColor() As Color _
Implements IFillable.FillBrushColor
Get
Return brushColor
End Get
Set(ByVal Value As Color)
brushColor = Value
End Set
End Property
Public Overrides Sub Draw(ByVal g As Graphics)
Dim p = New Pen(penColor)
Try
Fill(g)
MyBase.Draw(g)
Finally
p.Dispose()
End Try
End Sub
End Class
以下是有关这些类的注意事项。
从 HollowRectangle 中派生
我们从这个类的空心版本中派生出填充类。这个类中的多数内容都发生了改变:Draw 方法和构造函数都是新的(但两者都调用基类的版本),并且为 IFillable 接口的 Fill 方法以及 FillBrushColor 属性添加了实现。
需要新构造函数的原因是我们在这个类中包含了需要初始化的其他数据,即填充画笔。(您可以回顾我们前面讨论的画笔。)请注意此构造函数是如何调用基类构造函数的:在 C# 中,该调用被内置到声明 (: base(center, radius, penColor)) 中;在 Visual Basic .NET 中,我们将它明确放在 New 方法(即构造函数)的第一行 (MyBase.New(rect, penColor))。
因为我们已经向基类构造函数中传递了三个参数中的两个,现在只需初始化最后的字段即可。
绘图如何改变
您会注意到,Draw 方法与基类基本相同 -- 主要差别在于它调用了 Fill 方法,因为要完成绘制一个填充对象,所以需要对其进行填充。我们没有为绘制轮廓重写代码,而是再次调用了基类的方法:Visual Basic .NET 中的 MyBase.Draw(g) 或 C# 中的 base.Draw(g);。
因为我们正在指派用于绘制轮廓的笔,因此需要使用 using 或 Try/Finally 和 Dispose 以确保迅速释放 Windows 笔对象。(同样,如果非常确信所调用的方法不会引发异常,可以在完成笔的处理后,跳过异常处理,而只调用 Dispose。但我们必须调用 Dispose,无论是直接调用,还是通过 using 语句。
实现 Fill 方法
Fill 方法很简单:指派一个画笔,然后在屏幕上填充对象 -- 并确保 Dispose 画笔。
请注意,在 Visual Basic .NET 中,您必须明确指定实现一个接口的方法 (... Implements IFillable.Fill);而在 C# 中,实现接口中的方法或属性由方法或属性的签名确定(因为您编写了一个称为 Fill 的方法,该方法不返回任何内容并接受一个 Graphics,因此它必须是 IFillable.Fill 的实现)。非常奇怪,Dr. GUI 通常喜欢简洁的编程结构(如果不可能通过简单的编写完成),但实际上却倾向使用 Visual Basic 的语法,因为这种语法既清晰又灵活(Visual Basic 实现类中的方法名称不必与接口中的名称匹配,并且一个给定方法通常能够实现多个接口方法)。
实现属性
IFillable 接口还包含一个属性,从中可以 set 和 get 画笔颜色。(我们在 Change fills to hot pink [将填充色更改为粉红] 按钮处理程序中使用该属性。)
为实现公开属性,我们需要一个私有或保护的字段。这里我们选择了保护字段,以便能够方便地从派生类(而不允许任何类)对其进行访问。
具有该字段后,我们可以轻松地编写一个很简单的 set 和 get 方法对以实现属性。
请再次注意,在 Visual Basic .NET 中,必须明确指定所实现的属性。
接口还是抽象 (MustInherit) 基类?
在面向对象的编程中,最常见的争论之一就是,是使用抽象基类还是使用接口。
接口可以提供一些额外的灵活性,但也要付出一定代价:对于实现该接口的每一个类,必须实现其中的所有内容。我们可以使用一个 helper 类来协助这项工作(稍后会提供一个相关示例),但您仍然必须在所有地方实现所有内容。并且接口不能包含数据(虽然如此,与在 Brand J 的系统中不同,它们可以包含属性,因此它们可以看起来好象包含了数据)。
在本例中,Dr. GUI 为 DShape 选择了使用一个抽象基类而不是一个接口,因为他不想在每个类中将数据作为属性重复实现。此外,还因为从 DShape 派生出的所有内容都是形状,由于可填充对象仍然是形状,因而也可以进行填充。
您的选择可能有所不同,但 Dr. GUI 认为他在此做出的选择非常正确。
绘图对象的容器
因为要重复绘制我们的对象(在 Windows 窗体版本中,每次都将绘制图像;在 ASP.NET 版本中,每次都将重新加载 Web 页),因此需要将它们放在一个容器中,以便能够反复访问它们。
Dr. GUI 更进一步,将容器变得智能化,使其知道如何绘制所包含的对象。以下是这个容器类的 C# 代码:
C#
public class DShapeList {
ArrayList wholeList = new ArrayList();
ArrayList filledList = new ArrayList();
public void Add(DShape d) {
wholeList.Add(d);
if (d is IFillable)
filledList.Add(d);
}
public void DrawList(Graphics g) {
if (wholeList.Count == 0)
{
Font f = new Font("Arial", 10);
g.DrawString("没有任何要绘制的内容;列表为空...",
f, Brushes.Gray, 50, 50);
}
else
{
foreach (DShape d in wholeList)
d.Draw(g);
}
}
public IFillable[] GetFilledList() {
return (IFillable[])filledList.ToArray(typeof(IFillable));
}
}
以下为等同类的 Visual Basic .NET 代码:
Visual Basic
.NET Public Class DShapeList
Dim wholeList As New ArrayList()
Dim filledList As New ArrayList()
Public Sub Add(ByVal d As DShape)
wholeList.Add(d)
If TypeOf d Is IFillable Then filledList.Add(d)
End Sub
Public Sub DrawList(ByVal g As Graphics)
If wholeList.Count = 0 Then
Dim f As New Font("Arial", 10)
g.DrawString("没有任何要绘制的内容;列表为空...", _
f, Brushes.Gray, 50, 50)
Else
Dim d As DShape
For Each d In wholeList
d.Draw(g)
Next
End If
End Sub
Public Function GetFilledList() As IFillable()
Return filledList.ToArray(GetType(IFillable))
End Function
End Class
维护两个列表
因为我们要改变对象的填充颜色以实现 Change fill to hot pink 按钮,因此维护了两个可绘制对象列表:一个列表是全部对象,另一个列表是可填充对象。我们为这两个列表都使用了 ArrayList 类。ArrayList 对象包含一组 Object 引用 -- 这样一个 ArrayList 可以包含系统中任何类型的混合。
这实际上并没有什么帮助 -- 我们希望 ArrayList 仅仅包括可绘制/可填充对象。为此,我们将 ArrayList 对象设为私有;然后将向列表添加对象的过程设为一个方法,该方法只接受一个 DShape。
当使用 Add 方法向列表中添加对象时,我们将所有对象添加到 wholeList 中,然后检查对象是否还应添加到 filledList 集合中。
请记住,Add 方法(以及列表)具有类型安全特性:它只接受 DShape(或者从 DShape 派生的类型,例如我们在上面创建的所有类型)。您不能将整数或字符串添加到列表中,这样我们便可以知道这个列表只包含可绘制对象。能够确知这一点是很方便的!
绘制项
我们还有一个 DrawList 方法,用于在它作为参数传递的 Graphics 对象上绘制列表中的对象。此方法具有两种情况:如果列表为空,它绘制一个字符串,说明列表为空。如果列表不为空,它使用一个 for each 构造函数遍历该列表,并在每个对象上调用 Draw。实际的遍历和绘图代码再简单不过了,如下面的 Visual Basic 所示。
Visual Basic
.NET Dim d As DShape
For Each d In wholeList
d.Draw(g)
Next
C# 代码几乎完全相同(当然,其行数更少)。
C#
foreach (DShape d in wholeList)
d.Draw(g);
由于列表是封装的,我们知道它具有类型安全特性,因此可以仅调用 Draw 方法而不必检查对象的类型。
返回可填充列表
最后,我们的 Change fills to hot pink(将填充色更改为粉红)按钮需要一个对所有可填充对象的引用数组,以便更改其 FillBrushColor 属性。虽然可以编写一个方法遍历列表并将颜色更改为传入的值,但这一次 Dr. GUI 选择了返回一个对象引用数组。幸运的是,ArrayList 类具有一个 ToArray 方法,利用它可以创建一个传递数组。该方法获取我们需要的数组元素类型 -- 从而可以传递回所需的类型 -- IFillable 数组。
C#
public IFillable[] GetFilledList() {
return (IFillable[])filledList.ToArray(typeof(IFillable));
}
Visual Basic
.NET Public Function GetFilledList() As IFillable()
Return filledList.ToArray(GetType(IFillable))
End Function
在两种语言中,我们都使用了一个内置运算符获取给定类型的 Type 对象 -- 在 C# 中,是 typeof(IFillable);在 Visual Basic 中,是 GetType(IFillable)。
调用程序使用此数组在可填充对象引用数组中遍历。例如,将填充颜色更改为粉红的 Visual Basic 代码如下所示:
Dim filledList As IFillable() = drawingList.GetFilledList()
Dim i As IFillable
For Each i In filledList
i.FillBrushColor = Color.HotPink
Next
用于分解出公共代码的 Helper 方法和类
您可能注意到,Draw 和 Fill 方法有很多共同的代码。确切地说,每个类中创建笔或画笔的代码、建立 Try/Finally 块的代码以及清理笔或画笔的代码都是相同的 -- 唯一的区别是进行绘图或填充时调用的实际方法。(由于 C# 中 using 语法非常简洁,因而多余代码的数量并不明显。)在 Visual Basic .NET 中,每五行代码中可能有一行特殊的代码在所有实现中都是相同的。
总之,如果存在大量重复代码,就需要寻求分解出公共的代码,以便形成为所有类所共享的公共子例程。这类方法有很多,Dr. GUI 非常高兴为您展示其中的两种。第一种方法仅用于类,第二种方法可用于类或接口,在本例中只用于接口。
方法 1:公共入口点调用虚拟方法
在第一个方法中,我们利用了类(不同于接口)可以包含代码这一事实。所以我们提供了一个用于创建笔的 Draw 方法的实现,以及一个异常处理程序和 Dispose,然后调用实际进行绘图的 abstract/MustOverride 方法。确切地说,我们更改了 DShapes 类以适应新的 Draw 方法,然后声明了新的 JustDraw 方法:
Public MustInherit Class DShape
' Draw 不是虚拟的,这似乎有些不寻常……
' Draw 本应是抽象的 (MustOverride)。
' 但此方法是绘图的框架,而不是绘图代码本身,
' 绘图代码在 JustDraw 中完成。
' 还请注意,这意味着同原版本相比,这些类具有
' 不同的接口,虽然它们完成的工作相同。
Public Sub Draw(ByVal g As Graphics)
Dim p = New Pen(penColor)
Try
JustDraw(g, p)
Finally
p.Dispose()
End Try
End Sub
' 这里是需要成为多态的部分 -- 因此是抽象的
Protected MustOverride Sub JustDraw(ByVal g As Graphics, _
ByVal p As Pen)
Protected bounding As Rectangle
Protected penColor As Color ' 还应具有属性
' 还应具有移动、调整大小等方法。
End Class
一个值得注意的有趣的地方:Draw 方法并不是 virtual/Overridable。因为所有派生类都将以相同的方式完成这部分绘图(如果在 Graphics 上绘图 [如本例中的定义],则必须指派并清理笔),因此它不需要是 virtual/Overridable。
实际上,Dr. GUI 认为在本例中,Draw 不应该是 virtual/Overridable。如果确实要覆盖 Draw 的行为(而不仅是 JustDraw 的行为),则可以将它设置为 virtual/Overridable。但在本例中,没有理由覆盖 Draw 的行为,如果鼓励程序员进行覆盖还会带来隐患 -- 他们可能不会正确处理笔,或者使用其他方法绘制对象而不是调用 JustDraw,这就违反了我们内置到类中的假设。因此,将 Draw 设置为非虚拟(顺便说一下,在 Brand J 中没有这个选项)可能会降低代码的灵活性,但会更加可靠 -- Dr. GUI 认为在本例中,这样做非常值得。
JustDraw 的典型实现如下所示:
Protected Overrides Sub JustDraw(ByVal g As Graphics, ByVal p As Pen)
g.DrawEllipse(p, bounding)
End Sub
如您所见,我们获得了所希望的简洁的派生类实现。(可填充类中的实现只是略微复杂一些 -- 稍后会看到。)
请注意,我们在接口中添加了一个额外的公开方法 JustDraw,除了要绘制的 Graphics 对象外,该方法还引用我们在 Draw 中创建的 Pen 对象。因为该方法需要是 abstract/MustOverride,因此必须是公开的。
这并不是一个大问题,但它确实更改了类的公开接口。所以即使这个分解出公共代码的方法非常简单方便,也应当尽可能选择其他方法以避免更改公开接口。
方法 2:虚拟方法调用公共 helper 方法,使用回调
在实现接口的 Fill 方法时,代码的复杂程度也很类似:每六行代码中可能有一行特殊的代码在所有实现中都是相同的。但是我们不能将公共的实现放到接口中,因为接口只是声明,它们不包含代码或数据。此外,上面列出的方法是不能接受的,因为它会更改接口 -- 我们可能并不希望这样,或者因为是其他人创建的接口,我们根本不可能更改!
所以,我们需要编写一个 helper 方法以设置并回调我们的类,以便进行实际的填充。对于本例,Dr. GUI 将代码放在一个单独的类中,这样任何类都可以使用该代码。(如果采用该方法来实现 Draw,则可以将 helper 方法作为抽象基类中的私有方法实现。)
暂时不进一步展开,以下是我们创建的类:
' 请注意,该 delegate 提供的帮助仍然具有多态行为。
Class FillHelper
Public Delegate Sub Filler(ByVal g As Graphics, ByVal b As Brush)
Shared Sub SafeFill(ByVal i As IFillable, ByVal g As Graphics, _
ByVal f As Filler)
Dim b = New SolidBrush(i.FillBrushColor)
Try
f(g, b)
Finally
b.dispose()
End Try
End Sub
End Class
我们的 helper 方法调用了 SafeFill,该方法接受一个可填充对象(请注意,这里我们使用了 IFillable 接口类型,而不是 DShape,从而只能传递可填充对象)、一个要在其上进行绘图的 Graphics 和一个称为 delegate 的私有变量。我们可以将 delegate 视为一个对方法(而不是对象)的引用 -- 如果您经常使用 C 或 C 编程,则可以将其视为具有类型安全特性的函数指针。可以将 delegate 设置为指向任何具有相同参数类型和返回值的方法,无论是实例方法还是 static/Shared 方法。将 delegate 设置为指向相应的方法后(例如在调用 SafeFill 时),我们可以通过 delegate 间接调用该方法。(顺便说一下,Brand J 中没有 delegate,这时如果使用此方法,会非常困难并且很不灵活)。
delegate 类型 Filler 的声明位于类声明之上 -- 它被声明为一个不返回任何内容(在 Visual Basic .NET 中是一个 Sub)并且将 Graphics 和 Brush 作为参数传递的方法。我们会在将来的专栏中深入讨论 delegate。
SafeFill 的操作非常简单:它指派画笔并将 Try/Finally 和 Dispose 设置为公共代码。它通过调用我们作为参数接收的 delegate 所引用的方法进行各种操作:f(g, b)。
要使用这个类,需要向可填充对象类中添加一个可以通过 delegate 调用的方法,并确保将该方法的引用(地址)传递到 SafeFill,我们将在接口的 Fill 实现中调用 SafeFill。以下是 DFilledCircle 的代码:
Public Sub Fill(ByVal g As Graphics) Implements IFillable.Fill
FillHelper.SafeFill(Me, g, AddressOf JustFill)
End Sub
Private Sub JustFill(ByVal g As Graphics, ByVal b As Brush)
g.FillEllipse(b, bounding)
End Sub
这样,当需要填充对象时,便在该对象上调用 IFillable.Fill。它将调用我们的 Fill 方法,而 Fill 方法调用 FillHelper.SafeFill,后者传递一个对我们的可填充对象的引用、所传递的要在其上进行绘图的 Graphics 对象以及一个对实际完成填充的方法的引用 -- 在本例中,该方法是私有的 JustFill 方法。
然后,SafeFill 通过 delegate -- JustFill 方法来设置画笔和调用,JustFill 方法通过调用 Graphics.FillEllipse 进行填充并返回值。SafeFill 将清理画笔并返回到 Fill,Fill 再返回到调用者。
最后是 JustDraw,它和原始版本中的 Draw 很类似,因为我们都调用了 Fill,并调用了基类的 Draw 方法(这是我们以前所做的)。以下是相关代码:
Protected Overrides Sub JustDraw(ByVal g As Graphics, ByVal p As Pen)
Fill(g)
MyBase.JustDraw(g, p)
End Sub
请记住,指派画笔和笔的复杂之处在于它在 helper 函数中的处理 -- 在 Draw 中,它位于基类中;在 Fill 中,它位于 helper 类中。
如果您认为这比以前复杂了,那么确实如此。如果您认为由于额外的调用和需要处理 delegate,速度比以前缓慢了,也确实如此。在生活中总是有很多东西需要进行权衡。
那么,这样做值得吗?也许值得。这取决于公共代码的复杂程度,以及该代码需要重复的次数。也就是说,需要权衡。如果我们决定删除 Try/Finally,而只在完成绘图后清理笔和画笔,代码便会非常简单,这些方法也就用不上。并且在 C# 中,using 语句非常简洁,我们也不必费神使用这些方法。Dr. GUI 认为,在 Visual Basic 中使用 Try/Finally 时,可以使用、也可以不使用这些方法,这里旨在向大家展示这些方法,以便在遇到具有大量公共代码的情况时使用。
使我们的对象可序列化
为在 ASP.NET 中使用可绘制对象类,我们需要对其再进行一项更改。这些类需要是可序列化的,以便能够在主要的 Web 页和生成该图像的 Web 页之间传递数据(后面将详述)。序列化是这样的过程:将某个类的数据以某种方式写入存储介质,以便存储和/或传递数据并在以后反序列化。反序列化是从序列化数据中重新创建对象的过程。我们会在将来的专栏中深入讨论这个问题。
Dr. GUI 最开始作为 Windows 窗体应用程序编写此应用程序时,只使用了 .NET Framework 和操作系统预先分配的 Brushes 和 Pens 类中的可用常用画笔和笔。因为这些已经分配完毕,保持对它们的引用不会有任何妨碍,同时也无需对其进行 Dispose。
但由于笔和画笔是非常复杂的对象,不能是可序列化的,因此 Dr. GUI 必须改变其策略,转而决定存储笔和画笔的颜色,然后在需要绘制和填充对象时动态创建笔和画笔。
如何使之可序列化?
序列化是 .NET Framework 的一个重要部分,因此也使序列化对象的工作变得很简单。
我们只需使用 Serializable 属性标记一个类便可使之可序列化。(这与我们以前用于在枚举上将其标记为一套标志的属性是同一种属性。)在 C# 和 Visual Basic .NET 中的语法如下所示:
C#
[Serializable]
class Foo // ...
Visual Basic
.NET _
Class Foo ' ...
注意:除了将类标记为可序列化外,还必须使类中包含的所有数据可序列化,否则在试图序列化数据时,序列化框架会引发一个异常。
使容器可序列化
.NET Framework 的一大优点是可以使容器类可序列化。这意味着如果将对象存储在可序列化的容器中,容器可以自动序列化对象。
因此在本例中,DShapeList 类包含了两个 ArrayList 对象。由于 ArrayList 是可序列化的,因此要使 DShapeList 可序列化,只需将其标记为 Serializable 属性即可,如下所示:
Visual Basic
.NET _
Public Class DShapeList
Dim wholeList As New ArrayList()
Dim filledList As New ArrayList() ' ...
C#
[Serializable]
public class DShapeList {
ArrayList wholeList = new ArrayList();
ArrayList filledList = new ArrayList();
假设我们放在 DShapeList 中的对象都是可序列化的,这时便可以使用单个语句序列化和反序列化整个列表!
顺便说一下,这对于该应用程序的 Windows 窗体版本也是一个很好的改变,因为它使我们能够将绘图写入磁盘文件并重新加载。
可绘制对象的三个版本;任何一个都可以在任何上下文中使用
您可能已经注意到,我们有三种版本的可绘制对象代码:在 C# 和 Visual Basic .NET 中各有一个不使用我们在上面编写的 helper 方法的版本,另一个是 Visual Basic .NET 中使用 helper 方法的版本。
在这里还有一点微小的差别:使用 helper 的文件中的数据类被标记为可序列化;其他文件中的数据类则没有标记为可序列化。
但是,请注意下面很重要的一点:如果我们返回去并将所有文件中的数据类标记为可序列化,那么将能够在任何应用程序中使用任何类。我们将能够混合使用 C# 和 Visual Basic .NET。并且能够在 ASP.NET 应用程序中使用最初为 Windows 窗体应用程序编写的代码。
这种简便的代码重用意味着您编写的代码更具价值,因为代码可以在很多不同的环境中重复使用。
在 Windows 窗体应用程序中使用可绘制对象
我们已经讨论了可绘制对象类,下面谈谈如何在 Windows 窗体应用程序中使用这些类。首先谈一下 Windows 窗体应用程序是怎样工作的。
Windows 窗体应用程序的主要部分
简单的 Windows 窗体应用程序包含一个主窗口(或窗体),其中包含控件子项。如果您是一位 Visual Basic 程序员,就会发现这个模型非常熟悉。
主窗口
任何 Windows 窗体应用程序中的关键对象都是主窗口。该窗体将在应用程序的 static/Shared Main 方法中创建,如下所示。
在一个简单的 Windows 窗体应用程序(例如我们所编写的)中,所有其他控件都是此主窗体的子项。
按钮和文本框
我们的窗体具有一套按钮和一些文本框。每个按钮有一个处理程序,可以向列表中添加形状,并绘制列表。所包含的文本框用于显示如何从窗体中获得输入。还有一个分组框,提供了有关文本框和相关按钮的可视指示。
PictureBox
左边是最重要的控件:PictureBox。这是绘制和显示图像的位置。在 Windows 应用程序中,您可能需要随时重绘图像 -- 例如,如果窗口被最小化或被其他窗口覆盖,则再次显示窗口时便需要进行重绘。
在响应画图 (Paint) 消息时便会完成这种按需绘图,由父窗体窗口类中的一个事件处理程序处理。
Windows 窗体应用程序中的主要例程
我们简单看一下 Windows 窗体应用程序中的重要例程。请注意,用户界面的代码与可绘制对象的代码相比非常简短。这就是使用 .NET Framework 完成诸多工作的好处。(这也表明我们使用可绘制对象类完成的工作确实很好。)
窗体方法
窗体(或主窗口)是从 System.Windows.Forms.Form 中派生的,所以继承了其所有行为。所有这些控件都声明为这个类的成员,这样在清理类时它们也将被清理(清理是在 Dispose 方法中实际明确完成的)。
它还包含了我们所需数据的声明(DShapeList 和一个随机数生成器对象)、Main 以及用于按钮单击事件和 PictureBox 画图事件的事件处理程序。
Main
Main 的任务就是创建和运行主窗口对象。其 C# 代码如下所示。
C#
[STAThread]
static void Main()
{
Application.Run(new MainWindow());
}
STAThread 属性对于 Windows 窗体应用程序的 Main 非常重要 -- 您应当始终使用该项,以便依赖于 OLE Automation(例如拖放和剪贴板)的功能能够正常工作。
在 Microsoft Visual Studio? 生成的 Visual Basic .NET 源代码中不会找到此方法,但是如果使用 ILDASM 在 .exe 中查找,便会找到一个与上面所述功能相同的 Main -- 可能是由 Visual Basic .NET 编译器生成的。
InitializeComponent
在 Windows Form Designer generated code(Windows 窗体设计器生成的代码)下(如果不能看到此区域中的代码,单击小加号),会看到用于创建和初始化所有按钮和窗体上其他控件的代码。
数据声明/随机数生成
除了在代码的隐藏区域中声明的所有控件外,我们还需要声明两个变量:存放绘图列表的数据结构,以及一个 Random 类型的对象。我们使用 Random 对象为所创建的对象的位置生成随机数。
数据声明位于 MainWindow 类内,但位于任何方法之外。在 C# 和 Visual Basic .NET 中,其代码如下所示:
C#
DShapeList drawingList = new DShapeList();
Random randomGen = new Random();
Visual Basic
.NET Dim drawingList As New DShapeList()
Dim randomGen As New Random()
我们还编写了一个 helper 方法以获得一个随机点:
C#
private Point GetRandomPoint() {
return new Point(randomGen.Next(30, 320), randomGen.Next(30, 320));
}
Visual Basic
.NET Private Function GetRandomPoint() As Point
Return New Point(randomGen.Next(30, 320), randomGen.Next(30, 320))
End Function
它生成两个位于 30 和 320 之间的随机数,作为随机点的坐标。
按钮单击事件处理程序
接下来就是每个按钮的按钮单击事件处理程序。多数仅仅是向绘图列表中添加一个新的可绘制对象,然后调用 PictureBox 上的 Invalidate,从而使用更新的绘图列表进行重绘。典型的按钮事件处理程序代码如下所示:
C#
private void AddPoint_Click(object sender, System.EventArgs e) {
drawingList.Add(new DPoint(GetRandomPoint(), Color.Blue));
Drawing.Invalidate();
}
Visual Basic
.NET Private Sub AddPoint_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles AddPoint.Click
drawingList.Add(New DPoint(GetRandomPoint(), Color.Blue))
Drawing.Invalidate()
End Sub
Change fills to hot pink(将填充色更改为粉红)按钮有一些不同 -- 它在列表中获得一个所有可填充对象的数组,然后将它们的画笔颜色更改为粉红。这部分代码显示在前面“返回可填充列表”一节的末尾。(此外还必须使 PictureBox 无效。)
最后,Erase All(全部删除)按钮简单地创建了一个新的绘图列表,并将我们的 drawingList 字段指向该列表。这样便释放了旧的绘图列表以进行最后的内存回收。然后使 PictureBox 无效,把自己也删除掉。
PictureBox 画图事件处理程序
我们要注意的最后一项就是在 PictureBox 中画出图像。为此,需要处理 PictureBox 生成的 Paint 事件,然后使用通过此事件传递的 Graphics 对象在其上进行绘图。要进行绘图,只需调用绘图列表的 DrawList 方法 -- 一个 for each 循环和多态将负责处理剩下的工作!
C#
private void Drawing_Paint(object sender,
System.Windows.Forms.PaintEventArgs e) {
drawingList.DrawList(e.Graphics);
}
Visual Basic
.NET Private Sub Drawing_Paint(ByVal sender As Object, _
ByVal e As System.Windows.Forms.PaintEventArgs) _
Handles Drawing.Paint
drawingList.DrawList(e.Graphics)
End Sub
我们的 Windows 窗体应用程序之旅到此结束 -- 请斟酌这些代码并进行修改,这样可以学到更多内容!
在 ASP.NET 应用程序中使用可绘制对象
虽然 ASP.NET Web 应用程序和 Windows 窗体应用程序之间存在某些不同,但两者的相似性还是令 Dr. GUI 感到惊奇!
Web 窗体应用程序的主要部分
ASP.NET Web 窗体应用程序的主要部分与 Windows 窗体应用程序的各部分非常对应。
页面
此项对应 Windows 窗体应用程序中的主窗口。页面是所有按钮和其他控件的容器。
按钮
同样,这里有一组按钮,可用于在窗体上执行各种操作。请注意,与以前的应用程序不同,我们将页面文档的 pageLayout 属性设置为 GridLayout 而不是 FlowLayout。这意味着我们可以通过像素位置定位每个按钮(以及其他控件)。
请注意,这里也有一些文本框。
还要注意,您不能向 Web 复制和粘贴 Windows 窗体控件 -- 必须重新创建整个页面。
图像控件
图像控件对应于 Windows 窗体应用程序中的 PictureBox。但两者有一些重要的差别:图像控件不生成 Paint 消息,而是包含加载图像的 URL。
我们将这个 URL 设置为第二个 Web 页,ImageGen.aspx。换句话说,我们有一个 Web 页,它的全部工作就是从我们的绘图列表中生成图像中的位,然后将图像发送到客户端的 Web 浏览器。
我们将在下面讨论其代码。
Web 窗体应用程序的主要例程
Windows 窗体应用程序和 Web 窗体应用程序的代码之间存在一些重要不同 -- 但也有某些有趣的相似之处。还要注意,可绘制对象文件中的所有代码都可以用于三种应用程序中的任何一种。
我们的页面是从 System.Web.UI.Page 派生的,除了以下内容外,还包含一组用于所有控件的声明:
完全相同的内容:数据声明和 GetRandomPoint
此代码与 Visual Basic .NET Windows 窗体应用程序中的代码几乎完全相同。如果愿意,可以再看一下上面的这段代码。它们之间只有一个不同之处,就是对字段进行了声明而没有将其初始化。它们将在 Page_Load 方法中被初始化(如后面所示)。
GetRandomPoint 方法与其他应用程序完全相同。能够重复使用代码真的不错,不是吗?
非常相似的内容:按钮单击事件处理程序
按钮单击事件处理程序与 Windows 窗体应用程序相同,只有一个例外:在 Web 窗体中,由于每次单击按钮时都将重绘图像,因此无需(也不能)使图像控件无效。此外,它还将自动进行重绘 -- 因此唯一要调用的就是绘图列表的 Add 方法。
以下是一个典型的按钮事件处理程序 -- 用于绘制一个点。
Private Sub AddPoint_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles AddPoint.Click drawingList.Add(New DPoint(GetRandomPoint(), Color.Blue)) End Sub
其他按钮事件处理程序都与 Windows 窗体的情况类似,当然,有一种例外情况除外,即不调用任何一种方法使图像无效。
差别很大的内容:页面加载和卸载处理程序
页面加载和卸载处理程序方法是完全不同的。
请记住,我们的页面对象是使用每一个 HTTP 请求重新创建的。由于每个请求都将创建一个新页面,因此我们不能象在 Windows 窗体中那样将数据作为成员变量存储,在 Windows 窗体中,主窗口将伴随应用程序而存在。
因此我们必须在某种状态变量中存储请求和页面之间所需的信息。这里有几种选择 -- 下面将就此进行讨论。
在页面和请求之间传递状态
为使应用程序能够工作,它需要能够维护请求之间的状态并将状态传递给绘图页面(如下所示)。
维护和传递状态有多种方式。如果应用程序是严格的单页面应用程序(和以前的应用程序一样),则可以使用视图状态,其中数据被编码存储在 Web 页的隐藏输入字段中。
但是我们的图像控件是在单独的页面中进行绘图的,因此需要某些更灵活的东西。最好的选择就是 cookie 和会话状态。会话状态非常灵活,但要求使用服务器资源。浏览器可以保留 cookie,但其大小非常有限。
Page_Load
Page_Load 是在创建页面对象之后以及在运行所有事件处理程序之前被调用的。因此 Page_Load 方法是加载永久数据的理想所在。如果找不到数据,就创建新的数据。以下是相关代码:
Private Sub Page_Load(ByVal sender As System.Object, _
ByVal e As System.EventArgs) _
Handles MyBase.Load
randomGen = ViewState("randomGen")
If randomGen Is Nothing Then randomGen = New Random()
' 选项之一:使用会话状态获得绘图列表
'(保存在 Page_Unload 中)
'(注意:要求服务器上的状态存储)
drawingList = Session("drawingList")
If drawingList Is Nothing Then drawingList = New DShapeList()
' 选择之二:从用户浏览器上的 cookie 中
' 检索绘图状态
'(注意:不需要服务器存储,但有些用户会禁用 cookie)
'(注意之二:cookie 不会自动反序列化!:( )
' 注意之三:使用 cookie 将限制能够绘制的形状数量
'Dim drawingListCookie As HttpCookie
'drawingListCookie = Request.Cookies("drawingList")
'If drawingListCookie Is Nothing Then
' drawingList = New DShapeList()
'Else
' drawingList = _
' SerialHelper.DeserializeFromBase64String( _
' drawingListCookie.Value)
'End If
End Sub
首先,我们尝试从视图状态加载随机数发生器状态。如果存在,则使用存储的值。如果不存在,则创建一个新的 Random 对象。
接下来,我们尝试从会话状态加载绘图列表。同样,如果不存在绘图列表,则创建一个新的空列表。
如果需要,视图状态和会话状态都会自动序列化我们的对象。视图状态始终被序列化,因此可以表示为浏览器隐藏输入字段中的编码的字符串。会话状态当存储在数据库中或者在服务器间进行传递时被序列化,但是如果应用程序运行在单个服务器上(例如在开发机器上进行测试时),则不会将其序列化。
被注释的代码试图从 cookie 加载绘图列表。请注意,处理 cookie 要比处理视图或会话状态复杂得多。首先就是不能自动序列化。为序列化为一个字符串,我们在一个新类当中编写了 helper 函数,如下所示:
Public Shared Function DeserializeFromBase64String( _
ByVal base64String As String) As Object
Dim formatter As New BinaryFormatter()
Dim bytes() As Byte = Convert.FromBase64String(base64String)
Dim serialMemoryStream As New MemoryStream(bytes)
Return formatter.Deserialize(serialMemoryStream)
End Function
Dr. GUI 使用了二进制格式化程序并转换为可打印的 base 64 字符串,因为无论是 SOAP 还是 XML 格式化程序都不适用于此应用程序。我们必须从纯二进制表示转换为 base 64 字符串,以避免因简单复制字节而产生字符串中控制字符的潜在问题。base 64 字符串使用一个字符 A-Z、a-z、0-9、 或 /(共 64 个或 2^6 个字符)来表示二进制字符串中的每六位,因此四个字符表示三个字节 -- 第一个字符表示第一个字节中的六位,第二个字符表示第一个字节的末两位和第二个字节的前四位,以此类推。同样,使用 base 64 字符串关键在于可以将字符串限制为可打印字符,这样就避免了任何控制字符出现潜在问题。
XML 格式化程序不会序列化私有数据 -- 而 Dr. GUI 也不打算为绘图列表中的私有数据添加公开访问权限。SOAP 格式化程序不存在这种限制,但它不会序列化空列表以便进行反序列化。相反,它不为空列表向数据流写入任何东西,这样当尝试反序列化时就会引发一个异常。(Dr. GUI 认为这是一个错误。)
Dr. GUI 更喜欢以可读的 XML 格式进行序列化,但由于两种 XML 序列化格式化程序都无法完成此项工作,所以最终选择了二进制格式化程序并转换为 base 64 字符串。
Page_Unload
Page_Unload 是在破坏页面对象(包括任何所包含的数据)之前被调用的,因此是永久放置重要数据的理想位置,这样我们便可以在将来从 Page_Load(或者从图像的 Page_Load)中取出这些数据。
因此,我们将数据保存在 Page_Unload 中,并从 Page_Load 中检索数据。虽然这有些奇怪,但却是正确的。
以下是 Page_Unload 的代码:
Private Sub Page_Unload(ByVal sender As Object, _
ByVal e As System.EventArgs) _
Handles MyBase.PreRender
ViewState("randomGen") = randomGen
' 选项之一:编写会话状态
Session("drawingList") = drawingList
' 选项之二:编写一个 cookie。必须编写代码进行序列化。
' 注意:使用 cookie 将限制能够绘制的形状数量!
'Dim drawingListString As String =
' SerialHelper.SerializeToBase64String(drawingList)
'Response.Cookies.Add(New HttpCookie("drawingList", _
' drawingListString))
End Sub
此代码稍微有些简单,因为我们不必查看状态是否已经存在于视图或会话状态对象中,而只需将其无条件写出。
同样,视图状态和会话状态可以自动对自身进行序列化,而 cookie 则不能,因此我们需要亲自执行。Dr. GUI 编写了下面的 helper 函数(在单独的类中),代码如下所示:
Public Shared Function SerializeToBase64String(ByVal o As Object) _
As String
Dim formatter As New BinaryFormatter()
Dim serialMemoryStream As New MemoryStream()
formatter.Serialize(serialMemoryStream, o)
Dim bytes() As Byte = serialMemoryStream.ToArray()
Return Convert.ToBase64String(bytes)
End Function
在单独的页面中绘图
正如前面提到的,绘图是在单独的页面中进行的。以下是该页面的代码:
Private Sub Page_Load(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles MyBase.Load
Dim drawingList As DShapeList
' 获取绘图列表选项之一:使用会话状态...
drawingList = Session("drawingList")
If drawingList Is Nothing Then drawingList = New DShapeList()
' 获取绘图列表选项之二:使用 cookie...
'(请查看主页代码以了解更多注释)
'Dim drawingListCookie As HttpCookie
'drawingListCookie = Request.Cookies("drawingList")
'If drawingListCookie Is Nothing Then
'drawingList = New DShapeList()
'Else
' drawingList = _
' SerialHelper.DeserializeFromBase64String( _
' drawingListCookie.Value)
'End If
Response.ContentType = "image/gif"
Dim bitMap As New Bitmap(368, 376)
Dim g As Graphics = Graphics.FromImage(bitMap)
Try
g.Clear(Color.White)
drawingList.DrawList(g)
bitMap.Save(Response.OutputStream, ImageFormat.Gif)
Finally
g.Dispose()
bitMap.Dispose()
End Try
End Sub
首先,我们从会话状态或 cookie 中获取绘图列表。(这部分代码与上面的 Page_Load 方法类似。)
然后,我们将正在编写的响应流的 ContentType 设置为一个 GIF 图像。
接下来,我们要做一些真正美妙的事情:按照所需的大小(本例按照与 Windows 窗体应用程序中相同的大小)创建一个位图。
然后,我们得到一个与该位图相关联的 Graphics 对象,清除该对象,并在其中绘制我们的列表。
下面的步骤很重要:接下来,我们将 GIF 格式的图像内容写出到响应流(即浏览器)中。我们设置了这种响应类型以确保浏览器能够正确解释图像,然后发送图像的位。(.NET Framework 使该操作变得相当简单。而在原来的 Windows GDI 时代,仅在位图上进行绘制都是非常痛苦的!)
另一个重要步骤就是要记住清理 Graphics 和 Bitmap 对象 -- 并使用 Try/Finally,以便即使出现异常也会清理对象。
嗨!步骤真多。但是为了让此应用程序能够作为 ASP.NET 应用程序运行,还是值得的 -- 并且更好的是,这种应用程序不需要依赖客户端脚本。
试一试!
![](http://writeblog.csdn.net/attachment/Image/2008412902515177803.gif)
图一
如果您手头有 .NET,学习它的最好方法就是试一试。如果没有,就请想办法得到。如果您每周在 Dr. GUI .NET 上花费一个小时左右,那么在了解 .NET 之前您将已经成为一名 .NET Framework 专家了。
从您开始 -- 并邀请您的朋友!
作为第一个学习新技术的人,感觉一定不错,但如果和朋友们分享则乐趣更多!为享受更多乐趣,邀请朋友共同学习 .NET 吧!
应进行的尝试...
首先试一下这里给出的代码。其中有一些是从大型程序中节选下来的,围绕这些代码片断创建程序会取得不错的效果。(如果必须,也可以使用 Dr. GUI 提供的代码。)琢磨一下代码。
向绘图程序添加一些不同的形状,包括填充和不填充的形状。注意:虽然我们没有对其进行转换,但所添加的类可能存在于来自其他文件的不同程序集中(因而是不同的可执行文件)。这意味着所添加的类的语言甚至可以和其他类不同。当您阅读并尝试有关的必要工作后,会更加确信这一点。
请向这里的类添加一些方法,并尝试改动用户界面。自己制作一个可爱的 CAD 小程序。
在自己选择的项目中使用继承、abstract/MustInherit 类、接口和多态。当类系列具有很多共同部分时,使用继承的效果最佳。如果类并不具有很多共同部分,但功能相似,这时使用接口的效果最好。
尝试用 ASP.NET 编写自己的绘图应用程序。请注意,如果能够运行 .NET Framework,则只需 Microsoft Internet Information Server (IIS) 便可以在自己的计算机上运行 ASP.NET -- 无需服务器!Dr. GUI 认为,在便携式计算机上仅使用标准操作系统和免费的 .NET Framework 创建和测试 Web 应用程序,感觉实在好极了。(但 Dr. GUI 还是倾向于使用 Visual Studio,或者至少 Visual Basic 或 Microsoft Visual C#? 标准版。)看看吧!不需要服务器!甚至不需要 Internet 连接!(可在 Brand J 的扩展版上尝试…)
标题:ArrayList使用toArray()构造数组时的问题
关键字:toArray 构造数组
作者:jrq
关键字:toArray 构造数组
作者:jrq
关键字:toArray 构造数组
作者:jrq
关键字:toArray 构造数组
作者:jrq
关键字:toArray 构造数组
作者:jrq
摘要:解决使用ArrayList.toArray()构造数组时的问题。做备忘。
链接: http://blog.csdn.net/jrq/archive/2005/10/27/517428.aspx
链接: http://blog.csdn.net/jrq/archive/2005/10/27/517428.aspx
正文:
1. 为了方面按列作外循环,想把ArrayList构造成一个二维数组,如下:
......
ArrayList result=GetResult();
int n=result.size();
String[][] myArray=new String[n][]; //定义二维数组
for (int i=0;i<n;i ) //构造二维数组
{
ArrayList tempArray= (ArrayList)result.get(i);
myArray[i]=(String[])tempArray.toArray();
}
......
程序可以编译通过。
但在运行到myArray[i]=(String[])tempArray.toArray()时,出现java.lang.ClassCastException的错误,很奇怪。
花了一晚上时间,查了N多资料,总算搞定了。现把问题记录下来,以备参考。
2. 此事从头说起。
ArrayList类扩展AbstractList并执行List接口。ArrayList支持可随需要而增长的动态数组。
ArrayList有如下的构造函数:
ArrayList( )
ArrayList(Collection c)
ArrayList(int capacity)
如果调用new ArrayList()构造时,其默认的capacity(初始容量)为10。
参见ArrayList源码,其中是这样定义的:
public ArrayList() {
this(10);
}
默认初始化内部数组大小为10。为什么是10?不知道。可能SUN觉得这样比较爽吧。
程序编译后执行ArrayList.toArray(),把ArrayList转化为数组时,该数组大小仍为capacity(为10)。
当装入的数据和capacity值不等时(小于capacity),比如只装入了5个数据,数组中后面的(capacity - size)个对象将置为null,此时当数组强制类型转换时,容易出现一些问题,如java.lang.ClassCastException异常等。
解决办法是:在用ArrayList转化为数组装数据后,使用trimToSize()重新设置数组的真实大小。
3. 本例修改后的代码修如下,可顺利运行:
for (int i=0;i<n;i ) //构造二维数组
{
ArrayList tempArray= (ArrayList)result.get(i);
myArray[i]=(String[])tempArray.toArray(new String[0]); //注意此处的写法
}
看看下面这些也许就明白了--
ArrayList.toArray()之一:
public Object[] toArray() {
Object[] result = new Object[size];
System.arraycopy(elementData, 0, result, 0, size);
return result;
}
返回ArrayList元素的一个数组,注意这里虽然生成了一个新的数组,但是数组元素和集合中的元素是共享的,Collection接口中说这个是安全的,这是不严格的。
下面的例子演示了这个效果。
ArrayList al=new ArrayList();
al.add(new StringBuffer("hello"));
Object[] a=al.toArray();
StringBuffer sb=(StringBuffer)a[0];
sb.append("changed"); //改变数组元素同样也改变了原来的ArrayList中的元素
System.out.println(al.get(0));
这里不要用String来代替StringBuffer,因为String是常量。
ArrayList.toArray()之二:
public Object[] toArray(Object a[]) {
if (a.length < size)
a = (Object[])java.lang.reflect.Array.newInstance(a.getClass().getComponentType(), size);
System.arraycopy(elementData, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}
这个方法有可能不需要生成新的数组,注意到如果数组a容量过大,只在size处设置为null。
如果这个数组a足够大,就会把数据全放进去,返回的数组也是指向这个数组,(数组多余的空间存储的是null对象);要是不够大,就申请一个跟参数同样类型的数组,把值放进去,然后返回。
4. 网上的资料一:
public String[] getPlatformIDList()
{
Vector result = new Vector();
try
{
Statement stmt = conn.createStatement();
String sql = "SELECT PlatformID FROM Platform";
rs = stmt.executeQuery(sql);
while(rs.next())
{
result.add(rs.getString(1));
}
if (result.size() > 0)
{
String[] str = (String[]) result.toArray(); // 出现ClassCastException
return str;
}
else
return null;
}
catch(Exception e)
{
System.err.println(e);
return null;
}
finally
{
try
{
rs.close();
conn.close();
}
catch(Exception e2)
{}
}
}
程序运行后,发现不能将Vector类经过toArray()方法得到的Object[]直接转换成String[]。
找到用另一个带有参数的 toArray(T[] a)方法才可以。
将该语句改为:
String[] str = (String[]) result.toArray(new String[1]);即告诉Vector,我要得到的数组的类型。
回想一下,应该是java中的强制类型转换只是针对单个对象的,想要偷懒,将整个数组转换成另外一种类型的数组是不行的。
5. 网上的资料二:
正确使用List.toArray()--
在程序中,往往得到一个List,程序要求对应赋值给一个array,可以这样写程序:
Long [] l = new Long[list.size()];
for(int i=0;i<list.size();i )
l[i] = (Long) list.get(i);
要写这些code,似乎比较繁琐,其实List提供了toArray()的方法。
但是要使用不好,就会有ClassCastExceptiony异常。究竟这个是如何产生的,且看代码:
List list = new ArrayList();
list.add(new Long(1));list.add(new Long(2));
list.add(new Long(3));list.add(new Long(4));
Long[] l = (Long[])list.toArray();
for(int i=0; i<l.length; i )
System.out.println(l[i].longValue());
红色代码会抛java.lang.ClassCastException。
当然,为了读出值来,你可以这样code:
Object [] a = list.toArray();
for(int i=0;i<a.length;i )
System.out.println(((Long)a[i]).longValue());
但是让数组丢失了类型信息,这个不是我们想要得。
toArray()正确使用方式如下:
1) Long[] l = new Long[<total size>];
list.toArray(l);
2) Long[] l = (Long []) list.toArray(new Long[0]);
3) Long [] a = new Long[<total size>];
Long [] l = (Long []) list.toArray(a);
6. 总结补充:
java sdk doc 上讲:
public Object[] toArray(Object[] a)
a--the array into which the elements of this list are to be stored, if it is big enough; otherwise, a new array of the same runtime type is allocated for this purpose.
如果这个数组a足够大,就会把数据全放进去,返回的数组也是指向这个数组,(数组多余的空间存储的是null对象);要是不够大,就申请一个跟参数同样类型的数组,把值放进去,然后返回。
需要注意的是:你要是传入的参数为9个大小,而list里面有5个object,那么其他的四个很可能是null , 使用的时候要特别注意。
7. 完毕。
相关文章:ASP.net(VB)编程入门进阶 Ⅰ
写在前面的话
.net已经上路了,前几天着重写了写asp.net(VB)的基本安装、语法、变量、原计划把服务器控制结构和过程编程也写上,但,这和asp里面的没什么区别,在循环结构里面多了一个with...end with操作一个对象的属性的,其他的都差不多,想看详细教程的请查阅Msdn,我就跳过这一节了,另外跳转结构---子例程和函数也跳过,不过,里面的模块化很有意思的,还有传递参数的时候按照引用还是值传递这个很是重要,至于其中的区别,学过c 的都知道,不太明白的请查阅相关资料......由于篇幅关系,我把事件驱动编程和回送也跳过,相应用户的事件组,这个很有必要在这里说说。
官方定义事件:事件是由来自代码外部的某种力量在应用程序中引发的一个操作.将事件驱动环境按照顺序分为以下4个部分:
1、发生一个事件,like用户单击按钮;
2、系统检测到事件,asp.net注册已发生的事件;
3、系统对事件做反应,执行一些代码;
4、系统返回其初始状态,等待下一事件;
html叶子是按照代码的顺序依次执行的,而事件驱动编程强调的是相应用户,执行事件,等待相应的思想。
ASP.NET支持3个主要的事件组,第一组是html内部的事件,这些事件在浏览器上执行。第二组包含在asp.net生成页面时自动发生,我们使用这些事件建立页面.第三种包含了用户与页面交互时发生的所有事件(这种最强大)。
事件驱动编程和回送回送是处理过程,该过程包括:浏览器将信息发送到浏览器,告诉服务器处理事件,服务器执行次事件处理程序中的代码,并将得到的html再传送到浏览器,回送只作用于有属性runat="server"的web窗口,且只有asp.net控件才能将信息传送到服务器。
下面看个事件驱动的实例结束本节.呵呵.
实例:我们要做这件事,设计一个"东西",要实现的是能加减乘除操作,并且显示出计算结果。
现看看算法吧:
分析上面代码:page_load事件当整个页面第一次可见时发生,这里实现了令4个按钮的背景颜色为淡灰色。子例程ca执行的是用户点击按钮操作后发生的事件,然后回送labtxt上,最后改变选择web控件对象(按钮)的背景颜色,注意到:
它是事件处理程序提供的参数向处理程序传递参数事件信息,其中第一个参数sender提供了引发事件对象的引用,第二个参数E是一个事件类,用于捕捉所处理时间状态的信息,并传递与该事件对应的对象。
下面在视图中拉上这7个控件,注意这里需要正确填写每个控件的ID,并且要在每个按钮上添加事件:
意思就是说当点击这个按钮是发生事件ca,这是不可缺少的。
一切搞定了的话,最后的aspx页面效果就像这样:
所有源码:
<%@ Page Language="VB" %>
<script runat="server">
sub page_load(sender as object,e as eventargs)
btnadd.backcolor=system.drawing.color.lightgray
btnsubtract.backcolor=system.drawing.color.lightgray
btnfactor.backcolor=system.drawing.color.lightgray
btnra.backcolor=system.drawing.color.lightgray
end sub
sub ca(sender as object,e as eventargs)
select case sender.id
case "btnadd"
labtxt.text=cdbl(input1.text) cdbl(input2.text)
case "btnsubtract"
labtxt.text=cdbl(input1.text)-cdbl(input2.text)
case "btnfactor"
labtxt.text=cdbl(input1.text)*cdbl(input2.text)
case "btnra"
labtxt.text=cdbl(input1.text)/cdbl(input2.text)
end select
sender.backcolor=system.drawing.color.yellow
end sub
</script>
<html>
<head>
</head>
<body>
<form runat="server">
<p>
<asp:TextBox id="input1" runat="server"></asp:TextBox>
<asp:Button id="btnadd" runat="server" Text=" " οnclick="ca"></asp:Button>
<asp:Button id="btnsubtract" runat="server" Text=" - " οnclick="ca"></asp:Button>
</p>
<p>
<asp:TextBox id="input2" runat="server"></asp:TextBox>
<asp:Button id="btnfactor" runat="server" Text=" * " οnclick="ca"></asp:Button>
<asp:Button id="btnra" runat="server" Text=" ÷ " οnclick="ca"></asp:Button>
</p>
<p>
<asp:Label id="labtxt" runat="server" Width="201px" Height="26px"></asp:Label>
</p>
<!-- Insert content here -->
</form>
</body>
</html>
对象就是能看到,感到,听到,触摸到,尝到或闻到的东西,在这里我们这样“定义”:对象是一个自包含的实体,用一组可识别的特性和行为来标识。
在面向对象的编程(oop)的编程方式,用使用下面的两个术语。
类:这是对象的模板,定义了对象的特性。
实例:这是一个真实的对象,可以与之交互的东西。
属性,方法和事件
在OOP中,下面的术语描述对象的特性。
属性:这是一个名次,描述了某个对象的属性
方法:这是一个动词,描述了对象可以完成的工作,或者希望它完成的工作。
事件:描述了对象为相应某个动作而执行的操作。
.net中的对象
在.net中,其实所有的东西都是对象。为什么要使用对象?
在编程时,对象的面向对象编程和面向对象设计的一部分,它们具有非常大的优势,许多人认为这是一个复杂的主题,但实际上,它非常简单,可以用四个简单的术语来解释:抽象、封装、多态和继承。
抽象:这是一个隐藏复杂性,类的内部工作情况,所以用户不必知道它的运作方式,就像。如果想要看电视,就不必知道电视机时如何工作的,只需打开电视机,搜索频道即可,on/off开关抽象了实际的操作,在string例子里,有一个trim方法,它可以删除字符串尾部的空格,同样不需要知道他是如何完成这个任务的,只要知道它有这个功能即可。
封装:每个对象都包含进行操作所需要的所有信息,这个对象称为封装,因此对象不比依赖其他对象来完成自己的操作,在术语TOupper()方法中,string不必到其他地方获取信息来把所有的字符转换为大写。
多态:这个术语用于表示不同的对象可以执行相同的动作,但要通过他们自己的实现代码来执行,名称一样,但底层实现的代码是不一样的。
继承:它定义了类如何相互关联,共享特性的,继承的工作方式是,定义类和子类,其中子类继承了父类的所有特性,继承的重要性是,它迫使类型相似的类具有一致性,并允许共享代码,如果决定创建一个新类,就不必定义父类的所有特性。
好了,我们看个实例吧,创建一个user类,他具有一下特性:
属性:name ,point,level
方法:post(),reply(),change()
ok,现在在你的webmatrix里面创建吧。
看看以下源码:
public sub new()
end sub
private _name as string
private _point as integer
private _level as string
public property name as string
get
return _name
end get
set(byval value as string)
_name=value
end set
end property
public property point as integer
get
return _point
end get
set(byval value as integer)
if value<0 then
value=0
end if
_point=value
end set
end property
public property level as string
get
return _level
end get
set(byval value as string)
_level=value
end set
end property
end class
这里定义的就是上面我们想要的,注意,如果你以前不了解一门面向对象设计的语言的话理解起来比较困难的。创建实例的是:
html视图还是注意ID写上3各label控件就ok了,可以看到效果:
name: 5do8 point:5000 level:终极会员
代码分析如下:
1、首先用class关键字定义类,后面是类名,而public是表示可以用于其他的程序;
2、然后是构造函数,new(),如果不写的话也行,因为,net默认提供构造函数的,这和c 里面的一个模式;
3、定义私有变量,用来存储类的属性,这些变量在代码的外部不能访问,如何使用这些变量,就要使用property语句即可;
get
return _name
end get
set (byval value as string)
_name=value
end set
end property
第一行里面的public声名在外部可以访问,property声明一个属性,它后面是属性的名及其类型。get返回私有变量的值,这是内部机制,接下来的set语句,在属性中存储一个值时执行这个语句,这里仅仅把私有属性设置为参数传送的值,set 语句有些像方法调用,只传一个参数,该参数在设置属性时由.net自动赋值。
..........其他的依次同理....最后是结束类end class。
创建实例的时候:
dim blueidea as new user()
blueidea.name="5do8"
blueidea.point="5000"
blueidea.level="终极会员"
第一行创建user类的一个实例,名称是blueidea。接下来是设置它的属性值。最后绑定在web控件上。
这就是一个很简单的使用类的实例,但不像c 那样,asp.net好像没有提供析构函数释放一些不必要的内存占用等。
另外,高级类属性和方法就不说了,很多,慢慢的在实践中应该可以掌握的。
.net框架包含几个命名空间,其中有几十个用于数据库访问的类,主要有system.data.sqlclient和system.data.oledb两大类,这里我主要介绍小巧一点的system.data.oledb空间中的类,因为system.data.sqlclient只与microsoft sql server数据库一起工作,而前者是支持access或者oracle 数据库。
system.data.oledb名称空间包含以下这些类:
1、oledbconnection(代表一个打开的数据库连接)
2、oledbcommand(代表一个sql语句或存储过程)
3、oledbdatareader(代表从数据库查询返回的结果)
执行常见的数据库任务
在接下来的一些日子里面,我会陆续的写上如何创建和打开数据库,获取和显示数据库记录,添加新的数据库记录,更新数据库记录,删除数据库记录等。这些都是很基本的操作,但能令初学者心血彭湃的。呵呵。
当您使用microsoft access或者oracle,需要使用以下的页面指令来导入system.data.oledb命名空间:
使用sql server数据库的链接的话是:
打开数据库
要访问数据库,首先要创建和打开数据库链接,创建连接的方式看你的数据库类型,如下代码创建了一个sql数据库的连接:
<script runat="server">
sub page_load
dim sqlconn as sqlconnection
sqlconn=new sqlconnection("server=localhost;uid=sa;pwd=sd;database=db")
sqlconn.open()
end sub
</script>
第一行导入命名空间,在page_load子例程中创建和打开,先创建一个名为sqlconn的实例,通过向sqlconnection类的构造器传递一个字符串参数,对sqlconn类进行初始化,最后通过sqlconnection类的open()方法实际打开链接。
同样的道理,我们可以类似的打开access的数据库,如下的代码:
<%@ import namespace ="system.data.oledb" %>
<srcipt runat="server">
sub page_load()
dim conn as oledbconnection
conn=new oledbconnection("provider=microsoft.jet.oledb.4.0;data source=d:/web/web/net/data/db.mdb")
conn.open()
end sub
</srcipt>
在使用完数据库链接时,应尽快关闭它是很重要的,每个数据库都有连接数量的限制,关闭链接使它可以供其他的页面使用,应使用下面的语句关闭sqlconnection或者oledbconnection:
在ASP.net页面中最常用的是SQL中的select语句:
from site_data
where id='2'
在asp.net页面执行一个select语句需要以下4步完成:
1、创建和打开数据库
2、创建沂河代表sql select语句的数据库命令
3、用executereader()方法执行这个命令,并且返回一个datareader.
4、遍历datareader,显示查询的结果。
在使用ado.net查询中,查询的结果在一个datareader中返回,更准确的说,查询的结果由一个sqldatareader或oledbdatareader表示,datareader代表一个只向前的数据库记录流,这意味着datareader每次只代表一个记录,要想获取流中的下一个记录,必须调用read()方法,要想显示所有的记录,必须反复调用read()的方法,直到流的尾部,不能回头。例如asp里面的游标只向前移动。
下面的程序显示了sql server读取数据库的记录:
<%
dim sqlconn as sqlconnection
dim sqlcmd as sqlcommand
dim sqlreader as sqldatareader
sqlconn=new sqlconnection("server=localhost;uid=sa;pwd=sd;database=data")
sqlconn.open()
sqlcmd=new sqlcommand("select szd_first from site_data",sqlconn)
sqlreader=sqlcmd.executereader()
while sqlconn.read()
response.write("<li>")
response.write(sqlreader("szd_first"))
end while
sqlreader.close
sqlconn.close
%>
分析:先导入sql server的ado.net类需要的名称空间,然后创建一个数据库conn,这和asp相似,接着用一个sql select语句的sql字符串初始化sqlcmd对象,这个语句从名为site_data的表中读取所有的记录。然后调用sqlcommand类的executereader()方法执行这个命令,返回执行结果的sqlreader,然后遍历所有记录。
下面演示一个从access数据库里面读出数据的源码:
<%@ Page Language="VB" %>
<%@ import namespace="system.data.oledb"%>
<script runat="server">
sub page_load()
dim conn as oledbconnection
dim accmd as oledbcommand
dim acreader as oledbdatareader
conn= new oledbconnection("provider=microsoft.jet.oledb.4.0;data source=d:/web/web/net/data/db.mdb")
conn.open()
accmd=new oledbcommand("select szd_lastname from site_n",conn)
acreader=accmd.executereader()
while acreader.read()
response.write("<LI>" & acreader("szd_lastname"))
end while
acreader.close
conn.close
end sub
</script>
有时候,我们要查询符合条件的一条记录,如果按照上面的executereader,效率显然很差,这里介绍另外一种方法executescalar,在几张集聚功能的count,max,min等,这个例子:
dim conn as oledbconnection
dim accmd as oledbcommand
conn= new oledbconnection("provider=microsoft.jet.oledb.4.0;data source=d:/web/web/net/data/db.mdb")
conn.open()
accmd=new oledbcommand("select count(*) from site_n ",conn)
actxt.text=accmd.executescalar()
conn.close
end sub
sub page_load(sender as object,e as eventargs)
dim blueidea as new user()
blueidea.name="5do8"
blueidea.point="5000"
blueidea.level="终极会员"
name.text=blueidea.name
point.text=blueidea.point
level.text=blueidea.level
end sub
'some code .............
end sub
btnadd.backcolor=system.drawing.color.lightgray
btnsubtract.backcolor=system.drawing.color.lightgray
btnfactor.backcolor=system.drawing.color.lightgray
btnra.backcolor=system.drawing.color.lightgray
end sub
sub ca(sender as object,e as eventargs)
select case sender.id
case "btnadd"
labtxt.text=cdbl(input1.text) cdbl(input2.text)
case "btnsubtract"
labtxt.text=cdbl(input1.text)-cdbl(input2.text)
case "btnfactor"
labtxt.text=cdbl(input1.text)*cdbl(input2.text)
case "btnra"
labtxt.text=cdbl(input1.text)/cdbl(input2.text)
end select
sender.backcolor=system.drawing.color.yellow
end sub
最近用VS.NET2005 beta2做了个小工具试用了一下,的确是方便易用啊,其实我VS.NET2003都没怎么用过.不过就是喜欢尝鲜,没办法(不知道是不是有很多人跟我一样,看到新出的东西就想拿来试一试,搞得我的操作系统总是坏的极快).当然,我用VS.NET2005还有一个原因是它提供了SerialPort类来操作串口,跟一些嵌入式系统的串口通讯更方便.
在使用的过程中发现一个小问题,由于只是小工具,不想使用数据库,所以选用了DataSet的Untyped dataset功能,来存取Binary文件,结果在使用DataTable.Find函数的时候总是找不到记录.我的代码是这样的:
public void Create()
{
dataSet = new DataSet();
dataSet.RemotingFormat = SerializationFormat.Binary;
dataTable = new DataTable();
dataSet.Tables.Add(dataTable);
dataTable.Columns.Add(new DataColumn("card_no"));
dataTable.Columns.Add(new DataColumn("member_no"));
dataTable.Columns.Add(new DataColumn("serial_no"));
dataTable.Columns.Add(new DataColumn("expiry_date", Type.GetType("System.DateTime")));
DataColumn[] keys = new DataColumn[1];
keys[0] = dataTable.Columns["serial_no"];
dataTable.PrimaryKey = keys;
}
public void Open(String fileName)
{
if (File.Exists(fileName))
{
BinaryFormatter bf = new BinaryFormatter();
FileStream fs = new FileStream(fileName, FileMode.Open);
try
{
//dataSet.ReadXml(fileName);
//dataSet.AcceptChanges();
dataSet = (DataSet)bf.Deserialize(fs);
dataSet.AcceptChanges();
}
finally
{
fs.Close();
}
public void Save(String fileName)
{
if (dataSet.HasChanges())
{
BinaryFormatter bf = new BinaryFormatter();
FileStream fs = new FileStream(fileName, FileMode.Create);
try
{
//dataSet.WriteXml(fileName);
bf.Serialize(fs, dataSet);
dataSet.AcceptChanges();
}
finally
{
fs.Close();
}
}
}
用dataTable.Rows.Find()的时候,明明Table中有要找的数据,却始终返回null,而使用ReadXml,WriteXml函数操作Xml格式来存储就没问题(就是漫一点,这是我想用Binary格式的原因).不知是不是我忽略了某些步骤,MSDN2上的说明还是有点简单啊,半天没找来答案.期等正式版!