修改和使用 PopupControlExtender 创建嵌套弹出窗口
改进的 AJAX 控件工具包 PopupControlExtender 控件及其使用方法,用于创建嵌套弹出控件。
引言
本文介绍了一个改进的 AJAX 控件工具包 PopupControlExtender
控件,以及如何使用它来创建嵌套弹出控件。
背景
PopupControlExtender
是一个有用的用户界面控件,可以改善 Web 应用程序的用户体验。使用 PopupControlExtender
,您可以点击页面上的其他部分来取消操作并关闭打开的弹出控件,甚至可以使用同一次点击打开另一个弹出控件。弹出控件广泛用于实现数据选择控件,例如自定义组合框、日期选择器、用户名选择器等。
PopupControlExtender
本身对于大多数场景已经足够强大,但它一次只能打开一个弹出窗口,因此您无法创建嵌套的弹出控件。如果您尝试在一个已打开的弹出窗口内打开另一个弹出窗口,已打开的弹出窗口将被关闭,而新的弹出窗口将不会显示。
为了解决这个问题,我对 PopupControlExtender
控件进行了一些改进,使其能够同时打开多个弹出控件。
使用代码
下载演示项目,先进行尝试,然后可以直接将演示项目中 bin 目录下的 AjaxControlToolkit.dll 引用到您的项目中。基于 2011 年 4 月 1 日发布的源代码,我创建了 .NET 3.5 的调试版 AjaxControlToolkit.dll。如果您需要发布版 DLL 或 .NET 4.0 的 DLL,请下载 AJAX 控件工具包的源代码,搜索并替换文章附件中的 PopupControlBehavior.pre.js 文件,然后进行构建。
这是演示项目中的一段代码示例
Date:<asp:TextBox ID="TextBox1" Width="100px" autocomplete="off"
ReadOnly="false" runat="server"></asp:TextBox>
<asp:Panel ID="Panel1" Style="display: none;" runat="server">
<div style="background-color: #7777cc; border: solid 2px #234389;
width: 400px; padding: 10px;">
<div style="padding: 2px; background-color: #234389;
color: White; font-weight: bold;">
Popup1 (yyyy-MM-dd)</div>
<br />
<asp:UpdatePanel ID="UpdatePanel1" UpdateMode="Conditional" runat="server">
<ContentTemplate>
<asp:TextBox ID="TextBox2" runat="server"></asp:TextBox>
<span style="color: White;">(Please select Year and Month)</span>
<asp:Panel ID="Panel2" runat="server">
<!-- content of Panel2 was omitted -->
</asp:Panel>
<asp:PopupControlExtender ID="PopupControlExtender2"
PopupControlID="Panel2" TargetControlID="TextBox2"
Position="Bottom" runat="server">
</asp:PopupControlExtender>
<br />
<br />
<span style="color: White;">Day:</span>
<asp:TextBox ID="TextBox2_Other1" runat="server"></asp:TextBox>
<asp:Button ID="Button2" OnClick="Button2_Click"
runat="server" UseSubmitBehavior="false" Text="Button2" />
<br />
<br />
<asp:Button ID="ButtonOk1" runat="server" UseSubmitBehavior="false"
Text="Ok1" OnClick="ButtonOk1_Click" />
<asp:Button ID="ButtonCancel1" runat="server" UseSubmitBehavior="false"
Text="Cancel1" OnClick="ButtonCancel1_Click" />
<div style="width:10px; height:90px;"></div>
<asp:TextBox ID="TextBox2_2" runat="server"></asp:TextBox>
<span style="color: White;">
(Anthor popup in the same nested level.)</span>
<asp:Panel ID="Panel2_2" runat="server">
<div style="width:100px; height:60px;background-color: orange;
border: solid 2px #884322;" ></div>
</asp:Panel>
<asp:PopupControlExtender ID="PopupControlExtender2_2"
PopupControlID="Panel2_2" TargetControlID="TextBox2_2"
Position="Bottom" runat="server">
</asp:PopupControlExtender>
</ContentTemplate>
</asp:UpdatePanel>
</div>
</asp:Panel>
<asp:PopupControlExtender ID="PopupControlExtender1" PopupControlID="Panel1"
TargetControlID="TextBox1" Position="Bottom" runat="server">
</asp:PopupControlExtender>
这是代码隐藏文件
#region PopupControlExtender1
protected void Button2_Click(object sender, EventArgs e)
{
this.TextBox2_Other1.Text = DateTime.Now.Day.ToString("D2");
}
protected void ButtonOk1_Click(object sender, EventArgs e)
{
this.PopupControlExtender1.Commit(this.TextBox2.Text + "-" +
this.TextBox2_Other1.Text);
}
protected void ButtonCancel1_Click(object sender, EventArgs e)
{
this.PopupControlExtender1.Cancel();
}
#endregion
在此演示项目中,我们创建了五个 PopupControlExtender
控件实例,它们构成了一个三级嵌套结构。我们按级别描述弹出控件。例如,在演示中,文档的 body 是第 0 级,弹出窗口“Popup1”是第 1 级,“Popup2”是第 2 级,依此类推。弹出规则如下:
- 如果您点击
PopupControlExtender
的目标控件,当前级别的弹出窗口将被关闭,然后PopupControlExtender
的弹出窗口将被打开。 - 如果您点击一个弹出窗口,当前级别所有已打开的弹出窗口都将被关闭。
- 如果您点击文档的 body,所有已打开的弹出窗口都将被关闭。
- 在同一级别,同一时间最多只能打开一个弹出窗口。
将 UpdatePanel
放置在弹出控件内部是使用 PopupControlExtender
最常见的样式;否则,您将无法利用 Microsoft AJAX Framework 的优势,并且必须实现自己的客户端和服务器端逻辑来更新弹出控件的内容。在本文中,我们仅讨论使用 UpdatePanel
的情况。在演示中,所有按钮都是服务器端控件,点击这些按钮将导致部分回发。年、月、日的数值都由服务器生成。为了使 PopupControlExtender
能够正常工作,我们需要按照以下规则使用 UpdatePanel
:
- 所有
UpdatePanel
的UpdateMode
属性值应设置为“Conditional
”。 - 所有
Button
的UseSubmitBehavior
属性值应设置为“false”。
实际上,使用原始的 PopupControlExtender
和改进的 PopupControlExtender
之间没有区别,除了使用改进的 PopupControlExtender
,您可以将 PopupControlExtender
的目标设置在弹出控件上,并同时打开两个或多个嵌套的弹出控件。
顺便说一下,在实际项目中,您应该将这些弹出控件拆分成单独的用户控件,以使代码更具可读性、可维护性和可重用性。
对 PopupControlExtender 控件的改进
AJAX 控件工具包中的所有控件都有服务器控件和客户端行为。为了使 PopupControlBehavior
控件支持嵌套弹出窗口,我们需要修改 PopupControlExtender
控件的客户端行为,该行为定义在 PopupControlBehavior.pre.js 文件中。
如果您阅读了 PopupControlBehavior.pre.js 中的以下代码,您就会明白为什么 PopupControlExtender
一次最多只能显示一个弹出窗口。
// This global variable tracks the currently visible popup. Automatically
// hiding the popup when focus is lost does not work with our mechanism to
// hide the popup when something else is clicked... So we will instead go for
// the weaker strategy of letting at most one popup be visible at a time.
Sys.Extended.UI.PopupControlBehavior.__VisiblePopup = null;
是的,这就是它的设计方式。
在一个包含嵌套弹出控件的页面中,页面和所有弹出控件构成一个**弹出树**,文档的 body 是根节点。例如,在演示项目中,弹出树如下所示:
我们不需要创建数据结构来包含此弹出树的信息,我们只需要维护一个堆栈来记录所有已打开弹出控件的 PopupControlBehavior
对象,并按打开的顺序排列。因此,我们创建了一个静态字段,如下所示:
// @@@@@
// Tracks all the opened popups.
Sys.Extended.UI.PopupControlBehavior.__VisiblePopups = [];
例如,当 Popup3 打开时,__VisiblePopups
数组的值如下:[Popup1, Popup2, Popup3]。
当发生点击事件时:如果一个弹出控件捕获了事件,它会阻止事件冒泡,并找出其 PopupControlBehavior
对象在 __VisiblePopups
数组中的位置,然后弹出它之上的所有 PopupControlBehavior
对象,并调用这些对象的 hidePopup
方法。如果 body 捕获了点击事件,则调用 __VisiblePopups
中所有元素的 hidePopup
方法,并清空该数组。
当 PopupControlExtender
的目标控件捕获到点击或焦点事件时,找出相关的 PopupControlBehavior
对象在 __VisiblePopups
数组中的位置,弹出它之上的所有 PopupControlBehavior
对象,并调用这些对象的 hidePopup
方法,然后显示新打开的弹出控件。
您可以查看源代码以获取更多详细信息。