|
|||||||||||||||||||||||
|
|||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionEvery time I have to use the ASP.NET Of course, in a simple case, when there are only two or three levels of data, I can put as many I wanted this control to support declarative syntax (i.e. declare an BackgroundPlease note that the "How it Works" section of this article assumes that you're familiar with templated data-bound controls (see MSDN for further details). How to Use the NestedRepeaterAs an example for this document, I'll use the Data SourceFor the animal classification that I intend to display, data comes from the following SQL table, called
There can be as many columns as necessary, but the three columns seen here are the only ones required by
The public virtual DataSet DataSource;
As you can see, You just do the data binding as usual: SqlCommand cmd = new SqlCommand();
SqlConnection cnx = new SqlConnection(myCnxString);
cmd.Connection = cnx;
cmd.CommandText = "select * from ANIMALS";
SqlDataAdapter da = new SqlDataAdapter(cmd);
DataSet ds = new DataSet();
da.Fill(ds,"ani");
Then, you create a ds.Relations.Add(RelationName,
ds.Tables[0].Columns["ANI_ID"],
ds.Tables[0].Columns["ANI_PARENT"]);
At this time, the protected NestedRepeater myRep;
myRep.RelationName = RelationName;
myRep.RowFilterTop = "ANI_PARENT is null";
Optionally, you can use the Then: myRep.DataSource = ds;
myRep.DataBind();
Nothing to explain here. On the WebFormTo use the <%@ Register tagprefix="meg" Namespace="WebCustomControls"
Assembly="WebCustomControls"%>
If you use Visual Studio, you must add a reference to WebCustomControls.dll in your project for this line to work properly. Then: <meg:NestedRepeater id=myRep runat="server">
<HeaderTemplate>
This is the animal classification. <br>
</HeaderTemplate>
<FooterTemplate>
The end.
</FooterTemplate>
<ItemTemplate>
<img src="http://www.codeproject.com/pix.gif" height="10"
width="<%# (Container.Depth * 10) %>">
<%# (Container.DataItem as DataRow)["ANI_NAME"]%>
<br>
</ItemTemplate>
</meg:NestedRepeater>
Here,
The Then, the result should be as follows:
How it WorksThe work is done in two functions: CreateControlHierachyprotected virtual void CreateControlHierarchy(bool createFromDataSource)
{
int nbTopNodes = 0;
DataView dv = null;
// HeaderTemplate
if (m_headerTemplate != null)
// Do we have a <HeaderTemplate> section ?
{
NestedRepeaterHeaderFooter header =
new NestedRepeaterHeaderFooter();
m_headerTemplate.InstantiateIn(header);
if (createFromDataSource)
header.DataBind();
Controls.Add(header);
}
// ItemTemplate
if (createFromDataSource &&
DataSource != null &&
DataSource.Tables.Count != 0)
{
DataTable tbSource;
if (DataMember != String.Empty)
tbSource = DataSource.Tables[DataMember];
else
tbSource = DataSource.Tables[0];
if (tbSource == null)
throw new ApplicationException("No valid" +
" DataTable in the specified position.");
/* When creating from the ViewState (on PostBack),
* we'll need to know how many nodes
* there are under each node. So, when creating
* from the datasource, we store this
* information in m_lstNbChildren,
* which we'll also save in the viewstate.
* */
m_lstNbChildren = new ArrayList(tbSource.Rows.Count);
dv = new DataView(tbSource);
if (m_rowFilterTop != String.Empty)
dv.RowFilter = m_rowFilterTop;
nbTopNodes = dv.Count;
m_lstNbChildren.Add(nbTopNodes);
}
else
{
m_lstNbChildren = (ArrayList)ViewState["ListNbChildren"];
m_current = 0;
nbTopNodes = (int)m_lstNbChildren[m_current++];
}
NestedElementPosition currentPos;
for(int i=0; i< nbTopNodes; ++i)
{
if (i==0 && i==nbTopNodes-1)
currentPos = NestedElementPosition.OnlyOne;
else if (i ==0)
currentPos = NestedElementPosition.First;
else if (i == nbTopNodes - 1)
currentPos = NestedElementPosition.Last;
else
currentPos = NestedElementPosition.NULL;
if(createFromDataSource)
CreateItem(dv[i].Row, 0, currentPos);
else
CreateItem(null, 0, currentPos++);
}
if (createFromDataSource)
ViewState["ListNbChildren"] = m_lstNbChildren;
// FooterTemplate
if (m_footerTemplate != null)
{
NestedRepeaterHeaderFooter footer =
new NestedRepeaterHeaderFooter();
m_footerTemplate.InstantiateIn(footer);
if (createFromDataSource)
footer.DataBind();
Controls.Add(footer);
}
ChildControlsCreated = true;
}
This function is called upon databinding (or, on PostBack, during ViewState loading). It creates the header and determines the number of top nodes. For each of these top nodes, it calls CreateItemprivate void CreateItem(DataRow row, int depth, NestedElementPosition pos)
{
DataRow[] childRows;
int nbChildren=0;
if (m_itemTemplate != null)
{
NestedRepeaterItem item = new NestedRepeaterItem();
if (row != null)
{
childRows = row.GetChildRows(RelationName);
nbChildren = childRows.Length;
m_lstNbChildren.Add(nbChildren);
item.Position = pos;
item.NbChildren = childRows.Length;
item.Depth = depth;
}
else // we use the viewstate
{
nbChildren = (int)
m_lstNbChildren[m_current++];
childRows = new DataRow[nbChildren];
}
m_itemTemplate.InstantiateIn(item);
Controls.Add(item);
NestedRepeaterItemEventArgs args =
new NestedRepeaterItemEventArgs();
args.Item = item;
OnItemCreated(args);
if (row != null)
{
item.DataItem = row;
item.DataBind();
OnItemDataBound(args);
}
// Recursive call
NestedElementPosition currentPos;
for(int i =0; i< nbChildren; ++i)
{
if (i==0 && i==nbChildren-1)
currentPos = NestedElementPosition.OnlyOne;
else if (i ==0)
currentPos = NestedElementPosition.First;
else if (i == nbChildren-1)
currentPos = NestedElementPosition.Last;
else
currentPos = NestedElementPosition.NULL;
if (row != null)
CreateItem(childRows[i], depth + 1, currentPos);
else
CreateItem(null, depth + 1, currentPos);
}
}
}
This function instantiates an item template for each row of data found in the datasource: m_template.InstantiateIn(item);
public virtual ITemplate ItemTemplate
{
get{return m_itemTemplate;};
set{m_itemTemplate = value;};
}
When the WebForm is parsed by ASP.NET, the // this code is generated by .NET. We don't see it
void InstantiateIn(Control container)
{
HtmlImage img = new HtmlImage();
// further initialisation here
. . .
LiteralControl lit = new LiteralControl();
// further initialisation here
. . .
container.Controls.Add(img);
container.Controls.Add(lit);
. . .
}
When <%# (Container.Depth * 10) %>
and <%# (Container.DataItem as DataRow)["ANI_NAME"]%>
are evaluated. We can raise the OnItemDataBound(args);
item.Position = pos;
item.NbChildren = childRows.Length;
item.Depth = depth;
item.DataItem = row;
With the public enum NestedElementPosition
{
First, // current record is the first child of the immediate parent
Last, // current record is the last child of the immediate parent
OnlyOne, // current record is the only child of the immediate parent
NULL // None of the above
}
The The Once the item is added to the As we use a recursive function, there can be as many levels of data as necessary: there's no need to know in advance how many sublevels we have. That is: we can add another sublevel in the SQL table, without changing or adding any single line of code. UpdateA new property called // a NestedRepeater called myRepeater has been declared elsewhere...
foreach(NestedRepeaterItem item in myRepeater.Items)
{
DoSomething(item);
}
// DoSomething is a recursive function
private void DoSomething(NestedRepeaterItem current)
{
// do whatever is required with the current item
// * * *
// then call DoSomething recursively for all sub-items
foreach(NestedRepeaterItem child in current.Items)
{
DoSomething(child);
}
}
ConclusionThe The example I've used here is quite simple because I did not want to add useless stuff and just focus on the control itself. In a next article, I'll show how the
|
||||||||||||||||||||||