• 动态加载控件UserControl到页面上:视图状态问题


    前言:一些介绍

    动态加载控件

    视图状态的保存

    重构页面/控件

    初始化页面/控件/IsPostBack



     

    了解一下控件的生命周期。

    1Instantiate
    2
    Initialize
    3
    Begin Tracking View State
    4
    Load View State (postback only)
    5
    Load Postback Data (postback only)
    6
    Load
    7
    Raise Changed Events (postback only, optional)
    8
    Raise Postback Events (postback only, optional)
    9
    PreRender
    10
    SaveViewState
    11
    Render
    12
    Unload
    13
    Dispose

    视图状态ViewStateasp.net提供的一个集合,用来保存页面上控件的状态。

    视图状态的恢复asp.net会在某个特定的时刻(Load View State)把保留在ViewState集合中的数据恢复到相应的控件中(根据该控件的id

    控件状态的追赶论:在contrlParant(已经到状态n,n肯定就在上述13个中的一个)中加入一个contrlChild,那么controlChild的状态在controlParent.Control.Add(contrlChild)之后就会立即经历n-1个状态,到达和contrlParant同步的状态。

     

    -------------------------

     

    这是一个困扰了我近两年的问题。刚开始是无法恢复状态;后来想办法恢复了状态但是无法避免重复的初始化(浪费效率);再后来解决了,但是认识比较浅。今天终于有所悟。

    问题的提出:在一个aspx页面上,根据传递的参数来加载不同的UserControl。保证UserControl的顺利执行还有效率:

    今天在网上搜索,得到这样的文章http://www.cnblogs.com/alex.zhang/archive/2005/03/31/129427.html。里面写得洋洋洒洒。结合我自己的应用,简单的说一下。

    问题在哪里?

    比如你在一个Page_Load事件里面这样写

     

      protected void Page_Load(object sender, EventArgs e)
        
    {
            
    if (!IsPostBack)
            

                Button btn
    =new Button();
                btn.Click
    +=new EventHandler(btn_Click);
                btn.Text 
    = "Click Me";
                
    this.PlaceHolder1.Controls.Add(btn);
            }

        }

     

        
    void btn_Click(object sender, EventArgs e)
        
    {
              (sender 
    as Button).Text = "Got U";
        }

    那么,你下次回发这个页面之后(点击按钮之后),这个按钮就消失了。因为没有重构(控件只是生成了一次,因为在if (!IsPostBack)里面)。

    这部分,需要了解不少关于ViewStateasp.net的生命周期的知识。简单的说,asp.net根据页面上的控件树来恢复状态,但是由于控件是在程序中生成,并且只生成了一次,所以在叶面提交的时候asp.net没有找到该控件的id,自然也不会重构他,页面上也就显示不出来这个控减了。

    如果这样就可以重构:去掉if (!IsPostBack),每次都加载

       

    protected void Page_Load(object sender, EventArgs e)
        
    {
            Button btn 
    = new Button();
            btn.Click 
    += new EventHandler(btn_Click);
            btn.Text 
    = "Click Me";
          
            
    this.PlaceHolder1.Controls.Add(btn);
       
      }

    但是问题依然有,它的事件你可能捕捉不到(这个例子太简单,如果控件数量多,他们的id的生成就会很意外,导致找不到一样的id)。对象的状态的恢复是根据控件的id来进行的。如果id不确定(自动生成的id),那么它也就无法恢复状态。启动叶面的trace 可以看到这棵控件树。大体这样:

    __Page

    ASP.agent_default_aspx

    2411

    0

    0

        ctl02

    System.Web.UI.LiteralControl

    148

    0

    0

        ctl00

    System.Web.UI.HtmlControls.HtmlHead

    46

    0

    0

        ctl01

    System.Web.UI.HtmlControls.HtmlTitle

    33

    0

    0

        ctl03

    System.Web.UI.LiteralControl

    14

    0

    0

        form1

    System.Web.UI.HtmlControls.HtmlForm

    2183

    0

    0

        ctl04

    System.Web.UI.LiteralControl

    10

    0

    0

        ScriptManager1

    Microsoft.Web.UI.ScriptManager

    224

    0

    0

        ctl05

    System.Web.UI.LiteralControl

    29

    0

    0

        PlaceHolder1

    System.Web.UI.WebControls.PlaceHolder

    64

    0

    0

        btn_t

    System.Web.UI.WebControls.Button

    64

    0

    0

        ctl06

    System.Web.UI.LiteralControl

    14

    0

    0

        Button1

    System.Web.UI.WebControls.Button

    76

    0

    0

        ctl07

    System.Web.UI.LiteralControl

    22

    0

    0

        ctl08

    System.Web.UI.LiteralControl

    20

    0

    0

     

    如果一下子生成大量控件的时候,由于id的不确定,两次生成的控件id可能不一样,所以也无法正确恢复状态。

    所以我们需要确定的id。比如这样:

            Button btn = new Button();
            btn.Click 
    += new EventHandler(btn_Click);
            btn.Text 
    = "Click Me";
            btn.ID 
    ="btn_t";
            
    this.PlaceHolder1.Controls.Add(btn);


    这样呢?看上去可以了。但是问题是,你做了重复的初始化工作(每次Page_Load都在初始化这个控件)。如果我们把应用放到UserControl上,我们动态加载的是一个UserControl,而这个Control的初始化事件又非常耗时

    比如:

    public partial class Agent_uc1 : System.Web.UI.UserControl
    {
        
    protected void Page_Load(object sender, EventArgs e)
        
    {
            System.Threading.Thread.Sleep(
    20000);
        }

    }


    那么,这个实现就太没效率了,付出了高昂的代价。

    怎么解决呢?

    先看我引用的文章的一点描述,然后再说我的实现

    道行限制,也没仔细看,所以不敢说看懂了多少。

    文章主要使用那个“控件状态追赶论”来解释的。先加入控件(主要是id配对),然后就可以被正确加载(追赶过程中有一步会根据控件id来恢复视图状态)。

    我的实现就是这样的:

    //Page的基类
    public class BasePattern:Page
    {
        
    protected override void OnInit(EventArgs e)
        
    {
            
    string path= defaultLoadMoudle;//first, load default
             if(path!=null)
                
    this.RebuildControl(path);
            
    //
            base.OnInit(e);
        }

        
    private BaseView RebuildControl(string path)
        
    {
            Control ctl 
    = this.LoadControl("~/Module/" + path);
            
    if (ctl != null)
            
    {
                ctl.ID 
    = path;
                PlaceHolder container 
    = this.Master.FindControl("cph_view").FindControl("PlaceHolder1"as PlaceHolder;
                container.Controls.Clear();
                container.Controls.Add(ctl);
      
            }

            
    return ctl as BaseView;
        }

        
    #endregion
     
        
    public void LoadModule(string path)
        
    {
            BaseView view 
    = RebuildControl(path);
            view.BindEntity();
        }


    //UserControl的基类

    public class BaseView:UserControl
    {
        
    /// <summary>
        
    /// init the control
        
    /// </summary>

        virtual public void BindEntity()
        
    {
     
               //doing sth here,binding or init
        }

        
    public BasePattern ParentPattern
        
    {
            
    get return this.Page as BasePattern; }
        }


    //调用的时候

        protected void lbt_summary_Click(object sender, EventArgs e)
        
    {
            
    //show sumary and list
             string path = "ToDoList/ToDoList.ascx";
            
    this. ParentPattern.LoadModule(path);
        }


    1:必须每次都执行创建该控件的工作。其实主要是建立这个控件的名称,恢复控件树

       protected override void OnInit(EventArgs e)

       这个事件比Page_Load靠前。

    2:保证id一样

     ctl.ID = path;//让同一个控件的id唯一。

    3:区分重建和执行

    所以在Page德类里面我用的是两个函数Rebuild()和LoadModule(),目的就是区分这两个调用。内部的重建只是使用Rebuild,只是建立一个控件id的过程,外部调用的时候,就需要调用控件的初始化函数了

    结论就是

    1:在特定的时刻加入该控件的定义。至少在Page_Load以前,我用的Page_Init。晚了就执行不了了

    2:该控件的id必须一致。因为状态的恢复是根据控件id来完成的。

    3Rebuild的时候一定不要调用子控件的初始化的函数,这样会浪费时间。

     

    问题:可不可以通过设置UserControlIsPostBack属性来达到一种和Page类似的处理方式呢?这样在UserControl里面就可以使用if(IsPostBack)来做一些数据初始化了。

    我记得我曾经看过一篇文章,可以在某个事件中设置IsPostBack属性,但是现在怎么也找不到这篇文章了,可惜得很。

  • 相关阅读:
    Apple Swift编程语言入门教程
    网络请求错误
    Mac使用大全
    MPMovieplayerController添加新控件
    ios9 新变化
    UITableView总结
    UINavigationController的简单学习
    HTML
    谓词(NSPredicate)
    NSString字符串
  • 原文地址:https://www.cnblogs.com/netwom/p/953447.html
Copyright © 2020-2023  润新知