views:

230

answers:

3

My issue is that I want to display data in a hierarchal structure as so:

  • Democrat
    • County Clerk
      • Candidate 1
      • Candidate 2
    • Magistrate
      • Candidate 1
      • Candidate 2
      • Candidate 3

But I'm retrieving the dataset like this:

Party | Office | Candidate
--------------------------------------------
Democrat | County Clerk | Candidate 1
Democrat | County Clerk | Candidate 2
Democrat | Magistrate | Candidate 1
Democrat | Magistrate | Candidate 2
Democrat | Magistrate | Candidate 3

I planned on using nested repeaters, but I need a distinct value of Party, and then distinct values of office name within that party in order to do it.

Are there any .NET functions to easily do what I'm attempting to? Would there be a better way of displaying the information other than repeaters?

Thanks in advance!

A: 

A TreeView control would give you the format you want. However populating it from the database takes some effort. There is an article here about populating asp.net TreeViews from a database: http://www.codeproject.com/KB/tree/TreeViewWithDatabase.aspx

Marc Tidd
+1  A: 

Hi,

I am not sure if you would be interested to use a third party control, but I had EXACTLY SAME requirement of yours and needed to display the data hierarchically, showing repeating values as single one with collapsible nodes (like trees). However, Tree controls are not data bound and still needs a fair bit of coding. But this style of Tree building based on non-normalized data can be easily done with DataBinding. The FlexGrid I have used was DataBound to the DataTable and just needed a few lines of code to display EXACTLY what you require, albeit based on a Third Party Control.

I have used the ComponentOne FlexGrid control and used its 'SubTotal' feature to generate the hierarchical data. I am pasting the code below, just in case you are interested to use the ComponentOne FlexGrid. You can download a Demo copy and check.

        // Showing master policy in GROUPS
        // -----------------------------------------------------------------------------------
        // set tree mode to show settings in GROUPS
        flxAvailableSettings.Tree.Style = TreeStyleFlags.Simple;
        // show outline tree on column 1.
        flxAvailableSettings.Tree.Column = 0;
        flxAvailableSettings.Cols[0].Visible = true;
        flxAvailableSettings.Cols[0].Width = 15;
        flxAvailableSettings.AllowMerging = AllowMergingEnum.Nodes;
        // subtotal on column 1, outline level 0
        flxAvailableSettings.Subtotal(AggregateEnum.None, 0, 0, 0, "{0}");
        // use owner draw to suppress repeating group names in Non-Node rows
        flxAvailableSettings.DrawMode = DrawModeEnum.OwnerDraw;
        flxAvailableSettings.OwnerDrawCell += new C1.Win.C1FlexGrid.OwnerDrawCellEventHandler(flxAvailableSettings_OwnerDrawCell);
        // done, autosize columns to finish
        flxAvailableSettings.AutoSizeCols();
        // -----------------------------------------------------------------------------------

    private void flxAvailableSettings_OwnerDrawCell(object sender, C1.Win.C1FlexGrid.OwnerDrawCellEventArgs e)
    {
        if (!flxAvailableSettings.Rows[e.Row].IsNode && flxAvailableSettings.Cols[e.Col].Name == "PolicyGroup")
            e.Text = "";
    }

The flxAvailableSettings.Subtotal(AggregateEnum.None, 0, 0, 0, "{0}") line generates the Tree Groups, though I needed to group on a single column, you can use groups on multiple columns. Just refer their docs example on Subtotalling.

Hope this helps.

Rajarshi
+1  A: 

If you can modify the stored procedure, I would recommend a result set in the form of an adjacency list:

ID    Name          ParentID
1     Democrat      NULL
2     County Clerk  1
3     Magistrate    1
4     Candidate 1   2
5     Candidate 2   2
6     Candidate 1   3
7     Candidate 2   3
8     Candidate 3   3

It reduces the amount of data retrieved, and allows you to program recursively against the parent-child relationships. You simply start at the root. This approach avoids the hassle of using nested Repeaters by directly building the string literal in a StringBuilder.

StringBuilder sb = new StringBuilder();
DataTable dt = new DataTable();
someDataAdapter.Fill(dt);

// find the roots:
DataRow[] roots = dt.Select("parentId is null");

sb.Append("<ul>");
foreach (DataRow child in roots)
{
    WriteNode(child, sb);
}
sb.Append("</ul>");


// recursively write out each node
public static void WriteNode(DataRow row, StringBuilder sb) {
    sb.Append("<li>"); 
    sb.Append(row["Name"]);

    // find children rows...
    DataRow[] children = row.Table.Select("parentId = " + row["id"].ToString());
        if (children.Length > 0)
        {
            sb.Append("<ul>");
            foreach (DataRow child in children)
            {
                WriteNode(child, sb);
            }
            sb.Append("</ul>");
        }

        sb.Append("</li>");
    }
Jeff Meatball Yang
Very good! Thank you so much.
treefrog
There is something else if you wouldn't mind answering it:I need to display aggregate data (percentages of votes) in each of the candidate nodes. I won't know the value until I traverse the list. The issue is that using this method makes it too late at that point to display the aggregate data.The way I see it, I have 2 options:- Don't display until all data for a particular office has been completely retrieved- Dynamically add in labels/literals to do this at a later dateThanks a ton.
treefrog
You can calculate these percentages within the stored procedure, ideally. Otherwise, the percentages can be pre-calculated most easily by using LINQ, and then selected from a list, as the nodes are written.
Jeff Meatball Yang