RadzenTree checked state after collapse and expand

Hi,
I have a RadzenTree that works nicely. Now I'd like to add checkboxes. Since @bind-CheckedValues did not really work (I guess because of Expand=@LoadTreeFiles) I tried to go with the CheckedValuesChanged event. My RadzenTree tag looks like this:

<RadzenTree @ref="dataTree" Data=@treeItems
            AllowCheckBoxes="true"
            AllowCheckChildren="true"
            AllowCheckParents="true"
            CheckedValuesChanged=@CheckedValuesChanged
            Expand=@LoadTreeFiles
            Change=@OnSelectionChange
            ItemRender="@TreeItemRender">
    <RadzenTreeLevel Text=@GetTextForTreeNode Template=@TreeTemplate ChildrenProperty="Children" Expanded=@ShouldExpand HasChildren=HasChildren  />
</RadzenTree>

I understood from RadzenTree checkbox selectedvalue issue that I cannot expect the children of an unexpanded parent to be checked when I check that parent.

But there's a behaviour I do not really understand:

When I check one or more (but not all) childen of an expanded parent item and collapse that parent and expand it again, all children are unchecked.
However if I select ALL children of that same parent and collapse and expand it again, the children are kept checked. Which is nice.

My goal is to keep my checked elements even after applying a filter and the first behaviour prevents me from doing so.

Is there something I missed? Maybe somebody can give me a hint on how to handle this.
As always thanks for your support!
Cheers!

We added recently ItemRender event that can be used to set the checked state of a parent item item knowing the state of children without need to expand them. The event is illustrated on our demo:


Hi @enchev,

thanks for looking into my issue. I've seen that, but I'm afraid I don't really understand how that helps me if the internal checked state seems to be reset.

Maybe you could help me out with some advice?

To demonstrate my problem I created a small example:

@page "/treecheck"

@using System.Collections
@using System.ComponentModel.DataAnnotations
@using Radzen
@using Radzen.Blazor

@inherits TreeCheckBase


<RadzenTree @ref="dataTree" Data=@treeItems
            AllowCheckBoxes="true"
            AllowCheckChildren="true"
            AllowCheckParents="true"
            CheckedValuesChanged=@CheckedValuesChanged
            Expand=@LoadTreeFiles
            Change=@OnSelectionChange
            ItemRender="@TreeItemRender">
    <RadzenTreeLevel Text=@GetTextForTreeNode Template=@TreeTemplate ChildrenProperty="Children" Expanded=@ShouldExpand HasChildren=HasChildren />
</RadzenTree>
using Microsoft.AspNetCore.Components;
using Radzen;
using Radzen.Blazor;

namespace TabsIssue.Components.Pages
{
    public class TreeModel
    {
        public string DataElementId { get; set; }

        public string Name { get; set; }

        public bool IsGroup { get; set; }
    }

    public class TreeItem<T>
    {
        public T Item { get; set; }
        public IEnumerable<TreeItem<T>> Children { get; set; }
        public bool? Checked { get; set; }
        public bool Expanded { get; set; }
    }


    public class TreeCheckBase : ComponentBase
    {
        protected RadzenTree dataTree;
        protected List<TreeItem<TreeModel>> treeItems;


        protected override async Task OnInitializedAsync()
        {
            treeItems = new();
            treeItems.Add(new TreeItem<TreeModel>
            {
                Item = new TreeModel { DataElementId = "1", Name = "Root", IsGroup = true },
                Children = new List<TreeItem<TreeModel>>
                {
                    new TreeItem<TreeModel>
                    {
                        Item = new TreeModel { DataElementId = "2", Name = "Child 1", IsGroup = false },
                        Children = new List<TreeItem<TreeModel>>
                        {
                            new TreeItem<TreeModel>
                            {
                                Item = new TreeModel { DataElementId = "3", Name = "Child 1.1", IsGroup = false },
                                Children = new List<TreeItem<TreeModel>>()
                            },
                            new TreeItem<TreeModel>
                            {
                                Item = new TreeModel { DataElementId = "4", Name = "Child 1.2", IsGroup = false },
                                Children = new List<TreeItem<TreeModel>>()
                            }
                        }
                    },
                    new TreeItem<TreeModel>
                    {
                        Item = new TreeModel { DataElementId = "4", Name = "Child 2", IsGroup = false },
                        Children = new List<TreeItem<TreeModel>>()
                    }
                }
            });


            await base.OnInitializedAsync();
        }

        protected void LoadTreeFiles(TreeExpandEventArgs args)
        {
            var selected = args.Value as TreeItem<TreeModel>;

            args.Children.Data = selected.Children;
            args.Children.Text = GetTextForTreeNode;

            args.Children.HasChildren = (path) =>
            {
                if (path is TreeItem<TreeModel> treeNode)
                {
                    if (treeNode.Children != null)
                    {
                        return treeNode.Children.Any();
                    }
                }
                return false;
            };
            args.Children.Template = TreeTemplate;
        }

        protected async Task CheckedValuesChanged(IEnumerable<object> selectedItems)
        {
            var checkedIds = selectedItems.Select(ti =>
            {
                return (ti as TreeItem<TreeModel>).Item.DataElementId;
            }).ToList();


            var checkedItems = selectedItems.Select(ti => ti as TreeItem<TreeModel>).ToList();
        }


        protected async Task OnSelectionChange()
        {

        }

        protected void TreeItemRender(TreeItemRenderEventArgs args)
        {
            if (args.Value is TreeItem<TreeModel> treeItem)
            {
                //bool? checkedState = TreeHelper.CheckedChildren(treeItem);
                //args.Checked = checkedState;
            }
        }

        protected RenderFragment<RadzenTreeItem> TreeTemplate = (context) => builder =>
        {
            TreeItem<TreeModel> path = context.Value as TreeItem<TreeModel>;

            bool isDirectory = false;

            if (path is TreeItem<TreeModel> treeNode)
            {
                isDirectory = treeNode.Item.IsGroup;
            }

            builder.OpenComponent<RadzenIcon>(0);
            builder.AddAttribute(1, nameof(RadzenIcon.Icon), isDirectory ? "folder" : "insert_drive_file");
            builder.CloseComponent();
            builder.AddContent(2, context.Text);

        };


        protected string GetTextForTreeNode(object data)
        {
            return (data as TreeItem<TreeModel>).Item.Name;
        }

        protected bool ShouldExpand(object data)
        {
            if (data is TreeItem<TreeModel> treeItem)
            {
                var expanded = treeItem.Expanded;
                return expanded;
            }
            return false;
        }

        protected bool HasChildren(object data)
        {
            if (data is TreeItem<TreeModel> treeItem)
            {
                var hasChildren = treeItem.Children.Any();
                return hasChildren;
            }
            return false;
        }
    }
}

Would you mind trying the following?

  1. expand Root
  2. expand Child1
  3. check Child 1.2
  4. collapse Child1
  5. expand Child1

The selectedItems of the CheckedValuesChanged callback is now an empty list. And Child 1.2 is unchecked.
If you do the same procedure, but in step 3 check both children, everything works as I wish.
Is this expected behaviour?

Thanks again for your support!

Cheers and best!