webabcd - 专注于asp.net

ASP.NET
从现在开始 一切都不晚
posts - 149, comments - 4064, trackbacks - 328, articles - 0
  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理
原文地址:http://www.dotnetbips.com/articles/36f06160-5221-4d27-a177-320927f4b962.aspx
[原文源码下载]
[译者改后源码下载]


[翻译]使用ASP.NET AJAX让GridView的数据行显示提示框(ToolTip)


原文发布日期:2007.05.06
作者:Bipin Joshi
翻译:webabcd


介绍
ASP.NET AJAX可以使你的web应用程序具有更丰富的功能和更多的用户响应。 本文中,我将演示如何通过ASP.NET AJAX的帮助,给像GridView这样的数据绑定控件的数据行增加popup提示框。

初看这个需求后,你可能会想到使用AJAX Control Toolkit来实现这个功能。 因为AJAX Control ToolKit里包含一个悬浮菜单控件(HoverMenu)。 但是,如果菜单是动态读取的话,你就不能使用这个控件了。 一个HoverMenu Extender仅能被附加到一个目标控件。 我们可以想象一下,如果你有一个GridView,它里面有一个HyperLinkField类型的列。 当用户的鼠标经过这个HyperLink的时候,你希望弹出一个Popup提示框来显示该数据行的详细信息。 此时,使用AJAX Control Toolkit就不能达到我们的要求了。 本文中,我将图文结合的演示如何完成这样的功能。


提示框的图例
在开讲之前让我们先来看一看这个提示框是如何显现的。 请看下图:


上图演示了一个内含GridView控件的Web窗体。 这个GridView控件由一个HyperLinkField类型的列构成。 当你的鼠标浮动到任何超链上的时候,就会弹出一个提示框,用来显示改数据行的更详细的信息。 这段通过提示框显示的描述文本是从数据库中动态读取的,也就是说它不是静态文本。

现在就让我们开始开发这个提示框吧!

首先新建一个名为AJAXTooltip的ASP.NET AJAX-Enabled Web Site。



创建数据库
为了实现本示例的功能,你需要创建一个数据库,该数据库内有一个名为Menus的表。 右键单击App_Data文件夹,选择“添加新项…”。 然后添加一个名为Database.mdf的SQL数据库。


在服务器资源浏览器中创建一个名为Menus的表。 其表结构如下:


MenuID列是关键字,它是每一条菜单的唯一ID。 Text列保存着用来在浏览器中显示的菜单项的文本。 NavigateUrl列为单击该菜单项后应当链接到的URL。 最后的TooltipText列保存的是当鼠标经过菜单项后所显示的详细文本。

建完表后,再在表中插入一些数据,这样就会给之后测试你的web程序时提供方便。


创建Web Service
ASP.NET AJAX允许你通过客户端脚本调用web services。 在我们的这个例子中就使用了这项技术。 首先在你的web程序中新建一个web service(WebService.asmx),并在其内添加一个web method。代码如下:
[WebMethod]
public string GetToolTipText(int menuid) 
{
    
string strConn = ConfigurationManager.ConnectionStrings["connstr"].ConnectionString;
    SqlConnection cnn 
= new SqlConnection(strConn);
    
    cnn.Open();
    
    SqlCommand cmd 
= new SqlCommand();
    cmd.Connection 
= cnn;
    cmd.CommandText 
= "select tooltiptext from menus where menuid=@id";
    
    SqlParameter p 
= new SqlParameter("@id", menuid);
    cmd.Parameters.Add(p);
    
    
object obj = cmd.ExecuteScalar();
    
    cnn.Close();
    
    
return obj.ToString();
}

其中的GetToolTipText()方法需要一个菜单ID作为参数,然后返回提示框文本。 这段代码首先使用ConfigurationManager类从web.config中获取数据库连接字符串。 然后创建一个SqlConnection和一个SqlCommand对象。 最后,使用SqlCommand类的ExecuteScalar()方法,根据菜单ID获得相应的提示框文本。

