/***************************************************
 * This code is Copyright 2008 Michael J. Ellison.  All rights reserved.
 * Usage and distribution of this code is governed by the
 * CodeProject Open License.  A copy of the license may be found
 * at:
 * http://www.codeproject.com/info/cpol10.aspx
 * **************************************************/
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Reflection;
using System.Web.UI.Design.WebControls;
using System.Web.UI.Design;

namespace UNLV.IAP.WebControls
{

    /// <summary>
    /// Displays the values of a data source by using user-defined templates, categorized
    /// by a field in the data source.
    /// </summary>
    /// <remarks>
    /// <para>
    /// The GroupingView control is used to display the values from a datasource.  It is
    /// similar to a <see cref="Repeater">Repeater</see> in that user-defined templates are 
    /// used to provide the display layout.  Unlike the Repeater, the GroupingView 
    /// automatically categorizes the display according to a user-specified field in the
    /// data source. 
    /// </para>
    /// <para>
    /// Set the <see cref="GroupingDataField" /> property to the name of
    /// the field by which you wish to group.  If <see cref="GroupingDataField"/> is left
    /// blank, then the datasource is treated as a single group of records.  When bound to
    /// data, the layout and contents defined by the <see cref="GroupTemplate" /> property 
    /// are applied once for each group, using the first data item in the group for binding
    /// expressions.  If the <see cref="AutobindDataSourceChildren" /> property is <c>true</c> (the default), then
    /// all child controls with a <c>DataSource</c> property (such as a <see cref="System.Web.UI.WebControls.GridView">GridView</see>)
    /// are automatically bound to the subset of the data source that represents the group
    /// (based on <see cref="GroupingDataField" />).  This autobinding behavior may be adjusted by
    /// explicitly specifying control IDs in either the <see cref="AutobindInclusions" />
    /// or <see cref="AutobindExceptions" /> properties.
    /// </para>
    /// <para>
    /// Alternatively, to have more explicit control over the display of items within
    /// a group, include a placeholder control (either a bonafide <see cref="PlaceHolder" />
    /// or other container control) in the GroupTemplate.  This placeholder control should have an
    /// ID of <c>itemPlaceholder</c>; to use a different ID, set the <see cref="ItemPlaceholderID" />
    /// property of the GroupingView to the desired ID.  With an item placeholder control,
    /// define the contents for individual items using the
    /// <see cref="ItemTemplate" /> property.
    /// </para>
    /// <para>
    /// The GroupingView, in conjunction with the <see cref="Aggregation" /> display control,
    /// supports a number of different aggregation functions that may be applied across
    /// a group of records.  To display an aggregation for a group, place an <see cref="Aggregation" />
    /// control in the GroupTemplate, set its <see cref="Aggregation.Function">Function</see> property
    /// to the desired operation (one of the <see cref="AggregationFunction" /> options),
    /// and specify the data field to aggregate in the <see cref="Aggregation.DataField">DataField</see>
    /// property.  As long as the GroupingView's <see cref="AutobindDataSourceChildren" />
    /// is set to <c>true</c>, aggregations are databound and evaluated using the appropriate
    /// subset of the data source.
    /// </para>
    /// <para>
    /// For additional levels of grouping based on secondary fields in the datasource,
    /// a GroupingView control may be nested within the GroupTemplate of a parent.
    /// </para>
    /// <para>
    /// <b>Templates</b>
    /// <br />
    /// In order for the GroupingView to display content, you must create templates for
    /// different parts of the control.  Only the <see cref="GroupTemplate" /> is required.
    /// All other templates are optional.  
    /// </para>
    /// <list type="table">
    /// <listheader>
    /// <term>Template type</term>
    /// <description>Description</description>
    /// </listheader>
    /// <item>
    /// <term><see cref="GroupTemplate"/></term>
    /// <description>
    /// The root template that defines the content for a group of data items
    /// sharing the same <see cref="GroupingDataField" /> value.
    /// </description>
    /// </item>
    /// <item>
    /// <term><see cref="ItemTemplate"/></term>
    /// <description>Defines the data-bound content to display for individual items within a group.</description>
    /// </item>
    /// <item>
    /// <term><see cref="GroupSeparatorTemplate"/></term>
    /// <description>Defines the content to render between groups.</description>
    /// </item>
    /// <item>
    /// <term><see cref="ItemSeparatorTemplate"/></term>
    /// <description>Defines the content to render between items within a group.</description>
    /// </item>
    /// <item>
    /// <term><see cref="EmptyDataTemplate"/></term>
    /// <description>Defines the content to render when the data source for a GroupingView control contains no data items.</description>
    /// </item>
    /// </list>
    /// <para>
    /// To display the value of a field in a template, use a data-binding expression.
    /// </para>
    /// <para>
    /// <b>Binding to a Data Source</b>
    /// <br />
    /// You can bind the GroupingList control to a data source control and to any data source
    /// that implements the <see cref="System.Collections.IEnumerable" /> interface.
    /// To bind to a data source control, set the <see cref="GroupingView.DataSourceID">DataSourceID</see> property 
    /// of the GroupingView control to the ID value of a data source control.
    /// To bind to an IEnumerable source, programmatically set the <see cref="GroupingView.DataSource">DataSource</see>
    /// property of the GroupingView control and call the <see cref="GroupingView.DataBind">DataBind</see> method.
    /// </para>
    /// <para>
    /// <b>Events</b>
    /// <br />
    /// The following table lists the events that are supported by the GroupingView control.
    /// <list type="table">
    /// <listheader>
    /// <term>Event</term><description>Description</description>
    /// </listheader>
    /// <item>
    /// <term><see cref="GroupCreated" /></term>
    /// <description>
    /// Occurs when a new group is created in the GroupingView control.  This event is often
    /// used to modify the content of a group when it is created.
    /// </description>
    /// </item>
    /// <item>
    /// <term><see cref="GroupDataBound" /></term>
    /// <description>
    /// Occurs when a data item is bound to a group in a GroupingView control.  This event is
    /// often used to modify the content of a group after it is bound to data.
    /// </description>
    /// </item>
    /// <item>
    /// <term><see cref="ItemCreated" /></term>
    /// <description>
    /// Occurs when a new item is created within a group in the GroupingView control.  
    /// This event is often used to modify the content of a group's item when it is created.
    /// </description>
    /// </item>
    /// <item>
    /// <term><see cref="ItemDataBound" /></term>
    /// <description>
    /// Occurs when a data item is bound to an item within a group in a GroupingView control.
    /// This event is often used to modify the content of a group's item after it is bound
    /// to data.
    /// </description>
    /// </item>
    /// <item>
    /// <term><see cref="Command" /></term>
    /// <description>
    /// Occurs when a button is clicked in the GroupingView control.  This event is often used
    /// to perform a custom task when a button is clicked in the control.
    /// </description>
    /// </item>
    /// </list>
    /// </para>
    /// </remarks>
    /// <example>
    /// The following examples assume a SQL data table as the source, with column names
    /// "Region", "City", "State", and "Sales".  The "Region" field is used to identify
    /// groups.
    /// <para>
    /// This example lists cities and states, grouped by their Region descriptor.  The
    /// region name is displayed in a Label, and the cities are displayed through a
    /// nested GridView.  Only the GroupTemplate is used, as the individual items are 
    /// handled with the GridView and AutobindDataSourceChildren set to <c>true</c>.
    /// A count of the cities is provided as a footer to each group using an Aggregation 
    /// control.
    /// </para>
    /// <code>
    /// <![CDATA[
    ///<%@ Page Language="C#" %>
    ///<%@ Register Assembly="GroupingView" 
    ///             Namespace="UNLV.IAP.WebControls" 
    ///             TagPrefix="cc1" 
    ///             %>
    ///
    ///<!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">
    ///    <title>GroupingView Example</title>
    ///</head>
    ///<body>
    ///  <form id="form1" runat="server">
    ///    <h3>GroupingView Example</h3>
    ///    
    ///    <cc1:GroupingView ID="GroupingView1" runat="server"
    ///                      GroupingDataField="Region"
    ///                      AutobindDataSourceChildren="true"
    ///                      DataSourceID="sql1"
    ///                      >
    ///        
    ///        <GroupTemplate>
    ///            <asp:Label ID="lblRegion" runat="server"
    ///                       Text='<%# Eval("Region") %>'
    ///                       Font-Bold="true"
    ///                       />
    ///            
    ///            <asp:GridView ID="gvCities" runat="server" 
    ///                          AutoGenerateColumns="false"
    ///                          ShowHeader="false"
    ///                          >
    ///                <Columns>
    ///                    <asp:BoundField DataField="City" />
    ///                    <asp:BoundField DataField="State" />
    ///                </Columns>    
    ///            </asp:GridView> 
    ///            <br />
    ///            Count of cities:  
    ///            <cc1:Aggregation runat="server" 
    ///                             Function="Count" />                                                                         
    ///            <br />
    ///            <br />
    ///            <br />                                 
    ///        </GroupTemplate>
    ///        
    ///    </cc1:GroupingView>
    ///            
    ///    <asp:SqlDataSource ID="sql1" runat="server" 
    ///        ConnectionString="<%$ ConnectionStrings:ConnectionString %>" 
    ///        SelectCommand="SELECT * FROM [Cities]">        
    ///    </asp:SqlDataSource>
    /// 
    ///  </form>
    ///</body>
    ///</html>
    /// ]]>
    /// </code>
    /// <para>
    /// This example demonstrates full control over the display of individual items,
    /// rather than use a GridView.  The GroupTemplate and ItemTemplate properties
    /// are both used, with an itemPlaceholder defined in the GroupTemplate.  Total
    /// and average sales figures for each group are displayed with Aggregation
    /// controls, as well as a grand total following all groups.
    /// </para>
    /// <code>
    /// <![CDATA[
    ///<%@ Page Language="C#" %>
    ///<%@ Register Assembly="GroupingView" 
    ///             Namespace="UNLV.IAP.WebControls" 
    ///             TagPrefix="cc1" 
    ///             %>
    ///
    ///<!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">
    ///    <title>GroupingView Example</title>
    ///</head>
    ///<body>
    ///  <form id="form1" runat="server">
    ///    <h3>GroupingView Example</h3>
    ///    
    ///    <cc1:GroupingView ID="GroupingView1" runat="server"
    ///                      GroupingDataField="Region"
    ///                      DataSourceID="sql1"
    ///                      AutobindDataSourceChildren="true"
    ///                      >
    ///        
    ///        <GroupTemplate>
    ///            <asp:Label ID="lblRegion" runat="server"
    ///                       Text='<%# Eval("Region") %>'
    ///                       Font-Bold="true"
    ///                       Font-Size="Large"
    ///                       ForeColor="ForestGreen"
    ///                       Font-Names="Tahoma,Verdana,Arial,Sans-Serif"
    ///                       />
    ///            
    ///            <table>
    ///                <%-- group header --%>
    ///                <tr>
    ///                   <th width="100" align="left">City</th>
    ///                   <th width="100" align="left">State</th>
    ///                   <th width="100" align="right">Sales</th>
    ///                </tr>
    ///                
    ///                <%-- group data items --%>
    ///                <asp:PlaceHolder ID="itemPlaceholder" runat="server" />
    ///                
    ///                <%-- group footer --%>
    ///                <tr><td colspan="3"><hr /></td></tr>
    ///                <tr>
    ///                    <td align="right" colspan="2">Group total:</td>
    ///                    <td align="right">
    ///                        <cc1:Aggregation runat="server" Function="Sum"
    ///                                         DataField="Sales"
    ///                                         FormatString="{0:#,##0.00}"
    ///                                         />
    ///                    </td>
    ///                </tr>
    ///                <tr>
    ///                    <td align="right" colspan="2">Group average:</td>
    ///                    <td align="right">
    ///                        <cc1:Aggregation runat="server" Function="Avg"
    ///                                         DataField="Sales"
    ///                                         FormatString="{0:#,##0.00}"
    ///                                         />
    ///                    </td>
    ///                </tr>
    ///            </table>                
    ///            <br /><br />                                 
    ///        </GroupTemplate>
    ///        
    ///        <ItemTemplate>
    ///            <%-- define items within a group --%>
    ///            <tr>
    ///                <td><%#Eval("City") %></td>
    ///                <td><%#Eval("State") %></td>
    ///                <td align="right"><%#Eval("Sales", "{0:#,##0.00}") %></td>
    ///            </tr>
    ///        </ItemTemplate>
    ///        
    ///    </cc1:GroupingView>
    ///    
    ///    Grand Total:
    ///    <cc1:Aggregation runat="server" Function="Sum"
    ///                     DataSourceID="sql1"
    ///                     DataField="Sales"
    ///                     FormatString="{0:#,##0.00}"
    ///                     />
    ///    <br />
    ///    Average per city:
    ///    <cc1:Aggregation ID="Aggregation1" runat="server" Function="Avg"
    ///                     DataSourceID="sql1"
    ///                     DataField="Sales"
    ///                     FormatString="{0:#,##0.00}"
    ///                     />
    ///                     
    ///            
    ///    <asp:SqlDataSource ID="sql1" runat="server" 
    ///        ConnectionString="<%$ ConnectionStrings:ConnectionString %>" 
    ///        SelectCommand="SELECT * FROM [Cities] Order By Region, State, City">        
    ///    </asp:SqlDataSource>
    ///    
    ///  </form>
    ///</body>
    ///</html>
    /// ]]>
    /// </code>
    /// </example>    
    [ToolboxData("<{0}:GroupingView runat=server></{0}:GroupingView>")]
    [Designer(typeof(GroupingViewDesigner))]
    public class GroupingView : CompositeDataBoundControl
    {
        // the ViewState key for storing an array of group and item counts
        private const string kViewState_ItemsPerGroup = "__!ItemsPerGroup";

