Search code examples
c#.netwinformsmenutree-structure

Create Object in Every Loop Iteration


I am using z80 navigation control that's built-in and here is the link for demonstration: Z80 Navigation Menu

If anyone see the control, it has an object to create menus like parent menus and under it, child menus. Something like the following:

public List<NavBarItem> sampleDynamicNav; //List of navbar objects
public DemoItems()
{
    //Create object instance here and assign the parent as well child menus here
    sampleDynamicNav = new List<NavBarItem> {
    new NavBarItem {ID = 1, Text = "UserInfo", Icon = new ItemIcon {Default = SampleProject.Properties.Resources.nav_new_home, Hover = SampleProject.Properties.Resources.nav_new_home, Selected = SampleProject.Properties.Resources.nav_new_home}, ToolTip = "tooltip Main Menu", Height = 40,
        Icon = new ItemIcon {Default = SampleProject.Properties.Resources.nav_new_home, Hover = SampleProject.Properties.Resources.nav_new_home, Selected = SampleProject.Properties.Resources.nav_new_home }, ToolTip = "tooltip Desktop"},
        Childs = new List<NavBarItem> {
                    new NavBarItem {ID = 41, Text = "Add/Edit Users", Height = 30 },
                    new NavBarItem {ID = 42, ParentID = 1, Text = "Inactive User", Height = 30}
    };
}

This is pretty simple if we assign the menus statically. But I stuck with it, when trying to add them dynamically I mean creating the menu from database as follows:

public DemoItems()
{
    foreach (var parent in GetParent("USER-0001"))
    {
          foreach (var child in GetChild(parent.MenuNo))
          {
            sampleDynamicNav = new List<NavBarItem> {
                 new NavBarItem {
                 ID = parent.MenuNo, Text = parent.MenuName, Icon = new ItemIcon {Default =  SampleProject.Properties.Resources.nav_new_home, Hover = SampleProject.Properties.Resources.nav_new_home, Selected = SampleProject.Properties.Resources.nav_new_home}, ToolTip = "tooltip Main Menu", Height = 40,
                 Childs = new List<NavBarItem> {
                                    new NavBarItem {ID = child.MenuNo, ParentID = parent.MenuNo, Text = child.MenuName, Height = 30 },
                           }
                     }
               };
          }
     }
}

With the above code, it's supposed to get the parent menus at least in the navigation bar. For now, leaving the child menus aside, it shows one parent menu in the navigation bar as follows:

Sample 1

But it's supposed to be like the below as there are two parent menus and iterated the list (GetParents() returns a list of object) with foreach loop:

Sample 2

I don't know if I've to do anything else for it and wondering if I can loop through the child properties of the navigation bar, something as follows:

foreach (var child in GetChild(parent.MenuNo))
{
   Childs = new List<NavBarItem> {
            new NavBarItem {ID = child.MenuNo, ParentID = parent.MenuNo, Text = child.MenuName, Height = 30 },
}

N.B: When try to iterate the child properties with loop, it throws error right now. The second inner loop works and takes out the child menus as well but say, the parent menu has two sub-menus, it returns 1 at the time. I debugged the list and it returns the two parent menu as usual but doesn't show up in the navigation bar.

GetParents Method:

/**Get Menu Details - Starts**/
public IEnumerable<UserViewModel> GetParent(string empNo)
{
       List<UserViewModel> lstUser = new List<UserViewModel>();

       string query = "SELECT DISTINCT M.PARENT, M.MENUNO, M.MENUNAME FROM (SELECT DISTINCT M.MENUNO, M.MENUNAME, M.PARENT " +
                      "FROM USER_DETAILS U INNER JOIN USER_GROUP_DETAILS UG ON UG.EMPNO = U.EMPNO " +
                      "INNER JOIN ASSIGN_MENU_DETAILS AM ON AM.GROUPNO = UG.GROUPNO INNER JOIN MENU_DETAILS M " +
                      "ON M.MENUNO = AM.MENUNO WHERE U.EMPNO = '" + empNo + "' " +
                      "UNION ALL " +
                      "SELECT DISTINCT M.MENUNO, M.MENUNAME, " +
                      "M.PARENT FROM MENU_DETAILS M " +
                      "INNER JOIN MENU_DETAILS C " +
                      "ON C.PARENT = M.MENUNO) m WHERE M.PARENT = '0' ORDER BY M.PARENT";

        DataTable dt = SelectData(query);

        if (dt != null && dt.Rows.Count > 0)
        {
            foreach (DataRow dr in dt.Rows)
            {
                UserViewModel bo = new UserViewModel();
                bo.Parent = Convert.ToInt32(dr["PARENT"].ToString());
                bo.MenuNo = Convert.ToInt32(dr["MENUNO"].ToString());
                bo.MenuName = dr["MENUNAME"].ToString();

                lstUser.Add(bo);
            }
        }
   return lstUser;
}
/**Get Menu Details - Ends**/

Solution

  • You can create the following helper methods and use them to create a List<NavBarItem accepting any kind of data sources as input, including DataTable, List<YourEntity> or anything else which is IEnumerable<T>.

    So regardless of the data store which you have, you can use the following methods.

    It relies on a recursive algorithm for creating tree. For creating a tree from any kind of data source, you need to have the following information:

    1. Data source
    2. How to detect if an item in data source is a root item
    3. How to find child items of an item in data source
    4. How to create tree item from data source item.

    The following method creates a list of NavBarItem hierarchy by asking about above information:

    private IEnumerable<NavBarItem> GetNavBarItems<T>(
        IEnumerable<T> source,
        Func<T, Boolean> isRoot,
        Func<T, IEnumerable<T>, IEnumerable<T>> getChilds,
        Func<T, NavBarItem> getItem)
    {
        IEnumerable<T> roots = source.Where(x => isRoot(x));
        foreach (T root in roots)
            yield return ConvertEntityToNavBarItem(root, source, getChilds, getItem); ;
    }
    
    private NavBarItem ConvertEntityToNavBarItem<T>(
        T entity,
        IEnumerable<T> source,
        Func<T, IEnumerable<T>, IEnumerable<T>> getChilds,
        Func<T, NavBarItem> getItem)
    {
        NavBarItem node = getItem(entity);
        var childs = getChilds(entity, source);
        foreach (T child in childs)
            node.Childs.Add(ConvertEntityToNavBarItem(child, source, getChilds, getItem));
        return node;
    }
    

    Example

    I assume you have loaded data into the following structure:

    var dt = new DataTable();
    dt.Columns.Add("Id", typeof(int));
    dt.Columns.Add("Name", typeof(string));
    dt.Columns.Add("ParentId", typeof(int));
    
    dt.Rows.Add(1, "Menu 1", DBNull.Value);
    dt.Rows.Add(11, "Menu 1-1", 1);
    dt.Rows.Add(111, "Menu 1-1-1", 11);
    dt.Rows.Add(112, "Menu 1-1-2", 11);
    dt.Rows.Add(12, "Menu 1-2", 1);
    dt.Rows.Add(121, "Menu 1-2-1", 12);
    dt.Rows.Add(122, "Menu 1-2-2", 12);
    dt.Rows.Add(123, "Menu 1-2-3", 12);
    dt.Rows.Add(124, "Menu 1-2-4", 12);
    dt.Rows.Add(2, "Menu 2", DBNull.Value);
    dt.Rows.Add(21, "Menu 2-1", 2);
    dt.Rows.Add(211, "Menu 2-1-1", 21);
    

    Then to convert it to a List<NavBarItem>, you can use the following code:

    var source = dt.AsEnumerable();
    var list = GetNavBarItems(
            source,
            (r) => r.Field<int?>("ParentId") == null,
            (r, s) => s.Where(x => x.Field<int?>("ParentId") == r.Field<int?>("Id")),
            (r) => new NavBarItem()
            {
                ID = r.Field<int>("Id"),
                Text = r.Field<string>("Name"),
                ParentID = r.Field<int?>("ParentId")
            }).ToList();
    

    As a result, you will have the following structure:

    Menu 1
       Menu 1-1
           Menu 1-1-1
           Menu 1-1-2
       Menu 1-2
           Menu 1-2-1
           Menu 1-2-2
           Menu 1-2-3
           Menu 1-2-4
    Menu 2
        Menu 2-1
            Menu 2-1-1
    

    Note

    For those who don't want to install the package, but want to test the structure, you can use the following NavBarItem class:

    public class NavBarItem
    {
        public NavBarItem()
        {
            Childs = new List<NavBarItem>();
        }
        public int ID { get; set; }
        public int? ParentID { get; set; }
        public string Text { get; set; }
        public List<NavBarItem> Childs { get; set; }
        public override string ToString()
        {
            return Text;
        }
    }