Multilayer Checkbox Tree - Bad Checkbox Display When Collapsing Layers

Hi,

I created a test project to describe the behavior.

Can anyone assist with the behavior below?
BlazorApp1 - Google Chrome 2022-02-17 21-59-09

This is the code that I put in my index.razor

@page "/"

<h1>Tree checkboxes</h1>
<p>Get or set the selected items of RadzenTree</p>
<div class="row my-5">
    <div class="col-lg-6 offset-lg-3">
        <RadzenCard>
            <RadzenTree AllowCheckBoxes="true" @bind-CheckedValues="@CheckedValues" Style="width: 100%; height: 300px" Data="@categories">
                <RadzenTreeLevel TextProperty="Name" ChildrenProperty="Children" />
                <RadzenTreeLevel TextProperty="Name" ChildrenProperty="GrandChildren" />
                <RadzenTreeLevel TextProperty="Name" ChildrenProperty="GrandGrandChildren" />
                <RadzenTreeLevel TextProperty="Name" HasChildren="@(arg => false)" />
            </RadzenTree>
        </RadzenCard>
    </div>
</div>

@code {
    IEnumerable<Parent> categories = new []
    {
        new Parent()
        {
            Name = "Parent",
            Children = new []
            {
                new Child()
                {
                    Name = "Child",
                    GrandChildren = new []
                    {
                        new GrandChild()
                        {
                            Name = "Grand Child",
                            GrandGrandChildren = new []
                            {
                                new GrandGrandChild()
                                {
                                    Name = "Grand Grand Child 1"
                                },
                                new GrandGrandChild()
                                {
                                    Name = "Grand Grand Child 2"
                                },
                                new GrandGrandChild()
                                {
                                    Name = "Grand Grand Child 3"
                                },
                            }
                        }
                    }
                }
            }
        },
    };

    private IEnumerable<object> CheckedValues { get; set; } = Enumerable.Empty<object>();

    private class Parent
    {
        public string Name { get; set; }
        public IEnumerable<Child> Children { get; set; } = Enumerable.Empty<Child>();
    }
    
    private class Child
    {
        public string Name { get; set; }
        public IEnumerable<GrandChild> GrandChildren { get; set; } = Enumerable.Empty<GrandChild>();
    }
    
    private class GrandChild
    {
        public string Name { get; set; }
        public IEnumerable<GrandGrandChild> GrandGrandChildren { get; set; } = Enumerable.Empty<GrandGrandChild>();
    }
    private class GrandGrandChild
    {
        public string Name { get; set; }
    }
}

Hi Everyone,

When looking through the RadzenTreeItem.razor.cs file I found that every time you expand or collapse a layer of the tree, the GetAllChildValues method is called.

IEnumerable<object> GetAllChildValues(Func<object, bool> predicate = null)
        {
            var children = items.Concat(items.SelectManyRecursive(i => i.items)).Select(i => i.Value);

            return predicate != null ? children.Where(predicate) : children;
        }

This seems to return all children that are rendered on the screen. In the example above where one set of the children are collapsed and not shown on the screen, those children are not found in this call.

Does anyone have an idea on how to get the GetAllChildValues to return collapsed / nonrendered tree items?

Hi Everyone,

Upon more research, I found that when collapsing a layer, the Dispose method is called on all the tree items within the layer. As a result they are all removed from the parent. That means when collapsing the next parent layer, the IsChecked method only evaluates the layer below it leading to the behavior deminstrated in the GIF from the first post.

public void Dispose()
        {
            if (ParentItem != null)
            {
                ParentItem.RemoveItem(this);
            }
            else if (Tree != null)
            {
                Tree.RemoveItem(this);
            }
        }

HI Everyone,

I created a solution for the behvaior in the first post. I created a pull request so anyone can review the code.

I also created an example on the Tree-Checkbox page.

https://github.com/radzenhq/radzen-blazor/pulls

This is indeed an issue which we have to solve. We will look for a general purpose implementation instead of introducing a new property as in your pull request.

Thank you for looking through the pull request. This implementation works great for my use case where I am only concerned with the check status of the lowest level of the tree. Looking forward to the more general solution.

This commit should take care of the situation - nodes will not get disposed on collapse and won't lose their checked status.

Hi Korchev,

Thank you for the quick response. While testing the new implmentation in the commit above I only found one strange behavior. In my use case I toggle between views on the main data. When I switch back and forth between the two views, the state of the checks is breaks. Can you take a look? Happy to build an example.

Video here:

Here is the code in my project that causes the behavior.

<RadzenSelectBar @bind-Value="_selectedRequestTypeId" TValue="long">
    <Items>
    @foreach (RequestTypeDto requestType in _requestTypes)
    {
        <RadzenSelectBarItem Text="@requestType.Name" Value="@requestType.Id"/>
    }
    </Items>
</RadzenSelectBar>
    <RadzenTree AllowCheckBoxes="true" @bind-CheckedBottomLayerValues="@_checkedValuesDict[_selectedRequestTypeId]" Data="@_KnowledgeDomainDict[_selectedRequestTypeId]">
    <RadzenTreeLevel TextProperty="Name" ChildrenProperty="MainRollupL0s"/>
    <RadzenTreeLevel TextProperty="Name" ChildrenProperty="MainRollupL1s"/>
    <RadzenTreeLevel TextProperty="Name" ChildrenProperty="BaselineCategories"/>
    <RadzenTreeLevel TextProperty="Name" HasChildren="@(baselineCategories => false)"/>
</RadzenTree>

These are the properties in my view model

private long _selectedRequestTypeId { get; set; } = 1;
private Dictionary<long, KnowledgeDomainTopDownDto[]> _KnowledgeDomainDict { get; set; } = new ();
private Dictionary<long, IEnumerable<object>> _checkedValuesDict { get; set; } = new ();

Best,
Garrett

Hi Korchev,

I found out it is behaving the way in the video. It seems that when a parent node in the tree, which has been checked before, gets set to the dash state because one of it lowest level chilren has changed, it is not removed the parents that moved from a check state to the dash state from the CheckedValues IEnumerable on the Tree. As a result, when the old check values get rebound to the component, it believes all of the parents who were displayed as dashes are now checks.

Is there any way to remove the parent nodes that move from a check to a dash from the following parameter on the RadzenTree.razor.cs?

[Parameter]
public IEnumerable<object> CheckedValues { get; set; } = Enumerable.Empty<object>();

Best,
Garrett

If you don't want the indeterminate state set the AllowCheckParents property to false.