Virtualization and Bring[Item/Index]IntoView problems

8 posts, 0 answers
  1. Ammaar
    Ammaar avatar
    12 posts
    Member since:
    Dec 2010

    Posted 14 Dec 2010 Link to this post

    Hi, I have the following RadTreeView:

    <tc:RadTreeView x:Name="tree"
                    ItemsSource="{Binding CategoryCollectionView, UpdateSourceTrigger=PropertyChanged}"
                    SelectionChanged="RadTreeView_SelectionChanged"
                    LoadOnDemand="tree_LoadOnDemand"
                    IsVirtualizing="True"
                    tc:TreeViewPanel.VirtualizationMode="Standard"
                    ta:AnimationManager.IsAnimationEnabled="False"
                    IsDragTooltipEnabled="False"
                    IsDragDropEnabled="True"
                    SelectionMode="Multiple">
        <tc:RadTreeView.Resources>
            <Style TargetType="{x:Type ScrollBar}" />
     
            <HierarchicalDataTemplate DataType="{x:Type local:CategoryTreeItemViewModel}"
                                      ItemsSource="{Binding ChildrenCollectionView}">
                 <!--snip-->
            </HierarchicalDataTemplate>
     
            <DataTemplate DataType="{x:Type local:EventNodeViewModel}">
                <!-- snip -->
            </DataTemplate>
     
        </tc:RadTreeView.Resources>
        <tc:RadTreeView.ItemContainerStyle>
            <Style TargetType="{x:Type tc:RadTreeViewItem}">
                <Setter Property="IsExpanded"
                        Value="{Binding IsExpanded, Mode=TwoWay}" />
                <Setter Property="IsSelected"
                        Value="{Binding IsSelected, Mode=TwoWay}" />
                <Style.Triggers>
                    <DataTrigger Binding="{Binding DataContext.IsMainCategoryTree, ElementName=tree}"
                                 Value="True">
                        <Setter Property="IsLoadOnDemandEnabled"
                                Value="{Binding LoadOnDemand}" />
                    </DataTrigger>
                </Style.Triggers>
            </Style>
        </tc:RadTreeView.ItemContainerStyle>
    </tc:RadTreeView>

    I am doing a refresh of the tree using a button which recreates all the item ViewModels in the tree and then I am attempting to restore the state of the tree i.e. expand previously expanded nodes and set previously selected item. This is all done from within the tree's ViewModel.

    This all works fine as long as the user has not scrolled down. Unfortunately, when the user has scrolled down and a refresh happens, the user will then only see the root nodes (not expanded) unless they scroll back up to the top of the tree.

    In order to work around this I subscribed to the tree's ItemContainerGenerator.StatusChanged event after refreshing the data to check when the items are finished generating and then I used the BringItemIntoView or BringIndexIntoView methods to bring to the selected item into view.

    However, this doesn't currently work due to the following exception being thrown when either of these methods are called.:
    Cannot call StartAt when content generation is in progress.

    Any help would be appreciated with this problem,

    Thanks,

    A.
  2. Hristo
    Admin
    Hristo avatar
    352 posts

    Posted 14 Dec 2010 Link to this post

    Hello Ammaar,

    Actually you should call the bring into view methods after the containers have been generated. Not when the container generator status has changed (meaning that you should wait for ContainersGenerated status).

    Also you could use get item by path method (must called it after the tree view is loaded) and get the item container you want. The you can call the BringIntoView method of the RadTreeViewItem.

    Hope this helps. Let me know if you need more help.

    All the best,
    Hristo
    the Telerik team
    Browse the videos here>> to help you get started with RadControls for WPF
  3. UI for WPF is Visual Studio 2017 Ready
  4. Ammaar
    Ammaar avatar
    12 posts
    Member since:
    Dec 2010

    Posted 14 Dec 2010 Link to this post

    Well, originally I was doing this:
    I fired an event from the ViewModel before refreshing data. This then invoked an event handler that switched off tree virtualization and subscribed to the ItemContainerGenerator.StatusChanged event:
    ViewModel.BeforeTreeRefresh += ViewModel_BeforeTreeRefresh;

    private void ViewModel_BeforeTreeRefresh( object sender, EventArgs e )
    {
         tree.IsVirtualizing = false;
         tree.ItemContainerGenerator.StatusChanged += ItemContainerGenerator_StatusChanged;
    }

    Then after the data was finished refreshing I fired another event from my ViewModel (TreeRefreshed) and the handler for this event was supposed to bring the selected item into view and turn virtualization back on:
    ViewModel.TreeRefreshed += ItemContainerGenerator_StatusChanged;

    void ItemContainerGenerator_StatusChanged( object sender, EventArgs e )
    {
        var itemContainerGenerator = sender as ItemContainerGenerator;
     
        if ( itemContainerGenerator != null && itemContainerGenerator.Status == GeneratorStatus.ContainersGenerated )
        {
            //find item to bring into view and call Bring[Item/Index]IntoView on it's container
     
               tree.IsVirtualizing = true;
            itemContainerGenerator.StatusChanged -= ItemContainerGenerator_StatusChanged;
        }
    }

    However, this approach as I said in my original post was throwing an exception so I changed my approach.

    Instead of bringing the selected item into view, I inherited the RadTreeView and created a public method which calls ChangeVisualState(). I then called this method where I was originally trying to bring the selected item into view.

    This way the user will be in the same place in the tree as they were before they requested a refresh which is a more desirable behaviour than bringing the selected item into view as the user could select an item and then scroll away from it before they invoke a refresh.
  5. Ammaar
    Ammaar avatar
    12 posts
    Member since:
    Dec 2010

    Posted 15 Dec 2010 Link to this post

    I seem to have another issue when refreshing the RadTreeView. Before refreshing items can be selected fine, but after the refresh, when the user selects items only the focus changes to that item while the selection does not change. I have an event handler for the SelectedItemChanged event, which gets fired before refresh, but not after refresh.

    Any idea what is causing this? (screenshots attached)
  6. Hristo
    Admin
    Hristo avatar
    352 posts

    Posted 20 Dec 2010 Link to this post

    Hi Ammaar,

    Can you provide us a sample project demonstrating the issue in order to investigate it in detail. I tried to reproduce it but couldn't do it. The issue could also be associated to the ChangeVisualState call that you are making, but this is only a hypothesis.

    All the best,
    Hristo
    the Telerik team
    Browse the videos here>> to help you get started with RadControls for WPF
  7. Ristogod
    Ristogod avatar
    63 posts
    Member since:
    Aug 2008

    Posted 21 Jun 2011 Link to this post

    Can you please give an example of what you are talking about. I am running into the same error and am not sure what you are talking about.
  8. Yavor
    Yavor avatar
    19 posts
    Member since:
    Apr 2009

    Posted 23 Jun 2011 Link to this post

    Hello,

    We're running into a similar problem. The thing is that we should bring certain item of a data-bound treeview into view before the item has been generated. If we try to use ItemContainerGenerator, it returns null. I think the milestone here is to find a way to forcefully generate the item, knowing where it is situated in the hierarchy, provided by the data context.
  9. Ammaar
    Ammaar avatar
    12 posts
    Member since:
    Dec 2010

    Posted 23 Jun 2011 Link to this post

    Hi guys,

    This is what I ended up doing. Inherit from RadTreeView and expose it's ChangeVisualState method (which is protected):

    public class RadTreeViewEx : RadTreeView
    {
        public new void ChangeVisualState()
        {
            base.ChangeVisualState();
        }
    }

    Then in your view-model expose 2 events, one for before your refresh and one for after, e.g.:
    private void RefreshTree()
    {
        InvokeBeforeTreeRefresh( null );
     
        //  Refresh logic here...
     
        InvokeTreeRefreshedEvent( null );
    }
    public event EventHandler BeforeTreeRefresh;
     
    private void InvokeBeforeTreeRefresh( EventArgs e )
    {
        EventHandler handler = BeforeTreeRefresh;
        if ( handler != null ) handler( this, e );
    }
     
    public event EventHandler TreeRefreshed;
     
    private void InvokeTreeRefreshedEvent( EventArgs e )
    {
        EventHandler handler = TreeRefreshed;
        if ( handler != null ) handler( this, e );
    }

    Lastly, in the UserControl where you are using the RadTreeViewEx, you should have something like the following in the codebehind (make sure your RadTreeViewEx has an x:Name property set in the XAML - here I have called it "tree"):

    using System;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Controls.Primitives;
    using System.Windows.Data;
    using Telerik.Windows.Controls;
     
    public partial class MyUserControl
    {
        public MyUserControl()
        {
            InitializeComponent();
     
            DataContextChanged += MyUserControl_DataContextChanged;
        }
         
        void MyUserControl_DataContextChanged( object sender, DependencyPropertyChangedEventArgs e )
        {
            var viewModel = (MyViewModel) DataContext;
            if (viewModel ==null)
            {
                return;
            }
     
            viewModel.BeforeTreeRefresh += ViewModel_BeforeTreeRefresh;
            viewModel.TreeRefreshed += ItemContainerGenerator_StatusChanged;
        }
     
        private void ViewModel_BeforeTreeRefresh( object sender, EventArgs e )
        {
            tree.IsVirtualizing = false;
            tree.ItemContainerGenerator.StatusChanged += ItemContainerGenerator_StatusChanged;
        }
     
        void ItemContainerGenerator_StatusChanged( object sender, EventArgs e )
        {
            var itemContainerGenerator = sender as ItemContainerGenerator;
     
            if ( itemContainerGenerator != null && itemContainerGenerator.Status == GeneratorStatus.ContainersGenerated )
            {
                tree.ChangeVisualStatePublic();
     
                itemContainerGenerator.StatusChanged -= ItemContainerGenerator_StatusChanged;
            }
        }
     
    }


    This is a bit of a hacky solution, but it works for us.
Back to Top
UI for WPF is Visual Studio 2017 Ready