WPF: TreeView, Virtualization, Select and BringIntoView issue

22 Views Asked by At

I have a TreeView that must be virtualized for performance reasons. The problem is that if I change the bound IsSelected property the TreeViewItem.Selected event is not called until it is visible (when scrolled down).

So I can not implement the 'BringIntoView when selected pattern' that is demonstrated here.

I know there was answered already a similiar question. But the answers give no real solution. And I have no credits to add comments so far.

How can I overcome this?

Here is my sample code:

XAML:

<Window x:Class="VirtualizedTreeView.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:VirtualizedTreeView"
        mc:Ignorable="d"
        x:Name="Window"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <Button Grid.Row="0"
                Content="Select Element 5"
                Click="Button_Click_5" />
        <Button Grid.Row="1"
                Content="Select Element 500"
                Click="Button_Click_500" />
        <TreeView Grid.Row="2"
                  VirtualizingStackPanel.IsVirtualizing="True"
                  VirtualizingStackPanel.VirtualizationMode="Recycling"
                  ScrollViewer.CanContentScroll="True"
                  
                  ItemsSource="{Binding ElementName=Window, Path=Items}" >
            <TreeView.ItemContainerStyle>
                <Style TargetType="{x:Type TreeViewItem}">
                    <Setter Property="IsSelected"
                            Value="{Binding IsSelected}" />
                    <EventSetter Event="TreeViewItem.Selected"
                                 Handler="TreeViewItem_Selected" />
                </Style>
            </TreeView.ItemContainerStyle>
        </TreeView>
    </Grid>
</Window>

Xaml.cs:

    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            for (var i = 0; i < 1000; i++)
            {
                Items.Add(new ItemsViewModel($"Item{i}"));
            }
        }

        public Collection<ItemsViewModel> Items { get; }
            = new Collection<ItemsViewModel>();

        private void TreeViewItem_Selected(object sender, RoutedEventArgs e)
        {
            // Fired immediately for Item 5 but not for item 500
        }

        private void Button_Click_5(object sender, RoutedEventArgs e)
        {
            Items[5].IsSelected = true;
        }

        private void Button_Click_500(object sender, RoutedEventArgs e)
        {
            Items[500].IsSelected = true;
        }
    }

ItemViewModel.cs:

    public class ItemViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        private string header;

        public ItemViewModel(string header)
        {
            this.header = header;
        }

        public override string ToString()
            => header;

        private bool isSelected;
        public bool IsSelected
        {
            get => isSelected;
            set
            {
                if (isSelected != value)
                {
                    isSelected = value;
                    OnNotifyPropertyChanged();
                }
            }
        }

        private void OnNotifyPropertyChanged([CallerMemberName] String propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }

I already tried to use VirtualizationMode.Standard and using ScrollViewer.CanContentScroll="True".

I could try to somehow set the BringIntoView manually when IsSelected is set to true in the ViewModel but this feels a bit clumsy.

0

There are 0 best solutions below