        private ITemplate _layoutTemplate;
        private ITemplate _itemTemplate;
        private ITemplate _groupSeparatorTemplate;
        private ITemplate _itemSeparatorTemplate;
        private ITemplate _emptyDataTemplate;

        private string _itemPlaceholderId = "itemPlaceholder";
        private string _groupingDataField = "";
        private bool _autobindChildren = true;
        private string _autobindExceptions = "";
        private string _autobindInclusions = "";

        #region Event Support

        /// <summary>
        /// Occurs when a group is created.
        /// </summary>
        /// <remarks>
        /// The GroupCreated event is fired after a GroupTemplate is applied for a group and
        /// its child controls are created.
        /// </remarks>
        /// <example>
        /// The following demonstrates a code procedure that handles the GroupCreated event.
        /// <code>
        /// <![CDATA[
        ///protected void GroupingView1_GroupCreated
        ///  (object o, GroupingViewEventArgs e)
        ///{
        ///    // the GroupingViewEventArgs contains the
        ///    // Item property, which represents the Group item
        ///    // that has just been created.  Find and adjust
        ///    // a control within the item.
        ///    Label lbl = e.Item.FindControl("lblGroupNumber") as Label;
        ///    lbl.ForeColor = System.Drawing.Color.DarkGreen;
        ///}
        /// ]]>
        /// </code>
        /// </example>
        public event GroupingViewEventHandler GroupCreated;


        /// <summary>
        /// Occurs after a group has been bound to a data item.
        /// </summary>
        /// <remarks>
        /// The GroupDataBound event is fired after a GroupTemplate is applied for a group,
        /// its child controls are created, and then bound to the first data item in the group.
        /// </remarks>
        /// <example>
        /// The following demonstrates a code procedure that handles the GroupDataBound event.
        /// <code>
        /// <![CDATA[
        ///protected void GroupingView1_GroupDataBound
        ///    (object o, GroupingViewEventArgs e)
        ///{
        ///    // the GroupingViewEventArgs contains the
        ///    // Item property, which represents the Group item
        ///    // that has just been databound.
        ///    // Use the GroupIndex property to populate a label
        ///    // control in the item to provide line numbering
        ///    // in the output.
        ///    
        ///    // get the label control for the item
        ///    // (defined in the GroupTemplate)
        ///    Label lbl = e.Item.FindControl("lblGroupNumber") as Label;
        ///    
        ///    // get the Region code from the data item for this group
        ///    string rgn = DataBinder.Eval(e.Item.DataItem, "Region").ToString();
        ///
        ///    // assign the index number and region code to the label     
        ///    lbl.Text = string.Format("{0} ({1})", e.GroupIndex, rgn);
        ///}
        /// ]]>
        /// </code>
        /// </example>
        public event GroupingViewEventHandler GroupDataBound;


        /// <summary>
        /// Occurs when an item within a group is created.
        /// </summary>
        /// <remarks>
        /// The ItemCreated event is fired after an ItemTemplate is applied for an
        /// individual item within a group and its child controls are created.
        /// </remarks>
        /// <example>
        /// The following demonstrates a code procedure that handles the ItemCreated event.
        /// <code>
        /// <![CDATA[
        ///protected void GroupingView1_ItemCreated
        ///  (object o, GroupingViewEventArgs e)
        ///{
        ///    // the GroupingViewEventArgs contains the
        ///    // Item property, which represents the item
        ///    // that has just been created.  Find and adjust
        ///    // a control within the item.
        ///    Label lbl = e.Item.FindControl("lblItemNumber") as Label;
        ///    lbl.ForeColor = System.Drawing.Color.DarkGreen;
        ///}
        /// ]]>
        /// </code>
        /// </example>
        public event GroupingViewEventHandler ItemCreated;


        /// <summary>
        /// Occurs after a group has been bound to a data item.
        /// </summary>
        /// <remarks>
        /// The ItemDataBound event is fired after an ItemTemplate is applied for an item
        /// within a group, its child controls are created, and then bound to a
        /// data item.
        /// </remarks>
        /// <example>
        /// The following demonstrates a code procedure that handles the ItemDataBound event.
        /// <code>
        /// <![CDATA[
        ///protected void GroupingView1_ItemDataBound
        ///    (object o, GroupingViewEventArgs e)
        ///{
        ///    // the GroupingViewEventArgs contains the
        ///    // Item property, which represents the item within a group
        ///    // that has just been databound.
        ///    // Use the ItemIndexWithinGroup property to populate a label
        ///    // control in the item and provide line numbering
        ///    // in the output.
        ///    
        ///    // get the label control for the item
        ///    // (defined in the GroupTemplate)
        ///    Label lbl = e.Item.FindControl("lblItemNumber") as Label;
        ///
        ///    // assign the index number to the label     
        ///    lbl.Text = e.Item.ItemIndexWithinGroup.ToString();
        ///}
        /// ]]>
        /// </code>
        /// </example>
        public event GroupingViewEventHandler ItemDataBound;



