Dynamically adding a user control to a repeater

2019-04-30 05:29发布

问题:

I have a class (MyClass) which represents a nested hierarchy, so the class has a property which is a collection of MyClass. MyClass also has a title property

To show it on a web page, I was hoping to create a user control which had a repeater. In the item template I would have literal to display the title property, and on the ItemCreated event of the repeater, I would create a new instance of the usercontrol, and add it into the current item in the repeater.

My problem is, when the Page_Load event in the usercontrol fires, if the control was dynamically added, the repMyClass repeater poroperty is null, even if I call EnsureChildControls. Am I missing something here? If I create a repeater and drop my userctonrol in the item template it works fine. My can't I get this to work dynamically?

User Control:

<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="MyControl.ascx.cs" Inherits="MyControl" %>
Items:<br/>
<asp:Repeater ID="repMyClass" runat="server" EnableViewState="false" 
    OnItemCreated="repMenuItems_ItemCreated">
    <HeaderTemplate><ul><HeaderTemplate>
    <ItemTemplate>
        <li><%# Eval("Title") %>
            <div><asp:PlaceHolder ID="SubItemPlaceholder" runat="server" /></div>
        </li></ItemTemplate>
    <FooterTemplate></ul></FooterTemplate>
</asp:Repeater>

User Control Code:

public partial class MyControl: System.Web.UI.UserControl
{
    public IEnumerable<MyClass> ChildItems { get; set; }
    protected void Page_Load(object sender, EventArgs e)
    {
        this.repMyClass.DataSource = ChildItems;
        this.repMyClass.DataBind();
    }

    protected void repMenuItems_ItemCreated(object Sender, RepeaterItemEventArgs  e)
    {
        if (e.Item.ItemType == ListItemType.AlternatingItem || e.Item.ItemType == ListItemType.Item)
        {
            //Get the MyClass instance for this repeater item
            MyClass mcCurrent = (MyClass)e.Item.DataItem;

            //If the MyClass instance has child instances
            if (mcCurrent.Children != null && mcCurrent.Children.Length > 0)
            {
                //Add a new user control and set it's property so it can bind
                PlaceHolder ph = (PlaceHolder)e.Item.FindControl("SubItemPlaceholder");

                MyControl ctl = (MyControl)Page.LoadControl(typeof(MyControl),new object[] {});

                ctl.ChildItems = mcCurrent.Children;
                ph.Controls.Add(ctl);
            }
        }
    }
}

回答1:

I had done this a long time ago for creating nested reports using Accordions.

In Index, when you want to dynamically add User Control instances:

// Declare Placeholder    
PlaceHolder ph = (PlaceHolder)e.Item.FindControl("SubItemPlaceholder")

// Adding some literal controls to header
ph.Controls.Add(new LiteralControl("This is the accordion header!!"));

// Declare new control variable
crt = new Control();

// Load up your User Control
crt = LoadControl("~/MyControl.ascx");

// Check if it has loaded properly
if (crt != null)
{
    // GET / SET any custom properties of the User Control
    ((myClass)crt).title = "Welcome";

    // Add the new User Control to the placeholder's controls collection
    ph.Controls.Add(crt);
}

Note: In the User Control, you must add the "ClassName" in the declaration tag

<%@ Control Language="C#" AutoEventWireup="true" CodeFile="MyControl.ascx.cs" Inherits="myTest" ClassName="myClass" %>

Also, any properties you wish to expose when creating instances dynamically, you declare them as follows:

public string title { get; set; }

So if you want to force a value when creating for "repMyClass" you can set it as a property and assign whatever value you want to it programmaticaly.



回答2:

You can make another constructor in the control that takes the property you want and use it in the load event, and then pass it in the currently empty object array in LoadControl() call.

Another approach is to fire the logic in the setter of the property instead of in the Load event of the control, since is essentially the real load here and fires once per request per control if I get it right.

Another approach is not to load it dynamically as all.

inside your item template you can have something like:

<uc:MyControl runat="server 
    ChildItems='<%# ((MyClass) Container.DataItem).Children %>'
    Visible='<%# ((MyClass) Container.DataItem).Children.Length > 0 %>'
    />

Update

One more approach that never gave me errors with child controls in user controls inside repeaters or page life cycle, is to use the Item Data Bound event instead of the Item Created.

Actually, when I think about it now, your code shouldn't because e.Item.DataItem hasn;t been bound to yet.

Try to change the event to ItemDataBound and see if this works.

.

I still though recommend you include the control in the item markup, and control the visibility of the control in the event (now to be ItemDataBound). You can refer to the control by something like e.Item.FindControl("ControlId") as MyControl (will return null if not found in current item).

Try combining both, or at least changing event to data bound instead of created, and let's see...