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); } }
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.
PingBack from http://blog.a-foton.ru/2008/07/dynamically-loading-listview-templates/
1.44个令人惊奇的Silverlight视频教程。2.ExpressionEncoder2VB.NETupdate3.HDViewforInternetExplore...
1. 44个令人惊奇的Silverlight视频教程。 2. Expression Encoder 2 VB.NET update 3. HD View for Internet Explorer HD
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.
???
Hi Jordan. The LayoutTemplate acts as a container so items become children of the LayoutTemplate.
Mike
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.
And i am afraid this solution is impossible when using direct databinding without a datasource
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
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();
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
I got quite a few comments on my post on Dynamically Loading ListView Templates so rather than trying
Just blogged an updated version. Mike