4

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

Gilad
  • 548
  • 1
  • 7
  • 22

3 Answers3

3

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

Gilad
  • 548
  • 1
  • 7
  • 22
2

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

Luke Kim
  • 1,046
  • 10
  • 11
1

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.

Community
  • 1
  • 1
Alex Paven
  • 5,539
  • 2
  • 21
  • 35
  • It would be ridiculous for me to spend so much time and put my system in such a risk. We are developing systems and not controls. it is much easier to simply tweak the tree view into this. I never say never and if there wasn't a simple solution - we as developers need to do what ever is required, but since there is better alternative here - i don't see why taking the risk. – Gilad Apr 16 '12 at 11:28
  • 1
    BTW: This is a bug. Microsoft should take care of this. There is no conflict whatsoever between pixel based scrolling and virtualization. The proof for that is that it works just well in TreeView, they didn't have a choice there due to the heretical structure. Someone was lazy there... :-) – Gilad Apr 16 '12 at 11:31
  • Actually the TreeView doesn't have virtualization at all, and that's another headache. Implementing it properly is a proper pain. Of course it's only difficult because each parent's items are represented by another ItemsControl, and making each embedded ItemsControl aware of the outermost container's size and properties so that virtualization could work is hard to do properly. That's the reason the TreeView's performance really sucks for anything beyond a small number of items. – Alex Paven Apr 18 '12 at 08:22