• WPF 虚拟化 VirtualizingWrapPanel 和 VirtualLizingTilePanel


    一、 UI  上两个扩展

        public class VirtualizingWrapPanel : VirtualizingPanel, IScrollInfo
        {
            #region Fields
    
            UIElementCollection _children;
            ItemsControl _itemsControl;
            IItemContainerGenerator _generator;
            private Point _offset = new Point(0, 0);
            private Size _extent = new Size(0, 0);
            private Size _viewport = new Size(0, 0);
            private int firstIndex = 0;
            private Size childSize;
            private Size _pixelMeasuredViewport = new Size(0, 0);
            Dictionary<UIElement, Rect> _realizedChildLayout = new Dictionary<UIElement, Rect>();
            WrapPanelAbstraction _abstractPanel;
    
    
            #endregion
    
            #region Properties
    
            private Size ChildSlotSize
            {
                get
                {
                    return new Size(ItemWidth, ItemHeight);
                }
            }
    
            #endregion
    
            #region Dependency Properties
    
            [TypeConverter(typeof(LengthConverter))]
            public double ItemHeight
            {
                get
                {
                    return (double)base.GetValue(ItemHeightProperty);
                }
                set
                {
                    base.SetValue(ItemHeightProperty, value);
                }
            }
    
            [TypeConverter(typeof(LengthConverter))]
            public double ItemWidth
            {
                get
                {
                    return (double)base.GetValue(ItemWidthProperty);
                }
                set
                {
                    base.SetValue(ItemWidthProperty, value);
                }
            }
    
            public Orientation Orientation
            {
                get { return (Orientation)GetValue(OrientationProperty); }
                set { SetValue(OrientationProperty, value); }
            }
    
            public static readonly DependencyProperty ItemHeightProperty = DependencyProperty.Register("ItemHeight", typeof(double), typeof(VirtualizingWrapPanel), new FrameworkPropertyMetadata(double.PositiveInfinity));
            public static readonly DependencyProperty ItemWidthProperty = DependencyProperty.Register("ItemWidth", typeof(double), typeof(VirtualizingWrapPanel), new FrameworkPropertyMetadata(double.PositiveInfinity));
            public static readonly DependencyProperty OrientationProperty = StackPanel.OrientationProperty.AddOwner(typeof(VirtualizingWrapPanel), new FrameworkPropertyMetadata(Orientation.Horizontal));
    
            #endregion
    
            #region Methods
    
            public void SetFirstRowViewItemIndex(int index)
            {
                SetVerticalOffset((index) / Math.Floor((_viewport.Width) / childSize.Width));
                SetHorizontalOffset((index) / Math.Floor((_viewport.Height) / childSize.Height));
            }
    
            private void Resizing(object sender, EventArgs e)
            {
                if (_viewport.Width != 0)
                {
                    int firstIndexCache = firstIndex;
                    _abstractPanel = null;
                    MeasureOverride(_viewport);
                    SetFirstRowViewItemIndex(firstIndex);
                    firstIndex = firstIndexCache;
                }
            }
    
            public int GetFirstVisibleSection()
            {
                int section;
                var maxSection = _abstractPanel.Max(x => x.Section);
                if (Orientation == Orientation.Horizontal)
                {
                    section = (int)_offset.Y;
                }
                else
                {
                    section = (int)_offset.X;
                }
                if (section > maxSection)
                    section = maxSection;
                return section;
            }
    
            public int GetFirstVisibleIndex()
            {
                int section = GetFirstVisibleSection();
                var item = _abstractPanel.Where(x => x.Section == section).FirstOrDefault();
                if (item != null)
                    return item._index;
                return 0;
            }
    
            private void CleanUpItems(int minDesiredGenerated, int maxDesiredGenerated)
            {
                for (int i = _children.Count - 1; i >= 0; i--)
                {
                    GeneratorPosition childGeneratorPos = new GeneratorPosition(i, 0);
                    int itemIndex = _generator.IndexFromGeneratorPosition(childGeneratorPos);
                    if (itemIndex < minDesiredGenerated || itemIndex > maxDesiredGenerated)
                    {
                        _generator.Remove(childGeneratorPos, 1);
                        RemoveInternalChildRange(i, 1);
                    }
                }
            }
    
            private void ComputeExtentAndViewport(Size pixelMeasuredViewportSize, int visibleSections)
            {
                if (Orientation == Orientation.Horizontal)
                {
                    _viewport.Height = visibleSections;
                    _viewport.Width = pixelMeasuredViewportSize.Width;
                }
                else
                {
                    _viewport.Width = visibleSections;
                    _viewport.Height = pixelMeasuredViewportSize.Height;
                }
    
                if (Orientation == Orientation.Horizontal)
                {
                    _extent.Height = _abstractPanel.SectionCount + ViewportHeight - 1;
    
                }
                else
                {
                    _extent.Width = _abstractPanel.SectionCount + ViewportWidth - 1;
                }
                _owner.InvalidateScrollInfo();
            }
    
            private void ResetScrollInfo()
            {
                _offset.X = 0;
                _offset.Y = 0;
            }
    
            private int GetNextSectionClosestIndex(int itemIndex)
            {
                var abstractItem = _abstractPanel[itemIndex];
                if (abstractItem.Section < _abstractPanel.SectionCount - 1)
                {
                    var ret = _abstractPanel.
                        Where(x => x.Section == abstractItem.Section + 1).
                        OrderBy(x => Math.Abs(x.SectionIndex - abstractItem.SectionIndex)).
                        First();
                    return ret._index;
                }
                else
                    return itemIndex;
            }
    
            private int GetLastSectionClosestIndex(int itemIndex)
            {
                var abstractItem = _abstractPanel[itemIndex];
                if (abstractItem.Section > 0)
                {
                    var ret = _abstractPanel.
                        Where(x => x.Section == abstractItem.Section - 1).
                        OrderBy(x => Math.Abs(x.SectionIndex - abstractItem.SectionIndex)).
                        First();
                    return ret._index;
                }
                else
                    return itemIndex;
            }
    
            private void NavigateDown()
            {
                var gen = _generator.GetItemContainerGeneratorForPanel(this);
                UIElement selected = (UIElement)Keyboard.FocusedElement;
                int itemIndex = gen.IndexFromContainer(selected);
                int depth = 0;
                while (itemIndex == -1)
                {
                    selected = (UIElement)VisualTreeHelper.GetParent(selected);
                    itemIndex = gen.IndexFromContainer(selected);
                    depth++;
                }
                DependencyObject next = null;
                if (Orientation == Orientation.Horizontal)
                {
                    int nextIndex = GetNextSectionClosestIndex(itemIndex);
                    next = gen.ContainerFromIndex(nextIndex);
                    while (next == null)
                    {
                        SetVerticalOffset(VerticalOffset + 1);
                        UpdateLayout();
                        next = gen.ContainerFromIndex(nextIndex);
                    }
                }
                else
                {
                    if (itemIndex == _abstractPanel._itemCount - 1)
                        return;
                    next = gen.ContainerFromIndex(itemIndex + 1);
                    while (next == null)
                    {
                        SetHorizontalOffset(HorizontalOffset + 1);
                        UpdateLayout();
                        next = gen.ContainerFromIndex(itemIndex + 1);
                    }
                }
                while (depth != 0)
                {
                    next = VisualTreeHelper.GetChild(next, 0);
                    depth--;
                }
                (next as UIElement).Focus();
            }
    
            private void NavigateLeft()
            {
                var gen = _generator.GetItemContainerGeneratorForPanel(this);
    
                UIElement selected = (UIElement)Keyboard.FocusedElement;
                int itemIndex = gen.IndexFromContainer(selected);
                int depth = 0;
                while (itemIndex == -1)
                {
                    selected = (UIElement)VisualTreeHelper.GetParent(selected);
                    itemIndex = gen.IndexFromContainer(selected);
                    depth++;
                }
                DependencyObject next = null;
                if (Orientation == Orientation.Vertical)
                {
                    int nextIndex = GetLastSectionClosestIndex(itemIndex);
                    next = gen.ContainerFromIndex(nextIndex);
                    while (next == null)
                    {
                        SetHorizontalOffset(HorizontalOffset - 1);
                        UpdateLayout();
                        next = gen.ContainerFromIndex(nextIndex);
                    }
                }
                else
                {
                    if (itemIndex == 0)
                        return;
                    next = gen.ContainerFromIndex(itemIndex - 1);
                    while (next == null)
                    {
                        SetVerticalOffset(VerticalOffset - 1);
                        UpdateLayout();
                        next = gen.ContainerFromIndex(itemIndex - 1);
                    }
                }
                while (depth != 0)
                {
                    next = VisualTreeHelper.GetChild(next, 0);
                    depth--;
                }
                (next as UIElement).Focus();
            }
    
            private void NavigateRight()
            {
                var gen = _generator.GetItemContainerGeneratorForPanel(this);
                UIElement selected = (UIElement)Keyboard.FocusedElement;
                int itemIndex = gen.IndexFromContainer(selected);
                int depth = 0;
                while (itemIndex == -1)
                {
                    selected = (UIElement)VisualTreeHelper.GetParent(selected);
                    itemIndex = gen.IndexFromContainer(selected);
                    depth++;
                }
                DependencyObject next = null;
                if (Orientation == Orientation.Vertical)
                {
                    int nextIndex = GetNextSectionClosestIndex(itemIndex);
                    next = gen.ContainerFromIndex(nextIndex);
                    while (next == null)
                    {
                        SetHorizontalOffset(HorizontalOffset + 1);
                        UpdateLayout();
                        next = gen.ContainerFromIndex(nextIndex);
                    }
                }
                else
                {
                    if (itemIndex == _abstractPanel._itemCount - 1)
                        return;
                    next = gen.ContainerFromIndex(itemIndex + 1);
                    while (next == null)
                    {
                        SetVerticalOffset(VerticalOffset + 1);
                        UpdateLayout();
                        next = gen.ContainerFromIndex(itemIndex + 1);
                    }
                }
                while (depth != 0)
                {
                    next = VisualTreeHelper.GetChild(next, 0);
                    depth--;
                }
                (next as UIElement).Focus();
            }
    
            private void NavigateUp()
            {
                var gen = _generator.GetItemContainerGeneratorForPanel(this);
                UIElement selected = (UIElement)Keyboard.FocusedElement;
                int itemIndex = gen.IndexFromContainer(selected);
                int depth = 0;
                while (itemIndex == -1)
                {
                    selected = (UIElement)VisualTreeHelper.GetParent(selected);
                    itemIndex = gen.IndexFromContainer(selected);
                    depth++;
                }
                DependencyObject next = null;
                if (Orientation == Orientation.Horizontal)
                {
                    int nextIndex = GetLastSectionClosestIndex(itemIndex);
                    next = gen.ContainerFromIndex(nextIndex);
                    while (next == null)
                    {
                        SetVerticalOffset(VerticalOffset - 1);
                        UpdateLayout();
                        next = gen.ContainerFromIndex(nextIndex);
                    }
                }
                else
                {
                    if (itemIndex == 0)
                        return;
                    next = gen.ContainerFromIndex(itemIndex - 1);
                    while (next == null)
                    {
                        SetHorizontalOffset(HorizontalOffset - 1);
                        UpdateLayout();
                        next = gen.ContainerFromIndex(itemIndex - 1);
                    }
                }
                while (depth != 0)
                {
                    next = VisualTreeHelper.GetChild(next, 0);
                    depth--;
                }
                (next as UIElement).Focus();
            }
    
    
            #endregion
    
            #region Override
    
            protected override void OnKeyDown(KeyEventArgs e)
            {
                switch (e.Key)
                {
                    case Key.Down:
                        NavigateDown();
                        e.Handled = true;
                        break;
                    case Key.Left:
                        NavigateLeft();
                        e.Handled = true;
                        break;
                    case Key.Right:
                        NavigateRight();
                        e.Handled = true;
                        break;
                    case Key.Up:
                        NavigateUp();
                        e.Handled = true;
                        break;
                    default:
                        base.OnKeyDown(e);
                        break;
                }
            }
    
    
            protected override void OnItemsChanged(object sender, ItemsChangedEventArgs args)
            {
                base.OnItemsChanged(sender, args);
                _abstractPanel = null;
                ResetScrollInfo();
            }
    
            protected override void OnInitialized(EventArgs e)
            {
                this.SizeChanged += new SizeChangedEventHandler(this.Resizing);
                base.OnInitialized(e);
                _itemsControl = ItemsControl.GetItemsOwner(this);
                _children = InternalChildren;
                _generator = ItemContainerGenerator;
            }
    
            protected override Size MeasureOverride(Size availableSize)
            {
                if (_itemsControl == null || _itemsControl.Items.Count == 0)
                    return availableSize;
                if (_abstractPanel == null)
                    _abstractPanel = new WrapPanelAbstraction(_itemsControl.Items.Count);
    
                _pixelMeasuredViewport = availableSize;
    
                _realizedChildLayout.Clear();
    
                Size realizedFrameSize = availableSize;
    
                int itemCount = _itemsControl.Items.Count;
                int firstVisibleIndex = GetFirstVisibleIndex();
    
                GeneratorPosition startPos = _generator.GeneratorPositionFromIndex(firstVisibleIndex);
    
                int childIndex = (startPos.Offset == 0) ? startPos.Index : startPos.Index + 1;
                int current = firstVisibleIndex;
                int visibleSections = 1;
                using (_generator.StartAt(startPos, GeneratorDirection.Forward, true))
                {
                    bool stop = false;
                    bool isHorizontal = Orientation == Orientation.Horizontal;
                    double currentX = 0;
                    double currentY = 0;
                    double maxItemSize = 0;
                    int currentSection = GetFirstVisibleSection();
                    while (current < itemCount)
                    {
                        bool newlyRealized;
    
                        // Get or create the child                    
                        UIElement child = _generator.GenerateNext(out newlyRealized) as UIElement;
                        if (newlyRealized)
                        {
                            // Figure out if we need to insert the child at the end or somewhere in the middle
                            if (childIndex >= _children.Count)
                            {
                                base.AddInternalChild(child);
                            }
                            else
                            {
                                base.InsertInternalChild(childIndex, child);
                            }
                            _generator.PrepareItemContainer(child);
                            child.Measure(ChildSlotSize);
                        }
                        else
                        {
                            // The child has already been created, let's be sure it's in the right spot
                            Debug.Assert(child == _children[childIndex], "Wrong child was generated");
                        }
                        childSize = child.DesiredSize;
                        Rect childRect = new Rect(new Point(currentX, currentY), childSize);
                        if (isHorizontal)
                        {
                            maxItemSize = Math.Max(maxItemSize, childRect.Height);
                            if (childRect.Right > realizedFrameSize.Width) //wrap to a new line
                            {
                                currentY = currentY + maxItemSize;
                                currentX = 0;
                                maxItemSize = childRect.Height;
                                childRect.X = currentX;
                                childRect.Y = currentY;
                                currentSection++;
                                visibleSections++;
                            }
                            if (currentY > realizedFrameSize.Height)
                                stop = true;
                            currentX = childRect.Right;
                        }
                        else
                        {
                            maxItemSize = Math.Max(maxItemSize, childRect.Width);
                            if (childRect.Bottom > realizedFrameSize.Height) //wrap to a new column
                            {
                                currentX = currentX + maxItemSize;
                                currentY = 0;
                                maxItemSize = childRect.Width;
                                childRect.X = currentX;
                                childRect.Y = currentY;
                                currentSection++;
                                visibleSections++;
                            }
                            if (currentX > realizedFrameSize.Width)
                                stop = true;
                            currentY = childRect.Bottom;
                        }
                        _realizedChildLayout.Add(child, childRect);
                        _abstractPanel.SetItemSection(current, currentSection);
    
                        if (stop)
                            break;
                        current++;
                        childIndex++;
                    }
                }
                CleanUpItems(firstVisibleIndex, current - 1);
    
                ComputeExtentAndViewport(availableSize, visibleSections);
    
                return availableSize;
            }
            protected override Size ArrangeOverride(Size finalSize)
            {
                if (_children != null)
                {
                    foreach (UIElement child in _children)
                    {
                        var layoutInfo = _realizedChildLayout[child];
                        child.Arrange(layoutInfo);
                    }
                }
                return finalSize;
            }
    
            #endregion
    
            #region IScrollInfo Members
    
            private bool _canHScroll = false;
            public bool CanHorizontallyScroll
            {
                get { return _canHScroll; }
                set { _canHScroll = value; }
            }
    
            private bool _canVScroll = false;
            public bool CanVerticallyScroll
            {
                get { return _canVScroll; }
                set { _canVScroll = value; }
            }
    
            public double ExtentHeight
            {
                get { return _extent.Height; }
            }
    
            public double ExtentWidth
            {
                get { return _extent.Width; }
            }
    
            public double HorizontalOffset
            {
                get { return _offset.X; }
            }
    
            public double VerticalOffset
            {
                get { return _offset.Y; }
            }
    
            public void LineDown()
            {
                if (Orientation == Orientation.Vertical)
                    SetVerticalOffset(VerticalOffset + 20);
                else
                    SetVerticalOffset(VerticalOffset + 1);
            }
    
            public void LineLeft()
            {
                if (Orientation == Orientation.Horizontal)
                    SetHorizontalOffset(HorizontalOffset - 20);
                else
                    SetHorizontalOffset(HorizontalOffset - 1);
            }
    
            public void LineRight()
            {
                if (Orientation == Orientation.Horizontal)
                    SetHorizontalOffset(HorizontalOffset + 20);
                else
                    SetHorizontalOffset(HorizontalOffset + 1);
            }
    
            public void LineUp()
            {
                if (Orientation == Orientation.Vertical)
                    SetVerticalOffset(VerticalOffset - 20);
                else
                    SetVerticalOffset(VerticalOffset - 1);
            }
    
            public Rect MakeVisible(Visual visual, Rect rectangle)
            {
                var gen = (ItemContainerGenerator)_generator.GetItemContainerGeneratorForPanel(this);
                var element = (UIElement)visual;
                int itemIndex = gen.IndexFromContainer(element);
                while (itemIndex == -1)
                {
                    element = (UIElement)VisualTreeHelper.GetParent(element);
                    itemIndex = gen.IndexFromContainer(element);
                }
                int section = _abstractPanel[itemIndex].Section;
                Rect elementRect = _realizedChildLayout[element];
                if (Orientation == Orientation.Horizontal)
                {
                    double viewportHeight = _pixelMeasuredViewport.Height;
                    if (elementRect.Bottom > viewportHeight)
                        _offset.Y += 1;
                    else if (elementRect.Top < 0)
                        _offset.Y -= 1;
                }
                else
                {
                    double viewportWidth = _pixelMeasuredViewport.Width;
                    if (elementRect.Right > viewportWidth)
                        _offset.X += 1;
                    else if (elementRect.Left < 0)
                        _offset.X -= 1;
                }
                InvalidateMeasure();
                return elementRect;
            }
    
            public void MouseWheelDown()
            {
                PageDown();
            }
    
            public void MouseWheelLeft()
            {
                PageLeft();
            }
    
            public void MouseWheelRight()
            {
                PageRight();
            }
    
            public void MouseWheelUp()
            {
                PageUp();
            }
    
            public void PageDown()
            {
                SetVerticalOffset(VerticalOffset + _viewport.Height * 0.8);
            }
    
            public void PageLeft()
            {
                SetHorizontalOffset(HorizontalOffset - _viewport.Width * 0.8);
            }
    
            public void PageRight()
            {
                SetHorizontalOffset(HorizontalOffset + _viewport.Width * 0.8);
            }
    
            public void PageUp()
            {
                SetVerticalOffset(VerticalOffset - _viewport.Height * 0.8);
            }
    
            private ScrollViewer _owner;
            public ScrollViewer ScrollOwner
            {
                get { return _owner; }
                set { _owner = value; }
            }
    
            public void SetHorizontalOffset(double offset)
            {
                if (offset < 0 || _viewport.Width >= _extent.Width)
                {
                    offset = 0;
                }
                else
                {
                    if (offset + _viewport.Width >= _extent.Width)
                    {
                        offset = _extent.Width - _viewport.Width;
                    }
                }
    
                _offset.X = offset;
    
                if (_owner != null)
                    _owner.InvalidateScrollInfo();
    
                InvalidateMeasure();
                firstIndex = GetFirstVisibleIndex();
            }
    
            public void SetVerticalOffset(double offset)
            {
                if (offset < 0 || _viewport.Height >= _extent.Height)
                {
                    offset = 0;
                }
                else
                {
                    if (offset + _viewport.Height >= _extent.Height)
                    {
                        offset = _extent.Height - _viewport.Height;
                    }
                }
    
                _offset.Y = offset;
    
                if (_owner != null)
                    _owner.InvalidateScrollInfo();
    
                //_trans.Y = -offset;
    
                InvalidateMeasure();
                firstIndex = GetFirstVisibleIndex();
            }
    
            public double ViewportHeight
            {
                get { return _viewport.Height; }
            }
    
            public double ViewportWidth
            {
                get { return _viewport.Width; }
            }
    
            #endregion
    
            #region helper data structures
    
            class ItemAbstraction
            {
                public ItemAbstraction(WrapPanelAbstraction panel, int index)
                {
                    _panel = panel;
                    _index = index;
                }
    
                WrapPanelAbstraction _panel;
    
                public readonly int _index;
    
                int _sectionIndex = -1;
                public int SectionIndex
                {
                    get
                    {
                        if (_sectionIndex == -1)
                        {
                            return _index % _panel._averageItemsPerSection - 1;
                        }
                        return _sectionIndex;
                    }
                    set
                    {
                        if (_sectionIndex == -1)
                            _sectionIndex = value;
                    }
                }
    
                int _section = -1;
                public int Section
                {
                    get
                    {
                        if (_section == -1)
                        {
                            return _index / _panel._averageItemsPerSection;
                        }
                        return _section;
                    }
                    set
                    {
                        if (_section == -1)
                            _section = value;
                    }
                }
            }
    
            class WrapPanelAbstraction : IEnumerable<ItemAbstraction>
            {
                public WrapPanelAbstraction(int itemCount)
                {
                    List<ItemAbstraction> items = new List<ItemAbstraction>(itemCount);
                    for (int i = 0; i < itemCount; i++)
                    {
                        ItemAbstraction item = new ItemAbstraction(this, i);
                        items.Add(item);
                    }
    
                    Items = new ReadOnlyCollection<ItemAbstraction>(items);
                    _averageItemsPerSection = itemCount;
                    _itemCount = itemCount;
                }
    
                public readonly int _itemCount;
                public int _averageItemsPerSection;
                private int _currentSetSection = -1;
                private int _currentSetItemIndex = -1;
                private int _itemsInCurrentSecction = 0;
                private object _syncRoot = new object();
    
                public int SectionCount
                {
                    get
                    {
                        int ret = _currentSetSection + 1;
                        if (_currentSetItemIndex + 1 < Items.Count)
                        {
                            int itemsLeft = Items.Count - _currentSetItemIndex;
                            ret += itemsLeft / _averageItemsPerSection + 1;
                        }
                        return ret;
                    }
                }
    
                private ReadOnlyCollection<ItemAbstraction> Items { get; set; }
    
                public void SetItemSection(int index, int section)
                {
                    lock (_syncRoot)
                    {
                        if (section <= _currentSetSection + 1 && index == _currentSetItemIndex + 1)
                        {
                            _currentSetItemIndex++;
                            Items[index].Section = section;
                            if (section == _currentSetSection + 1)
                            {
                                _currentSetSection = section;
                                if (section > 0)
                                {
                                    _averageItemsPerSection = (index) / (section);
                                }
                                _itemsInCurrentSecction = 1;
                            }
                            else
                                _itemsInCurrentSecction++;
                            Items[index].SectionIndex = _itemsInCurrentSecction - 1;
                        }
                    }
                }
    
                public ItemAbstraction this[int index]
                {
                    get { return Items[index]; }
                }
    
                #region IEnumerable<ItemAbstraction> Members
    
                public IEnumerator<ItemAbstraction> GetEnumerator()
                {
                    return Items.GetEnumerator();
                }
    
                #endregion
    
                #region IEnumerable Members
    
                System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
                {
                    return GetEnumerator();
                }
    
                #endregion
            }
    
            #endregion
        }
    VirtualizingWrapPanel

    来源:http://www.codeproject.com/Articles/75847/Virtualizing-WrapPanel

        // 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 VirtualLizingTilePanel : 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(VirtualLizingTilePanel), new PropertyMetadata(1.0, HandleItemDimensionChanged));
    
            public static readonly DependencyProperty ItemHeightProperty =
                DependencyProperty.Register("ItemHeight", typeof(double), typeof(VirtualLizingTilePanel), new PropertyMetadata(1.0, HandleItemDimensionChanged));
    
            private static readonly DependencyProperty VirtualItemIndexProperty =
                DependencyProperty.RegisterAttached("VirtualItemIndex", typeof(int), typeof(VirtualLizingTilePanel), 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);
            }
    
            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 VirtualLizingTilePanel()
            {
                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, 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);
                                    }
    
                                    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 (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;
            }
    
            private void EnsureScrollOffsetIsWithinConstrains(ExtentInfo extentInfo)
            {
                _offset.Y = Clamp(_offset.Y, 0, extentInfo.MaxVerticalOffset);
            }
    
            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;
                _extentSize = new Size(availableSize.Width, extentInfo.ExtentHeight);
    
                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 itemHeight, 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
    
                var firstVisibleLine = (int)Math.Floor(VerticalOffset / itemHeight);
    
                var firstRealizedIndex = Math.Max(extentInfo.ItemsPerLine * firstVisibleLine - 1, 0);
                var firstRealizedItemLeft = firstRealizedIndex % extentInfo.ItemsPerLine * ItemWidth - HorizontalOffset;
                var firstRealizedItemTop = (firstRealizedIndex / extentInfo.ItemsPerLine) * itemHeight - VerticalOffset;
    
                var firstCompleteLineTop = (firstVisibleLine == 0 ? firstRealizedItemTop : firstRealizedItemTop + ItemHeight);
                var completeRealizedLines = (int)Math.Ceiling((availableSize.Height - firstCompleteLineTop) / itemHeight);
    
                var lastRealizedIndex = Math.Min(firstRealizedIndex + completeRealizedLines * extentInfo.ItemsPerLine + 2, _itemsControl.Items.Count - 1);
    
                return new ItemLayoutInfo
                {
                    FirstRealizedItemIndex = firstRealizedIndex,
                    FirstRealizedItemLeft = firstRealizedItemLeft,
                    FirstRealizedLineTop = firstRealizedItemTop,
                    LastRealizedItemIndex = lastRealizedIndex,
                };
            }
    
            private ExtentInfo GetExtentInfo(Size viewPortSize, double itemHeight)
            {
                if (_itemsControl == null)
                {
                    return new ExtentInfo();
                }
    
                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,
                };
            }
    
            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()
            {
                SetVerticalOffset(VerticalOffset - ScrollLineAmount * SystemParameters.WheelScrollLines);
            }
    
            public void MouseWheelDown()
            {
                SetVerticalOffset(VerticalOffset + ScrollLineAmount * SystemParameters.WheelScrollLines);
            }
    
            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, ItemHeight, GetExtentInfo(_viewportSize, ItemHeight));
            }
    
            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 VirtualLizingTilePanel);
    
                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 int TotalLines;
                public double ExtentHeight;
                public double MaxVerticalOffset;
            }
    
            public class ItemLayoutInfo
            {
                public int FirstRealizedItemIndex;
                public double FirstRealizedLineTop;
                public double FirstRealizedItemLeft;
                public int LastRealizedItemIndex;
            }
        }
    VirtualLizingTilePanel

    来源:

    // 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

    二、数据方面的一个处理

        /// <summary>
        /// 为ListBox支持数据虚拟化技术
        /// </summary>
        public class VirtualDataForListBox<T> : IDisposable, INotifyPropertyChanged where T : class
        {
            public event PropertyChangedEventHandler PropertyChanged;
    
            private DelayHelper delay;
    
            private ListBox listBox;
            /// <summary>
            /// 垂直滚动条
            /// </summary>
            private ScrollBar bar;
            /// <summary>
            /// 滚动视图
            /// </summary>
            private ScrollViewer viewer;
            /// <summary>
            /// 数据源
            /// </summary>
            private ObservableCollection<T> sources;
    
            /// <summary>
            /// 是否已初始化完毕
            /// </summary>
            protected bool Inited { get; set; }
    
            /// <summary>
            /// 偏移量
            /// </summary>
            protected double Offset { get; set; }
    
            /// <summary>
            /// 偏移数量
            /// </summary>
            protected int OffsetCount { get; set; }
    
            /// <summary>
            /// 偏移方向
            /// <para>True:向上</para>
            /// <para>False:向下</para>
            /// </summary>
            protected bool OffsetDirection { get; set; }
    
            public Func<bool> CheckCanScrollToBottom;
    
    
            #region 数据绑定
    
            private ObservableCollection<T> virtualData;
    
            /// <summary>
            /// 虚拟数据
            /// </summary>
            public ObservableCollection<T> VirtualData
            {
                get { return virtualData; }
                protected set
                {
                    virtualData = value;
                    if (this.PropertyChanged != null)
                    {
                        this.PropertyChanged(this, new PropertyChangedEventArgs(nameof(VirtualData)));
                    }
                }
            }
    
            #endregion
    
    
            #region 配置参数
    
            /// <summary>
            /// 初始化时最多加载的数据量
            /// <para>需要保证:如果数据未完全加载,ListBox一定可以出现滚动条</para>
            /// </summary>
            [DefaultValue(20)]
            public int InitLoadCount { get; set; }
    
            /// <summary>
            /// 递增的数量值
            /// <para>滚动条滚动到两端时,每次自动加载的数据量</para>
            /// <para>子项数量超过容器的最大数量<paramref name="MaxCount"/>时,自动减少的数量</para>
            /// </summary>
            [DefaultValue(20)]
            public int IncreasingCount { get; set; }
    
            /// <summary>
            /// 子项的最大数量
            /// </summary>
            [DefaultValue(60)]
            public int MaxCount { get; set; }
    
            #endregion
    
    
    
            /// <summary>
            /// 当前显示的虚拟数据起始索引
            /// </summary>
            protected int StartVirtualIndex { get; set; }
    
            /// <summary>
            /// 当前显示的虚拟数据的终止索引
            /// </summary>
            protected int EndVirtualIndex { get; set; }
    
            /// <summary>
            /// 忽略滚动条滚动事件
            /// </summary>
            protected bool IgnoreBarChanged { get; set; }
    
    
    
            public VirtualDataForListBox(ListBox listBox, ObservableCollection<T> sources)
            {
                if (listBox == null || sources == null)
                    throw new ArgumentException(" listBox or sources is null ");
    
                this.delay = new DelayHelper(25, DelayLayout);
    
                this.Inited = false;
                this.Offset = 0;
    
                this.listBox = listBox;
                this.sources = sources;
    
                this.InitLoadCount = 20;
                this.IncreasingCount = 20;
                this.MaxCount = 60;
    
                this.EndVirtualIndex = -1;
                this.StartVirtualIndex = -1;
    
                this.VirtualData = new ObservableCollection<T>();
            }
    
            /// <summary>
            /// 初始化
            /// </summary>
            public void Init()
            {
                if (this.Inited)
                    return;
    
                if (this.listBox == null)
                {
                    LogHelper.Warning("数据虚拟化-初始化失败");
                    return;
                }
    
                // 监控滚动条
                this.bar = this.listBox.GetFirstChildT<ScrollBar, ListBoxItem>(t => t.Orientation == Orientation.Vertical);
                this.viewer = this.listBox.GetFirstChildT<ScrollViewer, ListBoxItem>(null);
    
                if (this.bar == null || this.viewer == null)
                {
                    LogHelper.Warning("数据虚拟化-初始化失败");
                    return;
                }
    
                // 绑定数据源
                this.listBox.SetBinding(ListBox.ItemsSourceProperty, new Binding(nameof(this.VirtualData)) { Source = this, });
    
                this.ReloadEndData();
    
                // 监控滚动条
                this.bar.ValueChanged += Bar_ValueChanged;
                // 监控滚动视图
                this.viewer.LayoutUpdated += Viewer_LayoutUpdated;
                // 监控数据源
                this.sources.CollectionChanged += Sources_CollectionChanged;
    
                Inited = true;
            }
    
            private void Viewer_LayoutUpdated(object sender, EventArgs e)
            {
                if (!this.Inited)
                    return;
    
                Console.WriteLine(" Viewer_LayoutUpdated ");
    
                if (this.Offset == 0 || this.IgnoreBarChanged)
                    return;
    
                this.delay.DelayAction();
            }
    
            private void DelayLayout()
            {
                if (!this.Inited)
                    return;
    
                var view = new ViewDecorate(this.viewer);
    
                view.DispatcherAction(() =>
                {
                    if (this.Offset == 0)
                        return;
    
                    try
                    {
                        this.IgnoreBarChanged = true;
    
                        double temp = 0;
                        // 向上
                        if (this.OffsetDirection)
                        {
                            for (int i = 0; i < this.OffsetCount && i < this.VirtualData.Count; i++)
                            {
                                temp += (this.listBox.ItemContainerGenerator.ContainerFromIndex(i) as ListBoxItem).ActualHeight;
                            }
                        }
    
                        this.viewer.ScrollToVerticalOffset(this.Offset + temp);
                        Console.WriteLine(" Viewer_LayoutUpdated ----------------------- Over ");
                    }
                    finally
                    {
                        this.Offset = 0;
                        this.IgnoreBarChanged = false;
                    }
                });
            }
    
            /// <summary>
            /// 滚动条滚动
            /// </summary>
            /// <param name="sender"></param>
            /// <param name="e"></param>
            private void Bar_ValueChanged(object sender, System.Windows.RoutedPropertyChangedEventArgs<double> e)
            {
                if (!this.Inited)
                    return;
    
                if (this.IgnoreBarChanged || this.Offset != 0)
                {
                    e.Handled = true;
                    return;
                }
    
                try
                {
                    this.IgnoreBarChanged = true;
    
                    const int count = 100;
    
                    // 向下滚动到端部
                    if (e.NewValue > e.OldValue && e.NewValue + count >= this.bar.Maximum)
                    {
                        TryScrollDown(e.NewValue - e.OldValue);
                    }
                    // 向上滚动到端部
                    else if (e.NewValue < e.OldValue && e.NewValue - count <= 0)
                    {
                        TryScrollUp(e.OldValue - e.NewValue);
                    }
                }
                finally
                {
                    e.Handled = true;
                    this.IgnoreBarChanged = false;
                }
            }
    
            /// <summary>
            /// 数据源发生变化
            /// </summary>
            /// <param name="sender"></param>
            /// <param name="e"></param>
            private void Sources_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
            {
                if (!this.Inited)
                    return;
    
                if (e.Action == NotifyCollectionChangedAction.Add)
                {
                    // 新消息到达、尝试将滚动条滚动到底部
                    this.MoveToBottom();
                }
                else if (e.Action == NotifyCollectionChangedAction.Remove)
                {
                    this.IgnoreBarChanged = true;
    
                    // 移除旧数据
                    foreach (var item in e.OldItems)
                    {
                        if (item is T)
                            this.VirtualData.Remove(item as T);
                    }
    
                    this.ReCalIndex();
    
                    if (this.StartVirtualIndex == -1 || this.EndVirtualIndex == -1)
                    {
                        this.ReloadEndData();
                    }
                    else
                    {
                        if (this.VirtualData.Count < this.InitLoadCount)
                        {
                            // 数量过少、尝试填充数据
                            this.LoadMoreData();
                        }
                    }
    
                    this.IgnoreBarChanged = false;
                }
                // 撤回消息
                else if (e.Action == NotifyCollectionChangedAction.Replace)
                {
                    if (e.OldItems != null && e.OldItems.Count == 1 && e.NewItems != null && e.NewItems.Count == 1)
                    {
                        var oldT = e.OldItems[0] as T;
                        var newT = e.NewItems[0] as T;
                        int index = this.VirtualData.IndexOf(oldT);
                        if (index > -1)
                        {
                            this.VirtualData[index] = newT;
                        }
                    }
    
                }
                else if (e.Action == NotifyCollectionChangedAction.Reset)
                {
                    this.IgnoreBarChanged = true;
    
                    this.ReloadEndData();
    
                    this.IgnoreBarChanged = false;
                }
            }
    
    
            /// <summary>
            /// 将视图移动到某个索引的位置
            /// </summary>
            /// <param name="index"></param>
            public void MoveToIndex(int index)
            {
                if (!this.Inited)
                    return;
    
                if (index < 0 || index >= this.sources.Count)
                    return;
    
                var t = this.sources[index];
                if (this.VirtualData.IndexOf(t) > -1)
                {
                    listBox.ScrollIntoView(t);
                    return;
                }
    
                int start = index - this.InitLoadCount;
                if (start < 0)
                    start = 0;
    
                int end = index + this.InitLoadCount;
                if (end >= this.sources.Count)
                    end = this.sources.Count - 1;
    
                int count = end - start + 1;
                if (count == 0)
                    return;
    
                try
                {
                    this.IgnoreBarChanged = true;
    
                    var list = this.sources.Skip(start).Take(count);
    
                    this.VirtualData.Clear();
    
                    foreach (var item in list)
                    {
                        this.VirtualData.Add(item);
                    }
    
                    this.ReCalIndex();
    
                    listBox.ScrollIntoView(t);
                }
                finally
                {
                    this.IgnoreBarChanged = false;
                }
            }
    
    
            /// <summary>
            /// 将视图移动到底部
            /// </summary>
            public void MoveToBottom()
            {
                if (!this.Inited)
                    return;
    
                try
                {
                    this.IgnoreBarChanged = true;
    
                    // 询问是否可以将滚动条滚动到底部
                    if (this.CheckCanScrollToBottom != null && !this.CheckCanScrollToBottom())
                        return;
    
                    // 超过最大显示容量、则重新加载末端数据
                    if (this.StartVirtualIndex == -1 || this.sources.Count == 0 || this.sources.Count - this.StartVirtualIndex > this.MaxCount)
                    {
                        this.ReloadEndData();
                        return;
                    }
    
                    // 没有需要加载的数据
                    if (this.EndVirtualIndex == this.sources.Count - 1)
                    {
                        this.listBox.ScrollViewToBottom();
                        return;
                    }
    
                    // 平滑加载
                    var count = this.EndVirtualIndex + 1;
    
                    if (this.sources.Count > count)
                    {
                        var list = this.sources.Skip(count).ToList();
    
                        foreach (var item in list)
                        {
                            this.VirtualData.Add(item);
                        }
    
                        this.ReCalIndex();
                        this.listBox.ScrollViewToBottom();
                    }
                }
                catch (Exception ex)
                {
                    LogHelper.Execption(ex, "数据虚拟化");
                }
                finally
                {
                    this.IgnoreBarChanged = false;
                }
            }
    
    
            /// <summary>
            /// 重新计算索引值
            /// </summary>
            private void ReCalIndex()
            {
                if (this.VirtualData.Count > 0)
                {
                    this.StartVirtualIndex = this.sources.IndexOf(this.VirtualData[0]);
                    this.EndVirtualIndex = this.sources.IndexOf(this.VirtualData[this.VirtualData.Count - 1]);
    
                    if (this.StartVirtualIndex == -1 || this.EndVirtualIndex == -1 || this.EndVirtualIndex < this.StartVirtualIndex)
                    {
                        this.StartVirtualIndex = -1;
                        this.EndVirtualIndex = -1;
                        LogHelper.Warning("数据虚拟化-逻辑错误");
                    }
                }
                else
                {
                    this.StartVirtualIndex = -1;
                    this.EndVirtualIndex = -1;
                }
            }
    
    
            /// <summary>
            /// 重新初始化数据
            /// </summary>
            private void ReloadEndData()
            {
                if (this.VirtualData.Count > 0)
                {
                    this.VirtualData.Clear();
    
                    this.EndVirtualIndex = -1;
                    this.StartVirtualIndex = -1;
                }
    
                if (this.sources != null && this.sources.Count > 0)
                {
                    var list = this.sources.ListLastMaxCount(this.InitLoadCount);
    
                    if (list.Count > 0)
                    {
                        foreach (var item in list)
                        {
                            this.VirtualData.Add(item);
                        }
    
                        this.ReCalIndex();
    
                        // 滚动条滚动到最底部
                        this.listBox.ScrollViewToBottom();
                    }
                }
            }
    
    
            /// <summary>
            /// 删除数据时加载更多数据
            /// </summary>
            private void LoadMoreData()
            {
                List<T> data = this.sources.ListFindRangeWithMaxCount(this.StartVirtualIndex, this.InitLoadCount);
    
                if (data.Count <= this.VirtualData.Count)
                {
                    // 没有加载到更多数据
                    return;
                }
    
                int start = data.IndexOf(this.VirtualData[0]);
                int end = data.LastIndexOf(this.VirtualData[this.VirtualData.Count - 1]);
    
                if (start == -1 || end == -1 || end < start)
                {
                    LogHelper.Warning("数据虚拟化-逻辑错误");
                    return;
                }
    
                for (int i = 0; i < data.Count; i++)
                {
                    if (i < start)
                    {
                        this.VirtualData.Insert(i, data[i]);
                    }
                    else if (i > end)
                    {
                        this.VirtualData.Add(data[i]);
                    }
                }
    
                this.ReCalIndex();
            }
    
            /// <summary>
            /// 向上滚动
            /// </summary>
            private void TryScrollUp(double offset)
            {
                // 没有数据了
                if (this.StartVirtualIndex == -1 || this.StartVirtualIndex == 0)
                    return;
    
                double tempOffset = this.viewer.ContentVerticalOffset;
                // 释放捕获的鼠标
                this.bar.Track.Thumb.ReleaseMouseCapture();
                this.bar.Track.DecreaseRepeatButton.ReleaseMouseCapture();
    
                int tempCount = 0;
    
                var list = this.sources.ListLastMaxCount(this.StartVirtualIndex, this.IncreasingCount, false);
    
                // list 为反序结果
                foreach (var item in list)
                {
                    this.VirtualData.Insert(0, item);
                    tempCount++;
                }
    
                if (this.VirtualData.Count > this.MaxCount)
                {
                    for (int i = 0; i < this.IncreasingCount; i++)
                    {
                        this.VirtualData.RemoveAt(this.VirtualData.Count - 1);
                    }
                }
    
                this.ReCalIndex();
    
                this.OffsetDirection = true;
                this.OffsetCount = tempCount;
                this.Offset = tempOffset - offset;
    
                if (this.Offset == 0)
                    this.Offset = 1;
            }
    
    
            /// <summary>
            /// 向下滚动
            /// </summary>
            private void TryScrollDown(double offest)
            {
                // 没有数据了
                if (this.EndVirtualIndex == -1 || this.EndVirtualIndex == this.sources.Count - 1)
                    return;
    
                // 释放捕获的鼠标
                this.bar.Track.Thumb.ReleaseMouseCapture();
                this.bar.Track.IncreaseRepeatButton.ReleaseMouseCapture();
    
                double tempOffset = this.viewer.ContentVerticalOffset;
    
                var list = this.sources.Skip(this.EndVirtualIndex + 1).Take(this.IncreasingCount);
    
                foreach (var item in list)
                {
                    this.VirtualData.Add(item);
                }
    
                if (this.VirtualData.Count > this.MaxCount)
                {
                    for (int i = 0; i < this.IncreasingCount; i++)
                    {
                        tempOffset -= (this.listBox.ItemContainerGenerator.ContainerFromIndex(0) as ListBoxItem).ActualHeight;
                        this.VirtualData.RemoveAt(0);
                    }
                }
    
                this.ReCalIndex();
    
                this.OffsetDirection = false;
                this.OffsetCount = 0;
                this.Offset = tempOffset + offest;
    
                if (this.Offset == 0)
                    this.Offset = 1;
            }
    
    
            public void Dispose()
            {
                if (!this.Inited)
                    return;
    
                this.Inited = false;
                this.VirtualData.Clear();
    
                // 监控滚动条
                this.bar.ValueChanged -= Bar_ValueChanged;
                // 监控滚动视图
                this.viewer.LayoutUpdated -= Viewer_LayoutUpdated;
                // 监控数据源
                this.sources.CollectionChanged -= Sources_CollectionChanged;
    
                this.CheckCanScrollToBottom = null;
    
                this.delay.Dispose();
            }
        }
    VirtualDataForListBox

    该处理方式相当于根据滚动条的滚动适时增减items,当然该类的应用有一定的局限性,不过操作滚动条的方式还是具有借鉴意义的。

    源码出处不详。

    三、补充

    启用UI虚拟化的两个附加属性:

    1、ScrollViewer.CanContentScroll="True"

    2、VirtualizingStackPanel.IsVirtualizing="True"

  • 相关阅读:
    分布式事务基本概念
    rocketmq源码分析3-consumer消息获取
    Mac下的Eclipse不能记住工作空间问题
    rocketmq源码分析2-broker的消息接收
    rocketmq源码分析1-benchmark学习
    metrics
    slf4j与logback对接是如何将日志写到控制台的
    Spring AOP
    hibernate数据库连接池,常用配置
    动态代理
  • 原文地址:https://www.cnblogs.com/08shiyan/p/6019388.html
Copyright © 2020-2023  润新知