数据库连接字符串保存在web.config中的<connectionStrings>节点下。代码如下:
<connectionStrings>
    
<add name="connstr" connectionString="Data Source=.\SQLEXPRESS;AttachDbFilename=|DataDirectory|\Database.mdf;Integrated Security=True;User Instance=True" providerName="System.Data.SqlClient" />
</connectionStrings>

注意Database.mdf文件的路径使用的是AttachDbFilename属性。 用这种方法就会使App_Data文件夹(DataDirectory)中的数据库附加到SQL Server Express上。

到此之前都和以往开发web service是一样的。 但是,这个web service是从客户端脚本调用的。 也就是说,客户端脚本为了调用这个web service需要由一个代理。 我们可以给这个web service增加一个[ScriptService]属性来实现这个特性。 该属性在System.Script.Services命名空间内。 该命名空间在System.Web.Extensions程序集中。 [ScriptService]属性必须像下面这样应用到web service类中。
[ScriptService]
public class WebService : System.Web.Services.WebService

这样就完成了我们的web service


开发Web Form
现在打开默认的web form – Default.aspx – 确保其内有ScriptManager控件的实例。 拖拽一个SQL数据源控件到页上,并配置它,以使它可以从Menus表中读取全部记录。


接下来,拖拽一个GridView控件进来并设置它的DataSourceID属性为SqlDataSource1。 继续设置这个GridView控件,使它有一个类型为HyperLinkField的列。 然后如下设置该HyperLinkField列的属性:


指定DataNavigateUrlFields属性为一个用逗号分隔的字段列表,其中所有字段的值都可以作用于URL中。 DataNavigateUrlFormatString属性指向格式化后的URL。 因为我们的Menus表中的NavigateUrl列保存的就是URL,所以我们不需要做任何格式化的工作。 因此DataNavigateUrlFormatString属性只包含一个站位符 – {0}。 在运行时,NavigateUrl列的真实值将会替代{0}。 最后,DataTextField属性的值为超链所显示的文本。

在我们如此设置了GridView控件之后,再拖拽一个Panel控件到页上。 设置这个Panel控件的BorderStyle属性为Solid,BorderColor属性为#FF8000,BackColor属性为Yellow。 这个Panel控件是用来显示提示框的。

最后,我们需要使用ScriptManager控件添加一个指向到我们的web service的引用。 如下图所示,定位到ScriptManager的Services属性,然后添加一个指向到我们的web service的引用即可。


创建AJAX类
ASP.NET AJAX给客户端脚本增加一些面向对象的特性。 为了使用这些特性,我们需要开发一个名为ToolTip的类。 这个ToolTip类是用来显示提示框的,我们首先要隐藏它,然后调用我们的web service。 ToolTip类的全部代码如下:
<script type="text/javascript">
Type.registerNamespace(
"Demo");

Demo.ToolTip
=function(panelid)
{
  
this._panelid=panelid;
  
this.x=0;
  
this.y=0;
}

        
Demo.ToolTip.prototype
=
{
  get_PanelID:
function()
  
{
    
return this._panelid;
  }
,
            
  set_PanelID:
function(panelid)
  
{
    
this._panelid=panelid;
  }
,
            
  BeginShowToolTip:
function(event,menuid)
  
{
    WebService.GetToolTipText(menuid,
this.
    EndShowToolTip,
this.OnError,this.OnTimeOut);
    
this.x=event.clientX + 10;
    
this.y=event.clientY + 10;
  }
,
            
  EndShowToolTip:
function(result)
  
{
    
var pnl=$get(tooltip.get_PanelID());
    
if(pnl.innerText!=null)
    
{
      pnl.innerText
=result;
    }

    
else
    
{
      pnl.textContent
=result;
    }

  pnl.style.visibility
="visible";
  pnl.style.display
="inline";
  pnl.style.position
="absolute";
  pnl.style.left
= tooltip.x + "px";
  pnl.style.top
= tooltip.y + "px";
  }
,
            
  HideToolTip:
function()
  
{
    
var pnl=$get(this.get_PanelID());
    pnl.style.visibility
="hidden";
    pnl.style.display
="none";
  }
,
            
  OnError:
function(result)
  
{
    alert(result.get_message());
  }
,         
           
  OnTimeOut:
function(result)
  
{
    alert(result);
  }

}

        
Demo.ToolTip.registerClass(
"Demo.ToolTip");
</script>

