Search code examples
wpfpanelvirtualizationscrollviewerviewbox

VirtualizingPanel stops ScrollViewer working within Viewbox


I have a simple custom panel that is hosted within the ItemsPanel of an ItemsControl. The Template of the ItemsControl is updated to surround the ItemsPresenter with a Viewbox and a ScrollViewer. Here is the XAML code:

<ItemsControl ItemsSource="{Binding Buttons, RelativeSource={RelativeSource 
    AncestorType={x:Type Local:MainWindow}}}" 
    ScrollViewer.HorizontalScrollBarVisibility="Disabled" 
    ScrollViewer.VerticalScrollBarVisibility="Visible" 
    ScrollViewer.CanContentScroll="True">
    <ItemsControl.Template>
        <ControlTemplate TargetType="{x:Type ItemsControl}">
            <ScrollViewer CanContentScroll="True">
                <Viewbox Stretch="UniformToFill">
                    <ItemsPresenter />
                </Viewbox>
            </ScrollViewer>
        </ControlTemplate>
    </ItemsControl.Template>
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <Local:TestPanel />
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Button Height="250" Width="250" HorizontalAlignment="Center" 
                Content="{Binding}" />
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

And the Panel:

public class TestPanel : Panel
{
    protected override Size MeasureOverride(Size availableSize)
    {
        var desiredSize = new Size();
        var layoutSlotSize = availableSize;

        layoutSlotSize.Height = double.PositiveInfinity;
        for (int i = 0, count = InternalChildren.Count; i < count; ++i)
        {
            UIElement child = InternalChildren[i];
            if (child == null) continue;
            child.Measure(layoutSlotSize);
            var childDesiredSize = child.DesiredSize;
            desiredSize.Width = Math.Max(desiredSize.Width, childDesiredSize.Width);
            desiredSize.Height += childDesiredSize.Height;
        }
        return desiredSize;
    }

    protected override Size ArrangeOverride(Size finalSize)
    {
        double verticalOffset = 0;
        for (int i = 0, count = InternalChildren.Count; i < count; ++i)
        {
            UIElement child = InternalChildren[i];
            if (child == null) continue;
            child.Arrange(new Rect(0, verticalOffset, child.DesiredSize.Width, 
                child.DesiredSize.Height));
            verticalOffset += child.DesiredSize.Height;
        }
        return base.ArrangeOverride(finalSize);
    }
}

And finally, MainWindow.xaml.cs:

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

        Buttons = new ObservableCollection<string>();
        IEnumerable<int> characterCodes = Enumerable.Range(65, 26);
        foreach (int characterCode in characterCodes) 
            Buttons.Add(((char)characterCode).ToString().ToUpper());
    }

    public static readonly DependencyProperty ButtonsProperty = 
        DependencyProperty.Register(nameof(Buttons), typeof(ObservableCollection<string>), 
        typeof(MainWindow), null);

    public ObservableCollection<string> Buttons
    {
        get { return (ObservableCollection<string>)GetValue(ButtonsProperty); }
        set { SetValue(ButtonsProperty, value); }
    }
}

This all works as expected... so far, so good. The problem is when I change the base class from Panel to VirtualizingPanel, which I need to do to virtualise the data (not this example button data). After changing the base class, the panel immediately stops working. I am totally aware of how to virtualize data in a panel... I have a working example of this. My problem is when I want to put add a Viewbox inside the ScrollViewer.

Please note that this XAML will work fine with a normal Panel, or StackPanel, but as soon as I change it to VirtualizingPanel, it stops working (nothing is rendered, and the InternalChildren property contains no elements). Can anyone shed some light on this problem for me please?


Solution

  • I still do not know why the VirtualizingPanel does not work within a ViewBox within a ScrollViewer, but I have discovered that if I extend the VirtualizingStackPanel class in my panel instead, everything works as expected.

    Therefore, the solution for those who require virtualized items to be stacked is to extend the VirtualizingStackPanel class instead. For those who need other types of child arrangement, I'm sorry, but I have no answer, unless you remove the Viewbox.

    I would still be more than happy to receive any further information on this subject.