Search code examples
c#recursiontreeviewwin-universal-appwinrt-xaml-toolkit

Recursively Add Items to a TreeView


I have a TreeView imported from WinRT.XamlToolKit that I'm trying to use in a Universal App. It's important to note, I understand there are similar questions, but some don't work either because they predate Universal Apps, or they don't accommodate the usage of MVVM. Following a tutorial to understand how to populate the tree, I came across this recursive method:

private ObservableCollection<TreeViewItemModel> BuildTree(int depth, int branches)
    {
        var tree = new ObservableCollection<TreeViewItemModel>();

        if (depth <= 0) return tree;
        var depthIndices = Enumerable.Range(0, branches).Shuffle();

        for (var i = 0; i < branches; i++)
        {
            var d = depthIndices[i] % depth;
            var b = _rand.Next(branches / 2, branches);
            tree.Add(
                new TreeViewItemModel
                {
                    Branch = b,
                    Depth = d,
                    Text = "Item " + _itemId++,
                    Children = BuildTree(d, b)
                });
        }
        return tree;
    }

It's called as such TreeItems = BuildTree(5,5); I then bind my TreeView ItemSource to TreeItems and attach to the Text property of the TreeViewItemModel.

This works for a pre-determined set, and then as seen in the method, uses random numbers to work out how the tree will get displayed. (In what depths, how many branches, etc.)

What I'm not sure of is how to implement a similar recursive function to populate the tree with a different ItemModel with an undetermined number of children. In my setup, I have a Space, that can have children Spaces, Devices, and/or Sensors. A space could have no devices and no sensors, but one or more child spaces, or it could have no child spaces, but it could have devices and/or sensors. In my Sensor class it has a Parent Id relevant to the Id of the Space it's bound to and the Device it reports to.

So, in short, I need to populate my ObservableCollection<TreeViewSpaceModel> recursively from a list of Spaces.

TreeViewSpaceModel class:

public class TreeViewSpaceModel : NotifyObject
{
    private string _name;
    private Guid _iD;
    private string _parentName;
    private ObservableCollection<TreeViewSpaceModel> _children = new ObservableCollection<TreeViewSpaceModel>();
    private ObservableCollection<TreeViewDeviceModel> _devices = new ObservableCollection<TreeViewDeviceModel>();
    private ObservableCollection<TreeViewSensorModel> _sensors = new ObservableCollection<TreeViewSensorModel>();

    public string Name
    {
        get { return _name; }
        set { SetProperty(ref _name, value); }
    }

    public string ParentName
    {
        get { return _parentName; }
        set { SetProperty(ref _parentName, value); }
    }

    public Guid Id
    {
        get { return _iD; }
        set { SetProperty(ref _iD, value); }
    }

    public ObservableCollection<TreeViewSpaceModel> Children
    {
        get { return _children; }
        set { SetProperty(ref _children, value); }
    }

    public ObservableCollection<TreeViewDeviceModel> Devices
    {
        get { return _devices; }
        set { SetProperty(ref _devices, value); }
    }

    public ObservableCollection<TreeViewSensorModel> Sensors
    {
        get { return _sensors; }
        set { SetProperty(ref _sensors, value); }
    }
}

The DeviceModel has no Children but it does have Sensors, and the SensorModel has no Children or Devices or Sensors, as it is the end of the hierarchy. Space class:

public class Space
{
    public Guid Id { get; set; }
    public string Name { get; set; }
    public int SpaceTypeId { get; set; }
    public virtual Space Parent { get; set; }
    public virtual ICollection<Sensor> Sensors { get; set; }
    public virtual ICollection<Device> Devices { get; set; }
    public virtual ICollection<Space> Children { get; set; }
}

The end result would ideally look something like this:

Space1
---- ChildSpace1
--------Device1
-------------Sensor1
-------------Sensor2 (Sensors report for Spaces, but are bound to Devices)
--------SubChildSpace1
-------------Sensor1
-------------Sensor2
--------SubChildSpace2
-------------Sensor1
------------Sensor2
--------Device2
---- ChildSpace2
Space2

If there's an easier way to do this instead of a recursive method, I'm happy to entertain it. While researching TreeViews for Universal Apps, I did come across someone else saying the user should be able to do something like items.Node(0).Add(stuffhere);however, I could not figure out how to achieve this as an ObservableCollection didn't offer the .Node extension.


Solution

  • I'm not quite sure why I couldn't get the answer Filip Skakun provided to work, but he still deserves some credit, as his example did help me think of how to solve the problem. I just used one class model -> TreeViewItemModel and constructed it like so to build my Observable Collection.

    TreeViewItemModel class:

    public class TreeViewItemModel : NotifyObject
    {
    
        private string _name;
        private Guid _iD;
        private string _parentName;
        private Guid _parentId;
        private string _parentDeviceName;
        private ObservableCollection<TreeViewItemModel> _children = new ObservableCollection<TreeViewItemModel>();
    
    
        public string Name
        {
            get { return _name; }
            set { SetProperty(ref _name, value); }
        }
    
        public Guid ParentId
        {
            get { return _parentId; }
            set { SetProperty(ref _parentId, value); }
        }
    
        public string ParentDeviceName
        {
            get { return _parentDeviceName; }
            set { SetProperty(ref _parentDeviceName, value); }
        }
        public string ParentName
        {
            get { return _parentName; }
            set { SetProperty(ref _parentName, value); }
        }
    
        public Guid Id
        {
            get { return _iD; }
            set { SetProperty(ref _iD, value); }
        }
    
        public ObservableCollection<TreeViewItemModel> Children
        {
            get { return _children; }
            set { SetProperty(ref _children, value); }
        }
    
    
        public TreeViewItemModel(object thing)
        {
            //GetDevices();
            if (thing.GetType() == typeof (Space))
            {
                var space = (Space)thing;
                var parentName = string.Empty;
                if (space.Parent != null)
                {
                    parentName = space.Parent.Name;
                }
                Name = space.Name;
                ParentName = parentName;
                Id = space.Id;
                Children = new ObservableCollection<TreeViewItemModel>(space.Children.Select(s => new TreeViewItemModel(s)).Union(space.Devices.Select(d => new TreeViewItemModel(d)).Union(space.Sensors.Select(sensor => new TreeViewItemModel(sensor)))));
            }
    
            else if (thing.GetType() == typeof (Device))
            {
                var device = (Device) thing;
                var parentName = device.Space.Name;
                Name = device.Name;
                ParentName = parentName;
                Id = device.Id;
                Children = new ObservableCollection<TreeViewItemModel>(device.Sensors.Select(s => new TreeViewItemModel(s)));
            }
    
            else if (thing.GetType() == typeof(Sensor))
            {
                var sensor = (Sensor) thing;
                var space = sensor.Space.Name ?? string.Empty;
                //var device = Devices.First(d => d.Id == sensor.DeviceId);
                var device = sensor.Device;
                ParentName = device == null ? "No Matching Device" : device.Name;
                Name = sensor.Id.ToString();
                Id = sensor.Id;
                ParentName = space;
    
                Children = null;
            }
    
        }
    

    I call it like so: TreeSpaces = BuildSpaceTree(AllSpaces); The BuildSpaceTree Method is as follows:

    private ObservableCollection<TreeViewItemModel> BuildSpaceTree(IEnumerable<Space> spaces)
        {
           return new ObservableCollection<TreeViewItemModel>(spaces.Where(space => space.Parent == null).Select(space => new TreeViewItemModel(space)));
        }
    

    The biggest problem I had with Filip's answer, is that it didn't like me trying to Union multiple lists of different class types.