Creating a TreeView control in .NET MAUI
Tags: dotnet, dotnetmaui, mauiuijuly, maui
Published: 2022-07-07 07:08:22

> This article is part of the [#MAUIUIJuly](https://goforgoldman.com/2022/05/19/maui-ui-july.html) initiative by [Matt Goldman](https://twitter.com/mattgoldman). You'll find other helpful articles and tutorials published daily by community members and experts there, so make sure to check it out every day.

A few years ago, a student asked me if it was possible to integrate a TreeView control in Xamarin.Forms in order to display a hierarchical set of items. The specific case: A project has tasks, each task is assigned to an employee. We found an interesting project [here](https://github.com/AdaptSolutions/Xamarin.Forms-TreeView) and modified it a bit [here](https://github.com/icebeam7/DemoTreeView).

This is what we got in the end:

![TreeView implementation in Xamarin.Forms](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ri53aq2uvlfoud56w7z8.png)

What does it take to implement such a control in .NET MAUI? Let's see...

**Step 1.** Add small images for the TreeView nodes 

![Images](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qwrea18p4hsg4d9vgnrk.png)

**Step 2.** Create a folder. Name it **Models**. Then, add a couple of classes inside: 

![Models](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o0gznhlnbcvsjg5wqn47.png)

2a. Every node will be represented as a **`XamlItem`** with a `Key` and `Id`.

```
namespace MauiTreeView.Models
{
    [Serializable]
    public class XamlItem
    {
        public string Key { get; set; }
        public int ItemId { get; set; }
    }
}
```

2b. Every group will be represented as a **`XamlItemGroup`** with a `Name`, `GroupId`, a collection of nodes (`XamlItems`) and a collection of sub-groups (`Children`) to represent the hierarchy.

```
namespace MauiTreeView.Models
{
    [Serializable]
    public class XamlItemGroup
    {
        public List<XamlItemGroup> Children { get; } = new ();
        public List<XamlItem> XamlItems { get; } = new ();

        public string Name { get; set; }
        public int GroupId { get; set; }
    }
}
```

**Step 3.** Create a folder. Name it **Controls**. Then, add four classes inside: 

![Custom controls](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x9ul73ndzjfjtwi1g4ox.png)

3a. Class **`ResourceImage`** assigns the image (icon) for a node in the TreeView. For example, an open folder when the user expands the node. It uses a `BindableProperty` and extends an `Image` control.

```
namespace MauiTreeView.Controls
{
    public class ResourceImage : Image
    {
        public static readonly BindableProperty ResourceProperty = BindableProperty.Create(nameof(Resource), typeof(string), typeof(string), null, BindingMode.OneWay, null, ResourceChanged);

        private static void ResourceChanged(BindableObject bindable, object oldvalue, object newvalue)
        {
            var resourceString = (string)newvalue;
            var imageControl = (Image)bindable;

            imageControl.Source = ImageSource.FromFile(resourceString);
        }

        public string Resource
        {
            get => (string)GetValue(ResourceProperty);
            set => SetValue(ResourceProperty, value);
        }
    }
}

```

3b. Next, we have the **`ExpandButtonContent`** class, which sets the specific icons that will be displayed for expanded/Collapsed Leaf Nodes or expanded, on-request nodes. It uses the **`ResourceImage`** and extends a **`ContentView`**. Although we have not created the TreeViewNode class, don't worry. We will in a minute :)


```
namespace MauiTreeView.Controls
{
    public class ExpandButtonContent : ContentView
    {
        protected override void OnBindingContextChanged()
        {
            base.OnBindingContextChanged();

            var node = BindingContext as TreeViewNode;
            bool isLeafNode = (node.ChildrenList == null || node.ChildrenList.Count == 0);

            //empty nodes have no icon to expand unless showExpandButtonIfEmpty is et to true which will show the expand
            //icon can click and populated node on demand propably using the expand event.
            if ((isLeafNode) && !node.ShowExpandButtonIfEmpty)
            {
                Content = new ResourceImage
                {
                    Resource = isLeafNode ? "blank.png" : "folderopen.png",
                    HeightRequest = 16,
                    WidthRequest = 16
                };
            }
            else
            {
                Content = new ResourceImage
                {
                    Resource = node.IsExpanded ? "openglyph.png" : "collpsedglyph.png",
                    HeightRequest = 16,
                    WidthRequest = 16
                };
            }
        }
    }
}

```


3c. Now we have the **`TreeViewNode`** class implementation. This is a recursive StackLayout with several elements. Among others:

- A BoxView (`_SpacerBoxView`) for sub-levels identation
- A ContentView (`_ExpandButtonContent`) with a `TapGestureRecognizer` that is used to determine if the user tapped on the image (to expand/hide the inner content).
- The `TreeViewNode` content is configured in the constructor. 
- The `ChildrenList` is a `IList<TreeViewNode>`, so that means this control is recursive: A node can contain other (sub)nodes.

```
using System.Collections.ObjectModel;
using System.Collections.Specialized;

namespace MauiTreeView.Controls
{
    public class TreeViewNode : StackLayout
    {
        private DataTemplate _ExpandButtonTemplate = null;

        private TreeViewNode _ParentTreeViewItem;

        private DateTime _ExpandButtonClickedTime;

        private readonly BoxView _SpacerBoxView = new BoxView() { Color = Colors.Transparent };
        private readonly BoxView _EmptyBox = new BoxView { BackgroundColor = Colors.Blue, Opacity = .5 };

        private const int ExpandButtonWidth = 32;
        private ContentView _ExpandButtonContent = new ();

        private readonly Grid _MainGrid = new Grid
        {
            VerticalOptions = LayoutOptions.Start,
            HorizontalOptions = LayoutOptions.Fill,
            RowSpacing = 2
        };

        private readonly StackLayout _ContentStackLayout = new StackLayout { Orientation = StackOrientation.Horizontal };

        private readonly ContentView _ContentView = new ContentView
        {
            HorizontalOptions = LayoutOptions.Fill,
        };

        private readonly StackLayout _ChildrenStackLayout = new StackLayout
        {
            Orientation = StackOrientation.Vertical,
            Spacing = 0,
            IsVisible = false
        };

        private IList<TreeViewNode> _Children = new ObservableCollection<TreeViewNode>();
        private readonly TapGestureRecognizer _TapGestureRecognizer = new TapGestureRecognizer();
        private readonly TapGestureRecognizer _ExpandButtonGestureRecognizer = new TapGestureRecognizer();
        private readonly TapGestureRecognizer _DoubleClickGestureRecognizer = new TapGestureRecognizer();

        internal readonly BoxView SelectionBoxView = new BoxView { Color = Colors.Blue, Opacity = .5, IsVisible = false };

        private TreeView ParentTreeView => Parent?.Parent as TreeView;
        private double IndentWidth => Depth * SpacerWidth;
        private int SpacerWidth { get; } = 30;
        private int Depth => ParentTreeViewItem?.Depth + 1 ?? 0;

        private bool _ShowExpandButtonIfEmpty = false;
        private Color _SelectedBackgroundColor = Colors.Blue;
        private double _SelectedBackgroundOpacity = .3;

        public event EventHandler Expanded;

        /// <summary>
        /// Occurs when the user double clicks on the node
        /// </summary>
        public event EventHandler DoubleClicked;

        protected override void OnParentSet()
        {
            base.OnParentSet();
            Render();
        }

        public bool IsSelected
        {
            get => SelectionBoxView.IsVisible;
            set => SelectionBoxView.IsVisible = value;
        }
        public bool IsExpanded
        {
            get => _ChildrenStackLayout.IsVisible;
            set
            {
                _ChildrenStackLayout.IsVisible = value;

                Render();
                if (value)
                {
                    Expanded?.Invoke(this, new EventArgs());
                }
            }
        }

        /// <summary>
        /// set to true to show the expand button in case we need to poulate the child nodes on demand
        /// </summary>
        public bool ShowExpandButtonIfEmpty
        {
            get { return _ShowExpandButtonIfEmpty; }
            set { _ShowExpandButtonIfEmpty = value; }
        }

        /// <summary>
        /// set BackgroundColor when node is tapped/selected
        /// </summary>
        public Color SelectedBackgroundColor
        {
            get { return _SelectedBackgroundColor; }
            set { _SelectedBackgroundColor = value; }
        }

        /// <summary>
        /// SelectedBackgroundOpacity when node is tapped/selected
        /// </summary>
        public Double SelectedBackgroundOpacity
        {
            get { return _SelectedBackgroundOpacity; }
            set { _SelectedBackgroundOpacity = value; }
        }

        /// <summary>
        /// customize expand icon based on isExpanded property and or data 
        /// </summary>
        public DataTemplate ExpandButtonTemplate
        {
            get { return _ExpandButtonTemplate; }
            set { _ExpandButtonTemplate = value; }
        }

        public View Content
        {
            get => _ContentView.Content;
            set => _ContentView.Content = value;
        }

        public IList<TreeViewNode> ChildrenList
        {
            get => _Children;
            set
            {
                if (_Children is INotifyCollectionChanged notifyCollectionChanged)
                {
                    notifyCollectionChanged.CollectionChanged -= ItemsSource_CollectionChanged;
                }

                _Children = value;

                if (_Children is INotifyCollectionChanged notifyCollectionChanged2)
                {
                    notifyCollectionChanged2.CollectionChanged += ItemsSource_CollectionChanged;
                }

                TreeView.RenderNodes(_Children, _ChildrenStackLayout, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset), this);

                Render();
            }
        }

        /// <summary>
        /// TODO: Remove this. We should be able to get the ParentTreeViewNode by traversing up through the Visual Tree by 'Parent', but this not working for some reason.
        /// </summary>
        public TreeViewNode ParentTreeViewItem
        {
            get => _ParentTreeViewItem;
            set
            {
                _ParentTreeViewItem = value;
                Render();
            }
        }

        /// <summary>
        /// Constructs a new TreeViewItem
        /// </summary>
        public TreeViewNode()
        {
            var itemsSource = (ObservableCollection<TreeViewNode>)_Children;
            itemsSource.CollectionChanged += ItemsSource_CollectionChanged;

            _TapGestureRecognizer.Tapped += TapGestureRecognizer_Tapped;
            GestureRecognizers.Add(_TapGestureRecognizer);

            _MainGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
            _MainGrid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
            _MainGrid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });

            _MainGrid.Children.Add(SelectionBoxView);

            _ContentStackLayout.Children.Add(_SpacerBoxView);
            _ContentStackLayout.Children.Add(_ExpandButtonContent);
            _ContentStackLayout.Children.Add(_ContentView);

            SetExpandButtonContent(_ExpandButtonTemplate);

            _ExpandButtonGestureRecognizer.Tapped += ExpandButton_Tapped;
            _ExpandButtonContent.GestureRecognizers.Add(_ExpandButtonGestureRecognizer);

            _DoubleClickGestureRecognizer.NumberOfTapsRequired = 2;
            _DoubleClickGestureRecognizer.Tapped += DoubleClick;
            _ContentView.GestureRecognizers.Add(_DoubleClickGestureRecognizer);

            _MainGrid.SetRow((IView)_ChildrenStackLayout, 1);
            _MainGrid.SetColumn((IView)_ChildrenStackLayout, 0);

            _MainGrid.Children.Add(_ContentStackLayout);
            _MainGrid.Children.Add(_ChildrenStackLayout);

            base.Children.Add(_MainGrid);

            HorizontalOptions = LayoutOptions.Fill;
            VerticalOptions = LayoutOptions.Start;

            Render();
        }

        void _DoubleClickGestureRecognizer_Tapped(object sender, EventArgs e)
        {
        }

        private void ChildSelected(TreeViewNode child)
        {
            //Um? How does this work? The method here is a private method so how are we calling it?
            ParentTreeViewItem?.ChildSelected(child);
            ParentTreeView?.ChildSelected(child);
        }

        private void Render()
        {
            _SpacerBoxView.WidthRequest = IndentWidth;

            if ((ChildrenList == null || ChildrenList.Count == 0) && !ShowExpandButtonIfEmpty)
            {
                SetExpandButtonContent(_ExpandButtonTemplate);
                return;
            }

            SetExpandButtonContent(_ExpandButtonTemplate);

            foreach (var item in ChildrenList)
            {
                item.Render();
            }
        }

        /// <summary>
        /// Use DataTemplae 
        /// </summary>
        private void SetExpandButtonContent(DataTemplate expandButtonTemplate)
        {
            if (expandButtonTemplate != null)
            {
                _ExpandButtonContent.Content = (View)expandButtonTemplate.CreateContent();
            }
            else
            {
                _ExpandButtonContent.Content = (View)new ContentView { Content = _EmptyBox };
            }
        }

        private void ExpandButton_Tapped(object sender, EventArgs e)
        {
            _ExpandButtonClickedTime = DateTime.Now;
            IsExpanded = !IsExpanded;
        }

        private void TapGestureRecognizer_Tapped(object sender, EventArgs e)
        {
            //TODO: Hack. We don't want the node to become selected when we are clicking on the expanded button
            if (DateTime.Now - _ExpandButtonClickedTime > new TimeSpan(0, 0, 0, 0, 50))
            {
                ChildSelected(this);
            }
        }

        private void DoubleClick(object sender, EventArgs e)
        {
            DoubleClicked?.Invoke(this, new EventArgs());
        }

        private void ItemsSource_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            TreeView.RenderNodes(_Children, _ChildrenStackLayout, e, this);
            Render();
        }
    }
}

```

3d. And finally, the **`TreeView`** control implementation! 

- The public collection `RootNodes` needs to be assigned (later) in order to display the nodes. It's a `List` of `TreeViewNode`.

- You can call the public `ProcessXamlItemGroups` method to map the hierarchy of nodes (`xamlItemGroups`) into an `ObservableCollection` of `TreeViewNode`.

```
using MauiTreeView.Models;
using System.Collections.ObjectModel;
using System.Collections.Specialized;

namespace MauiTreeView.Controls
{
    public class TreeView : ScrollView
    {
        private readonly StackLayout _StackLayout = new StackLayout { Orientation = StackOrientation.Vertical };

        //TODO: This initialises the list, but there is nothing listening to INotifyCollectionChanged so no nodes will get rendered
        private IList<TreeViewNode> _RootNodes = new ObservableCollection<TreeViewNode>();
        private TreeViewNode _SelectedItem;

        /// <summary>
        /// The item that is selected in the tree
        /// TODO: Make this two way - and maybe eventually a bindable property
        /// </summary>
        public TreeViewNode SelectedItem
        {
            get => _SelectedItem;

            set
            {
                if (_SelectedItem == value)
                {
                    return;
                }

                if (_SelectedItem != null)
                {
                    _SelectedItem.IsSelected = false;
                }

                _SelectedItem = value;

                SelectedItemChanged?.Invoke(this, new EventArgs());
            }
        }


        public IList<TreeViewNode> RootNodes
        {
            get => _RootNodes;
            set
            {
                _RootNodes = value;

                if (value is INotifyCollectionChanged notifyCollectionChanged)
                {
                    notifyCollectionChanged.CollectionChanged += (s, e) =>
                    {
                        RenderNodes(_RootNodes, _StackLayout, e, null);
                    };
                }

                RenderNodes(_RootNodes, _StackLayout, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset), null);
            }
        }

        /// <summary>
        /// Occurs when the user selects a TreeViewItem
        /// </summary>
        public event EventHandler SelectedItemChanged;

        public TreeView()
        {
            Content = _StackLayout;
        }

        private void RemoveSelectionRecursive(IEnumerable<TreeViewNode> nodes)
        {
            foreach (var treeViewItem in nodes)
            {
                if (treeViewItem != SelectedItem)
                {
                    treeViewItem.IsSelected = false;
                }

                RemoveSelectionRecursive(treeViewItem.ChildrenList);
            }
        }

        private static void AddItems(IEnumerable<TreeViewNode> childTreeViewItems, StackLayout parent, TreeViewNode parentTreeViewItem)
        {
            foreach (var childTreeNode in childTreeViewItems)
            {
                if (!parent.Children.Contains(childTreeNode))
                {
                    parent.Children.Add(childTreeNode);
                }

                childTreeNode.ParentTreeViewItem = parentTreeViewItem;
            }
        }

        /// <summary>
        /// TODO: A bit stinky but better than bubbling an event up...
        /// </summary>
        internal void ChildSelected(TreeViewNode child)
        {
            SelectedItem = child;
            child.IsSelected = true;
            child.SelectionBoxView.Color = child.SelectedBackgroundColor;
            child.SelectionBoxView.Opacity = child.SelectedBackgroundOpacity;
            RemoveSelectionRecursive(RootNodes);
        }

        internal static void RenderNodes(IEnumerable<TreeViewNode> childTreeViewItems, StackLayout parent, NotifyCollectionChangedEventArgs e, TreeViewNode parentTreeViewItem)
        {
            if (e.Action != NotifyCollectionChangedAction.Add)
            {
                //TODO: Reintate this...
                //parent.Children.Clear();
                AddItems(childTreeViewItems, parent, parentTreeViewItem);
            }
            else
            {
                AddItems(e.NewItems.Cast<TreeViewNode>(), parent, parentTreeViewItem);
            }
        }

        // Main code: 
        private TreeViewNode CreateTreeViewNode(object bindingContext, Label label, bool isItem)
        {
            var node = new TreeViewNode
            {
                BindingContext = bindingContext,
                Content = new StackLayout
                {
                    Children =
                    {
                        new ResourceImage
                        {
                            Resource = isItem? "item.png" :"folderopen.png" ,
                            HeightRequest= 16,
                            WidthRequest = 16
                        },
                        label
                    },
                    Orientation = StackOrientation.Horizontal
                }
            };

            //set DataTemplate for expand button content
            node.ExpandButtonTemplate = new DataTemplate(() => new ExpandButtonContent { BindingContext = node });

            return node;
        }

        private void CreateXamlItem(IList<TreeViewNode> children, XamlItem xamlItem)
        {
            var label = new Label
            {
                VerticalOptions = LayoutOptions.Center,
                TextColor = Colors.Black
            };
            label.SetBinding(Label.TextProperty, "Key");

            var xamlItemTreeViewNode = CreateTreeViewNode(xamlItem, label, true);
            children.Add(xamlItemTreeViewNode);
        }

        public ObservableCollection<TreeViewNode> ProcessXamlItemGroups(XamlItemGroup xamlItemGroups)
        {
            var rootNodes = new ObservableCollection<TreeViewNode>();

            foreach (var xamlItemGroup in xamlItemGroups.Children.OrderBy(xig => xig.Name))
            {
                var label = new Label
                {
                    VerticalOptions = LayoutOptions.Center,
                    TextColor = Colors.Black
                };
                label.SetBinding(Label.TextProperty, "Name");

                var groupTreeViewNode = CreateTreeViewNode(xamlItemGroup, label, false);

                rootNodes.Add(groupTreeViewNode);

                groupTreeViewNode.ChildrenList = ProcessXamlItemGroups(xamlItemGroup);

                foreach (var xamlItem in xamlItemGroup.XamlItems)
                {
                    CreateXamlItem(groupTreeViewNode.ChildrenList, xamlItem);
                }
            }

            return rootNodes;
        }
    }
}

```

And that's it! Well, it needs a bit polishing (implement some bindables for example, make it a Nuget package...), but for the moment let's test it.

**Step 4.** Create the following structure in your project

![Sample TreeView](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6vhfagkdi1c80klsqxsr.png)

4a. **Models**. For this example, the hierarchy will be like this: A company has departments, and each department includes employees. So the classes (models) are as follows:

Company:

```
namespace MauiTreeView.Sample.Models
{
    public class Company
    {
        public int CompanyId { get; set; }
        public string CompanyName { get; set; }
    }
}
```

Department:

```
namespace MauiTreeView.Sample.Models
{
    public class Department
    {
        public int DepartmentId { get; set; }
        public string DepartmentName { get; set; }
        public int ParentDepartmentId { get; set; }
        public int CompanyId { get; set; }
    }
}
```

Employee:

```
namespace MauiTreeView.Sample.Models
{
    public class Employee
    {
        public int EmployeeId { get; set; }
        public string EmployeeName { get; set; }
        public int DepartmentId { get; set; }
    }
}
```

4b. For the **Services** layer, data is hard-coded. The DataService contains methods to return a collection of Department, Employee, and Company:

```
using MauiTreeView.Sample.Models;

namespace MauiTreeView.Sample.Services
{
    public class DataService
    {
        public Company GetCompany()
        {
            return new Company()
            {
                CompanyId = 1,
                CompanyName = "TC Solutions"
            };
        }

        public IEnumerable<Department> GetDepartments()
        {
            return new List<Department>()
            {
                new Department() { CompanyId = 1, DepartmentId = 1, DepartmentName = "IT", ParentDepartmentId = -1 },
                new Department() { CompanyId = 1,  DepartmentId = 2, DepartmentName = "Accounting", ParentDepartmentId = -1 },
                new Department() { CompanyId = 1,  DepartmentId = 3, DepartmentName = "Production", ParentDepartmentId = -1 },
                new Department() { CompanyId = 1,  DepartmentId = 4, DepartmentName = "Software", ParentDepartmentId = 1 },
                new Department() { CompanyId = 1,  DepartmentId = 5, DepartmentName = "Support", ParentDepartmentId = 1 },
                new Department() { CompanyId = 1,  DepartmentId = 6, DepartmentName = "Testing", ParentDepartmentId = 4 },
                new Department() { CompanyId = 1,  DepartmentId = 7, DepartmentName = "Accounts receivable", ParentDepartmentId = 2 },
                new Department() { CompanyId = 1,  DepartmentId = 8, DepartmentName = "Accounts payable", ParentDepartmentId = 2 },
                new Department() { CompanyId = 1,  DepartmentId = 9, DepartmentName = "Customers and services", ParentDepartmentId = 8 }
            };
        }

        public IEnumerable<Employee> GetEmployees()
        {
            return new List<Employee>()
            {
                new Employee() { EmployeeId = 1, EmployeeName = "Luis", DepartmentId = 1 },
                new Employee() { EmployeeId = 2, EmployeeName = "Pepe", DepartmentId = 1 },
                new Employee() { EmployeeId = 3, EmployeeName = "Juan", DepartmentId = 2 },
                new Employee() { EmployeeId = 4, EmployeeName = "Inés", DepartmentId = 3 },
                new Employee() { EmployeeId = 5, EmployeeName = "Sara", DepartmentId = 3 },
                new Employee() { EmployeeId = 6, EmployeeName = "Sofy", DepartmentId = 4 },
                new Employee() { EmployeeId = 7, EmployeeName = "Hugo", DepartmentId = 5 },
                new Employee() { EmployeeId = 8, EmployeeName = "Gema", DepartmentId = 5 },
                new Employee() { EmployeeId = 9, EmployeeName = "Olga", DepartmentId = 6 },
                new Employee() { EmployeeId = 1, EmployeeName = "Otto", DepartmentId = 6 },
                new Employee() { EmployeeId = 2, EmployeeName = "Axel", DepartmentId = 6 },
                new Employee() { EmployeeId = 3, EmployeeName = "Eloy", DepartmentId = 7 },
                new Employee() { EmployeeId = 4, EmployeeName = "Flor", DepartmentId = 8 },
                new Employee() { EmployeeId = 5, EmployeeName = "Aída", DepartmentId = 9 },
                new Employee() { EmployeeId = 6, EmployeeName = "Ruth", DepartmentId = 9 }
            };
        }
    }
}

```

4c. Class `CompanyTreeViewBuilder` is a specific mapping from our hierarchy to XamlItemGroup hierarchy (which is used by the TreeView control).

- `FindParentDepartment` method compares the ParentDepartmentId to evaluate if the current department belongs to another one (or if it's a root one). 

- The public method `GroupData` maps your hierarchy to a `XamlItemGroup` instance. It requires a DataService connection to obtain the data (companies, departments, and employees).

```
using MauiTreeView.Models;
using MauiTreeView.Sample.Models;
using MauiTreeView.Sample.Services;

namespace MauiTreeView.Sample.Helpers
{
    public class CompanyTreeViewBuilder
    {
        private XamlItemGroup FindParentDepartment(XamlItemGroup group, Department department)
        {
            if (group.GroupId == department.ParentDepartmentId)
                return group;

            if (group.Children != null)
            {
                foreach (var currentGroup in group.Children)
                {
                    var search = FindParentDepartment(currentGroup, department);

                    if (search != null)
                        return search;
                }
            }

            return null;
        }

        public XamlItemGroup GroupData(DataService service)
        {
            var company = service.GetCompany();
            var departments = service.GetDepartments().OrderBy(x => x.ParentDepartmentId);
            var employees = service.GetEmployees();

            var companyGroup = new XamlItemGroup();
            companyGroup.Name = company.CompanyName;

            foreach (var dept in departments)
            {
                var itemGroup = new XamlItemGroup();
                itemGroup.Name = dept.DepartmentName;
                itemGroup.GroupId = dept.DepartmentId;

                // Employees first
                var employeesDepartment = employees.Where(x => x.DepartmentId == dept.DepartmentId);

                foreach (var emp in employeesDepartment)
                {
                    var item = new XamlItem();
                    item.ItemId = emp.EmployeeId;
                    item.Key = emp.EmployeeName;

                    itemGroup.XamlItems.Add(item);
                }

                // Departments now
                if (dept.ParentDepartmentId == -1)
                {
                    companyGroup.Children.Add(itemGroup);
                }
                else
                {
                    XamlItemGroup parentGroup = null;

                    foreach (var group in companyGroup.Children)
                    {
                        parentGroup = FindParentDepartment(group, dept);

                        if (parentGroup != null)
                        {
                            parentGroup.Children.Add(itemGroup);
                            break;
                        }
                    }
                }
            }

            return companyGroup;
        }
    }
}
```

4d. Then, `CompanyPage` is where we will use our control! There is XAML and C# code required for the setup. The XAML part is simple:

```
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="MauiTreeView.Sample.Views.CompanyPage"
             xmlns:controls="clr-namespace:MauiTreeView.Controls"
             Title="Company Page">

    <controls:TreeView BackgroundColor="White"
                       Margin="4"
                       x:Name="TheTreeView"/>

</ContentPage>
```

And regarding the code-behind, here it is:

```
//S6 and S7
using MauiTreeView.Sample.Services;
using MauiTreeView.Sample.Helpers;

namespace MauiTreeView.Sample.Views;

public partial class CompanyPage : ContentPage
{
	DataService service;
    CompanyTreeViewBuilder companyTreeViewBuilder;

	public CompanyPage(DataService service, CompanyTreeViewBuilder companyTreeViewBuilder)
	{
		InitializeComponent();

		this.service = service;
        this.companyTreeViewBuilder = companyTreeViewBuilder;

		ProcessTreeView();
	}

	private void ProcessTreeView()
	{
        var xamlItemGroups = companyTreeViewBuilder.GroupData(service);
        var rootNodes = TheTreeView.ProcessXamlItemGroups(xamlItemGroups);
        TheTreeView.RootNodes = rootNodes;
    }
}
```

As you can see, you simply:

- call the GroupData (from CompanyTreeViewBuilder) public method passing a `DataService` instance. 

- then you pass the XamlItemGroup instance to ProcessXamlItemGroups method (from TreeView control) to actually display the content.

**Step 5.** Don't forget to configure your DI in MauiProgram.cs!

```
        builder.Services.AddSingleton<DataService>();
        builder.Services.AddSingleton<CompanyTreeViewBuilder>();
        builder.Services.AddTransient<CompanyPage>();
```

And set the initial page in AppShell.xaml:

```
<?xml version="1.0" encoding="UTF-8" ?>
<Shell
    x:Class="MauiTreeView.AppShell"
    xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:local="clr-namespace:MauiTreeView"
    xmlns:samples="clr-namespace:MauiTreeView.Sample.Views"
    Shell.FlyoutBehavior="Disabled">

    <ShellContent
        Title="Home"
        ContentTemplate="{DataTemplate samples:CompanyPage}"
        Route="MainPage" />

</Shell>

```
  
**Step 6.** Time to test our application!

Windows:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rq4621ztftmvqhxl7ooy.png)
 
Android:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tnk4w2hbhl6f5hxxsknd.png)
 
Cool, right? :-)

By the way, the project is [available on GitHub](https://github.com/icebeam7/MauiTreeView).

I hope that this blog post was interesting and useful for you. I invite you to visit [my blog](https://www.luisbeltran.mx/) for more technical posts about _Xamarin, .NET MAUI, Azure, and more. I write in Spanish language =)

Thanks for your time, and enjoy the rest of the [#MAUIUIJuly](https://goforgoldman.com/2022/05/19/maui-ui-july.html) publications!

See you next time,
Luis