Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Dynamic Columns in Listview

0.00/5 (No votes)
5 Jun 2013 1  
Create dynamic columns in listview control

4 columns - Click to enlarge image

10 columns - Click to enlarge image

Introduction

This article discusses how to create dynamic columns in listview control.

Background

Creating a GridView with dynamic columns generated is easy, it even comes without much effort by simply configuring the AutoGenerateColumns property.

Using the Code

First, we create a Employee-vs-Vendor table, using the Common Table Expressions(CTE) and PIVOT. Check it from the source attached.

On the listview layout template, we have to declare a placeholder:

<LayoutTemplate>
    <div class="listviewGrid">
        <table id="tblViewHdr" class="CollapsableExpandable" cellpadding="0"
        cellspacing="0" style="word-break:break-all;word-wrap:break-word">
            <tr class="head">
                <asp:PlaceHolder runat="server" ID="phDynamicHdr" />
            </tr>
            <tr id="itemPlaceholder" runat="server" />
        </table>
    </div>
</LayoutTemplate>

The CSS style used is to break the words if you specify too many columns.

Then in the ItemTemplate, we do not need to add extra dynamic columns we want, since it's going to be done programmatically.

<ItemTemplate>
    <tr id="row" runat="server" class="item">
        <td class="first">
            <img src="Images/minus.png" />
        </td>
    </tr>
    <asp:ListView runat="server"
    onitemdatabound="lst1stLevel_ItemDataBound" ID="lst1stLevel">
		<%-- more contents here--%>
	 </asp:ListView>
</ItemTemplate>

Depending on the level of the query, you have to create a nested ItemTemplate accordingly. I believe this can be done through AJAX. However, I leave this to the future.

Then when we start binding the query to the listview, we bind it level-by-level from the SQL retrieved from CTE & PIVOT above.

public void bindListView(DataTable dtEmpID)
{
    DataTable dt = new DataTable();
    dt = ((DataTable)ViewState["cachedTable"]).Clone();

    if (((DataTable)ViewState["cachedTable"]).Rows.Count > 0)
    {
        DataRow[] drResults = ((DataTable)ViewState
        ["cachedTable"]).Select("GENERATION = 0");

        foreach (DataRow dr in drResults)
        {
            object[] row = dr.ItemArray;
            dt.Rows.Add(row);
        }
    }

    lsvHighLvlFormAccess.DataSource = dt;
    lsvHighLvlFormAccess.DataBind();
}

When highest level binding is done, we can populate the header caption. This is the part when you set the column-span & generate the column header dynamically.

protected void lsvHighLvlFormAccess_DataBound
	(object sender, EventArgs e)
{
    PlaceHolder phDynamicHdr =
    (PlaceHolder)lsvHighLvlFormAccess.FindControl("phDynamicHdr");
    // check phDynamicHdr.Controls.Count;
    // could be called twice when postback
    if (phDynamicHdr != null /*&& phDynamicHdr.Controls.Count == 0*/)
    {
        Literal ltrl = new Literal();

        DataTable dt = ((DataTable)ViewState["cachedTable"]);
        if (dt != null && dt.Rows.Count > 0)
        {
            foreach (DataColumn dc in dt.Rows[0].Table.Columns)
            {
                if (!(dc.ColumnName.Equals("GENERATION") ||
                        dc.ColumnName.Equals("hierarchy") ||
                        dc.ColumnName.Equals("rowNo") ||
                        dc.ColumnName.Equals("EmployeeID")))
                {
                    if (dc.ColumnName.Equals("LoginID"))
                    {
                        ltrl.Text += "<th colspan=" + (I_COLSPAN + 1) +
                        ">" + dc.ColumnName + "</th>";
                    }
                    else
                        ltrl.Text += "<th>" + dc.ColumnName + "</th>";
                }
            }
        }

        if (phDynamicHdr.Controls.Count > 0)
        {
            // if current dynamic columns is different with existing dynamic columns
            if (!((Literal)phDynamicHdr.Controls[0]).Text.Equals(ltrl.Text))
            {
                // remove the whole previous dynamic columns
                phDynamicHdr.Controls.Remove(phDynamicHdr.Controls[0]);
                // replace with new dynamic columns
                phDynamicHdr.Controls.Add(ltrl);
            }
        }
        else
            phDynamicHdr.Controls.Add(ltrl);
    }

    HtmlTableCell td =
    (HtmlTableCell)lsvHighLvlFormAccess.FindControl("imgCollapseExpand");
    if (td != null)
    {
        DataTable dt = ((DataTable)ViewState["cachedTable"]);
        if (dt != null && dt.Rows.Count > 0)
        {
            td.ColSpan = dt.Rows[0].Table.Columns.Count +
            I_COLSPAN - 1;//I_COLSPAN - 1 : to put the 'minus.png'
        }
    }
}

On each level row is bound, we need to fire the event to populate its descendant. Here, checkboxes are used. You can replace with any other control too.

