Welcome to MSDN Blogs Sign in | Join | Help

Mike Ormond's Blog

In my world, things would be simpler than this...

News

  • Add to Technorati Favorites

    These postings are provided "AS IS" with no warranties, and confer no rights. The use of any script / code samples is subject to the terms specified here.

Dynamically Loading ListView Templates

I received an email question recently from a customer asking how they could dynamically load the LayoutTemplate for a ListView from a UserControl. I banged my head on this one for a while and eventually mailed our internal ASP.NET alias asking if someone could help. Fortunately David Fowler could. Hats off to him - the code below is his solution.

Let's start with a basic ListView page that we want to convert to load the LayoutTemplate from a UserControl. Something like this will do:

<%@ Page Language="C#" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server"></head>
<body>
  <form id="form1" runat="server">
    <asp:ListView ID="ListView1" runat="server"
      DataSourceID="XmlDataSource1">
      <LayoutTemplate>
        <ul>
          <asp:PlaceHolder ID="itemPlaceholder" runat="server"></asp:PlaceHolder>
        </ul>
      </LayoutTemplate>
      <ItemTemplate>
        <li><%# Eval("id") %></li>
      </ItemTemplate>
    </asp:ListView>
    <asp:XmlDataSource ID="XmlDataSource1" runat="server"
      XPath="/catalog/book"
      DataFile="~/XMLFile.xml">
    </asp:XmlDataSource>
  </form>
</body>
</html>

We have an XmlDataSource (in my case pointing to the MSDN sample XML file) and I'm binding the book ids to list items in an unordered list using a ListView control. Pretty straightforward so far.

Imagine I refactored this a little to use a UserControl for the LayoutTemplate

<%@ Control Language="C#" %>
<ul>
  <asp:PlaceHolder ID="itemPlaceholder" runat="server"></asp:PlaceHolder>
</ul>

and one for the ItemTemplate

<%@ Control Language="C#" %>
<li>
  <%# Eval("id") %>
</li>

That means I could replace my original aspx file with one that looks like this

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default2.aspx.cs" Inherits="Default2" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
</head>
<body>
  <form id="form1" runat="server">
    <asp:ListView ID="ListView1" runat="server" 
      ItemPlaceholderID="MyLayout$itemPlaceholder"
      DataSourceID="XmlDataSource1">
    </asp:ListView>
    <asp:XmlDataSource ID="XmlDataSource1" runat="server" 
      XPath="/catalog/book"
      DataFile="~/XMLFile.xml">
    </asp:XmlDataSource>
  </form>
</body>
</html>

ie I've defined no LayoutTemplate or ItemTemplate - we'll load these dynamically. The only other change is I've specified an ItemPlaceholderID on the ListView. We'll see why in a second.

using System;
using System.Web.UI;

public partial class Default2 : System.Web.UI.Page
{
  protected void Page_Load(object sender, EventArgs e)
  {
    ListView1.LayoutCreated += new EventHandler(ListView1_LayoutCreated);
    ListView1.LayoutTemplate = LoadTemplate("LayoutTemplate.ascx");
    ListView1.ItemTemplate = LoadTemplate("ItemTemplate.ascx");
  }

  void ListView1_LayoutCreated(object sender, EventArgs e)
  {
    //remove the layout template
    ListView1.Controls.RemoveAt(0);

    //recreate it
    Control newLayoutContainer = new Control();
    ListView1.LayoutTemplate.InstantiateIn(newLayoutContainer);
    var userControl = newLayoutContainer.Controls[0];
    userControl.ID = "MyLayout";
    ListView1.Controls.Add(newLayoutContainer);
  }
}

image If you just go ahead and load the templates from the UserControls, you'll get the yellow screen of death telling you "An item placeholder must be specified on ListView 'ListView1'. Specify an item placeholder by setting a control's ID property to "itemPlaceholder". The item placeholder control must also specify runat="server"." The ListView simply can't find the ItemPlaceholder.

The key here is that the UserControl is a naming container so the ListView can't find the ItemPlaceholderID because it's been modified to avoid naming clashes on the page. In the code above we recreate the LayoutTemplate and set the UserControl's ID to "MyLayout" allowing us to specify that the ItemPlaceholderID will be "MyLayout$itemPlaceholder".