我们需要把上面的这段脚本放到ScriptManager标记之后。 一般来说,你应该把它放到</form>标签之后。

这段代码首先使用Type类的registerNamespace()方法注册了一个名为Demo的命名空间。

然后创建了一个名为ToolTip的类。 这个ToolTip类的构造函数需要一个用来显示提示框的Panel的ID作为参数。 这个Panel的ID存储在一个名为_panelid的私有变量中。 另外,构造函数内还声明两个私有变量 – (x 和 y) – 它们用来保存鼠标的坐标。

在ToolTip类的原型中定义了一些属性和方法。 getter和setter方法用来读取和设置PanelID这个属性。 PanelID这个属性非常简单,就是设置和返回变量_panelid而已。

BeginShowToolTip()方法用来调用web service的GetToolTipText()方法。 它需要两个参数 - (event和menuid)。 这两个参数所提供的信息分别是当前的JavaScript事件和需要显示的提示框的菜单项的ID。 其内部会调用web service的GetToolTipText()方法。 注意,WebService会为真实的web service生成一个客户端代理。 GetToolTipText()方法的第1个参数是menuid。 第2、3、4个参数分别是执行成功、失败、超时后所调用的方法。 最后,变量x和y被设置为鼠标当前的位置。 为了确保弹出框不会显示在鼠标下面,所以给x和y坐标分别加上10个像素的偏移量。 你也可以根据不同的需求调整这个偏移量。 当用户的鼠标经过HyperLinkField列的超链上的时候,就会调用这个BeginShowToolTip()方法。

一旦web service返回了提示框的文本就会调用EndShowToolTip()方法。 web service的返回值就是该方法的result参数。 就下来的代码通过设置Panel的visibility、position、left和top属性来显示它自己。 注意,为了使我们的这个类能在IE和FireFox中工作,必须要保证innerText属性是有效的。

HideToolTip()方法会在鼠标离开超链后被调用,它会通过设置Panel的CSS属性来隐藏掉Panel。

OnError()和OnTimeout()方法会以弹出窗口的方式来显示相应的错误信息。

最后,用我们刚创建好的这个ToolTip类的registerClass()方法将ToolTip类注册到ASP.NET AJAX的客户端框架。

现在,将如下这段脚本添加到web form的<head>节点中。
<script type="text/javascript">
var tooltip=null;    
function pageLoad()
{
tooltip
=new Demo.ToolTip("Panel1");
tooltip.HideToolTip();
}

</script>

这段脚本中有一个名为pageLoad()的函数。 当该网页在浏览器中加载的时候,这个函数会被ASP.NET AJAX框架自动地调用。 在这个函数内部首先会创建一个ToolTip类的实例, 然后调用ToolTip类的HideToolTip()方法来隐藏掉Panel。


