4

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**/
Reza Aghaei
  • 120,393
  • 18
  • 203
  • 398
AT-2017
  • 3,114
  • 3
  • 23
  • 39
  • You are initializing sampleDynamicNav = new List () collection every time. While you should be initializing once and then adding new items to this collection. Please check. – Anupam Sharma Dec 24 '18 at 10:43

2 Answers2

3

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;
    }
}
Reza Aghaei
  • 120,393
  • 18
  • 203
  • 398
  • In fact I've created a more generic version of the method. Let me know if you are interested to use that. It allows you to convert all kind of data sources to all kind of tree structures :D. – Reza Aghaei Dec 24 '18 at 11:40
  • I'll look into that @Reza Aghaei and thanks a lot for it. Though I am not sure if I can use the same for the navigation bar that I am using. I am trying to create the exact tree structure you shared. – AT-2017 Dec 24 '18 at 16:21
  • You're welcome. Give the exact code a try and see the result. I cannot see anything which stops you from applying above code onto your navigation menu. – Reza Aghaei Dec 24 '18 at 16:24
  • Alright @Reza Aghaei. I'll definitely try that now and will let you know on this. Thanks again for your time and effort. – AT-2017 Dec 24 '18 at 16:34
  • I've made it work @Reza Aghaei and with your code sample, I've slightly modified the existing one to work as required. Will share the code sample here, so in future others can use it. Nothing that completed, a simple one - Thanks a lot again and will knock you back if any idea required to improve the code sample. – AT-2017 Dec 25 '18 at 17:03
  • You're welcome :) In fact I also can make it working by a simple recursive function, but this code is a generic solution which doesn't have any dependency to the data source type. It relies on an algorithm. For creating a tree from any kind of data source, you need to have: 1)data source 2)detect which items in data source are root items 3) what are child items of an item in data source 4)how to create tree item from data source item. That's all. The generic method which I created is implements an algorithm by asking for those information. – Reza Aghaei Dec 25 '18 at 17:09
  • I can see that @Reza Aghaei and really a good one. Appreciate the time you took to write the sample code. Could you just check the code sample that I tried? – AT-2017 Dec 25 '18 at 18:05
  • Sure I will take a look at that. – Reza Aghaei Dec 25 '18 at 18:33
1

I tried the following with your code sample @Reza Aghaei and stuck a bit using TreeView control as follows: (It works but getting an error with a line below)

private void frmSampleApp_Load(object sender, EventArgs e)
{
    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);

    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();

    foreach (var item in list)
    {
        TreeNode parentNode = null;
        parentNode = treeView1.Nodes.Add(item.Text.ToString());

        BindData(Convert.ToInt32(item.ParentID), parentNode);
    }
}

public void BindData(int parentId, TreeNode parentNode)
{
    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);

    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();
        TreeNode childNode; 

    foreach (var item in list)
    {
        if (parentNode == null)

            childNode = treeView1.Nodes.Add(item.Text.ToString());

        else

            childNode = parentNode.Nodes.Add(item.Text.ToString());
        BindData(Convert.ToInt32(item.ID.ToString()), childNode); //An unhandled exception of type 'System.StackOverflowException' occurred in System.Windows.Forms.dll
    }
}

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;
    }
}

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;
    }
}

N.B: Never mind. I know, it has been a mess here and just trying to figure out for learning purpose - Thanks.

AT-2017
  • 3,114
  • 3
  • 23
  • 39
  • If you are going to use the same solution to add `TreeNode` to `TreeView`, then it's better to change the `GetNavBarItems` and `ConvertEntityToNavBarItem` to work with `TreeNode`. `NavBarItem` is just an example and you can use any other types instead, like `TreeNode`. To change it, just replace `NavBarItem` with `TreeNode` and `Childs` with `Nodes`. Then it will work for tree view and after you get `List` as result, you can easily add them to `Nodes` collection of `TreeView`. Each node has its nodes as well. – Reza Aghaei Dec 25 '18 at 18:40
  • I posted the answer for `TreeView` here: [Populate Tree View from datatable](https://stackoverflow.com/a/53924862/3110834) – Reza Aghaei Dec 25 '18 at 18:58