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"
"https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="https://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"
"https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="https://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);
}
}
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: asp.net,listview
Comments
Anonymous
July 25, 2008
PingBack from http://blog.a-foton.ru/2008/07/dynamically-loading-listview-templates/Anonymous
July 26, 2008
1.44个令人惊奇的Silverlight视频教程。2.ExpressionEncoder2VB.NETupdate3.HDViewforInternetExplore...Anonymous
July 26, 2008
- 44个令人惊奇的Silverlight视频教程。 2. Expression Encoder 2 VB.NET update 3. HD View for Internet Explorer HD
Anonymous
August 05, 2008
The comment has been removedAnonymous
August 07, 2008
Hi Jordan. The LayoutTemplate acts as a container so items become children of the LayoutTemplate. MikeAnonymous
August 15, 2008
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.Anonymous
August 16, 2008
And i am afraid this solution is impossible when using direct databinding without a datasourceAnonymous
August 20, 2008
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 greetingsAnonymous
August 20, 2008
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();Anonymous
August 20, 2008
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 regardsAnonymous
August 21, 2008
I got quite a few comments on my post on Dynamically Loading ListView Templates so rather than tryingAnonymous
August 21, 2008
Just blogged an updated version. Mike