写了很久的Winform,简单的探出对话框处理一直都没有引起什么注意,最近加到一个Asp.NET项目组,虽然对于从Winform到Web会比较麻烦这点早就有所觉悟的,但是没有想到的是第一只拦路虎居然就是探出对话框。主要遇到的麻烦就是:
一、弹出消息框的时候,线程并不会阻塞等待对话框的确认,而是直接就执行下去了。尤其是在弹出确认对话框后进行页面跳转的时候,没来得及探出对话框就直接执行跳转了。二、使用Ajax的页面直接灸无法正常弹出提示框来。没办法,网上调查了一下,再向同事们请教,总结出了这么些个项目中常用的方法。
为了提高网站的访问的并发度和吞吐量,与其它服务器脚本一样, ASP.NET 同样使用了客户端脚本来减轻服务器的压力。 ASP.NET 到现在 (1.1 版 ) 为止并不直接支持弹出窗口,必须通过 JavaScript (或 VBScript )来使用客户端弹出窗口。
一、 警告窗口与在 CodeBehind 中使用客户端脚本的方式
要在浏览器中弹出一个最简单的警告窗口,可以使用 JavaScript 语句:
window.alert( [sMessage])
|
Private Sub btAlert_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btAlert.Click ’ 演示 Response.Write 方法和 alert 窗口。 Response.Write(" ") End Sub |
Overridable Public Sub RegisterStartupScript( _ ByVal key As String, _ ByVal script As String _ ) |
Private Sub btConfirm_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btConfirm.Click ’ 演示 RegisterClientScriptBlock 方法和 confirm 窗口 If (Not IsClientScriptBlockRegistered("clientScript")) Then ‘ 判断是否已经加入了该脚本,没有则加入。 Dim strScript As String strScript = " " ‘ 注册脚本 RegisterClientScriptBlock("clientScript", strScript) ‘ 如果选择 ” 否 ” ,则继续向下执行。 End If End Sub |
光有提示窗口还远远不能满足我们的要求,在程序中,我们常常需要弹出指定页面。此时可以使用 JavaScript 的 window.open 方法。配合前面的 RegisterClientSciptBlock 方法,我们就可以实现指定页面的弹出。
以下代码展示了如何弹出指定页面 :
Private Sub btWinOpen_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btWinOpen.Click ’ 使用 window.open 与 registerStartupScript 简单演示。 If (Not IsClientScriptBlockRegistered("OpenScript")) Then ’ 判断是否已经加入了该脚本,没有则加入。 Dim strScript As String = " " RegisterStartupScript("OpenScript", strScript) End If End Sub |
这段程序在 IE 中直接使用一切正常。但如果你正在使用类如 GoSurf 、 MyIE2 、 NetCapter 之类的浏览器,那么,很不幸! 你将看不到弹出窗口。这就是我们将要讨论的弹出窗口过滤问题。
三、 非标准 IE 浏览器对弹出窗口的过滤行为讨论
广告窗口的泛滥使得不少网民不堪铺天盖地的广告骚扰纷纷放弃标准 IE 浏览器而使用诸如 GoSurf 、 MyIE2 、 NetCapter 这样的使用 IE 内核支持多页面并能自动屏蔽广告的 软件 。据说在即将发布的 IE6 sp2 中 微软 也将加入封杀广告窗口功能。这对大多数网民当然是件好事,可对于程序员而言,我们使用弹出窗口的方式与一般广告并无本质的不同,这样的窗口也会被弹出窗口管理器不分青红皂白的封杀,其结果当然是我们不愿看到的。有没有一个标准的方式能让窗口正常的弹出呢?这就要求我们了解浏览器封杀广告的原理。通常的广告封杀器使用以下三种方式进行广告过滤:
( 1 )、基于窗口标题的封杀方式
这种封杀方式的原理是定时检查所有的 IE 窗口标题 , 然后于已经有的列表(由程序维护的一个数组列表)来比较 , 如果有相同的 , 我们就关闭这个窗口。显然,这种方式有着诸多缺陷,它封杀了所有的弹出的窗口,管得太死,在程序真正使用的很少。不过,依据它进行的变形方式倒使用得相当的普遍。那就是,基于窗口标题名称的智能过滤技术,它根据弹出窗口的标题是否含有关于广告的关键字进行封杀,这为提高过滤效果作出了很好的探索。
( 2 )、基于窗口类和位置的封杀方式
经过分析发现正常浏览窗口的类名是 IEFRAME 和 CabinetWClass, 而广告窗口的类名是 CabinetWClass 。进一步分析发现:广告窗口的 WorkerA 类和 Shell DocObject View 类的 rect.top 的值是相同的,正常 IE 窗口的 WorkerA 类和 Shell DocObject View 类的 rect.top 的值是不相同的。根据以上两点就可以书写广告杀手程序了。 事实上,我对此程序的通用性持怀疑态度。因为笔者用 Spy++ 分析发现,在 Windows2000( 笔者使用的操作系统 ) 中, IE 窗口的类都为 IEFrame 。同时,由于 Win2000 是一个基于 Unicode 代码的操作系统,所以没有 WorkerA 类,而以 WorkerW 类取而代之。同时,也不存在 rect.top 不相同的情况,由于笔者没有 WindowsXP 操作系统,所以不能针对 WindowsXP 作进一步的试验。
( 3 )、基于 IE COM 组件的封杀方式
以上两种方式都是把 IE 窗口当作一个普通的 Windows 窗口对待,进行判断的。事实上, IE 是一个典型的基于 COM 组件的浏览器,所有的基于 IE 内核的浏览器都是包装 shdocvw.dll 文件,然后书写相应的 BHO 代码。只有这样才能做到真正的控制 IE 浏览器,而不是方法一、二这样的隔靴搔痒。
还有一种基于 IE 内核的弹出窗口封杀方法。它可以在弹出窗口打开之前加以拦截。其原理是:每当 IE 打开一个新的窗口时候都会触发 NewWindow 事件,执行 OnNewWindow2([out] IDispatch*, [out] BOOL *bCancel) 方法。重载此方法,判断打开新窗口事件是否发生在浏览页面已经 下载 完毕之后。如果是,说明是正常的弹出窗口,反之加以拦截。
由于 Gosurf 这样的浏览器本身就重载了 Shocvm.dll 组件,所以使用第三种方法就自然成了顺理成章的事。然而在使用过程中有时也会发现,广告过滤不很完美,但原理基本如此。
1、 点击按钮确定取消
在page_load里加入bt_Del.Attributes["onclick"]="javascript:return confirm('确认删除?')";
2、 需要弹出提示对话框后跳转到页面
Response.Write("<script>alert('注册成功!');location.href='userlogin.aspx';</script>");
3、 允许 ASP.NET 服务器控件在 Page 中发出客户端脚本块:
public virtual void RegisterStartupScript(string key,string script);
举例如下:
if(!this.IsStartupScriptRegistered("hello"))
this.RegisterStartupScript("hello"," <script type="text/javascript"> </script>");
#region 弹出信息框提示
#region 同步事件触发的弹出消息框
public static string ShowMessageBox(Page p_pPage, string IMessageId)
{
string rtStr = string.Empty;
string messageString;
messageString = ConfigurationManager.AppSettings["MSG_COM_" + IMessageId];
string categoryId = IMessageId.Substring(0, 2);
p_pPage.RegisterStartupScript("ErrorInfo", "<script>alert('" + messageString + "');</script>");
rtStr = messageString;
return rtStr;
}
#endregion
#region 异步事件触发的弹出消息框
public static string ShowAsyncMessageBox(UpdatePanel p_oUpdatepanel, string IMessageId)
{
string rtStr = string.Empty;
string messageString;
messageString = ConfigurationManager.AppSettings["MSG_COM_" + IMessageId];
string categoryId = IMessageId.Substring(0, 2);
ScriptManager.RegisterClientScriptBlock(p_oUpdatepanel, p_oUpdatepanel.GetType(), "", "alert('" + messageString + "')", true);
rtStr = messageString;
return rtStr;
}
#endregion
#endregion
RegisterClientScriptBlock的原型与RegisterStartupScript相同,两个函数不同在于将其包含的脚本代码写入到HTML文件的不同位置。RegisterClientScriptBlock在 Page 对象的元素的开始标记后立即发出客户端脚本,RegisterStartupScript则是在Page 对象的元素的结束标记之前发出该脚本。如果你的脚本有与页面对象(doucument对象)进行交互的语句(这在我们后面的例子中看到),则推荐使用RegisterStartupScript,反之如果要想客户端脚本尽可能早的执行,则可以使用RegisterClientScriptBlock或Response.Write。
为了防止在页面中反复加入脚本,在注册脚本时ReisterStartupScript/RegisterClientScriptBlock使用了key作为注册的Key,然后在程序中可以使用IsClientScriptBlockRegistered作判断。
以下例子将使用RegisterClientScriptBlock来演示confirm的使用方法。 (2)使用RegisterXXX方法
如果你观察Response.Write的生成HTML代码,你会发现Response.Write方法生成的代码是写到了HTML代码的最开始,即标签之前。此时,所有的 HTML对象都还没有生成,如果要想使用HTML内的对象,并与之交互,就会出现“找不到对象”的错误。因此,笔者推荐一个更加符合CodeBehind方式的方式----使用RegisterXXX方法。RegisterXXX包括:RegisterClientScriptBlock、RegisterStartupScript以及用于判断的IsStartupScriptRegistered函数。
RegisterStartupScript 的原型是: 其中, sMessage 是提示信息。可惜,这样的弹出窗口是只有一个 “ 确定 ” 按钮,只能起到提示作用。如果我们要在删除记录时候弹出一个询问的弹出窗口,此时你需要使用:
bConfirmed = window.confirm( [sMessage])
其中: bConfirmed 是返回值, sMessage 是提示信息。这个弹出窗口有两种选择: “ 确定 ” 或 “ 放弃 ” ,其选择的返回值放在 bConfirmed 中,可供代码作出判断。
为了提高代码的可重用性与可读性,应当使 JavaScript 与 Codehind 相互溶合。通常有两种方式可以达到这样的效果。
( 1 ) 使用 Response.Write 方法:
使用 Response.Write 方法早在 ASP 时代就已经被支持了。它可以把代码写到客户端,是一种相当方便且直观的方法。以下代码演示了如何使用 Response.Write 方法来显示一个警告信息。