protected void lsvHighLvlFormAccess_ItemDataBound
	(object sender, ListViewItemEventArgs e)
{
    HtmlTableRow row = (HtmlTableRow)e.Item.FindControl("row");
    ListViewDataItem item = (ListViewDataItem)e.Item;
    System.Data.DataRowView drv = (System.Data.DataRowView)item.DataItem;

    dynamicPopulateRow(row, drv, 0);

    // more contents here
}

where dynamicPopulateRow() is:

private void dynamicPopulateRow(HtmlTableRow row,
	System.Data.DataRowView drv, int iGeneration)
{
    if (row != null)
    {
        foreach (DataColumn dc in drv.Row.Table.Columns)
        {
            string sEmployeeID = drv["LoginID"].ToString();

            if (dc.ColumnName.Equals("LoginID"))
            {
                // Define a new HtmlTableCell control.
                HtmlTableCell cell = new HtmlTableCell("td");

                // Create the text for the cell.
                cell.Controls.Add(new LiteralControl
                	(Convert.ToString(drv[dc.ColumnName])));
                cell.ColSpan = dc.ColumnName.Equals
                	("LoginID") ? I_COLSPAN - iGeneration : 1;

                // Add the cell to the HtmlTableRow Cells collection.
                row.Cells.Add(cell);
            }
            else if (!(dc.ColumnName.Equals("GENERATION") ||
                        dc.ColumnName.Equals("hierarchy") ||
                        dc.ColumnName.Equals("rowNo") ||
                        dc.ColumnName.Equals("EmployeeID")))
            {
                // Define a new HtmlTableCell control.
                HtmlTableCell cell = new HtmlTableCell("td");

                bool bIsNull = drv[dc.ColumnName] is System.DBNull;

                Literal ltrl = new Literal();
                ltrl.Text += "<input type=\"checkbox\"
                				name=\"" + dc.ColumnName + "\"" +
                                (bIsNull ? "" : " value=" +
                                	drv[dc.ColumnName].ToString()) +
                                " id=\"" + sEmployeeID + "~" +
                                dc.ColumnName.Replace(" ", "_") +
                                "\"" +//will be retrieved later
                                " onclick=\"didModify(this)\" " +
                                (bIsNull ? " disabled" : "") +
                                (!bIsNull && ((int)drv[dc.ColumnName]) > 0 ?
                                " checked>" : ">");

                cell.Controls.Add(ltrl);
                // Add the cell to the HtmlTableRow Cells collection.
                row.Cells.Add(cell);
            }
            else
            {
                //other rows
            }
        }
    }
}

If you more nested levels, just add these codes at the end of ItemDataBound event:

var lst1stLevel = (ListView)e.Item.FindControl("lst1stLevel");
populateLV(lst1stLevel, 1, (string)drv["hierarchy"], Convert.ToInt32(drv["rowNo"]));

where populateLV() is:

private void populateLV(ListView lv,
int iNextGeneration, string sHierarchy, int iCurrRowNo)
{
    if (lv != null)
    {
        DataTable dt = new DataTable();
        dt = ((DataTable)ViewState["cachedTable"]).Clone();// clone schema only

        List<int /> levels = ((DataTable)ViewState["cachedTable"]).
                            Select("GENERATION = " +
                            (iNextGeneration > 0 ? iNextGeneration - 1 : 0) +
                            " AND hierarchy LIKE '" + sHierarchy + "%'"). // no space
                            AsEnumerable().
                            Select(al => Convert.ToInt32
                            (al.Field<object>("rowNo"))).Distinct().ToList();

        // duplicate hierarchy, display at smallest rowNo will do
        if (levels.Count > 0 && levels.Min() == iCurrRowNo)
        {
            DataRow[] drResults = ((DataTable)ViewState["cachedTable"]).
                                Select("GENERATION = " +
                                iNextGeneration + " AND hierarchy LIKE '" +
                                sHierarchy + " %'");

            foreach (DataRow dr in drResults)
            {
                object[] obRow = dr.ItemArray;
                dt.Rows.Add(obRow);
            }
            lv.DataSource = dt;
            lv.DataBind();
        }
    }
}

Repeat this for second-level, third-level, etc.

protected void lst2ndLevel_ItemDataBound(object sender, ListViewItemEventArgs e)
{
    HtmlTableRow row = (HtmlTableRow)e.Item.FindControl("row");
    ListViewDataItem item = (ListViewDataItem)e.Item;
    System.Data.DataRowView drv = (System.Data.DataRowView)item.DataItem;
    int iCurrLvl = 2;
    //populate dynamic cells
    dynamicPopulateRow(row, drv, iCurrLvl);

    //populate nested LV
    var lst3rdLevel = (ListView)e.Item.FindControl("lst3rdLevel");
    populateLV(lst3rdLevel, iCurrLvl + 1, (string)drv["hierarchy"], 
    Convert.ToInt32(drv["rowNo"]));
}

Done. That's it.

Points of Interest

By using AJAX, we can dynamically populate the nested level as well.

History

  • 5th June, 2013: Initial post

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here