        /// <summary>
        /// Occurs when a command button is clicked in a <see cref="GroupingView"/> control.
        /// </summary>
        /// <remarks>
        /// Inspect the <see cref="GroupingViewCommandEventArgs.CommandName">CommandName</see> 
        /// and <see cref="GroupingViewCommandEventArgs.CommandArgument">CommandArgument</see>
        /// properties of the <see cref="GroupingViewCommandEventArgs" /> object passed to
        /// a handling procedure to determine the context of the button click.
        /// </remarks>
        /// <example>
        /// The following demonstrates an .aspx page with a procedure that handles 
        /// the Command event.
        /// <code>
        /// <![CDATA[
        ///<%@ Page Language="C#" %>
        ///<%@ Register Assembly="GroupingView" 
        ///             Namespace="UNLV.IAP.WebControls" 
        ///             TagPrefix="cc1" 
        ///             %>
        ///<%@ Import Namespace="UNLV.IAP.WebControls" %>             
        ///             
        ///<script runat="server">
        ///    protected void Page_Load(object sender, EventArgs e)
        ///    {
        ///        lblMessage.Text = "";
        ///    }
        /// 
        ///    protected void ShowMessage(string msg)
        ///    {
        ///        lblMessage.Text = msg + "<hr />";
        ///    }
        /// 
        ///    protected void GroupingView1_Command
        ///      (object o, GroupingViewCommandEventArgs e)
        ///    {
        ///        ShowMessage(string.Format(
        ///          "Command triggered in {2}:  "
        ///         +"CommandName='{0}'; "
        ///         +"CommandArgument='{1}'"
        ///         , e.CommandName, e.CommandArgument
        ///         , e.CommandTemplate.ToString()
        ///         ));
        ///    }
        ///</script>             
        ///
        ///<!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">
        ///    <title>GroupingView Command Example</title>
        ///    <style>
        ///        a {text-decoration: none; color: darkBlue;}
        ///        a:hover {text-decoration: underline; color: darkOrange;}
        ///    </style>
        ///</head>
        ///<body>
        ///<form id="form1" runat="server">
        ///  <h3>GroupingView Command Example</h3>
        ///  <asp:Label ID="lblMessage" runat="server" />
        ///  <div>
        ///    <cc1:GroupingView ID="GroupingView1" runat="server"     
        ///        DataSourceID="SqlDataSource1" 
        ///        GroupingDataField="Region" 
        ///        ItemPlaceholderID="itemPlaceholder" 
        ///        OnCommand="GroupingView1_Command"             
        ///        >
        ///        
        ///      <GroupTemplate>
        ///        <asp:Button ID="btn1" runat="server" 
        ///                    Text='<%#Eval("Region") %>'
        ///                    CommandName="Region" 
        ///                    CommandArgument='<%# Eval("Region") %>'
        ///                    />                            
        ///        <ul style="margin-top: 2px; margin-bottom: 34px;">
        ///            <asp:PlaceHolder ID="itemPlaceholder" runat="server" />
        ///        </ul>                            
        ///      </GroupTemplate>
        ///        
        ///      <ItemTemplate>
        ///        <li>
        ///            <asp:LinkButton ID="hl1" runat="server"
        ///                Text='<%# Eval("City") %>'
        ///                CommandName="City"
        ///                CommandArgument='<%# Eval("City") + "|" + Eval("State") + "|" + Eval("Region") %>'
        ///                />, 
        ///            <asp:LinkButton ID="hl2" runat="server"
        ///                Text='<%# Eval("State") %>'
        ///                CommandName="State"
        ///                CommandArgument='<%#Eval("State") + "|" + Eval("Region") %>'
        ///                />
        ///        </li>
        ///      </ItemTemplate>
        ///        
        ///    </cc1:GroupingView>
        ///        
        ///    <asp:SqlDataSource ID="SqlDataSource1" runat="server" 
        ///        ConnectionString="<%$ ConnectionStrings:ConnectionString %>" 
        ///        SelectCommand="SELECT * FROM [Cities]">
        ///    </asp:SqlDataSource>
        ///    
        ///  </div>
        ///  </form>
        ///</body>
        ///</html>
        /// ]]>
        /// </code>
        /// </example>
        public event GroupingViewCommandEventHandler Command;


        /// <summary>
        /// Raises the <see cref="GroupCreated" /> event.
        /// </summary>
        /// <param name="e">The event data.</param>
        protected void OnGroupCreated(GroupingViewEventArgs e)
        { if (GroupCreated != null) GroupCreated(this, e); }

        /// <summary>
        /// Raises the <see cref="GroupDataBound" /> event.
        /// </summary>
        /// <param name="e">The event data.</param>
        protected void OnGroupDataBound(GroupingViewEventArgs e)
        { if (GroupDataBound != null) GroupDataBound(this, e); }

        /// <summary>
        /// Raises the <see cref="ItemCreated" /> event.
        /// </summary>
        /// <param name="e">The event data.</param>
        protected void OnItemCreated(GroupingViewEventArgs e)
        { if (ItemCreated != null) ItemCreated(this, e); }

        /// <summary>
        /// Raises the <see cref="ItemDataBound" /> event.
        /// </summary>
        /// <param name="e">The event data.</param>
        protected void OnItemDataBound(GroupingViewEventArgs e)
        { if (ItemDataBound != null) ItemDataBound(this, e); }

        /// <summary>
        /// Raises the <see cref="Command" /> event.
        /// </summary>
        /// <param name="e">The event data.</param>
        protected void OnCommand(GroupingViewCommandEventArgs e)
        { if (Command != null) Command(this, e); }


        /// <summary>
        /// Determines whether an event for the <see cref="GroupingView" />
        /// control should be handled.
        /// </summary>
        /// <param name="source">The source of the event.</param>
        /// <param name="args">The event data.</param>
        /// <returns><c>true</c> when the GroupingView handles the event, <c>false</c> when the event should continue bubbling.</returns>
        protected override bool OnBubbleEvent(object source, EventArgs args)
        {
            CommandEventArgs e = (args as CommandEventArgs);
            if (e != null)
            {
                // establish our event args and raise the appropriate GroupCommand
                // or ItemCommand event
                int i = ControlInGroupOrItemTemplate(source as Control);
                GroupingViewTemplateEnum template = (i < 0 ? GroupingViewTemplateEnum.ItemTemplate : GroupingViewTemplateEnum.GroupTemplate);
                GroupingViewCommandEventArgs gva = new GroupingViewCommandEventArgs(e, template);
                OnCommand(gva);

                // halt the bubbling of the CommandEventArgs
                return true;
            }

            // otherwise, call on the base
            return base.OnBubbleEvent(source, args);
        }

