WPF 虚拟化 VirtualizingWrapPanel 和 VirtualLizingTilePanel
一、 UI 上两个扩展
- public class VirtualizingWrapPanel : VirtualizingPanel, IScrollInfo
- {
- #region Fields
- UIElementCollection _children;
- ItemsControl _itemsControl;
- IItemContainerGenerator _generator;
- private Point _offset = new Point(, );
- private Size _extent = new Size(, );
- private Size _viewport = new Size(, );
- private int firstIndex = ;
- private Size childSize;
- private Size _pixelMeasuredViewport = new Size(, );
- 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 != )
- {
- 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 ;
- }
- private void CleanUpItems(int minDesiredGenerated, int maxDesiredGenerated)
- {
- for (int i = _children.Count - ; i >= ; i--)
- {
- GeneratorPosition childGeneratorPos = new GeneratorPosition(i, );
- int itemIndex = _generator.IndexFromGeneratorPosition(childGeneratorPos);
- if (itemIndex < minDesiredGenerated || itemIndex > maxDesiredGenerated)
- {
- _generator.Remove(childGeneratorPos, );
- RemoveInternalChildRange(i, );
- }
- }
- }
- 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 - ;
- }
- else
- {
- _extent.Width = _abstractPanel.SectionCount + ViewportWidth - ;
- }
- _owner.InvalidateScrollInfo();
- }
- private void ResetScrollInfo()
- {
- _offset.X = ;
- _offset.Y = ;
- }
- private int GetNextSectionClosestIndex(int itemIndex)
- {
- var abstractItem = _abstractPanel[itemIndex];
- if (abstractItem.Section < _abstractPanel.SectionCount - )
- {
- var ret = _abstractPanel.
- Where(x => x.Section == abstractItem.Section + ).
- 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 > )
- {
- var ret = _abstractPanel.
- Where(x => x.Section == abstractItem.Section - ).
- 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 = ;
- while (itemIndex == -)
- {
- 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 + );
- UpdateLayout();
- next = gen.ContainerFromIndex(nextIndex);
- }
- }
- else
- {
- if (itemIndex == _abstractPanel._itemCount - )
- return;
- next = gen.ContainerFromIndex(itemIndex + );
- while (next == null)
- {
- SetHorizontalOffset(HorizontalOffset + );
- UpdateLayout();
- next = gen.ContainerFromIndex(itemIndex + );
- }
- }
- while (depth != )
- {
- next = VisualTreeHelper.GetChild(next, );
- 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 = ;
- while (itemIndex == -)
- {
- 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 - );
- UpdateLayout();
- next = gen.ContainerFromIndex(nextIndex);
- }
- }
- else
- {
- if (itemIndex == )
- return;
- next = gen.ContainerFromIndex(itemIndex - );
- while (next == null)
- {
- SetVerticalOffset(VerticalOffset - );
- UpdateLayout();
- next = gen.ContainerFromIndex(itemIndex - );
- }
- }
- while (depth != )
- {
- next = VisualTreeHelper.GetChild(next, );
- 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 = ;
- while (itemIndex == -)
- {
- 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 + );
- UpdateLayout();
- next = gen.ContainerFromIndex(nextIndex);
- }
- }
- else
- {
- if (itemIndex == _abstractPanel._itemCount - )
- return;
- next = gen.ContainerFromIndex(itemIndex + );
- while (next == null)
- {
- SetVerticalOffset(VerticalOffset + );
- UpdateLayout();
- next = gen.ContainerFromIndex(itemIndex + );
- }
- }
- while (depth != )
- {
- next = VisualTreeHelper.GetChild(next, );
- 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 = ;
- while (itemIndex == -)
- {
- 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 - );
- UpdateLayout();
- next = gen.ContainerFromIndex(nextIndex);
- }
- }
- else
- {
- if (itemIndex == )
- return;
- next = gen.ContainerFromIndex(itemIndex - );
- while (next == null)
- {
- SetHorizontalOffset(HorizontalOffset - );
- UpdateLayout();
- next = gen.ContainerFromIndex(itemIndex - );
- }
- }
- while (depth != )
- {
- next = VisualTreeHelper.GetChild(next, );
- 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 == )
- 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 == ) ? startPos.Index : startPos.Index + ;
- int current = firstVisibleIndex;
- int visibleSections = ;
- using (_generator.StartAt(startPos, GeneratorDirection.Forward, true))
- {
- bool stop = false;
- bool isHorizontal = Orientation == Orientation.Horizontal;
- double currentX = ;
- double currentY = ;
- double maxItemSize = ;
- 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 = ;
- 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 = ;
- 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 - );
- 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 + );
- else
- SetVerticalOffset(VerticalOffset + );
- }
- public void LineLeft()
- {
- if (Orientation == Orientation.Horizontal)
- SetHorizontalOffset(HorizontalOffset - );
- else
- SetHorizontalOffset(HorizontalOffset - );
- }
- public void LineRight()
- {
- if (Orientation == Orientation.Horizontal)
- SetHorizontalOffset(HorizontalOffset + );
- else
- SetHorizontalOffset(HorizontalOffset + );
- }
- public void LineUp()
- {
- if (Orientation == Orientation.Vertical)
- SetVerticalOffset(VerticalOffset - );
- else
- SetVerticalOffset(VerticalOffset - );
- }
- public Rect MakeVisible(Visual visual, Rect rectangle)
- {
- var gen = (ItemContainerGenerator)_generator.GetItemContainerGeneratorForPanel(this);
- var element = (UIElement)visual;
- int itemIndex = gen.IndexFromContainer(element);
- while (itemIndex == -)
- {
- 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 += ;
- else if (elementRect.Top < )
- _offset.Y -= ;
- }
- else
- {
- double viewportWidth = _pixelMeasuredViewport.Width;
- if (elementRect.Right > viewportWidth)
- _offset.X += ;
- else if (elementRect.Left < )
- _offset.X -= ;
- }
- 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 < || _viewport.Width >= _extent.Width)
- {
- offset = ;
- }
- 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 < || _viewport.Height >= _extent.Height)
- {
- offset = ;
- }
- 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 = -;
- public int SectionIndex
- {
- get
- {
- if (_sectionIndex == -)
- {
- return _index % _panel._averageItemsPerSection - ;
- }
- return _sectionIndex;
- }
- set
- {
- if (_sectionIndex == -)
- _sectionIndex = value;
- }
- }
- int _section = -;
- public int Section
- {
- get
- {
- if (_section == -)
- {
- return _index / _panel._averageItemsPerSection;
- }
- return _section;
- }
- set
- {
- if (_section == -)
- _section = value;
- }
- }
- }
- class WrapPanelAbstraction : IEnumerable<ItemAbstraction>
- {
- public WrapPanelAbstraction(int itemCount)
- {
- List<ItemAbstraction> items = new List<ItemAbstraction>(itemCount);
- for (int i = ; 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 = -;
- private int _currentSetItemIndex = -;
- private int _itemsInCurrentSecction = ;
- private object _syncRoot = new object();
- public int SectionCount
- {
- get
- {
- int ret = _currentSetSection + ;
- if (_currentSetItemIndex + < Items.Count)
- {
- int itemsLeft = Items.Count - _currentSetItemIndex;
- ret += itemsLeft / _averageItemsPerSection + ;
- }
- return ret;
- }
- }
- private ReadOnlyCollection<ItemAbstraction> Items { get; set; }
- public void SetItemSection(int index, int section)
- {
- lock (_syncRoot)
- {
- if (section <= _currentSetSection + && index == _currentSetItemIndex + )
- {
- _currentSetItemIndex++;
- Items[index].Section = section;
- if (section == _currentSetSection + )
- {
- _currentSetSection = section;
- if (section > )
- {
- _averageItemsPerSection = (index) / (section);
- }
- _itemsInCurrentSecction = ;
- }
- else
- _itemsInCurrentSecction++;
- Items[index].SectionIndex = _itemsInCurrentSecction - ;
- }
- }
- }
- 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(-));
- 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 = ;
- 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 >= )
- {
- RemoveInternalChildRange(childCurrentIndex, );
- }
- 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 * >= availableSize.Width)
- {
- // wrap to a new line
- currentY += ItemHeight;
- currentX = ;
- }
- else
- {
- currentX += ItemWidth;
- }
- }
- }
- RemoveRedundantChildren();
- UpdateScrollInfo(availableSize, extentInfo);
- var desiredSize = new Size(double.IsInfinity(availableSize.Width) ? : availableSize.Width,
- double.IsInfinity(availableSize.Height) ? : availableSize.Height);
- _isInMeasure = false;
- return desiredSize;
- }
- private void EnsureScrollOffsetIsWithinConstrains(ExtentInfo extentInfo)
- {
- _offset.Y = Clamp(_offset.Y, , 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 >= )
- {
- _itemsGenerator.Recycle(generatorPosition, );
- }
- }
- SetVirtualItemIndex(child, -);
- }
- }
- 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 - ; i >= ; 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) == -)
- {
- RemoveInternalChildRange(i, );
- }
- }
- }
- 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 - , );
- var firstRealizedItemLeft = firstRealizedIndex % extentInfo.ItemsPerLine * ItemWidth - HorizontalOffset;
- var firstRealizedItemTop = (firstRealizedIndex / extentInfo.ItemsPerLine) * itemHeight - VerticalOffset;
- var firstCompleteLineTop = (firstVisibleLine == ? firstRealizedItemTop : firstRealizedItemTop + ItemHeight);
- var completeRealizedLines = (int)Math.Ceiling((availableSize.Height - firstCompleteLineTop) / itemHeight);
- var lastRealizedIndex = Math.Min(firstRealizedIndex + completeRealizedLines * extentInfo.ItemsPerLine + , _itemsControl.Items.Count - );
- 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), );
- 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, , ExtentWidth - ViewportWidth);
- _offset = new Point(offset, _offset.Y);
- InvalidateScrollInfo();
- InvalidateMeasure();
- }
- public void SetVerticalOffset(double offset)
- {
- if (_isInMeasure)
- {
- return;
- }
- offset = Clamp(offset, , 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()]
- public int InitLoadCount { get; set; }
- /// <summary>
- /// 递增的数量值
- /// <para>滚动条滚动到两端时,每次自动加载的数据量</para>
- /// <para>子项数量超过容器的最大数量<paramref name="MaxCount"/>时,自动减少的数量</para>
- /// </summary>
- [DefaultValue()]
- public int IncreasingCount { get; set; }
- /// <summary>
- /// 子项的最大数量
- /// </summary>
- [DefaultValue()]
- 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(, DelayLayout);
- this.Inited = false;
- this.Offset = ;
- this.listBox = listBox;
- this.sources = sources;
- this.InitLoadCount = ;
- this.IncreasingCount = ;
- this.MaxCount = ;
- this.EndVirtualIndex = -;
- this.StartVirtualIndex = -;
- 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 == || this.IgnoreBarChanged)
- return;
- this.delay.DelayAction();
- }
- private void DelayLayout()
- {
- if (!this.Inited)
- return;
- var view = new ViewDecorate(this.viewer);
- view.DispatcherAction(() =>
- {
- if (this.Offset == )
- return;
- try
- {
- this.IgnoreBarChanged = true;
- double temp = ;
- // 向上
- if (this.OffsetDirection)
- {
- for (int i = ; 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 = ;
- 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 != )
- {
- e.Handled = true;
- return;
- }
- try
- {
- this.IgnoreBarChanged = true;
- const int count = ;
- // 向下滚动到端部
- 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 <= )
- {
- 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 == - || this.EndVirtualIndex == -)
- {
- 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 == && e.NewItems != null && e.NewItems.Count == )
- {
- var oldT = e.OldItems[] as T;
- var newT = e.NewItems[] as T;
- int index = this.VirtualData.IndexOf(oldT);
- if (index > -)
- {
- 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 < || index >= this.sources.Count)
- return;
- var t = this.sources[index];
- if (this.VirtualData.IndexOf(t) > -)
- {
- listBox.ScrollIntoView(t);
- return;
- }
- int start = index - this.InitLoadCount;
- if (start < )
- start = ;
- int end = index + this.InitLoadCount;
- if (end >= this.sources.Count)
- end = this.sources.Count - ;
- int count = end - start + ;
- if (count == )
- 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 == - || this.sources.Count == || this.sources.Count - this.StartVirtualIndex > this.MaxCount)
- {
- this.ReloadEndData();
- return;
- }
- // 没有需要加载的数据
- if (this.EndVirtualIndex == this.sources.Count - )
- {
- this.listBox.ScrollViewToBottom();
- return;
- }
- // 平滑加载
- var count = this.EndVirtualIndex + ;
- 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 > )
- {
- this.StartVirtualIndex = this.sources.IndexOf(this.VirtualData[]);
- this.EndVirtualIndex = this.sources.IndexOf(this.VirtualData[this.VirtualData.Count - ]);
- if (this.StartVirtualIndex == - || this.EndVirtualIndex == - || this.EndVirtualIndex < this.StartVirtualIndex)
- {
- this.StartVirtualIndex = -;
- this.EndVirtualIndex = -;
- LogHelper.Warning("数据虚拟化-逻辑错误");
- }
- }
- else
- {
- this.StartVirtualIndex = -;
- this.EndVirtualIndex = -;
- }
- }
- /// <summary>
- /// 重新初始化数据
- /// </summary>
- private void ReloadEndData()
- {
- if (this.VirtualData.Count > )
- {
- this.VirtualData.Clear();
- this.EndVirtualIndex = -;
- this.StartVirtualIndex = -;
- }
- if (this.sources != null && this.sources.Count > )
- {
- var list = this.sources.ListLastMaxCount(this.InitLoadCount);
- if (list.Count > )
- {
- 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[]);
- int end = data.LastIndexOf(this.VirtualData[this.VirtualData.Count - ]);
- if (start == - || end == - || end < start)
- {
- LogHelper.Warning("数据虚拟化-逻辑错误");
- return;
- }
- for (int i = ; 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 == - || this.StartVirtualIndex == )
- return;
- double tempOffset = this.viewer.ContentVerticalOffset;
- // 释放捕获的鼠标
- this.bar.Track.Thumb.ReleaseMouseCapture();
- this.bar.Track.DecreaseRepeatButton.ReleaseMouseCapture();
- int tempCount = ;
- var list = this.sources.ListLastMaxCount(this.StartVirtualIndex, this.IncreasingCount, false);
- // list 为反序结果
- foreach (var item in list)
- {
- this.VirtualData.Insert(, item);
- tempCount++;
- }
- if (this.VirtualData.Count > this.MaxCount)
- {
- for (int i = ; i < this.IncreasingCount; i++)
- {
- this.VirtualData.RemoveAt(this.VirtualData.Count - );
- }
- }
- this.ReCalIndex();
- this.OffsetDirection = true;
- this.OffsetCount = tempCount;
- this.Offset = tempOffset - offset;
- if (this.Offset == )
- this.Offset = ;
- }
- /// <summary>
- /// 向下滚动
- /// </summary>
- private void TryScrollDown(double offest)
- {
- // 没有数据了
- if (this.EndVirtualIndex == - || this.EndVirtualIndex == this.sources.Count - )
- return;
- // 释放捕获的鼠标
- this.bar.Track.Thumb.ReleaseMouseCapture();
- this.bar.Track.IncreaseRepeatButton.ReleaseMouseCapture();
- double tempOffset = this.viewer.ContentVerticalOffset;
- var list = this.sources.Skip(this.EndVirtualIndex + ).Take(this.IncreasingCount);
- foreach (var item in list)
- {
- this.VirtualData.Add(item);
- }
- if (this.VirtualData.Count > this.MaxCount)
- {
- for (int i = ; i < this.IncreasingCount; i++)
- {
- tempOffset -= (this.listBox.ItemContainerGenerator.ContainerFromIndex() as ListBoxItem).ActualHeight;
- this.VirtualData.RemoveAt();
- }
- }
- this.ReCalIndex();
- this.OffsetDirection = false;
- this.OffsetCount = ;
- this.Offset = tempOffset + offest;
- if (this.Offset == )
- this.Offset = ;
- }
- 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"
WPF 虚拟化 VirtualizingWrapPanel 和 VirtualLizingTilePanel的更多相关文章
- wpf 虚拟化操作异常
根据这篇文章提供的方法会导致搜索变慢及有时候搜索不到 WPF中ItemsControl应用虚拟化时找到子元素的方法, 具体可以修改为下面代码: //Action action = () => / ...
- WPF之UI虚拟化
在WPF应用程序开发过程中,大数据量的数据展现通常都要考虑性能问题.有下面一种常见的情况:原始数据源数据量很大,但是某一时刻数据容器中的可见元素个数是有限的,剩余大多数元素都处于不可见状态,如果一次性 ...
- WPF Virtualization
WPF虚拟化技术分为UI 虚拟化和数据虚拟化 第一种方法被称为"UI 虚拟化".支持虚拟化用户界面的控件是足够聪明来创建只显示的是实际在屏幕上可见的数据项目所需的 UI 元素.例如 ...
- 【WPF】UI虚拟化之------自定义VirtualizingWrapPanel
原文:[WPF]UI虚拟化之------自定义VirtualizingWrapPanel 前言 前几天QA报了一个关于OOM的bug,在排查的过程中发现,ListBox控件中被塞入了过多的Item,而 ...
- wpf 客户端【JDAgent桌面助手】开发详解(三) 瀑布流效果实现与UI虚拟化优化大数据显示
目录区域: 业余开发的wpf 客户端终于完工了..晒晒截图 wpf 客户端[JDAgent桌面助手]开发详解-开篇 wpf 客户端[JDAgent桌面助手]详解(一)主窗口 圆形菜单... wpf 客 ...
- WPF ListBox/ListView/DataGrid 虚拟化时的滚动方式
ListBox的滚动方式 分为像素滚动和列表项滚动 通过ListBox的附加属性ScrollViewer.CanContentScroll来设置.因此ListBox的默认模板中,含有ScrollVie ...
- WPF:间接支持虚拟化的ListBox
/// <summary> /// 间接实现了虚拟化的ListBox /// 子项必须实现IVisible接口 /// 你可以在IsVisible发生改变时实现一系列自定义动作 /// 比 ...
- WPF 列表虚拟化时的滚动方式
ListBox的滚动方式 分为像素滚动和列表项滚动 通过ListBox的附加属性ScrollViewer.CanContentScroll来设置.因此ListBox的默认模板中,含有ScrollVie ...
- WPF的UI虚拟化
许多时候,我们的界面上会呈现大量的数据,如包含数千条记录的表格或包含数百张照片的相册.由于呈现UI是一件开销比较大的动作,一次性呈现数百张照片就目前的电脑性能来说是需要占用大量内存和时间的.因此需要对 ...
随机推荐
- 卡片抽奖插件 CardShow
这个小项目(卡片秀)是一个卡片抽奖特效插件,用开源项目这样的词语让我多少有些羞愧,毕竟作为一个涉世未深的小伙子,用项目的标准衡量还有很大差距.不过该案例采用 jQuery 插件方式编写,提供配置参数并 ...
- 【开源】.Net 动态脚本引擎NScript
开源地址: https://git.oschina.net/chejiangyi/NScript 开源QQ群: .net 开源基础服务 238543768 .Net 动态脚本引擎 NScript ...
- 趣说游戏AI开发:曼哈顿街角的A*算法
0x00 前言 请叫我标题党!请叫我标题党!请叫我标题党!因为下面的文字既不发生在美国曼哈顿,也不是一个讲述美国梦的故事.相反,这可能只是一篇没有那么枯燥的关于算法的文章.A星算法,这个在游戏寻路开发 ...
- javascript之闭包理解以及应用场景
半个月没写博文了,最近一直在弄小程序,感觉也没啥好写的. 之前读了js权威指南,也写了篇博文,但是实话实说当初看闭包确实还是一头雾水.现在时隔一个多月(当然这一段时间还是一直有在看闭包的相关知识)理解 ...
- Node.js npm 详解
一.npm简介 安装npm请阅读我之前的文章Hello Node中npm安装那一部分,不过只介绍了linux平台,如果是其它平台,有前辈写了更加详细的介绍. npm的全称:Node Package M ...
- web 前端(轮番插件)
<!DOCTYPE html> <html lang="zh-cn"> <head> <meta charset="UTF-8& ...
- 使用Hudson搭建自动构建服务器
环境: ubuntu1404_x64 说明: 使用hudson和git搭建自动构建服务器的简单示例 安装hudson及相关插件 安装hudson 安装命令如下: sudo sh -c "ec ...
- jquery.multiselect 多选下拉框实现
第一步:链接下列文件,如果没有,到此网页下载 https://github.com/ehynds/jquery-ui-multiselect-widget,此插件基于jquery ,所以jquery的 ...
- ucos实时操作系统学习笔记——任务间通信(互斥锁)
想讲一下ucos任务间通信中的mutex,感觉其设计挺巧妙,同sem一样使用的是event机制实现的,代码不每一行都分析,因为讲的没邵贝贝老师清楚,主要讲一下mutex的内核是如何实现的.可以理解互斥 ...
- C++的性能C#的产能?! - .Net Native 系列《三》:.NET Native部署测试方案及样例
之前一文<c++的性能, c#的产能?!鱼和熊掌可以兼得,.NET NATIVE初窥> 获得很多朋友支持和鼓励,也更让我坚定做这项技术的推广者,希望能让更多的朋友了解这项技术,于是先从官方 ...