WPF 虚拟化 VirtualizingWrapPanel 和 VirtualLizingTilePanel

2016-11-03 08:16:50来源:cnblogs.com作者:把爱延续人点击

一、 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"

 

最新文章

123

最新摄影

微信扫一扫

第七城市微信公众平台