虚拟化Wrap Panel WPF - 免费解决方案?

时间:2011-02-24 14:40:44

标签: wpf virtualization

我知道这是Is there a (good/free) VirtualizingWrapPanel available for WPF?的重复,但那里的答案不起作用,因为我需要它。示例:当我在局部视图中单击某个项目时,它会将我带到它下面的项目,或者当我刷新我的项目以使更改在我在幕后执行的UI上生效时,它会滚动到顶部。自去年以来,有没有人找到一个好的,免费的(或便宜的)解决方案?

现在我知道在http://www.binarymission.co.uk/Products/WPF_SL/virtualizingwrappanel_wpf_sl.htm有一个选项,这正是我需要它的方式,我真的不想花一个盛大的项目。即使它像200-300我已经买了它但是在一个项目(目前为止)中为这一个控件买了900美元,我无法证明这一点。

任何建议都会非常感谢!

Anthony F Greco

4 个答案:

答案 0 :(得分:4)

使用这个:http://virtualwrappanel.codeplex.com/

以下是您使用它的方式:

xmlns:local="clr-namespace:MyWinCollection"
...
<local:VirtualizingWrapPanel ..../>

答案 1 :(得分:0)

原始链接上的那个只需要进行一些调整就可以在父ListView上支持ScrollIntoView,这样当你刷新时可以将选择/滚动位置放回到你想要的位置。

