这是一个很常见的使用场景,尤其是出现了UpdatePanel之后:页面中有一排菜单,点击菜单中的每一项,都会使某个UpdatePanel中出现不同的控制面板。在开发时,往往会将每个的控制面板写成不同的用户控件,点击菜单时事实上就是在UpdatePanel中放入不同的用户控件。
如果要开发这样的功能,从理论上来说并不困难,但是如果要能够在控件之间灵活切换,甚至要从控件A的某个操作中切换到控件B,可能就需要增加控件之间的耦合度了。因此,如何控制这样的切换似乎需要细细考虑一下。
在这里,我选择使用一个第三方的控件来进行统一处理,这个控件就是SwitchPartManager。在了解这个控件的实现之前,我们先来看一下一个简单的使用示例吧。
使用效果
首先,在页面中,会使用两个按钮在两个用户控件之间进行切换。在SwitchPartManager的PlaceHolderUpdatePanelID属性指定了作为容器的UpdatePanel,而点击不同的按钮,则会调用ScriptManager的SwitchTo方法切换至不同的控件。如下:
<jeffz:SwitchPartManager ID="SwitchPartManager1" runat="server" PlaceHolderUpdatePanelID="UpdatePanel1" /> <asp:Button ID="ButtonA" runat="server" Text="ControlA" OnClick="ButtonA_Click" /> <asp:Button ID="ButtonB" runat="server" Text="ControlB" OnClick="ButtonB_Click" /> <hr /> <asp:UpdatePanel ID="UpdatePanel1" runat="server">asp:UpdatePanel>
protected void Page_Load(object sender, EventArgs e) { ScriptManager sm = ScriptManager.GetCurrent(this); sm.RegisterAsyncPostBackControl(this.ButtonA); sm.RegisterAsyncPostBackControl(this.ButtonB); } protected void ButtonA_Click(object sender, EventArgs e) { SwitchPartManager.GetCurrent(this).SwitchTo("ControlA"); } protected void ButtonB_Click(object sender, EventArgs e) { SwitchPartManager.GetCurrent(this).SwitchTo("ControlB"); }
在ControlA中有一个按钮,点击它之后将会切换到ControlB。如下:
This is Control A. <asp:LinkButton ID="LinkButton1" runat="server" OnClick="LinkButton1_Click">Switch To Control Basp:LinkButton>
protected void LinkButton1_Click(object sender, EventArgs e) { SwitchPartManager.GetCurrent(this.Page).SwitchTo("ControlB"); }
而在ControlB中,它自身含有一个UpdatePanel和一个按钮,点击按钮则可以刷新时间:
This is Control B <br /> <asp:UpdatePanel ID="UpdatePanel1" runat="server"> <ContentTemplate> <%= DateTime.Now %> <asp:Button ID="Button1" runat="server" Text="Refresh Time" /> ContentTemplate> asp:UpdatePanel>
编写代码,控件之间的切换都是非常的容易。大家可以点击这里查看使用效果。
SwitchPartManager控件实现
实现这么一个控件其实比想象中容易许多。处理这个问题的关键在于如何在(同步或异步)PostBack后正确地恢复当前已经加载的控件。只要能够正确恢复了控件的状态,剩下的问题都是由ASP.NET自身的机制来完成了,例如触发事件等等。
我们来看一下SwitchPartManager的关键实现代码:
[PersistChildren(false)] [ParseChildren(true)] [NonVisualControl] public class SwitchPartManager : Control { private const string HiddenElementName = "__PartType__"; private bool initialized = false; private string partTypeToSave = null; public static SwitchPartManager GetCurrent(Page page) { return page.Items[typeof(SwitchPartManager)] as SwitchPartManager; } protected override void OnInit(EventArgs e) { base.OnInit(e); if (this.Page.Items.Contains(typeof(SwitchPartManager))) { throw new InvalidOperationException("One SwitchPartManager per page."); } this.Page.Items[typeof(SwitchPartManager)] = this; this.Page.InitComplete += new EventHandler(Page_InitComplete); this.Page.PreRenderComplete += new EventHandler(Page_PreRenderComplete); } private void Page_InitComplete(object sender, EventArgs e) { this.initialized = true; string partType = this.Page.Request.Params[SwitchPartManager.HiddenElementName]; if (partType != null) { this.SwitchTo(partType); } } private void Page_PreRenderComplete(object sender, EventArgs e) { if (!String.IsNullOrEmpty(this.partTypeToSave)) { ScriptManager.RegisterHiddenField(
this.Page, SwitchPartManager.HiddenElementName, this.partTypeToSave); } } // 得到作为容器的UpdatePanel private UpdatePanel PlaceHolderUpdatePanel { get { // ... } } public void SwitchTo(string partType) { Control container = this.PlaceHolderUpdatePanel.ContentTemplateContainer; container.Controls.Clear(); Control control = this.Page.LoadControl(partType + ".ascx"); control.ID = "JustToPreserveUniqueName"; container.Controls.Add(control); this.partTypeToSave = partType; } }
SwitchTo方法是用于切换用户控件的方法,它会将UpdatePanel内已有的控件(例如从ControlA切换到ControlB时,UpdatePanel中已经有了ControlA)清除,然后再向UpdatePanel中添加新的控件。
我在这里将当前当前的控件标识记录在私有变量partTypeToSave中,它会在Page的PreRenderComplete时作为的形式输出。我在这里使用了HiddenField作为保留控件选择状态的方式,这样可以避免因为ViewState被禁用而导致的数据丢失。
这样,我们的页面在PostBack之后,就能够通过接受到的信息来恢复UpdatePanel的控件了。我在在Page的InitComplete时恢复UpdatePanel中的控件——很容易,直接使用SwitchTo方法就可以了。
这几乎就是SwitchPartManager的完整代码。其实这个类相当的简单,但是它的价值却不小。但是根据我的经验,似乎为了自己的项目开发Custom Control的朋友不多,大家大都是在写用户控件(ascx)。其实在有些时候,为自己的应用编写一个Custom Control,尤其是用于“管理”的控件,其实能够使页面的逻辑变得清晰许多。
点击这里下载SwitchPartManager的代码。