You could reduce this by determining up front the "native" ID of the UserControl ("ctl01" in this case) and setting the ItemPlaceholderID property of the ListView to "ctl01$itemPlaceholder". This would allow you to dispense with the LayoutCreated handler altogether. Of course if your UserControl ID then changes, it's going to break.

Technorati Tags: ,
Posted: Saturday, July 26, 2008 11:40 AM by MikeOrmond
Filed under: , ,

Comments

真见 said:

1.44个令人惊奇的Silverlight视频教程。2.ExpressionEncoder2VB.NETupdate3.HDViewforInternetExplore...

# July 26, 2008 9:44 PM

cnblogs.com said:

1. 44个令人惊奇的Silverlight视频教程。 2. Expression Encoder 2 VB.NET update 3. HD View for Internet Explorer HD

# July 26, 2008 10:21 PM

Jordan Weber-Flink said:

Does the newLayoutContainer's control collection act as a stack or a queue? It seems to me that the line

  ListView1.Controls.RemoveAt(0);

would remove your layoutTemplate when you load the ItemTemplate.

???

# August 5, 2008 2:31 PM

MikeOrmond said:

Hi Jordan. The LayoutTemplate acts as a container so items become children of the LayoutTemplate.

Mike

# August 7, 2008 8:27 AM

wuz said:

I guess this applies to template classes as well and so

namespace PresentationLayer

{

   public class LayoutTemplate : ITemplate

   {

           LiteralControl table = new LiteralControl("<table><tbody><asp:PlaceHolder runat=\"server\" ID=\"ctl01$itemPlaceholder\" /></tbody></table>");

           container.Controls.Add(table);

       }

   }

}

will also say that itemPlaceholder cannot be found cause it changed.

# August 15, 2008 10:42 AM

wuz said:

And i am afraid this solution is impossible when using direct databinding without a datasource

# August 16, 2008 4:53 PM

wuz said:

Hi,

Thx again this really seems to be the only workaroun unfortuantely it fails when i use user controls or custom controls within the LayoutTemplate:

http://forums.asp.net/p/1305569/2569260.aspx

greetings

# August 20, 2008 8:02 PM

MikeOrmond said:

Wuz - I have it working with databinding with no DataSource. My databinding code looks like this:

   XDocument xdoc = XDocument.Load(Server.MapPath("~/XmlFile.xml"));

   var query = from x in xdoc.Descendants("book")

               select new { id = (string)x.Attribute("id")};

   ListView1.DataSource = query;

   ListView1.DataBind();

# August 21, 2008 3:51 AM

wuz said:

Hello mike,

Thanks your entry really seems to be the only help for this topic. Unfortunately when I am using your suggestion it works for the first time when i am accessing the page, but not after the postback.

There seems to be a problem in Begin Loadstate:

An item placeholder must be specified on ListView 'lvList'. Specify an item placeholder by setting a control's ID property to "MyLayout$itemPlaceholder". The item placeholder control must also specify runat="server".

 bei System.Web.UI.WebControls.ListView.GetPreparedContainerInfo(Control outerContainer, Boolean isItem, Int32& placeholderIndex)

 bei System.Web.UI.WebControls.ListView.CreateItemsWithoutGroups(ListViewPagedDataSource dataSource, Boolean dataBinding, InsertItemPosition insertPosition, ArrayList keyArray)

 bei System.Web.UI.WebControls.ListView.CreateChildControls(IEnumerable dataSource, Boolean dataBinding)

 bei System.Web.UI.WebControls.ListView.CreateChildControls()

 bei System.Web.UI.Control.EnsureChildControls()

I have opened a thread with the complete sourcecode:

http://forums.asp.net/p/1305569/2558209.aspx

Thanks again and kind regards

# August 21, 2008 6:13 AM

Mike Ormond's Blog said:

I got quite a few comments on my post on Dynamically Loading ListView Templates so rather than trying

# August 22, 2008 7:31 AM

MikeOrmond said:

Just blogged an updated version. Mike

# August 22, 2008 7:32 AM
New Comments to this post are disabled
Page view tracker