将此额外覆盖添加到问题(found here

中提到的类
protected override void BringIndexIntoView(int index)
    {
        var currentVisibleMin = _offset.Y;
        var currentVisibleMax = _offset.Y + _viewportSize.Height - ItemHeight;
        var itemsPerLine = Math.Max((int)Math.Floor(_viewportSize.Width / ItemWidth), 1);

        var verticalOffsetRequiredToPutItemAtTopRow = Math.Floor((double)index / itemsPerLine) * ItemHeight;

        if (verticalOffsetRequiredToPutItemAtTopRow < currentVisibleMin) // if item is above visible area put it on the top row
            SetVerticalOffset(verticalOffsetRequiredToPutItemAtTopRow);
        else if (verticalOffsetRequiredToPutItemAtTopRow > currentVisibleMax) // if item is below visible area move to put it on bottom row
            SetVerticalOffset(verticalOffsetRequiredToPutItemAtTopRow -  _viewportSize.Height + ItemHeight);
    }

当您在那里时,可以按照以下方式更改以下几项功能以提高性能

protected override Size MeasureOverride(Size availableSize)
    {
        if (_itemsControl == null)
        {
            return availableSize;
        }

        _isInMeasure = true;
        _childLayouts.Clear();

        var extentInfo = GetExtentInfo(availableSize, ItemHeight);

        EnsureScrollOffsetIsWithinConstrains(extentInfo);

        var layoutInfo = GetLayoutInfo(availableSize, ItemHeight, extentInfo);

        RecycleItems(layoutInfo);

        // Determine where the first item is in relation to previously realized items
        var generatorStartPosition = _itemsGenerator.GeneratorPositionFromIndex(layoutInfo.FirstRealizedItemIndex);

        var visualIndex = 0;

        var currentX = layoutInfo.FirstRealizedItemLeft;
        var currentY = layoutInfo.FirstRealizedLineTop;

        using (_itemsGenerator.StartAt(generatorStartPosition, GeneratorDirection.Forward, true))
        {
            for (var itemIndex = layoutInfo.FirstRealizedItemIndex; itemIndex <= layoutInfo.LastRealizedItemIndex; itemIndex++, visualIndex++)
            {
                bool newlyRealized;

                var child = (UIElement)_itemsGenerator.GenerateNext(out newlyRealized);
                SetVirtualItemIndex(child, itemIndex);

                if (newlyRealized)
                {
                    InsertInternalChild(visualIndex, child);
                }
                else
                {
                    // check if item needs to be moved into a new position in the Children collection
                    if (visualIndex < Children.Count)
                    {
                        if (Children[visualIndex] != child)
                        {
                            var childCurrentIndex = Children.IndexOf(child);

                            if (childCurrentIndex >= 0)
                            {
                                RemoveInternalChildRange(childCurrentIndex, 1);
                                Debug.WriteLine("Moving child from {0} to {1}", childCurrentIndex, visualIndex);
                            }
                            else
                                Debug.WriteLine("Inserting child {0}", visualIndex);

                            InsertInternalChild(visualIndex, child);
                        }
                    }
                    else
                    {
                        // we know that the child can't already be in the children collection
                        // because we've been inserting children in correct visualIndex order,
                        // and this child has a visualIndex greater than the Children.Count
                        AddInternalChild(child);
                        Debug.WriteLine("Adding child at {0}", Children.Count-1);
                    }
                }

                // only prepare the item once it has been added to the visual tree
                _itemsGenerator.PrepareItemContainer(child);

                if (newlyRealized)
                    child.Measure(new Size(ItemWidth, ItemHeight));

                _childLayouts.Add(child, new Rect(currentX, currentY, ItemWidth, ItemHeight));

                if (currentX + ItemWidth * 2 >= availableSize.Width)
                {
                    // wrap to a new line
                    currentY += ItemHeight;
                    currentX = 0;
                }
                else
                {
                    currentX += ItemWidth;
                }
            }
        }

        RemoveRedundantChildren();
        UpdateScrollInfo(availableSize, extentInfo);

        var desiredSize = new Size(double.IsInfinity(availableSize.Width) ? 0 : availableSize.Width,
                                   double.IsInfinity(availableSize.Height) ? 0 : availableSize.Height);

        _isInMeasure = false;

        return desiredSize;
    }


        class RemoveRange
    {
       public int Start;
       public int Length;
    }
    private void RecycleItems(ItemLayoutInfo layoutInfo)
    {

        Queue<RemoveRange> ranges = null;

        int idx = 0;
        RemoveRange nextRange = null;
        foreach (UIElement child in Children)
        {

            var virtualItemIndex = GetVirtualItemIndex(child);

            if (virtualItemIndex < layoutInfo.FirstRealizedItemIndex || virtualItemIndex > layoutInfo.LastRealizedItemIndex)
            {
                var generatorPosition = _itemsGenerator.GeneratorPositionFromIndex(virtualItemIndex);
                if (generatorPosition.Index >= 0)
                {
                    _itemsGenerator.Recycle(generatorPosition, 1);
                }
                if (nextRange == null)
                    nextRange = new RemoveRange() { Start = idx, Length = 1 };
                else
                    nextRange.Length++;
            }
            else if (nextRange!= null)
            {
                if (ranges== null)
                    ranges = new Queue<RemoveRange>();
                ranges.Enqueue(nextRange);
                nextRange = null;
            }

            SetVirtualItemIndex(child, -1);
            idx++;
        }
        if (nextRange != null)
        {
            if (ranges == null)
                ranges = new Queue<RemoveRange>();
            ranges.Enqueue(nextRange);
        }
        int removed = 0;
        if (ranges != null)
        {
            foreach (var range in ranges)
            {
                RemoveInternalChildRange(range.Start-removed, range.Length);
                removed +=range.Length;
            }
        }
    }

答案 2 :(得分:0)

在尝试修复http://virtualwrappanel.codeplex.com/控件的问题后,我最终得到了来自https://stackoverflow.com/a/13560758/5887121的解决方案。我还添加了BringIntoView方法和DP来设置方向。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Media;

namespace Wpf.Controls
{
    // class from: https://github.com/samueldjack/VirtualCollection/blob/master/VirtualCollection/VirtualCollection/VirtualizingWrapPanel.cs
    // MakeVisible() method from: http://www.switchonthecode.com/tutorials/wpf-tutorial-implementing-iscrollinfo
    public class VirtualizingWrapPanel : VirtualizingPanel, IScrollInfo
    {
    private const double ScrollLineAmount = 16.0;

    private Size _extentSize;
    private Size _viewportSize;
    private Point _offset;
    private ItemsControl _itemsControl;
    private readonly Dictionary<UIElement, Rect> _childLayouts = new Dictionary<UIElement, Rect>();

    public static readonly DependencyProperty ItemWidthProperty =
        DependencyProperty.Register("ItemWidth", typeof(double), typeof(VirtualizingWrapPanel), new PropertyMetadata(1.0, HandleItemDimensionChanged));

    public static readonly DependencyProperty ItemHeightProperty =
        DependencyProperty.Register("ItemHeight", typeof(double), typeof(VirtualizingWrapPanel), new PropertyMetadata(1.0, HandleItemDimensionChanged));

    private static readonly DependencyProperty VirtualItemIndexProperty =
        DependencyProperty.RegisterAttached("VirtualItemIndex", typeof(int), typeof(VirtualizingWrapPanel), new PropertyMetadata(-1));

    private IRecyclingItemContainerGenerator _itemsGenerator;

    private bool _isInMeasure;

    private static int GetVirtualItemIndex(DependencyObject obj)
    {
        return (int)obj.GetValue(VirtualItemIndexProperty);
    }

    private static void SetVirtualItemIndex(DependencyObject obj, int value)
    {
        obj.SetValue(VirtualItemIndexProperty, value);
    }

    #region Orientation

    /// <summary>
    /// Gets and sets the orientation of the panel.
    /// </summary>
    /// <value>The orientation of the panel.</value>
    public Orientation Orientation
    {
        get
        {
            return (Orientation)GetValue(OrientationProperty);
        }
        set
        {
            SetValue(OrientationProperty, value);
        }
    }

    /// <summary>
    /// Identifies the Orientation dependency property.
    /// </summary>
    /// <remarks>
    /// Returns: The identifier for the Orientation dependency property.
    /// </remarks>
    public static readonly DependencyProperty OrientationProperty = StackPanel.OrientationProperty.AddOwner(typeof(VirtualizingWrapPanel), new FrameworkPropertyMetadata(Orientation.Horizontal));

    #endregion Orientation

    public double ItemHeight
    {
        get
        {
            return (double)GetValue(ItemHeightProperty);
        }
        set
        {
            SetValue(ItemHeightProperty, value);
        }
    }

    public double ItemWidth
    {
        get
        {
            return (double)GetValue(ItemWidthProperty);
        }
        set
        {
            SetValue(ItemWidthProperty, value);
        }
    }

    public VirtualizingWrapPanel()
    {
        if ( !DesignerProperties.GetIsInDesignMode(this) )
        {
            Dispatcher.BeginInvoke((Action)Initialize);
        }
    }

    private void Initialize()
    {
        _itemsControl = ItemsControl.GetItemsOwner(this);
        _itemsGenerator = (IRecyclingItemContainerGenerator)ItemContainerGenerator;

        InvalidateMeasure();
    }

    protected override void OnItemsChanged(object sender, ItemsChangedEventArgs args)
    {
        base.OnItemsChanged(sender, args);

        InvalidateMeasure();
    }

    protected override Size MeasureOverride(Size availableSize)
    {
        if ( _itemsControl == null )
        {
            return availableSize;
        }

        _isInMeasure = true;
        _childLayouts.Clear();

        var extentInfo = GetExtentInfo(availableSize);

        EnsureScrollOffsetIsWithinConstrains(extentInfo);

        var layoutInfo = GetLayoutInfo(availableSize, (Orientation == Orientation.Vertical) ? ItemHeight : ItemWidth, extentInfo);

        RecycleItems(layoutInfo);

        // Determine where the first item is in relation to previously realized items
        var generatorStartPosition = _itemsGenerator.GeneratorPositionFromIndex(layoutInfo.FirstRealizedItemIndex);

        var visualIndex = 0;

        Double currentX = layoutInfo.FirstRealizedItemLeft;
        Double currentY = layoutInfo.FirstRealizedLineTop;

        using ( _itemsGenerator.StartAt(generatorStartPosition, GeneratorDirection.Forward, true) )
        {
            for ( var itemIndex = layoutInfo.FirstRealizedItemIndex ; itemIndex <= layoutInfo.LastRealizedItemIndex ; itemIndex++, visualIndex++ )
            {
                bool newlyRealized;

                var child = (UIElement)_itemsGenerator.GenerateNext(out newlyRealized);
                SetVirtualItemIndex(child, itemIndex);

                if ( newlyRealized )
                {
                    InsertInternalChild(visualIndex, child);
                }
                else
                {
                    // check if item needs to be moved into a new position in the Children collection
                    if ( visualIndex < Children.Count )
                    {
                        if ( Children[visualIndex] != child )
                        {
                            var childCurrentIndex = Children.IndexOf(child);

                            if ( childCurrentIndex >= 0 )
                            {
                                RemoveInternalChildRange(childCurrentIndex, 1);
                            }

                            InsertInternalChild(visualIndex, child);
                        }
                    }
                    else
                    {
                        // we know that the child can't already be in the children collection
                        // because we've been inserting children in correct visualIndex order,
                        // and this child has a visualIndex greater than the Children.Count
                        AddInternalChild(child);
                    }
                }

                // only prepare the item once it has been added to the visual tree
                _itemsGenerator.PrepareItemContainer(child);

                child.Measure(new Size(ItemWidth, ItemHeight));

                _childLayouts.Add(child, new Rect(currentX, currentY, ItemWidth, ItemHeight));

                if ( Orientation == Orientation.Vertical )
                {
                    if ( currentX + ItemWidth * 2 > availableSize.Width )
                    {
                        // wrap to a new line
                        currentY += ItemHeight;
                        currentX = 0;
                    }
                    else
                    {
                        currentX += ItemWidth;
                    }
                }
                else
                {
                    if ( currentY + ItemHeight * 2 > availableSize.Height )
                    {
                        // wrap to a new column
                        currentX += ItemWidth;
                        currentY = 0;
                    }
                    else
                    {
                        currentY += ItemHeight;
                    }
                }
            }
        }

        RemoveRedundantChildren();
        UpdateScrollInfo(availableSize, extentInfo);

        var desiredSize = new Size(double.IsInfinity(availableSize.Width) ? 0 : availableSize.Width,
                                   double.IsInfinity(availableSize.Height) ? 0 : availableSize.Height);

        _isInMeasure = false;

        return desiredSize;
    }

    private void EnsureScrollOffsetIsWithinConstrains(ExtentInfo extentInfo)
    {
        if ( Orientation == Orientation.Vertical )
        {
            _offset.Y = Clamp(_offset.Y, 0, extentInfo.MaxVerticalOffset);
        }
        else
        {
            _offset.X = Clamp(_offset.X, 0, extentInfo.MaxHorizontalOffset);
        }
    }

    private void RecycleItems(ItemLayoutInfo layoutInfo)
    {
        foreach ( UIElement child in Children )
        {
            var virtualItemIndex = GetVirtualItemIndex(child);

            if ( virtualItemIndex < layoutInfo.FirstRealizedItemIndex || virtualItemIndex > layoutInfo.LastRealizedItemIndex )
            {
                var generatorPosition = _itemsGenerator.GeneratorPositionFromIndex(virtualItemIndex);
                if ( generatorPosition.Index >= 0 )
                {
                    _itemsGenerator.Recycle(generatorPosition, 1);
                }
            }

            SetVirtualItemIndex(child, -1);
        }
    }

    protected override Size ArrangeOverride(Size finalSize)
    {
        foreach ( UIElement child in Children )
        {
            child.Arrange(_childLayouts[child]);
        }

        return finalSize;
    }

    private void UpdateScrollInfo(Size availableSize, ExtentInfo extentInfo)
    {
        _viewportSize = availableSize;
        if ( Orientation == Orientation.Vertical )
        {
            _extentSize = new Size(availableSize.Width, extentInfo.ExtentHeight);
        }
        else
        {
            _extentSize = new Size(extentInfo.ExtentWidth, availableSize.Height);
        }

        InvalidateScrollInfo();
    }

    private void RemoveRedundantChildren()
    {
        // iterate backwards through the child collection because we're going to be
        // removing items from it
        for ( var i = Children.Count - 1 ; i >= 0 ; i-- )
        {
            var child = Children[i];

            // if the virtual item index is -1, this indicates
            // it is a recycled item that hasn't been reused this time round
            if ( GetVirtualItemIndex(child) == -1 )
            {
                RemoveInternalChildRange(i, 1);
            }
        }
    }

    private ItemLayoutInfo GetLayoutInfo(Size availableSize, double itemHeightOrWidth, ExtentInfo extentInfo)
    {
        if ( _itemsControl == null )
        {
            return new ItemLayoutInfo();
        }

        // we need to ensure that there is one realized item prior to the first visible item, and one after the last visible item,
        // so that keyboard navigation works properly. For example, when focus is on the first visible item, and the user
        // navigates up, the ListBox selects the previous item, and the scrolls that into view - and this triggers the loading of the rest of the items
        // in that row

        if ( Orientation == Orientation.Vertical )
        {
            var firstVisibleLine = (int)Math.Floor(VerticalOffset / itemHeightOrWidth);

            var firstRealizedIndex = Math.Max(extentInfo.ItemsPerLine * firstVisibleLine - 1, 0);
            var firstRealizedItemLeft = firstRealizedIndex % extentInfo.ItemsPerLine * ItemWidth - HorizontalOffset;
            var firstRealizedItemTop = (firstRealizedIndex / extentInfo.ItemsPerLine) * itemHeightOrWidth - VerticalOffset;

            var firstCompleteLineTop = (firstVisibleLine == 0 ? firstRealizedItemTop : firstRealizedItemTop + ItemHeight);
            var completeRealizedLines = (int)Math.Ceiling((availableSize.Height - firstCompleteLineTop) / itemHeightOrWidth);

            var lastRealizedIndex = Math.Min(firstRealizedIndex + completeRealizedLines * extentInfo.ItemsPerLine + 2, _itemsControl.Items.Count - 1);

            return new ItemLayoutInfo
            {
                FirstRealizedItemIndex = firstRealizedIndex,
                FirstRealizedItemLeft = firstRealizedItemLeft,
                FirstRealizedLineTop = firstRealizedItemTop,
                LastRealizedItemIndex = lastRealizedIndex,
            };
        }
        else
        {
            var firstVisibleColumn = (int)Math.Floor(HorizontalOffset / itemHeightOrWidth);

            var firstRealizedIndex = Math.Max(extentInfo.ItemsPerColumn * firstVisibleColumn - 1, 0);
            var firstRealizedItemTop = firstRealizedIndex % extentInfo.ItemsPerColumn * ItemHeight - VerticalOffset;
            var firstRealizedItemLeft = (firstRealizedIndex / extentInfo.ItemsPerColumn) * itemHeightOrWidth - HorizontalOffset;

            var firstCompleteColumnLeft = (firstVisibleColumn == 0 ? firstRealizedItemLeft : firstRealizedItemLeft + ItemWidth);
            var completeRealizedColumns = (int)Math.Ceiling((availableSize.Width - firstCompleteColumnLeft) / itemHeightOrWidth);

            var lastRealizedIndex = Math.Min(firstRealizedIndex + completeRealizedColumns * extentInfo.ItemsPerColumn + 2, _itemsControl.Items.Count - 1);

            return new ItemLayoutInfo
            {
                FirstRealizedItemIndex = firstRealizedIndex,
                FirstRealizedItemLeft = firstRealizedItemLeft,
                FirstRealizedLineTop = firstRealizedItemTop,
                LastRealizedItemIndex = lastRealizedIndex,
            };
        }
    }

    private ExtentInfo GetExtentInfo(Size viewPortSize)
    {
        if ( _itemsControl == null )
        {
            return new ExtentInfo();
        }

        if ( Orientation == Orientation.Vertical )
        {
            var itemsPerLine = Math.Max((int)Math.Floor(viewPortSize.Width / ItemWidth), 1);
            var totalLines = (int)Math.Ceiling((double)_itemsControl.Items.Count / itemsPerLine);
            var extentHeight = Math.Max(totalLines * ItemHeight, viewPortSize.Height);

            return new ExtentInfo
            {
                ItemsPerLine = itemsPerLine,
                TotalLines = totalLines,
                ExtentHeight = extentHeight,
                MaxVerticalOffset = extentHeight - viewPortSize.Height,
            };
        }
        else
        {
            var itemsPerColumn = Math.Max((int)Math.Floor(viewPortSize.Height / ItemHeight), 1);
            var totalColumns = (int)Math.Ceiling((double)_itemsControl.Items.Count / itemsPerColumn);
            var extentWidth = Math.Max(totalColumns * ItemWidth, viewPortSize.Width);

            return new ExtentInfo
            {
                ItemsPerColumn = itemsPerColumn,
                TotalColumns = totalColumns,
                ExtentWidth = extentWidth,
                MaxHorizontalOffset = extentWidth - viewPortSize.Width
            };
        }
    }

    public void LineUp()
    {
        SetVerticalOffset(VerticalOffset - ScrollLineAmount);
    }

    public void LineDown()
    {
        SetVerticalOffset(VerticalOffset + ScrollLineAmount);
    }

    public void LineLeft()
    {
        SetHorizontalOffset(HorizontalOffset - ScrollLineAmount);
    }

    public void LineRight()
    {
        SetHorizontalOffset(HorizontalOffset + ScrollLineAmount);
    }

    public void PageUp()
    {
        SetVerticalOffset(VerticalOffset - ViewportHeight);
    }

    public void PageDown()
    {
        SetVerticalOffset(VerticalOffset + ViewportHeight);
    }

    public void PageLeft()
    {
        SetHorizontalOffset(HorizontalOffset - ItemWidth);
    }

    public void PageRight()
    {
        SetHorizontalOffset(HorizontalOffset + ItemWidth);
    }

    public void MouseWheelUp()
    {
        if ( Orientation == Orientation.Vertical )
        {
            SetVerticalOffset(VerticalOffset - ScrollLineAmount * SystemParameters.WheelScrollLines);
        }
        else
        {
            MouseWheelLeft();
        }
    }

    public void MouseWheelDown()
    {
        if ( Orientation == Orientation.Vertical )
        {
            SetVerticalOffset(VerticalOffset + ScrollLineAmount * SystemParameters.WheelScrollLines);
        }
        else
        {
            MouseWheelRight();
        }
    }

    public void MouseWheelLeft()
    {
        SetHorizontalOffset(HorizontalOffset - ScrollLineAmount * SystemParameters.WheelScrollLines);
    }

    public void MouseWheelRight()
    {
        SetHorizontalOffset(HorizontalOffset + ScrollLineAmount * SystemParameters.WheelScrollLines);
    }

    public void SetHorizontalOffset(double offset)
    {
        if ( _isInMeasure )
        {
            return;
        }

        offset = Clamp(offset, 0, ExtentWidth - ViewportWidth);
        _offset = new Point(offset, _offset.Y);

        InvalidateScrollInfo();
        InvalidateMeasure();
    }

    public void SetVerticalOffset(double offset)
    {
        if ( _isInMeasure )
        {
            return;
        }

        offset = Clamp(offset, 0, ExtentHeight - ViewportHeight);
        _offset = new Point(_offset.X, offset);

        InvalidateScrollInfo();
        InvalidateMeasure();
    }

    public Rect MakeVisible(Visual visual, Rect rectangle)
    {
        if ( rectangle.IsEmpty ||
            visual == null ||
            visual == this ||
            !IsAncestorOf(visual) )
        {
            return Rect.Empty;
        }

        rectangle = visual.TransformToAncestor(this).TransformBounds(rectangle);

        var viewRect = new Rect(HorizontalOffset, VerticalOffset, ViewportWidth, ViewportHeight);
        rectangle.X += viewRect.X;
        rectangle.Y += viewRect.Y;

        viewRect.X = CalculateNewScrollOffset(viewRect.Left, viewRect.Right, rectangle.Left, rectangle.Right);
        viewRect.Y = CalculateNewScrollOffset(viewRect.Top, viewRect.Bottom, rectangle.Top, rectangle.Bottom);

        SetHorizontalOffset(viewRect.X);
        SetVerticalOffset(viewRect.Y);
        rectangle.Intersect(viewRect);

        rectangle.X -= viewRect.X;
        rectangle.Y -= viewRect.Y;

        return rectangle;
    }

    private static double CalculateNewScrollOffset(double topView, double bottomView, double topChild, double bottomChild)
    {
        var offBottom = topChild < topView && bottomChild < bottomView;
        var offTop = bottomChild > bottomView && topChild > topView;
        var tooLarge = (bottomChild - topChild) > (bottomView - topView);

        if ( !offBottom && !offTop )
            return topView;

        if ( (offBottom && !tooLarge) || (offTop && tooLarge) )
            return topChild;

        return bottomChild - (bottomView - topView);
    }

    public ItemLayoutInfo GetVisibleItemsRange()
    {
        return GetLayoutInfo(_viewportSize, (Orientation == Orientation.Vertical) ? ItemHeight : ItemWidth, GetExtentInfo(_viewportSize));
    }

    protected override void BringIndexIntoView(int index)
    {
        if ( Orientation == Orientation.Vertical )
        {
            var currentVisibleMin = _offset.Y;
            var currentVisibleMax = _offset.Y + _viewportSize.Height - ItemHeight;
            var itemsPerLine = Math.Max((int)Math.Floor(_viewportSize.Width / ItemWidth), 1);

            var verticalOffsetRequiredToPutItemAtTopRow = Math.Floor((double)index / itemsPerLine) * ItemHeight;

            if ( verticalOffsetRequiredToPutItemAtTopRow < currentVisibleMin ) // if item is above visible area put it on the top row
                SetVerticalOffset(verticalOffsetRequiredToPutItemAtTopRow);
            else if ( verticalOffsetRequiredToPutItemAtTopRow > currentVisibleMax ) // if item is below visible area move to put it on bottom row
                SetVerticalOffset(verticalOffsetRequiredToPutItemAtTopRow - _viewportSize.Height + ItemHeight);
        }
        else
        {
            var currentVisibleMin = _offset.X;
            var currentVisibleMax = _offset.X + _viewportSize.Width - ItemWidth;
            var itemsPerColumn = Math.Max((int)Math.Floor(_viewportSize.Height / ItemHeight), 1);

            var horizontalOffsetRequiredToPutItemAtLeftRow = Math.Floor((double)index / itemsPerColumn) * ItemWidth;

            if ( horizontalOffsetRequiredToPutItemAtLeftRow < currentVisibleMin ) // if item is left from the visible area put it at the left most column
                SetHorizontalOffset(horizontalOffsetRequiredToPutItemAtLeftRow);
            else if ( horizontalOffsetRequiredToPutItemAtLeftRow > currentVisibleMax ) // if item is right from the visible area put it at the right most column
                SetHorizontalOffset(horizontalOffsetRequiredToPutItemAtLeftRow - _viewportSize.Width + ItemWidth);
        }
    }

    public bool CanVerticallyScroll
    {
        get;
        set;
    }

    public bool CanHorizontallyScroll
    {
        get;
        set;
    }

    public double ExtentWidth
    {
        get
        {
            return _extentSize.Width;
        }
    }

    public double ExtentHeight
    {
        get
        {
            return _extentSize.Height;
        }
    }

    public double ViewportWidth
    {
        get
        {
            return _viewportSize.Width;
        }
    }

    public double ViewportHeight
    {
        get
        {
            return _viewportSize.Height;
        }
    }

    public double HorizontalOffset
    {
        get
        {
            return _offset.X;
        }
    }

    public double VerticalOffset
    {
        get
        {
            return _offset.Y;
        }
    }

    public ScrollViewer ScrollOwner
    {
        get;
        set;
    }

    private void InvalidateScrollInfo()
    {
        if ( ScrollOwner != null )
        {
            ScrollOwner.InvalidateScrollInfo();
        }
    }

    private static void HandleItemDimensionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var wrapPanel = (d as VirtualizingWrapPanel);

        if ( wrapPanel != null )
            wrapPanel.InvalidateMeasure();
    }

    private double Clamp(double value, double min, double max)
    {
        return Math.Min(Math.Max(value, min), max);
    }

    internal class ExtentInfo
    {
        public int ItemsPerLine;
        public Int32 ItemsPerColumn;
        public int TotalLines;
        public Int32 TotalColumns;
        public double ExtentHeight;
        public double ExtentWidth;
        public double MaxVerticalOffset;
        public double MaxHorizontalOffset;
    }

    public class ItemLayoutInfo
    {
        public int FirstRealizedItemIndex;
        public double FirstRealizedLineTop;
        public double FirstRealizedItemLeft;
        public int LastRealizedItemIndex;
    }
}
}

