Need your help. I have a ListBox (with virtualization) which displays a ScrollViewer.
My ListBox items are expandable, and while expanded their hight may exceed the visible scrolling area.
The problem i'm expiriencing is that when the list box item is exceeds the visible scrolling area - scrolling jumps to the next ListBox item rather than simply scrolling the view.
Check this code:
<ListBox Grid.Row="1" Grid.Column="0" DataContext="{Binding SpecPackageSpecGroupListViewModel}" VirtualizingStackPanel.IsVirtualizing="True" VirtualizingStackPanel.VirtualizationMode="Recycling"
ItemContainerStyle="{StaticResource SpecPackageSpecGroupListBoxStyle}" ScrollViewer.IsDeferredScrollingEnabled="True"
ItemsSource="{Binding SortedChildren}" ScrollViewer.CanContentScroll="True"
Background="Transparent"
BorderThickness="0" SelectionMode="Extended"
Margin="5,5,5,5">
<ListBox.ItemTemplate>
<DataTemplate>
<Controls:SpecPackageSpecGroupControl/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Of-course, i can't wrap my ListBox with another scroller since it will turn off the visualization (which is very impotent to me).
If i set CanContentScroll to False everything works as expected - but the virtualization stops working.
HELP!!!
Gili
Ok, so just before i was about to give up and somehow learn how to live with this bug i bumped into a post (which i can't seem to find now) that suggests that TreeView does support Pixel-Based scrolling (AKA Physical Scrolling) without turning off visualization.
So i tried this and indeed - it works! Made sure to verify that virtualization works, tested with ~1000 items and also set a break point on my control constructor and made sure it is called when my view is scrolled.
The only disadvantage of using TreeView instead of ListBox is that TreeView doesn't seem to support multiple item selection (which i needed) - but implementing this is way much easier than implementing the smart scrolling for ListBox.
I created a style for TreeViewItem that makes the TreeViewItem look and behave just like ListBoxItem, this is really not mandatory - but i preferred it like this (beside the fact that the basic style has stretching issues which i had to fix with styling). Basically i removed the ItemsPresenter and stayed only with the ContentPresenter since my data is not hierarchical:
<Style x:Key="MyTreeViewItemStyle" TargetType="{x:Type TreeViewItem}">
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="SnapsToDevicePixels" Value="true"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
<Setter Property="OverridesDefaultStyle" Value="true"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TreeViewItem}">
<Border Name="myBorder"
SnapsToDevicePixels="true"
CornerRadius="0,0,0,0"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
BorderThickness="0"
BorderBrush="Transparent"
Height="Auto"
Margin="1,1,1,3"
Background="Transparent">
<ContentPresenter Grid.Column="1" x:Name="PART_Header" HorizontalAlignment="Stretch" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" ContentSource="Header"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Now - the only thing i've got left to do is implement the multi-selection tree view.
There might be different approaches to implement such behavior, i took the ViewModel approach.
Derived from TreeView i created a new MultiSelectionTreeView:
public class MultiSelectionTreeView : TreeView
{
private static bool CtrlPressed
{
get
{
return Keyboard.IsKeyDown(Key.LeftCtrl);
}
}
protected override void OnSelectedItemChanged(RoutedPropertyChangedEventArgs<object> e)
{
base.OnSelectedItemChanged(e);
var previouseItemViewModel = e.OldValue as IMultiSelectionTreeViewItemViewModel;
if (previouseItemViewModel != null)
{
if (!CtrlPressed)
previouseItemViewModel.IsSelected = false;
}
var newItemViewModel = e.NewValue as IMultiSelectionTreeViewItemViewModel;
if (newItemViewModel != null)
{
if (!CtrlPressed)
newItemViewModel.ClearSelectedSiblings();
newItemViewModel.IsSelected = true;
}
}
}
Where IMultiSelectionTreeViewItemViewModel is as follows:
public interface IMultiSelectionTreeViewItemViewModel
{
bool IsSelected { get; set; }
void ClearSelectedSiblings();
}
Of course - now it is my responsibility to handle the way selected items are being presented - in my case it was given since my tree view items had their own DataTemplate which had indication for its selection.
If this is not your case and you need it, simply extent your tree view item data template to indicate its selection state according to its view model IsSelected property.
Hope this will help someone someday :-)
Have fun!
Gili
This question is still coming up in search engines, so I'll answer it 2 years later.
WPF 4.5 now supports pixel based virtualizing panels.
If you can target 4.5, then just add this to your ListBox:
<Setter Property="VirtualizingPanel.ScrollUnit" Value="Pixel"/>
For what's new in .NET 4.5 (which includes new stuff in WPF 4.5) see http://msdn.microsoft.com/en-us/library/ms171868.aspx
Take a look here (Bea Stollnitz) and here(Dan Crevier); basically you'll need to implement your own container that supports both virtualization and pixel-based scrolling. You can also look at this similar SO question for more details or a possible alternative. Deriving from VirtualizingStackPanel and modifying the scroll behavior seems to be the best bet.