代码改变世界

SwitchPartManager:在UpdatePanel中灵活地切换不同用户控件

2007-03-29 03:18  Jeffrey Zhao  阅读(8883)  评论(82编辑  收藏  举报

这是一个很常见的使用场景,尤其是出现了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 B</asp: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时作为<input type="hidden" />的形式输出。我在这里使用了HiddenField作为保留控件选择状态的方式,这样可以避免因为ViewState被禁用而导致的数据丢失。

这样,我们的页面在PostBack之后,就能够通过接受到的信息来恢复UpdatePanel的控件了。我在在Page的InitComplete时恢复UpdatePanel中的控件——很容易,直接使用SwitchTo方法就可以了。

这几乎就是SwitchPartManager的完整代码。其实这个类相当的简单,但是它的价值却不小。但是根据我的经验,似乎为了自己的项目开发Custom Control的朋友不多,大家大都是在写用户控件(ascx)。其实在有些时候,为自己的应用编写一个Custom Control,尤其是用于“管理”的控件,其实能够使页面的逻辑变得清晰许多。

 

点击这里下载SwitchPartManager的代码。