答案 3 :(得分:0)

我正在搜索一周的解决方案,而且我知道如何伪造VirtualizingWrapPanel。

如果您知道Items的宽度,则可以创建“行”作为StackPanel,并在VirtualizingStackPanel中呈现这些行。

XAML

<ItemsControl x:Name="Thumbs" VirtualizingStackPanel.IsVirtualizing="True" 
              VirtualizingPanel.ScrollUnit="Pixel" ScrollViewer.CanContentScroll="True">
  <ItemsControl.ItemsPanel>
    <ItemsPanelTemplate>
      <VirtualizingStackPanel/>
    </ItemsPanelTemplate>
  </ItemsControl.ItemsPanel>
  <ItemsControl.ItemTemplate>
    <DataTemplate>
      <ItemsControl ItemsSource="{Binding}">
        <ItemsControl.ItemsPanel>
          <ItemsPanelTemplate>
            <StackPanel Orientation="Horizontal"/>
          </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
        <ItemsControl.ItemTemplate>
          <DataTemplate>
            <Border Width="{Binding ThumbWidth}" Height="{Binding ThumbHeight}" 
                    BorderThickness="2" BorderBrush="Black" ClipToBounds="True">
              <Image Source="{Binding FilePathCacheUri}" Stretch="Fill" />
            </Border>
          </DataTemplate>
        </ItemsControl.ItemTemplate>
      </ItemsControl>
    </DataTemplate>
  </ItemsControl.ItemTemplate>
  <ItemsControl.Template>
    <ControlTemplate>
      <ScrollViewer>
        <ItemsPresenter />
      </ScrollViewer>
    </ControlTemplate>
  </ItemsControl.Template>