附加上JavaScript事件处理器
我们的GridView控件所包括的数据行是依靠于数据库中的Menus表中的记录的。 为了使用户的鼠标在经过任何超链上的时候都调用BeginShowToolTip()方法,我们需要在GridView的RowDataBound事件内写一些代码。 其全部内容如下:
protected void GridView1_RowDataBound(object sender, GridViewRowEventArgs e)
{
    
if (e.Row.RowType == DataControlRowType.DataRow)
    
{
        HyperLink hl 
= e.Row.Cells[0].Controls[0as HyperLink;
        
int menuid = Convert.ToInt32(GridView1.DataKeys[e.Row.RowIndex].Value);
        hl.Attributes.Add(
"onmouseover""tooltip.BeginShowToolTip(event," + menuid + ")");
        hl.Attributes.Add(
"onmouseout""tooltip.HideToolTip()");
    }

}

GridView的每一行,包括HeaderRow和FooterRow都会触发RowDataBound事件。 所以我们首先要检查当前行是否是DataRow,也就是说,行中必须包括实际的数据值。 如果是数据行,那么就使用Cell中的控件集合来得到HyperLink控件。 菜单ID可以通过GridView的DataKeys集合来获取。 最后,使用HyperLink控件的Attributes集合来增加名为onmouseover和onmouseout的客户端事件处理器。 onmouseover对应ToolTip类的BeginShowToolTip()方法,onmouseout对应ToolTip的HideToolTip()方法。

以上就是本文的全部内容。 你现在就可以运行一下这个web form,看看我们做的这个提示框的效果。


总结
ASP.NET AJAX允许你通过客户端脚本调用web services。 这个特性可以带给我们很多不同以往的实现。 在本例中,我们给GridView的数据行创建了一个动态的提示框。 ASP.NET AJAX extensions可以让我们以面向对象的方式来编写JavaScript,它将使我们的代码变得更健壮、更清晰。


作者:Bipin Joshi
Email:http://www.dotnetbips.com/contact.aspx
简介:Bipin Joshi是DotNetBips.com的管理员。他是http://www.binaryintellect.com/的发起人,这个公司提供.NET framwork的培训和咨询服务。他在印度孟买为开发者提供培训。他也是微软的MVP(ASP.Net)和ASPInsiders的会员。

Feedback

#1楼    回复  引用    

2007-05-16 15:54 by ivw [未注册用户]
支持。。。
这几天都很忙,都没有机会上网。呵呵。
你这个例子如果我人同时查看可能会对服务器做成很大的压力,每次经过都要读库,能否像asp那样把记录一次读完然后保存在tip这个属性里呢?

#2楼 [楼主]   回复  引用  查看    

2007-05-16 18:34 by webabcd      
@ivw
确实,实际应用中没人会这么做的
不过对于学习ajax来讲,这篇文还是很好的

#3楼    回复  引用    

2007-08-01 09:02 by John [未注册用户]
这段代码结构清晰, 就算一次全部读出来, 代码改动也很小.

#4楼 [楼主]   回复  引用  查看    

2007-08-01 09:43 by webabcd      
@John
嗯,是地

#5楼    回复  引用  查看    

2007-08-14 15:03 by Wolf.Jiang      
謝謝提供!

#6楼 [楼主]   回复  引用  查看    

2007-08-14 15:17 by webabcd      
@Wolf.Jiang
:)
不谢

#7楼    回复  引用  查看    

2007-08-20 14:19 by 细节决定成败,习惯决定未来      
注意大小写,贴出来的代码有问题.

#8楼 [楼主]   回复  引用  查看    

2007-08-20 18:40 by webabcd      
@细节决定成败,习惯决定未来
:)
嗯,原文作者喜欢开头大写,不符合规范

#9楼    回复  引用    

2007-10-11 16:33 by burningcpu [未注册用户]
为什么我将这个代码改用VB不行? 错误的可能有哪些?

#10楼 [楼主]   回复  引用  查看    

2007-10-11 17:49 by webabcd      
@burningcpu
可以啊
把相关的C#改成VB.NET就行了啊

#11楼    回复  引用    

2007-10-11 22:09 by burningcpu [未注册用户]
楼主你好,确实不行。我把default.aspx换着vb.net,调用vb写的webservice,单独运行,2者都没有问题。但是如果webservice用C#写,用vb调就没问题。不知道问题出在哪里,难道vb写的webserice和C#写的有区别吗?还是web.config里面需要修改(因为我是直接将web.config拷入我的vb工程里面的)。楼主也可以试试。可以联系我讨论一下:QQ103679201 MSN:ms_zhoulu@hotmail.com
以下是我转换的vb代码:
webservice:

Imports System
Imports System.Web
Imports System.Collections
Imports System.Web.Services
Imports System.Web.Services.Protocols
Imports System.Web.Script.Services
Imports System.Data
Imports System.Data.SqlClient
Imports System.Configuration

