Is 141 the maximum number of WPF panel items that

2019-05-07 15:56发布

I have designed several animated Panels in C# that all inherit from one base class that performs the actual animations on the Panel.Children. Up until recently, they've all worked perfectly... but recently, I was using one with a collection that had a large number of items in it and I started getting some strange visual errors.

When the application first loads the items, the first x number of items display correctly and then the remainder are all displayed in the first position (on top of each other). If I resize the window or select an item, they all rearrange themselves to display correctly. (Using the example code below, you will have to resize the window to see them correct themselves.)

After a lot of head scratching and debugging, I discovered that the correct values were still getting through to the animation code for each item, but the last x items were simply not being animated. Each item definitely passes through the animation code, but the last x items stay in the position set in the animated Panel.ArrangeOverride method.

In order to try to locate a problem in my code, I created a new UserControl and animated Panel class with the minimum code required to recreate the problem. Having done so, I was surprised that the problem was still reproducible. With 141 items in my example code, there was no display problem, but with any more, the problem appears.

Please excuse the long lines of code, but there is so much to display.

AnimatedStackPanel.cs class

public class AnimatedStackPanel : Panel
{
    protected override Size MeasureOverride(Size availableSize)
    {
        double x = 0, y = 0;
        foreach (UIElement child in Children)
        {
            child.Measure(availableSize);
            x = Math.Max(x, availableSize.Width == double.PositiveInfinity ? child.DesiredSize.Width : availableSize.Width);
            y += child.DesiredSize.Height;
        }
        return new Size(availableSize.Width == double.PositiveInfinity ? x : availableSize.Width, availableSize.Height == double.PositiveInfinity ? y : availableSize.Height);
    }

    protected override Size ArrangeOverride(Size finalSize)
    {
        if (this.Children == null || this.Children.Count == 0) return finalSize;
        Size previousChildSize = new Size();
        Point endPosition = new Point(0, 0);
        foreach (UIElement child in Children)
        {
            endPosition.Y += previousChildSize.Height;
            double childWidth = finalSize.Width;
            child.Arrange(new Rect(0, 0, childWidth, child.DesiredSize.Height));
            Point startPosition = new Point(endPosition.X, endPosition.Y + finalSize.Height);
            AnimatePosition(child, startPosition, endPosition, new TimeSpan(0, 0, 1));
            previousChildSize = child.DesiredSize;
        }
        return finalSize;
    }

    private void AnimatePosition(UIElement child, Point startPosition, Point endPosition, TimeSpan animationDuration)
    {
        DoubleAnimation xAnimation = new DoubleAnimation(startPosition.X, endPosition.X, animationDuration);
        DoubleAnimation yAnimation = new DoubleAnimation(startPosition.Y, endPosition.Y, animationDuration);
        TransformGroup transformGroup = child.RenderTransform as TransformGroup;
        if (transformGroup == null)
        {
            TranslateTransform translatationTransform = new TranslateTransform();
            transformGroup = new TransformGroup();
            transformGroup.Children.Add(translatationTransform);
            child.RenderTransform = transformGroup;
            child.RenderTransformOrigin = new Point(0, 0);
        }
        TranslateTransform translateTransform = (TranslateTransform)transformGroup.Children[0];
        translateTransform.BeginAnimation(TranslateTransform.XProperty, xAnimation, HandoffBehavior.Compose);
        translateTransform.BeginAnimation(TranslateTransform.YProperty, yAnimation, HandoffBehavior.Compose);
        //child.Arrange(new Rect(endPosition.X, endPosition.Y, child.DesiredSize.Width, child.DesiredSize.Height));
    }
}

AnimatedListBox.xaml UserControl

<UserControl x:Class="WpfTest.Views.AnimatedListBox"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:Controls="clr-namespace:WpfTest.Views.Controls">
    <UserControl.Resources>
        <ItemsPanelTemplate x:Key="AnimatedStackPanel">
            <Controls:AnimatedStackPanel />
        </ItemsPanelTemplate>
    </UserControl.Resources>
    <Grid>
        <ListBox ItemsSource="{Binding Data}" ItemsPanel="{StaticResource AnimatedStackPanel}" ScrollViewer.HorizontalScrollBarVisibility="Disabled" />
    </Grid>
</UserControl>

AnimatedListBox.xaml.cs UserControl code behind

public partial class AnimatedListBox : UserControl
{
    public AnimatedListBox()
    {
        InitializeComponent();
        Data = new ObservableCollection<int>();
        // change this 150 below to anything less than 142 and the problem disappears!!
        for (int count = 1; count <= 150; count++) Data.Add(count);
        DataContext = this;
    }

    public static DependencyProperty DataProperty = DependencyProperty.Register("Data", typeof(ObservableCollection<int>), typeof(AnimatedListBox));

    public ObservableCollection<int> Data
    {
        get { return (ObservableCollection<int>)GetValue(DataProperty); }
        set { SetValue(DataProperty, value); }
    }
}

While experimenting, I found something else that seems strange. If I change the position of the item in the Panel.ArrangeOverride method to the following, there are more unexpected results.

child.Arrange(new Rect(endPosition.X, endPosition.Y, childWidth, child.DesiredSize.Height));

Somehow, this changes the actual (animation) end positions, ie. where the items end up being positioned. It introduces some kind of double spacing of the items. How can arranging the item in its end position before it starts its animation change its final end position? Am I missing something obvious?

Also, if you uncomment the commented line in the AnimatedStackPanel.AnimatePosition method and comment out the two lines above (ie. cut out the animation and move the items directly to the same end positions that the animations would have moved them to) then you will see the problem disappear again... this applies no matter how many items are in the collection. This is what led me to think that the problem was animation related.

One last thing that I found out is that the problem exists with or without using DataTemplates for the item data type... I'm pretty sure that it's animation related. If anyone can see what I'm doing wrong, or find a solution for this, I'd be very grateful because this one has had me baffled. Can anyone even reproduce this problem with the example code? Many thanks in advance.

1条回答
甜甜的少女心
2楼-- · 2019-05-07 16:24

Sheridan, i played around with your code sample a bit and what i found out is that it apparently matters when you apply the RenderTransform. Instead of assigning it in AnimatePosition, i.e. during the first run of ArrangeOverride, i moved the code to MeasureOverride:

protected override Size MeasureOverride(Size availableSize)
{
    double x = 0, y = 0;
    foreach (UIElement child in Children)
    {
        TransformGroup transformGroup = child.RenderTransform as TransformGroup;
        if (transformGroup == null)
        {
            TranslateTransform translatationTransform = new TranslateTransform();
            transformGroup = new TransformGroup();
            transformGroup.Children.Add(translatationTransform);
            child.RenderTransform = transformGroup;
            child.RenderTransformOrigin = new Point(0, 0);
        }

        child.Measure(availableSize);
        x = Math.Max(x, availableSize.Width == double.PositiveInfinity ? child.DesiredSize.Width : availableSize.Width);
        y += child.DesiredSize.Height;
    }
    return new Size(
        availableSize.Width == double.PositiveInfinity ? x : availableSize.Width,
        availableSize.Height == double.PositiveInfinity ? y : availableSize.Height);
}

Now the animations behave as expected even on the first invocation of ArrangeOverride. Seems like the last few RenderTransforms were not yet attached to the control while already beeing animated.

By the way, the maximum number was 144 for me on two different systems (dual core and quad core) running 32-bit Windows 7.

查看更多
登录 后发表回答