</ItemsControl>

C#

public List<BaseMediaItem> MediaItems { get; set; }
public List<List<BaseMediaItem>> SplitedMediaItems { get; set; }

public MainWindow() {
  InitializeComponent();

  DataContext = this;
  MediaItems = new List<BaseMediaItem>();
  SplitedMediaItems = new List<List<BaseMediaItem>>();
}

private void MainWindow_OnLoaded(object sender, RoutedEventArgs e) {
  LoadMediaItems();
  SplitMediaItems();
  Thumbs.ItemsSource = SplitedMediaItems;
}

public void LoadMediaItems() {
  var sizes = new List<Point> {new Point(320, 180), new Point(320, 240), new Point(180, 320), new Point(240, 320)};
  MediaItems.Clear();
  var random = new Random();

  for (var i = 0; i < 5000; i++) {
    var size = sizes[random.Next(sizes.Count)];
    MediaItems.Add(new BaseMediaItem($"Item {i}") {
      ThumbWidth = (int)size.X,
      ThumbHeight = (int)size.Y
    });
  }
}

public void SplitMediaItems() {
  foreach (var itemsGroup in SplitedMediaItems) {
    itemsGroup.Clear();
  }
  SplitedMediaItems.Clear();

  var groupMaxWidth = Thumbs.ActualWidth;
  var groupWidth = 0;
  const int itemOffset = 6; //border, margin, padding, ...
  var row = new List<BaseMediaItem>();
  foreach (var item in MediaItems) {
    if (item.ThumbWidth + itemOffset <= groupMaxWidth - groupWidth) {
      row.Add(item);
      groupWidth += item.ThumbWidth + itemOffset;
    }
    else {
      SplitedMediaItems.Add(row);
      row = new List<BaseMediaItem>();
      row.Add(item);
      groupWidth = item.ThumbWidth + itemOffset;
    }
  }
  SplitedMediaItems.Add(row);
}