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"

 

posted @ 2016-11-01 15:05  把爱延续  阅读(4551)  评论(0编辑  收藏  举报