TreeListView with a ListCollectionView is leaking the performance

4 posts, 0 answers
  1. BENN
    BENN avatar
    59 posts
    Member since:
    Dec 2011

    Posted 22 Aug Link to this post

    I took an example for a TreeView where you filter items with a Boolean "IsRemovedByFilter". The example had the items re-created with a copy constructor.

    I did the same on a tree list view, and replaced the copy constructor with ListCollectionView.EditItem and CommitEdit.

     

    The more times you filter the items, the slower it gets.

     

    Here is the code:

    MainWindow:

    <Window x:Class="TreeListView_Filtering_Issue.MainWindow"
            Title="MainWindow" Height="350" Width="525">
         
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition />
            </Grid.RowDefinitions>
             
            <TextBox Text="{Binding FilterText, UpdateSourceTrigger=PropertyChanged}" />
             
            <telerik:RadTreeListView ItemsSource="{Binding Items}" AutoGenerateColumns="False" Grid.Row="1">
                <telerik:RadTreeListView.ChildTableDefinitions>
                    <telerik:TreeListViewTableDefinition ItemsSource="{Binding FilteredChildren}" />
                </telerik:RadTreeListView.ChildTableDefinitions>
                 
                <telerik:RadTreeListView.Columns>
                    <telerik:GridViewDataColumn DataMemberBinding="{Binding Name}" IsReadOnly="True"/>
                </telerik:RadTreeListView.Columns>
            </telerik:RadTreeListView>
        </Grid>
    </Window>

     

    The data context is being set by (just a quick and dirty way for this example):

            public MainWindow()
            {
                InitializeComponent();
                this.DataContext = new MainViewModel();
            }

     

     

    The ViewModels are:

    public class MainViewModel : ViewModelBase
    {
        private ObservableCollection<Person> _persons = new ObservableCollection<Person>();
        private ListCollectionView _items;
        private string _filterText = string.Empty;
     
        public MainViewModel()
        {
            for (int i = 0; i < 100; i++)
            {
                Person person = new Person(null)
                {
                    Name = "Test" + i,
                };
     
                _persons.Add(person);
     
                for (int j = 0; j < 20; j++)
                {
                    Person child = new Person(person)
                    {
                        Name = "Child." + i + "." + j,
                    };
     
                    person.Children.Add(child);
                }
            }
     
            _items = new ListCollectionView(_persons);
            _items.Filter = nonFilteredChidrenPredicate;
        }
     
        internal static bool nonFilteredChidrenPredicate(object obj)
        {
            if (obj is Person)
            {
                return !(obj as Person).IsRemovedByFilter;
            }
            return false;
        }
     
        public ListCollectionView Items
        {
            get
            {
                return _items;
            }
        }
     
        public string FilterText
        {
            get
            {
                return _filterText;
            }
            set
            {
                if (value == null)
                    value = string.Empty;
                _filterText = value;
                FilterItems();
            }
        }
     
        internal void FilterItems()
        {
            Queue<Person> queue = new Queue<Person>();
     
            foreach (var item in _persons)
            {
                queue.Enqueue(item);
            }
            while (queue.Count > 0)
            {
                Person current = queue.Dequeue();
                current.IsRemovedByFilter = false;
     
                if (current.Name.ToUpper().Contains(FilterText.ToUpper()))
                {
                    current.IsRemovedByFilter = false;
                    //if (!string.IsNullOrEmpty(FilterText))
                    //{
                    //    current.IsExpanded = true;
     
                    //    if (selectedItem == null)
                    //        selectedItem = current;
                    //}
                    // show and expand parent if one child fits with filter
                    setParentFilterStatus(current);
                }
                else
                {
                    current.IsRemovedByFilter = true;
                }
                // filter tree leaves
                if (current.Children.Count > 0)
                {
                    foreach (var item in current.Children)
                    {
                        queue.Enqueue(item);
                    }
                }
            }
     
            foreach (var root in _persons)
            {
                Items.EditItem(root);
                Items.CommitEdit();
                refreshFilterStatus(root, root.Children);
            }
        }
     
        private void refreshFilterStatus(Person parent, IEnumerable<Person> items)
        {
            foreach (var item in items)
            {
                parent.FilteredChildren.EditItem(item);
                parent.FilteredChildren.CommitEdit();
            }
            foreach (var item in items)
            {
                refreshFilterStatus(item, item.Children);
            }
        }
     
        private void setParentFilterStatus(Person person)
        {
            Person currentPerson = person;
            while (currentPerson.Parent != null)
            {
                currentPerson.Parent.IsRemovedByFilter = false;
     
                //// if a filter is applied, expand parent's nodes
                //if (!string.IsNullOrEmpty(FilterText))
                //    currentPerson.Parent.IsExpanded = true;
     
                currentPerson = currentPerson.Parent;
            }
        }
    }
     
    public class Person : ViewModelBase
    {
        public string Name { get; set; }
        public bool IsRemovedByFilter { get; set; }
        public Person Parent { get; private set; }
        public ObservableCollection<Person> Children { get; private set; }
        public ListCollectionView FilteredChildren { get; private set; }
     
        public Person(Person parent)
        {
            Children = new ObservableCollection<Person>();
            FilteredChildren = new ListCollectionView(Children);
        }
    }

     

     

    Run the example, and type the letter z. The filtering is fast. Delete the z, it now took a little more time.

    Repeat this procedure few more times. You should notice that it becomes slower and slower each time (my guess would be event handlers leaking, but I didn't confirm it).

     

    With dotTrace I can see that when the first z was typed, OnSourceViewColelctionChanged was called 5150 times.

    The the z was deleted, OnSourceViewColelctionChanged was called 15150 times.

    After repeating it 5 more times, OnSourceViewColelctionChanged was called 105150 times when the z was typed, and  115150 times when the z was deleted.

     

     

     

    Currently, it is much faster (also due to virtualization) to just refresh the entire ListCollectionView

  2. Ivan Ivanov
    Admin
    Ivan Ivanov avatar
    1127 posts

    Posted 25 Aug Link to this post

    Hi,

    Thank you for reporting the issue to us. I manage to reproduce it on our side. It is now logged in our system and here is the public item in our feedback portal, which you can follow to get update about our progress with it. I am adding 1000 Telerik points to your account, in accordance to this report.

    Regards,
    Ivan Ivanov
    Telerik by Progress
    Do you need help with upgrading your AJAX, WPF or WinForms project? Check the Telerik API Analyzer and share your thoughts.
  3. UI for WPF is Visual Studio 2017 Ready
  4. BENN
    BENN avatar
    59 posts
    Member since:
    Dec 2011

    Posted 25 Aug in reply to Ivan Ivanov Link to this post

    Thanks.

     

    Btw, I'm unable to access the link you gave me for the public portal. When trying to browse:

    http://tpdogfood.telerik.com/view#item/199578

     

    I get:

    Server Error in Application "TPPORTAL.TELERIK.COM"
    Internet Information Services 7.5
    Error Summary
    HTTP Error 403.6 - Forbidden
    The IP address from which you are browsing is not permitted to access the requested Web site.
    Detailed Error Information
    Module IpRestrictionModule
    Notification BeginRequest
    Handler ExtensionlessUrlHandler-Integrated-4.0
    Error Code 0x80070005
    Requested URL http://tpdogfood.telerik.com:80/view
    Physical Path C:\Telerik\WebSites\tpportal.telerik.com\view
    Logon Method Not yet determined
    Logon User Not yet determined

    Most likely causes:
    The server, site, application, or page requested has explicitly denied the IP address of the client computer.
    Things you can try:
    Verify the IP and domain restrictions in IIS Manager.
    Remove the IP restrictions from the configuration/system.webServer/security/ipSecurity section of the configuration file for the server, site, application, or page.

     

     

    I also tried browsing the link from a web proxy, but no avail.

     

  5. Ivan Ivanov
    Admin
    Ivan Ivanov avatar
    1127 posts

    Posted 26 Aug Link to this post

    Hello,

    Please excuse me. It is my bad. Here is the correct link to the public portal.

    Regards,
    Ivan Ivanov
    Telerik by Progress
    Do you need help with upgrading your AJAX, WPF or WinForms project? Check the Telerik API Analyzer and share your thoughts.
Back to Top