<WebService(Namespace:="http://tempuri.org/")> _
<WebServiceBinding(ConformsTo:=WsiProfiles.BasicProfile1_1)> _
<Global.Microsoft.VisualBasic.CompilerServices.DesignerGenerated()> _
Public Class WebService
Inherits System.Web.Services.WebService

<WebMethod()> _
Public Function GetToolTipText(ByVal menuid As Integer) As String
Dim strConn As String = ConfigurationManager.ConnectionStrings("connstr").ConnectionString
Dim cnn As SqlConnection = New SqlConnection(strConn)
cnn.Open()
Dim cmd As SqlCommand = New SqlCommand()
cmd.Connection = cnn
cmd.CommandText = "select tooltiptext from menus where menuid=@id"
Dim p As SqlParameter = New SqlParameter("@id", menuid)
cmd.Parameters.Add(p)
Dim obj As Object = cmd.ExecuteScalar()
cnn.Close()
Return obj.ToString()
End Function
End Class
----------------------------------------------
default.aspx.vb

Imports System
Imports System.Data
Imports System.Configuration
Imports System.Web
Imports System.Web.Security
Imports System.Web.UI
Imports System.Web.UI.WebControls
Imports System.Web.UI.WebControls.WebParts
Imports System.Web.UI.HtmlControls

Partial Class _Default
Inherits System.Web.UI.Page

Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load

End Sub

Protected Sub GridView1_RowDataBound(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.GridViewRowEventArgs) Handles GridView1.RowDataBound
If e.Row.RowType = DataControlRowType.DataRow Then
Dim hl As HyperLink = CType(e.Row.Cells(0).Controls(0), HyperLink)
Dim menuid As Integer = Convert.ToInt32(GridView1.DataKeys(e.Row.RowIndex).Value)
hl.Attributes.Add("onmouseover", "tooltip.BeginShowToolTip(event," & menuid & ")")
hl.Attributes.Add("onmouseout", "tooltip.HideToolTip()")
End If
End Sub
End Class

#12楼 [楼主]   回复  引用  查看    

2007-10-12 08:06 by webabcd      
@burningcpu
看你的代码应该是没有问题的
和web.config没关系

方便的话,把你的详细错误贴出来看看

#13楼    回复  引用    

2007-10-17 17:39 by KP_Grant [未注册用户]
樓主你好..將 default.aspx 與 webservice 改成使用 VB.NET 撰寫, 卻發生
無法叫用 webservice 的情況, 下面這一段碼像是沒有了效用一樣
<Services>
<asp:ServiceReference Path="~/WebService.asmx" />
</Services>

不知道這是甚麼原因, 尋求你的幫忙, 謝謝。

#14楼 [楼主]   回复  引用  查看    

2007-10-17 18:43 by webabcd      
@KP_Grant
对vb没研究,不过这应该和语言没关系
有问题的话,我感觉应该是web service的问题,可以单独测试一下看看

#15楼    回复  引用    

2007-10-22 02:33 by burningcpu [未注册用户]
我上次那个问题:如果使用VB改写的话,有问题。
这个问题已经解决了,原因非常简单,就是在webservice里面应该加上:[ScriptService](c#),<ScriptService()> _(vb) 而且还要注意引用Imports System.Web.Script.Services。问题就出在这里,因为如果不加这个的话,无法使用script调用webservice。

总结:想要使用ASP.NET AJAX在客户端JavaScript中异步调用服务器端Web Service,我们需要:

为Web Service类或需要暴露给客户端的Web Service方法添加[ScriptService]属性;
为Web Service中需要暴露给客户端的方法添加[WebMethod]属性;
在页面中的ScriptManager控件中添加对该Web Service的引用;
在客户端使用如下JavaScript语法调用该Web Service:
[NameSpace].[ClassName].[MethodName](param1, param2 ......, callbackFunction)
为客户端异步调用指定回调函数,在回调函数中接收返回值并进一步处理。

#16楼 [楼主]   回复  引用  查看    

2007-10-22 07:56 by webabcd      
@burningcpu
:)
能解决问题就好