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.