        /// <summary>
        /// Determines whether a control appears in a GroupTemplate or ItemTemplate. 
        /// </summary>
        /// <param name="source">The control</param>
        /// <returns>The template type</returns>
        protected int ControlInGroupOrItemTemplate(Control source)
        {
            // return 1 if the object is in a GroupTemplate, -1 if in an ItemTemplate,
            // or 0 if in neither
            if (source != null)
            {
                Control parent = source.Parent;
                while (parent != null)
                {
                    if (parent is GroupingViewItem)
                    {
                        if (parent is GroupingViewGroupItem) return 1; else return -1;
                    }

                    parent = parent.Parent;
                }
            }

            return 0;
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets the collection of <see cref="GroupingViewItem" /> objects 
        /// created in the GroupingView representing groups, items, and separators in total.
        /// </summary>
        /// <remarks>
        /// Use the <see cref="Items" /> property after databinding to enumerate the items
        /// created through the application of all templates.
        /// </remarks>
        /// <example>
        /// See the <see cref="GroupingView.Groups" /> property documentation for an example
        /// of using the Items property.
        /// </example>
        [Browsable(false)]
        public GroupingViewItemCollection Items
        {
            get 
            { 
                // items when created are stored as child controls
                GroupingViewItemCollection items = new GroupingViewItemCollection();
                AddItemsToCollection(items, this);

                return items;
            }
        }

        
        // recursive procedure to inspect child controls
        private void AddItemsToCollection(GroupingViewItemCollection coll, Control parent)
        {
            foreach (Control c in parent.Controls)
            {
                if (c is GroupingViewItem)
                    coll.Add(c as GroupingViewItem);

                if (c.HasControls())
                    AddItemsToCollection(coll, c);
            }
        }


        /// <summary>
        /// Gets the collection of <see cref="GroupingViewGroupItem" /> objects 
        /// created in the GroupingView.
        /// </summary>
        /// <remarks>
        /// Use the <see cref="Groups" /> property after databinding to enumerate the group items
        /// created through the application of GroupTemplates.
        /// </remarks>
        /// <example>
        /// The following demonstrates the code-behind for an .aspx page that uses the
        /// <see cref="Groups" />, <see cref="Items" />, and 
        /// <see cref="GroupingViewGroupItem.NestedItems">NestedItems</see> properties
        /// to inspect items created in a <see cref="GroupingView" />.
        /// <code>
        /// <![CDATA[
        ///public partial class Items1 : System.Web.UI.Page
        ///{
        ///    protected void Page_PreRender(object sender, EventArgs e)
        ///    {
        ///        // use a bulleted list to present our use of the Groups, Items, and NestedItems
        ///        // properties
        ///        BulletedList list = new BulletedList();
        ///
        ///        // demonstrate item counts
        ///        list.Items.Add("Count of groups (GroupingView1.Groups.Count): " + GroupingView1.Groups.Count.ToString());
        ///        list.Items.Add("Count of total items (GroupingView1.Items.Count): " + GroupingView1.Items.Count.ToString());
        ///
        ///        // demonstrate use of the NestedItems property for a single group
        ///        const int kGroupIndex = 2;
        ///        GroupingViewGroupItem group = GroupingView1.Groups[kGroupIndex];
        ///        int cnt = group.NestedItems.Count;
        ///
        ///        // demonstrate locating a control within the applied GroupTemplate
        ///        // for a single group
        ///        string region = "";
        ///        Label lblRegion = group.FindControl("lblRegion") as Label;
        ///        if (lblRegion != null)
        ///            region = lblRegion.Text;
        ///
        ///        // demonstrate iterating items from a single group
        ///        string letters = "";
        ///        foreach (GroupingViewItem item in GroupingView1.Groups[kGroupIndex].NestedItems)
        ///        {
        ///            // is this an genuine item and not a separator?
        ///            if (item is GroupingViewItem && !(item is GroupingViewSeparatorItem))
        ///            {
        ///                // locate the label in the item
        ///                Label lbl = item.FindControl("lblCity") as Label;
        ///                if (lbl != null)
        ///                    letters += lbl.Text.Substring(0,1) + " ";
        ///            }
        ///        }
        ///        
        ///        // put all that information for the single group into bulleted list lines
        ///        string tmpl = "items in Group index #{0} '{1}' (GroupingView1.Groups[{0}].NestedItems.Count): {2} ";
        ///        list.Items.Add(string.Format(tmpl, kGroupIndex, region, cnt));
        ///
        ///        tmpl = "first letters of cities in Group index #{0}: {1}";
        ///        list.Items.Add(string.Format(tmpl, kGroupIndex, letters));                
        ///
        ///
        ///        // display the bulleted list in the appropriate place holder
        ///        ph1.Controls.Add(list);
        ///    }
        ///
        ///}
        /// ]]>
        /// </code>
        /// The following is the associated .aspx page for the example:
        /// <code>
        /// <![CDATA[
        ///<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Items1.aspx.cs" Inherits="TestGroupingView.Items1"  
        ///    %>
        ///
        ///<%@ Register Assembly="GroupingView" Namespace="UNLV.IAP.WebControls" TagPrefix="cc1" %>
        ///
        ///<!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">
        ///    <title>GroupingView Examples</title>
        ///  </head>
        ///  <body>
        ///    <form id="form1" runat="server">
        ///    <div class="itemExample">
        ///        <cc1:GroupingView ID="GroupingView1" runat="server" GroupingDataField="Region"
        ///                          DataSourceID="sql1"
        ///                          >
        ///                          
        ///            <GroupTemplate>
        ///                <b><asp:Label ID="lblRegion" runat="server" Text='<%# Eval("Region") %>' /></b>
        ///                <br />
        ///                <asp:PlaceHolder ID="itemPlaceholder" runat="server" />
        ///                <br />
        ///                <br />
        ///            </GroupTemplate>                          
        ///            
        ///            <ItemTemplate>
        ///                <asp:Label ID="lblCity" runat="server" Text='<%#Eval("City") %>' />
        ///                &nbsp;
        ///            </ItemTemplate>
        ///            
        ///        </cc1:GroupingView>
        ///        <hr />
        ///        
        ///        <asp:PlaceHolder ID="ph1" runat="server" />
        ///            
        ///        <asp:SqlDataSource ID="sql1" runat="server" 
        ///            ConnectionString="<%$ ConnectionStrings:ConnectionString %>" 
        ///            SelectCommand="SELECT * FROM [Cities]">        
        ///        </asp:SqlDataSource>
        ///
        ///    </div>
        ///
        ///    </form>
        ///  </body>
        ///</html>
        ///
        /// ]]>
        /// </code>
        /// </example>
        /// <seealso cref="GroupTemplate"/>
        [Browsable(false)]
        public GroupingViewGroupItemCollection Groups
        {
            get
            {
                // items when created are stored as child controls
                GroupingViewGroupItemCollection items = new GroupingViewGroupItemCollection();
                foreach (Control c in this.Controls)
                    if (c is GroupingViewGroupItem)                        
                        items.Add(c as GroupingViewGroupItem);

                return items;
            }
        }



        /// <summary>
        /// Gets or sets the custom content for the root container in a
        /// <see cref="GroupingView" /> control.
        /// </summary>
        /// <remarks>
        /// <para>
        /// Use the GroupTemplate property to define a custom user interface for the 
        /// root container of the <see cref="GroupingView"/> control.  The GroupTemplate
        /// is required.
        /// </para>
        /// <para>
        /// The GroupTemplate is applied once for each group of records in the data source
        /// sharing the same value in the <see cref="GroupingDataField" /> field.  When
        /// applied during databinding, the html markup and server controls are created
        /// using the first record of the group for evaluating databinding expressions.
        /// </para>
        /// <para>
        /// The GroupTemplate may contain controls that themselves support a DataSource property,
        /// such as the <see cref="System.Web.UI.WebControls.GridView">GridView</see> 
        /// and <see cref="UNLV.IAP.WebControls.Aggregation">Aggregation</see>
        /// controls.
        /// The GroupingView can automatically bind these data source controls in the GroupTemplate
        /// to the subset of data items in the data source that comprise the group.
        /// In this way, the responsibility for listing individual items in the group
        /// may be delegated to a child data source control.
        /// To automatically bind such controls, set the <see cref="AutobindDataSourceChildren" />
        /// property of the GroupingView control to <c>true</c> (the default).  To prevent
        /// such behavior, set this property to <c>false</c>.  To automatically bind only 
        /// some of the child data source controls, set AutobindDataSourceChildren to <c>true</c>
        /// and specify the ID's of the controls to bind as a comma-separated string
        /// in the <see cref="AutobindInclusions" /> property of the GroupingView.  Alternatively,
        /// you may specify a list of ID's of the controls to prevent automatic binding by setting the
        /// <see cref="AutobindExceptions" /> property.  If both AutobindInclusions and 
        /// AutobindExceptions are set, AutobindExceptions is ignored.
        /// </para>
        /// <para>
        /// To exercise complete control over the display of individual items in the group,
        /// instead of including a child data source control you may include a placeholder
        /// control in the GroupTemplate indicating where in the group layout
        /// child items should be rendered.  This placeholder control may be a bonafide
        /// <see cref="System.Web.UI.WebControls.PlaceHolder">PlaceHolder</see> control
        /// or another control that would function as a container.  Either way, set the ID
        /// of this control to <c>itemPlaceholder</c>.  If you would like to use a different
        /// ID for the control that contains group items, set the <see cref="ItemPlaceholderID"/>
        /// property of the GroupingView control to the desired value.
        /// Then specify the user interface for each individual item in the 
        /// <see cref="ItemTemplate"/> property of the GroupingView.
        /// </para>
        /// </remarks>
        /// <example>
        /// <para>
        /// The following example shows the use of a GridView in a GroupTemplate to list
        /// items for each group in the data source sharing a common "Region" value.
        /// Note that since we are delegating the responsibilty for listing items to the child
        /// GridView control, the AutobindDataSourceChildren property is set to <c>true</c>,
        /// and there is no <c>itemPlaceholder</c> control nor ItemTemplate defined.
        /// </para>
        /// <code>
        /// <![CDATA[
        ///<%@ Page Language="C#" %>
        ///<%@ Register Assembly="GroupingView" 
        ///             Namespace="UNLV.IAP.WebControls" 
        ///             TagPrefix="cc1" 
        ///             %>
        ///
        ///<!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">
        ///    <title>GroupingView Example</title>
        ///</head>
        ///<body>
        ///  <form id="form1" runat="server">
        ///    <h3>GroupingView Example</h3>
        ///    
        ///    <cc1:GroupingView ID="GroupingView1" runat="server"
        ///                      GroupingDataField="Region"
        ///                      AutobindDataSourceChildren="true"
        ///                      DataSourceID="sql1"
        ///                      >
        ///        
        ///        <GroupTemplate>
        ///            <asp:Label ID="lblRegion" runat="server"
        ///                       Text='<%# Eval("Region") %>'
        ///                       Font-Bold="true"
        ///                       />
        ///            
        ///            <asp:GridView ID="gvCities" runat="server" 
        ///                          AutoGenerateColumns="false"
        ///                          ShowHeader="false"
        ///                          >
        ///                <Columns>
        ///                    <asp:BoundField DataField="City" />
        ///                    <asp:BoundField DataField="State" />
        ///                </Columns>    
        ///            </asp:GridView> 
        ///            <br />
        ///            Count of cities:  
        ///            <cc1:Aggregation runat="server" 
        ///                             Function="Count" />                                                                         
        ///            <br />
        ///            <br />
        ///            <br />                                 
        ///        </GroupTemplate>
        ///        
        ///    </cc1:GroupingView>
        ///            
        ///    <asp:SqlDataSource ID="sql1" runat="server" 
        ///        ConnectionString="<%$ ConnectionStrings:ConnectionString %>" 
        ///        SelectCommand="SELECT * FROM [Cities]">        
        ///    </asp:SqlDataSource>
        /// 
        ///  </form>
        ///</body>
        ///</html>
        /// ]]>
        /// </code>
        /// <para>
        /// In the next example, rather than use a data source control in the GroupTemplate
        /// to list individual items, we use an ItemTempate instead for complete control
        /// over the item markup.  Note that the GroupTemplate
        /// contains the required placeholder control with ID <c>itemPlaceholder</c>.
        /// <code>
        /// <![CDATA[
        ///<%@ Page Language="C#" %>
        ///<%@ Register Assembly="GroupingView" 
        ///             Namespace="UNLV.IAP.WebControls" 
        ///             TagPrefix="cc1" 
        ///             %>
        ///
        ///<!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">
        ///    <title>GroupingView Example</title>
        ///</head>
        ///<body>
        ///  <form id="form1" runat="server">
        ///    <h3>GroupingView Example</h3>
        ///    
        ///    <cc1:GroupingView ID="GroupingView1" runat="server"
        ///                      GroupingDataField="Region"
        ///                      DataSourceID="sql1"
        ///                      AutobindDataSourceChildren="true"
        ///                      >
        ///        
        ///        <GroupTemplate>
        ///            <asp:Label ID="lblRegion" runat="server"
        ///                       Text='<%# Eval("Region") %>'
        ///                       Font-Bold="true"
        ///                       Font-Size="Large"
        ///                       ForeColor="ForestGreen"
        ///                       Font-Names="Tahoma,Verdana,Arial,Sans-Serif"
        ///                       />
        ///            
        ///            <table>
        ///                <%-- group header --%>
        ///                <tr>
        ///                   <th width="100" align="left">City</th>
        ///                   <th width="100" align="left">State</th>
        ///                   <th width="100" align="right">Sales</th>
        ///                </tr>
        ///                
        ///                <%-- group data items --%>
        ///                <asp:PlaceHolder ID="itemPlaceholder" runat="server" />
        ///                
        ///                <%-- group footer --%>
        ///                <tr><td colspan="3"><hr /></td></tr>
        ///                <tr>
        ///                    <td align="right" colspan="2">Group total:</td>
        ///                    <td align="right">
        ///                        <cc1:Aggregation runat="server" Function="Sum"
        ///                                         DataField="Sales"
        ///                                         FormatString="{0:#,##0.00}"
        ///                                         />
        ///                    </td>
        ///                </tr>
        ///                <tr>
        ///                    <td align="right" colspan="2">Group average:</td>
        ///                    <td align="right">
        ///                        <cc1:Aggregation runat="server" Function="Avg"
        ///                                         DataField="Sales"
        ///                                         FormatString="{0:#,##0.00}"
        ///                                         />
        ///                    </td>
        ///                </tr>
        ///            </table>                
        ///            <br /><br />                                 
        ///        </GroupTemplate>
        ///        
        ///        <ItemTemplate>
        ///            <%-- define items within a group --%>
        ///            <tr>
        ///                <td><%#Eval("City") %></td>
        ///                <td><%#Eval("State") %></td>
        ///                <td align="right"><%#Eval("Sales", "{0:#,##0.00}") %></td>
        ///            </tr>
        ///        </ItemTemplate>
        ///        
        ///    </cc1:GroupingView>
        ///    
        ///    Grand Total:
        ///    <cc1:Aggregation runat="server" Function="Sum"
        ///                     DataSourceID="sql1"
        ///                     DataField="Sales"
        ///                     FormatString="{0:#,##0.00}"
        ///                     />
        ///    <br />
        ///    Average per city:
        ///    <cc1:Aggregation ID="Aggregation1" runat="server" Function="Avg"
        ///                     DataSourceID="sql1"
        ///                     DataField="Sales"
        ///                     FormatString="{0:#,##0.00}"
        ///                     />
        ///                     
        ///            
        ///    <asp:SqlDataSource ID="sql1" runat="server" 
        ///        ConnectionString="<%$ ConnectionStrings:ConnectionString %>" 
        ///        SelectCommand="SELECT * FROM [Cities] Order By Region, State, City">        
        ///    </asp:SqlDataSource>
        ///    
        ///  </form>
        ///</body>
        ///</html>
        /// ]]>
        /// </code>
        /// </para>
        /// </example>
        [PersistenceMode(PersistenceMode.InnerProperty), TemplateContainer(typeof(GroupingViewItem))]
        [Browsable(false)]
        [NotifyParentProperty(true)]
        public ITemplate GroupTemplate
        {
            get { return _layoutTemplate; }
            set { _layoutTemplate = value; }
        }

        /// <summary>
        /// Gets or sets the custom content for the container between groups in a
        /// <see cref="GroupingView"/> control.
        /// </summary>
        /// <remarks>
        /// The GroupSeparatorTemplate is optional.  If included, it is rendered after
        /// each group of items except for the last.
        /// </remarks>
        [PersistenceMode(PersistenceMode.InnerProperty), TemplateContainer(typeof(GroupingViewItem))]
        [Browsable(false)]
        [NotifyParentProperty(true)]
        public ITemplate GroupSeparatorTemplate
        {
            get { return _groupSeparatorTemplate; }
            set { _groupSeparatorTemplate = value; }
        }

        /// <summary>
        /// Gets or sets the custom content for the item container in a
        /// <see cref="GroupingView" /> control.
        /// </summary>
        /// <remarks>
        /// <para>
        /// Use the GroupTemplate property to define a custom user interface for the 
        /// item container of the <see cref="GroupingView"/> control.  While the 
        /// <see cref="GroupTemplate" /> is required for a GroupingView, 
        /// the ItemTemplate is optional.  
        /// </para>
        /// <para>
        /// You may choose to use a data source child control in a GroupTemplate to list
        /// individual items, such as a <see cref="System.Web.UI.WebControls.GridView">GridView</see>
        /// control, in which case an ItemTemplate is not necessary.
        /// If you prefer to have complete control over the display of individual
        /// items within a group, instead of listing items with a child data source control,
        /// specify an ItemTemplate for the GroupingView and include a placeholder control
        /// with the id <c>itemPlaceholder</c> in the GroupTemplate.
        /// </para>
        /// </remarks>
        /// <example>
        /// The following example demonstrates how to use an ItemTemplate to define the 
        /// appearance for individual items within a group.  Note the use of the <c>itemPlaceholder</c>
        /// control in the GroupTemplate to determine the position of the items.
        /// <code>
        /// <![CDATA[
        ///<%@ Page Language="C#" %>
        ///<%@ Register Assembly="GroupingView" 
        ///             Namespace="UNLV.IAP.WebControls" 
        ///             TagPrefix="cc1" 
        ///             %>
        ///
        ///<!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">
        ///    <title>GroupingView Example</title>
        ///</head>
        ///<body>
        ///  <form id="form1" runat="server">
        ///    <h3>GroupingView Example</h3>
        ///    
        ///    <cc1:GroupingView ID="GroupingView1" runat="server"
        ///                      GroupingDataField="Region"
        ///                      DataSourceID="sql1"
        ///                      AutobindDataSourceChildren="true"
        ///                      >
        ///        
        ///        <GroupTemplate>
        ///            <asp:Label ID="lblRegion" runat="server"
        ///                       Text='<%# Eval("Region") %>'
        ///                       Font-Bold="true"
        ///                       Font-Size="Large"
        ///                       ForeColor="ForestGreen"
        ///                       Font-Names="Tahoma,Verdana,Arial,Sans-Serif"
        ///                       />
        ///            
        ///            <table>
        ///                <%-- group header --%>
        ///                <tr>
        ///                   <th width="100" align="left">City</th>
        ///                   <th width="100" align="left">State</th>
        ///                   <th width="100" align="right">Sales</th>
        ///                </tr>
        ///                
        ///                <%-- group data items --%>
        ///                <asp:PlaceHolder ID="itemPlaceholder" runat="server" />
        ///                
        ///                <%-- group footer --%>
        ///                <tr><td colspan="3"><hr /></td></tr>
        ///                <tr>
        ///                    <td align="right" colspan="2">Group total:</td>
        ///                    <td align="right">
        ///                        <cc1:Aggregation runat="server" Function="Sum"
        ///                                         DataField="Sales"
        ///                                         FormatString="{0:#,##0.00}"
        ///                                         />
        ///                    </td>
        ///                </tr>
        ///                <tr>
        ///                    <td align="right" colspan="2">Group average:</td>
        ///                    <td align="right">
        ///                        <cc1:Aggregation runat="server" Function="Avg"
        ///                                         DataField="Sales"
        ///                                         FormatString="{0:#,##0.00}"
        ///                                         />
        ///                    </td>
        ///                </tr>
        ///            </table>                
        ///            <br /><br />                                 
        ///        </GroupTemplate>
        ///        
        ///        <ItemTemplate>
        ///            <%-- define items within a group --%>
        ///            <tr>
        ///                <td><%#Eval("City") %></td>
        ///                <td><%#Eval("State") %></td>
        ///                <td align="right"><%#Eval("Sales", "{0:#,##0.00}") %></td>
        ///            </tr>
        ///        </ItemTemplate>
        ///        
        ///    </cc1:GroupingView>
        ///    
        ///    Grand Total:
        ///    <cc1:Aggregation runat="server" Function="Sum"
        ///                     DataSourceID="sql1"
        ///                     DataField="Sales"
        ///                     FormatString="{0:#,##0.00}"
        ///                     />
        ///    <br />
        ///    Average per city:
        ///    <cc1:Aggregation ID="Aggregation1" runat="server" Function="Avg"
        ///                     DataSourceID="sql1"
        ///                     DataField="Sales"
        ///                     FormatString="{0:#,##0.00}"
        ///                     />
        ///                     
        ///            
        ///    <asp:SqlDataSource ID="sql1" runat="server" 
        ///        ConnectionString="<%$ ConnectionStrings:ConnectionString %>" 
        ///        SelectCommand="SELECT * FROM [Cities] Order By Region, State, City">        
        ///    </asp:SqlDataSource>
        ///    
        ///  </form>
        ///</body>
        ///</html>
        /// ]]>
        /// </code>
        /// </example>
        [PersistenceMode(PersistenceMode.InnerProperty), TemplateContainer(typeof(GroupingViewItem))]
        [Browsable(false)]
        [NotifyParentProperty(true)]
        public ITemplate ItemTemplate
        {
            get { return _itemTemplate; }
            set { _itemTemplate = value; }
        }

        /// <summary>
        /// Gets or sets the custom content for the container between items within a group in a
        /// <see cref="GroupingView"/> control.
        /// </summary>
        /// <remarks>
        /// The ItemSeparatorTemplate is optional.  If included, it is rendered after
        /// each individual item within a group except for the last.
        /// </remarks>
        [PersistenceMode(PersistenceMode.InnerProperty), TemplateContainer(typeof(GroupingViewItem))]
        [Browsable(false)]
        [NotifyParentProperty(true)]
        public ITemplate ItemSeparatorTemplate
        {
            get { return _itemSeparatorTemplate; }
            set { _itemSeparatorTemplate = value; }
        }


        /// <summary>
        /// Gets or sets the custom content to display when there is no data to display
        /// in a <see cref="GroupingView" /> control.
        /// </summary>
        /// <remarks>
        /// The EmptyDataTemplate is optional.  Use it to render custom content 
        /// under the circumstance that the GroupingView control's data source
        /// contains no data items.
        /// </remarks>
        /// <example>
        /// The following example demonstrates a GroupingView with
        /// an EmptyDataTemplate defined.
        /// <code>
        /// <![CDATA[
        ///    <cc1:GroupingView ID="GroupingView1" runat="server"
        ///                      GroupingDataField="Region"
        ///                      AutobindDataSourceChildren="true"
        ///                      DataSourceID="sql1"
        ///                      >
        ///        
        ///        <GroupTemplate>
        ///            <%# Eval("Region") %>
        ///            <br />
        ///            <asp:GridView ID="gvCities" runat="server" 
        ///                          AutoGenerateColumns="false"
        ///                          ShowHeader="false"
        ///                          >
        ///                <Columns>
        ///                    <asp:BoundField DataField="City" />
        ///                    <asp:BoundField DataField="State" />
        ///                </Columns>    
        ///            </asp:GridView> 
        ///            <br />
        ///        </GroupTemplate>
        ///        
        ///        <EmptyDataTemplate>
        ///            There is no data to display.
        ///        </EmptyDataTemplate>
        ///        
        ///    </cc1:GroupingView>
        /// ]]>
        /// </code>
        /// </example>
        [PersistenceMode(PersistenceMode.InnerProperty), TemplateContainer(typeof(GroupingViewItem))]
        [Browsable(false)]
        [NotifyParentProperty(true)]
        public ITemplate EmptyDataTemplate
        {
            get { return _emptyDataTemplate; }
            set { _emptyDataTemplate = value; }
        }



        /// <summary>
        /// Gets or sets the name of the field in the <see cref="GroupingView.DataSource">DataSource</see> that
        /// determines how subsets of data will be grouped in a 
        /// <see cref="GroupingView"/> control.
        /// </summary>
        /// <remarks>
        /// <para>
        /// When binding to data, a <see cref="GroupingView" /> control will instantiate the
        /// <see cref="GroupTemplate" /> once for each group of items, defined by the GroupingDataField property.
        /// Each item in the data source that shares a common value for the GroupingDataField
        /// is part of that value's group.  The GroupTemplate itself is bound to the first
        /// data item in the group, and therefore may use databinding expressions relevant
        /// to the first data item.
        /// </para>
        /// <para>
        /// If the GroupingDataField property is left blank, the entire data source is treated
        /// as a single group and the GroupTemplate is instantiated once when data-bound.
        /// </para>
        /// </remarks>
        [Category("Data")]
        [Bindable(false)]
        [DefaultValue("")]
        [Description("The field in the DataSource that determines how subsets of data will be grouped.")]
        public string GroupingDataField
        {
            get { return _groupingDataField; }
            set { _groupingDataField = value; }
        }


        /// <summary>
        /// Gets or sets a value that determines if controls in a 
        /// <see cref="GroupTemplate" /> or <see cref="ItemTemplate" />
        /// with a DataSource property are automatically bound to the grouping data subset
        /// (defaults to <c>true</c>).
        /// </summary>
        /// <remarks>
        /// <para>
        /// If AutobindDataSourceChildren is <c>true</c>, when the <see cref="GroupingView" />
        /// is bound to a data source, any control in a <see cref="GroupTemplate" />
        /// with a <c>DataSource</c> property is automatically bound to the grouping data
        /// subset.  The grouping data subset are those items within the data source that share a
        /// common value for the <see cref="GroupingDataField" />.
        /// </para>
        /// <para>
        /// If you prefer to autobind only certain child controls and not all, 
        /// specify <c>true</c> for AutobindDataSourceChildren, and either list the
        /// desired controls to automatically bind in the <see cref="AutobindInclusions" />
        /// property, or list the controls for which automatic binding should be avoided
        /// in the <see cref="AutobindExceptions"/> property.  If both AutobindInclusions
        /// and AutobindExceptions are provided, AutobindExceptions is ignored.
        /// </para>
        /// </remarks>
        [Category("Behavior")]
        [DefaultValue(true)]
        [Description("Determines if controls in GroupTemplate or ItemTemplate with a DataSource property are automatically bound to the grouping data subset.")]
        public bool AutobindDataSourceChildren
        {
            get { return _autobindChildren; }
            set { _autobindChildren = value; }
        }


        /// <summary>
        /// Gets or sets a comma-separated list of control IDs found in a 
        /// <see cref="GroupTemplate" />
        /// that should not be automatically bound to the grouping data subset 
        /// when <see cref="AutobindDataSourceChildren"/> is <c>true</c>.
        /// </summary>
        /// <remarks>
        /// <para>
        /// Use the AutobindExceptions property to identify controls in a 
        /// <see cref="GroupTemplate" /> or <see cref="ItemTemplate" /> of a 
        /// <see cref="GroupingView" /> control which should <i>not</i> be bound
        /// to the grouping data subset as the template is applied.  Specify the controls
        /// as a single string, listing each control separated with commas.
        /// </para>
        /// <para>
        /// If you set AutobindExceptions, then leave the <see cref="AutobindInclusions" />
        /// property blank.  If both are supplied, the AutobindExceptions property is
        /// ignored.
        /// </para>
        /// </remarks>
        [Category("Behavior")]
        [DefaultValue("")]
        [Description("Comma-separated list of control IDs that if supplied, prevents the application of autobinding the grouping data subset to the listed controls.")]
        public string AutobindExceptions
        {
            get { return _autobindExceptions; }
            set { _autobindExceptions = value; }
        }

        /// <summary>
        /// Gets or sets a comma-separated list of control IDs found in a 
        /// <see cref="GroupTemplate" />
        /// that limits which controls should be automatically bound to the grouping data subset 
        /// when <see cref="AutobindDataSourceChildren"/> is <c>true</c>.
        /// </summary>
        /// <remarks>
        /// <para>
        /// Use the AutobindInclusions property to identify controls in a 
        /// <see cref="GroupTemplate" /> or <see cref="ItemTemplate" /> of a 
        /// <see cref="GroupingView" /> control which <i>should</i> be bound
        /// to the grouping data subset as the template is applied.  Specify the controls
        /// as a single string, listing each control separated with commas.  Use this
        /// property to limit which controls are subject to autobinding.  To autobind
        /// all data source controls in a GroupTemplate or ItemTemplate, specify
        /// <c>true</c> for the <see cref="AutobindDataSourceChildren" /> property and
        /// leave AutobindInclusions blank.
        /// </para>
        /// <para>
        /// If you set AutobindInclusions, then leave the <see cref="AutobindExceptions" />
        /// property blank.  If both are supplied, the AutobindExceptions property is
        /// ignored.
        /// </para>
        /// </remarks>
        [Category("Behavior")]
        [DefaultValue("")]
        [Description("Comma-separated list of control IDs that if supplied, limits the application of autobinding the grouping data subset to the listed controls.")]
        public string AutobindInclusions
        {
            get { return _autobindInclusions; }
            set { _autobindInclusions = value; }
        }


        /// <summary>
        /// Gets the <see cref="AutobindExceptions" /> property as a string array.
        /// </summary>
        private string[] AutobindExceptionsArray
        {
            get {return GetTrimmedStringArray(AutobindExceptions);}
        }

        /// <summary>
        /// Gets the <see cref="AutobindInclusions" /> property as a string array.
        /// </summary>
        private string[] AutobindInclusionsArray
        {
            get { return GetTrimmedStringArray(AutobindInclusions); }
        }

        /// <summary>
        /// Returns an array of trimmed strings, given a string containing a comma-separated list
        /// </summary>
        private string[] GetTrimmedStringArray(string s)
        {
            // given a string array in s, return an array of trimmed strings
            if (string.IsNullOrEmpty(s)) return null;

            string[] arr = s.ToLower().Split(new char[] { ',', ';' });
            for (int i = 0; i < arr.Length; i++)
                arr[i] = arr[i].Trim();
            return arr;
        }


        /// <summary>
        /// Gets or sets the ID of the placeholder control within a
        /// <see cref="GroupingView"/> control's <see cref="GroupTemplate"/> property
        /// that serves to position individual items (defaults to <c>itemPlaceholder</c>).
        /// </summary>
        [Category("Behavior")]
        [DefaultValue("itemPlaceholder")]
        [Description("The ID of the placeholder control within the GroupTemplate to locate items applied with the ItemTemplate.")]
        public string ItemPlaceholderID
        {
            get { return _itemPlaceholderId; }
            set { _itemPlaceholderId = value; }
        }


        #endregion

        #region CompositeDataBoundControl method overrides

        /// <summary>
        /// Creates child controls for a
        /// <see cref="GroupingView"/> control
        /// in either a databinding or postback context
        /// </summary>
        /// <param name="dataSource">The datasource to bind, or <c>null</c> in a postback context.</param>
        /// <param name="dataBinding"><c>true</c> if in a databinding context, <c>false</c> if not</param>
        /// <returns>The number of data items in the given data source.</returns>
        protected override int CreateChildControls(System.Collections.IEnumerable dataSource, bool dataBinding)
        {
            int count = 0;

            if (dataBinding)
                count = CreateChildControls_BindingScenario(dataSource);
            else
            {
                // in a postback scenario, we'll rely on our own viewstate and not
                // the dummy datasource
                count = CreateChildControls_PostbackScenario();
            }

            // if our ViewState object isn't created, then we had no data;
            // if we have an EmptyDataTemplate, apply it under that circumstance
            if (ViewState[kViewState_ItemsPerGroup] == null && EmptyDataTemplate != null)
                ApplyEmptyDataTemplate(this);

            return count;
        }

        #endregion

        #region Methods supporting the creation of child controls in a DataBinding context
        
        /// <summary>
        /// Creates child controls for a <see cref="GroupingView" /> control
        /// in a databinding context.
        /// </summary>
        /// <param name="dataSource">The data source being bound to the GroupingView</param>
        /// <returns>The number of data items enumerated in the data source.</returns>
        protected int CreateChildControls_BindingScenario(IEnumerable dataSource)
        {
            // we're in a binding scenario; create the child controls by enumerating 
            // over the given datasource; return the total number of dataitems
            // enumerated
            int totalItemCount = 0;
            int groupCount = 0;
            int[] itemsPerGroup = null;   // for tracking the number of items per group

            if (dataSource != null)
            {
                if (!string.IsNullOrEmpty(GroupingDataField))
                {
                    // start by getting an array of grouping values
                    string[] groups = GetArrayOfGroupingValues(dataSource);
                    itemsPerGroup = new int[groups.Length];

                    // then loop through each and create an subset datasource
                    foreach (string groupValue in groups)
                    {
                        // apply a separator?
                        if (groupCount > 0 && GroupSeparatorTemplate != null)
                            ApplyGroupSeparatorTemplate_BindingScenario();

                        // apply the group
                        IEnumerable groupSource = DataSubsetForGroup(dataSource, groupValue);
                        int iCount = CreateChildControlsForGroup_BindingScenario(groupSource, groupCount, totalItemCount);
                        totalItemCount += iCount;
                        itemsPerGroup[groupCount] = iCount;
                        groupCount++;
                    }
                }
                else
                {
                    // if we don't have a GroupingDataField identified, treat the entire listing
                    // as one group
                    totalItemCount = CreateChildControlsForGroup_BindingScenario(dataSource, 0, 0);
                    groupCount = 1;
                    itemsPerGroup = new int[1];
                    itemsPerGroup[0] = totalItemCount;
                }
            }

            // to support the recreation of child controls upon postbacks,
            // store the itemsPerGroup array in ViewState
            ViewState[kViewState_ItemsPerGroup] = itemsPerGroup;

            // return the total number of data items iterated
            return totalItemCount;

        }

        /// <summary>
        /// Creates child controls for a single group in a databinding context.
        /// </summary>
        /// <param name="groupDataSource">The subset of the <see cref="GroupingView" /> control's
        /// data source which comprise the group
        /// </param>
        /// <param name="groupIndex">The 0-based index of the group among the total number of groups in the GroupingView's data source</param>
        /// <param name="itemIndex">The 0-based index of the first item within the group among the total number of items in the GroupingView's data source.</param>
        /// <returns>The number of data items enumerated for the group.</returns>
        protected int CreateChildControlsForGroup_BindingScenario(System.Collections.IEnumerable groupDataSource, int groupIndex, int itemIndex)
        {
            // create child controls for the given group datasource by 
            // applying the GroupTemplate (and within that, ItemTemplates are applied);
            // return the number of ItemTemplates applied;
            if (GroupTemplate != null)
                return ApplyGroupTemplate_BindingScenario(groupDataSource, groupIndex, itemIndex);
            else
                return 0;
        }


        /// <summary>
        /// Instantiates the <see cref="GroupTemplate" /> for a given group, 
        /// and binds data to the group's data source.
        /// </summary>
        /// <param name="groupDataSource">The subset of the <see cref="GroupingView" /> control's
        /// data source which comprise the group
        /// </param>
        /// <param name="groupIndex">The 0-based index of the group among the total number of groups in the GroupingView's data source</param>
        /// <param name="itemIndex">The 0-based index of the first item within the group among the total number of items in the GroupingView's data source.</param>
        /// <returns>The number of data items enumerated for the group.</returns>
        protected int ApplyGroupTemplate_BindingScenario(IEnumerable groupDataSource, int groupIndex, int itemIndex)
        {
            // using the first record in groupDataSource, apply the GroupTemplate;
            // return the total number of ItemTemplates applied across the groupDataSource
            int itemCount = 0;

            // get the first record in the groupDataSource and apply the GroupTemplate 
            // using it
            IEnumerator enumerator = groupDataSource.GetEnumerator();
            if (enumerator.MoveNext())
            {

                // instantiate the layout template using the first item of the data source
                object firstDataItem = enumerator.Current;
                GroupingViewItem groupItem = new GroupingViewGroupItem(firstDataItem, groupIndex, itemIndex, 0);
                GroupTemplate.InstantiateIn(groupItem);

                // add the instantiated groupItem to the controls collection
                this.Controls.Add(groupItem);

                // if we're databinding and want to autobind other children controls, 
                // assign all child controls with a DataSource property in the group layout
                // the same groupDataSource
                if (AutobindDataSourceChildren)
                    BindChildControlsToDataSource(groupItem, groupDataSource, AutobindInclusionsArray, AutobindExceptionsArray);

                // the GroupingViewItem is instatiated; before binding, fire the GroupCreated event
                OnGroupCreated(new GroupingViewEventArgs(groupItem, groupIndex, itemIndex, 0));

                // now evaluate databinding expressions in the group
                groupItem.DataBind();

                // finally, if the group contains an itemPlacholder, find it
                // and populate it for each dataItem in the group
                Control ph = groupItem.FindControl(_itemPlaceholderId);
                if (ph == null)
                {
                    // there isn't an itemplaceholder; return 0 items
                    itemCount = 0;
                }
                else
                {
                    if (ItemTemplate != null)
                        itemCount = ApplyItemTemplate_BindingScenario(ph, groupDataSource, groupIndex, itemIndex);
                    else
                        itemCount = 0;      // no ItemTemplates applied
                }

                // fire the GroupDataBound event
                OnGroupDataBound(new GroupingViewEventArgs(groupItem, groupIndex, itemIndex, 0));

            }

            return itemCount;

        }

        /// <summary>
        /// Instantiates the <see cref="GroupSeparatorTemplate" /> 
        /// </summary>
        protected void ApplyGroupSeparatorTemplate_BindingScenario()
        {
            GroupingViewSeparatorItem sepItem = new GroupingViewSeparatorItem();
            GroupSeparatorTemplate.InstantiateIn(sepItem);
            this.Controls.Add(sepItem);
            sepItem.DataBind();
        }

        /// <summary>
        /// Enumerates through the data items in a given group, instantiates 
        /// the <see cref="ItemTemplate"/> for each, and evaluates databinding expressions.
        /// </summary>
        /// <param name="ph">The placeholder control in which instantiated items should be located.</param>
        /// <param name="groupDataSource">The subset of the <see cref="GroupingView" /> control's
        /// data source which comprise the group of items.
        /// </param>
        /// <param name="groupIndex">The 0-based index of the group among the total number of groups in the GroupingView's data source</param>
        /// <param name="itemIndex">The 0-based index of the first item within the group among the total number of items in the GroupingView's data source.</param>
        /// <returns>The number of data items enumerated for the group.</returns>
        protected int ApplyItemTemplate_BindingScenario(Control ph, IEnumerable groupDataSource, int groupIndex, int itemIndex)
        {
            // loop through each dataItem in the given source applying the ItemTemplate
            // in the given placeholder; return the total number of dataItems applied
            int itemCount = 0;

            // loop through each dataitem
            foreach (object dataItem in groupDataSource)
            {
                if (itemCount > 0 && ItemSeparatorTemplate != null)
                    ApplyItemSeparatorTemplate_BindingScenario(ph);

                GroupingViewItem item = new GroupingViewItem(dataItem, groupIndex, itemIndex + itemCount, itemCount);
                ItemTemplate.InstantiateIn(item);
                ph.Controls.Add(item);

                // before binding, fire the ItemCreated event
                OnItemCreated(new GroupingViewEventArgs(item, groupIndex, itemIndex + itemCount, itemCount));

                // if we want to autobind children, do that now for the item layout
                // (though it is much more likely that these controls will be in the
                // GroupTemplate and not the ItemTemplate
                if (AutobindDataSourceChildren)
                    BindChildControlsToDataSource(item, groupDataSource, AutobindInclusionsArray, AutobindExceptionsArray);

                // now bind the item & children
                item.DataBind();

                // and raise the ItemDataBound event
                OnItemDataBound(new GroupingViewEventArgs(item, groupIndex, itemIndex + itemCount, itemCount));

                itemCount++;
            }

            return itemCount;
        }

        /// <summary>
        /// Instantiates the <see cref="ItemSeparatorTemplate"/> in the given
        /// placeholder control.
        /// </summary>
        /// <param name="ph">The placeholder control in which instantiated items should be located.</param>
        protected void ApplyItemSeparatorTemplate_BindingScenario(Control ph)
        {
            GroupingViewSeparatorItem sepItem = new GroupingViewSeparatorItem();
            ItemSeparatorTemplate.InstantiateIn(sepItem);
            ph.Controls.Add(sepItem);
            sepItem.DataBind();
        }


        #endregion

        #region Methods supporting the creation of child controls in a Postback context

        /// <summary>
        /// Creates child controls for a <see cref="GroupingView" /> control
        /// in a postback context.
        /// </summary>
        /// <returns>The number of items enumerated.</returns>
        protected int CreateChildControls_PostbackScenario()
        {
            // we're in a postback scenario, in which case the dataSource does not contain
            // valid data; we're expected to recreate child controls which will then 
            // repopulate themselves using ViewState; in this case, dataSource is a dummy
            // int array of the same length as the total number of dataitems initially
            // bound;

            // since we're binding based on groups first, we need to retrieve the ItemsPerGroup
            // array we stored in ViewState which gives both the number of groups, and the
            // number of items in each group.  With that, we'll reapply Group and ItemTemplates
            // and child controls will populate themselves from ViewState accordingly.

            int[] itemsPerGroup = (ViewState[kViewState_ItemsPerGroup] as int[]);
            if (itemsPerGroup == null)
                return 0;
            else
            {
                // the array length indicates the number of groups; loop through each
                // and apply GroupTemplates
                int itemCount = 0;
                for (int i = 0; i < itemsPerGroup.Length; i++)
                {
                    if (i > 0 && GroupSeparatorTemplate != null)
                        ApplyGroupSeparatorTemplate_PostbackScenario();

                    itemCount += ApplyGroupTemplate_PostbackScenario(itemsPerGroup[i], i, itemCount);
                }

                return itemCount;
            }

        }

        /// <summary>
        /// Creates child controls for a group in a postback context.
        /// </summary>
        /// <param name="numItems">The number of items in the group.</param>
        /// <param name="groupIndex">The 0-based index of the group among the total number of groups in the GroupingView's data source</param>
        /// <param name="itemIndex">The 0-based index of the first item within the group among the total number of items in the GroupingView's data source.</param>
        /// <returns>The number of items enumerated.</returns>
        protected int CreateChildControlsForGroup_PostbackScenario(int numItems, int groupIndex, int itemIndex)
        {
            // create child controls for a group with the given number of items
            // (within the context of a Postback scenario)
            if (GroupTemplate != null)
                return ApplyGroupTemplate_PostbackScenario(numItems, groupIndex, itemIndex);
            else
                return 0;
        }

        /// <summary>
        /// Instantiates the <see cref="GroupTemplate" /> for a given group in a postback
        /// context.
        /// </summary>
        /// <param name="numItems">The number of items in the group.</param>
        /// <param name="groupIndex">The 0-based index of the group among the total number of groups in the GroupingView's data source</param>
        /// <param name="itemIndex">The 0-based index of the first item within the group among the total number of items in the GroupingView's data source.</param>
        /// <returns>The number of items enumerated.</returns>
        protected int ApplyGroupTemplate_PostbackScenario(int numItems, int groupIndex, int itemIndex)
        {
            // for a postback scenario, apply the GroupTemplate the given number
            // of times to create child controls; individual controls will then 
            // repopulate themselves through their own viewStates.  
            // if numItems > 0 then locate the itemPlaceholder within the GroupTemplate
            // and apply ItemTemplates within it; return the number of items applied

            GroupingViewItem groupItem = new GroupingViewGroupItem(null, groupIndex, itemIndex, 0);
            GroupTemplate.InstantiateIn(groupItem);

            // add the instantiated groupItem to the controls collection
            this.Controls.Add(groupItem);

            // fire the GroupCreated event
            OnGroupCreated(new GroupingViewEventArgs(groupItem, groupIndex, itemIndex, 0));

            // if we have an itemPlaceholder and numItems > 0, apply ItemTemplates too
            if (numItems > 0)
            {
                Control ph = groupItem.FindControl(_itemPlaceholderId);
                if (ph == null)
                {
                    // there isn't an itemplaceholder; return 0 items
                    return 0;
                }
                else
                {
                    if (ItemTemplate != null)
                        return ApplyItemTemplate_PostbackScenario(ph, numItems, groupIndex, itemIndex);
                    else
                        return 0;      // no ItemTemplates applied
                }
            }
            else
                return 0;

        }

        /// <summary>
        /// Instantiates the <see cref="GroupSeparatorTemplate"/> in a postback context.
        /// </summary>
        protected void ApplyGroupSeparatorTemplate_PostbackScenario()
        {
            GroupingViewSeparatorItem sepItem = new GroupingViewSeparatorItem();
            GroupSeparatorTemplate.InstantiateIn(sepItem);
            this.Controls.Add(sepItem);
        }


        /// <summary>
        /// Instantiates the <see cref="ItemTemplate"/> for each item within a group in
        /// a postback context.
        /// </summary>
        /// <param name="ph">The placeholder control in which instantiated items should be located.</param>
        /// <param name="numItems">The number of items in the group.</param>
        /// <param name="groupIndex">The 0-based index of the group among the total number of groups in the GroupingView's data source</param>
        /// <param name="itemIndex">The 0-based index of the first item within the group among the total number of items in the GroupingView's data source.</param>
        /// <returns>The number of items enumerated.</returns>
        protected int ApplyItemTemplate_PostbackScenario(Control ph, int numItems, int groupIndex, int itemIndex)
        {
            // for a postback scenario, apply the ItemTemplate the given number
            // of times to create child controls in the given placeholder; individual
            // controls will then repopulate themselves through their own viewStates;
            // return numItems

            for (int i = 0; i < numItems; i++)
            {
                if (i > 0 && ItemSeparatorTemplate != null)
                    ApplyItemSeparatorTemplate_PostbackScenario(ph);

                GroupingViewItem item = new GroupingViewItem(null, groupIndex, itemIndex + i, i);
                ItemTemplate.InstantiateIn(item);
                ph.Controls.Add(item);

                // fire the ItemCreated event
                OnItemCreated(new GroupingViewEventArgs(item, groupIndex, itemIndex + i, i));
            }

            return numItems;
        }


        /// <summary>
        /// Instantiates the <see cref="ItemSeparatorTemplate"/> in a postback context.
        /// </summary>
        /// <param name="ph">The placeholder control in which instantiated items should be located.</param>
        protected void ApplyItemSeparatorTemplate_PostbackScenario(Control ph)
        {
            GroupingViewSeparatorItem sepItem = new GroupingViewSeparatorItem();
            ItemSeparatorTemplate.InstantiateIn(sepItem);
            ph.Controls.Add(sepItem);
        }


        #endregion

        #region Helper methods

        /// <summary>
        /// Instantiates the <see cref="EmptyDataTemplate" /> within the given control.
        /// </summary>
        /// <param name="ph">the control within which instantiated items will be added</param>
        protected void ApplyEmptyDataTemplate(Control ph)
        {
            // instantiate the empty data template in the given control
            GroupingViewItem item = new GroupingViewItem(null, -1, -1, -1);
            EmptyDataTemplate.InstantiateIn(item);

            // add the instantiated groupItem to the controls collection
            ph.Controls.Add(item);
            item.DataBind();
        }

        /// <summary>
        /// Returns a subset of data items within the given data source for the given group.
        /// </summary>
        /// <param name="dataSource">The data source to inspect.</param>
        /// <param name="groupingValue">The value within the <see cref="GroupingView" /> control's <see cref="GroupingDataField"/> which defines the group.</param>
        /// <returns>The group's subset of data</returns>
        protected IEnumerable DataSubsetForGroup(IEnumerable dataSource, string groupingValue)
        {
            try
            {
                foreach (object dataItem in dataSource)
                {
                    // use the databinder to get the grouping value
                    string itemValue = DataBinder.GetPropertyValue(dataItem, GroupingDataField).ToString();
                    if (itemValue.ToLower() == groupingValue.ToLower())
                        yield return dataItem;
                }
            }
            finally { }
        }

        /// <summary>
        /// Returns an array of distinct values found within the <see cref="GroupingDataField"/>
        /// of the given data source.
        /// </summary>
        /// <param name="dataSource">The data source to inspect.</param>
        /// <returns>The array of distinct values.</returns>
        protected string[] GetArrayOfGroupingValues(IEnumerable dataSource)
        {
            // return a string array of distinct values appearing in the GroupingDataField
            // for the given dataSource
            List<string> list = new List<string>();

            try
            {
                foreach (object dataItem in dataSource)
                {
                    // use the databinder to get the grouping value
                    string groupingValue = DataBinder.GetPropertyValue(dataItem, GroupingDataField).ToString();
                    if (!list.Contains(groupingValue))
                        list.Add(groupingValue);
                }
            }
            catch
            {
                // throw exception?
            }

            return list.ToArray();

        }

        /// <summary>
        /// Binds the children of the given control to the given data source, provided they
        /// expose a <c>DataSource</c> property.
        /// </summary>
        /// <param name="parent">The control whose DataSource children should be bound.</param>
        /// <param name="dataSource">The data source to bind.</param>
        /// <param name="inclusions">An array of IDs that identify controls to include in the binding application, or <c>null</c> if all controls should be included.</param>
        /// <param name="exceptions">An array of IDs that identify controls to exclude from the binding, or <c>null</c> if no controls should be excluded.</param>
        protected void BindChildControlsToDataSource(Control parent, IEnumerable dataSource, string[] inclusions, string[] exceptions)
        {
            // loop through all children of the given parent; if any exposes a DataSource
            // property, bind it to the given datasource;
            // if exceptions != null, do not include those control IDs;
            // if inclusions != null, only include those control IDs;
            foreach (Control c in parent.Controls)
            {
                // use reflection to get the DataSource property if available
                PropertyInfo pi = c.GetType().GetProperty("DataSource", BindingFlags.Public | BindingFlags.Instance);
                if (pi != null)
                {
                    bool bApply = true;
                    string id = c.ID;
                    if (!string.IsNullOrEmpty(id))
                    {
                        id = id.ToLower();
                        if (inclusions != null)
                        {
                            if (Array.IndexOf<string>(inclusions, id) < 0)
                                bApply = false;
                        }
                        else if (exceptions != null)
                        {
                            if (Array.IndexOf<string>(exceptions, id) >= 0)
                                bApply = false;
                        }
                    }
                    // set the value to the given datasource
                    if (bApply)
                        pi.SetValue(c, dataSource, null);
                }

                // recursively check for children
                if (c.HasControls())
                    BindChildControlsToDataSource(c, dataSource, inclusions, exceptions);
            }
        }

        #endregion

    }
}
