一、 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的更多相关文章

  1. wpf 虚拟化操作异常

    根据这篇文章提供的方法会导致搜索变慢及有时候搜索不到 WPF中ItemsControl应用虚拟化时找到子元素的方法, 具体可以修改为下面代码: //Action action = () => / ...

  2. WPF之UI虚拟化

    在WPF应用程序开发过程中,大数据量的数据展现通常都要考虑性能问题.有下面一种常见的情况:原始数据源数据量很大,但是某一时刻数据容器中的可见元素个数是有限的,剩余大多数元素都处于不可见状态,如果一次性 ...

  3. WPF Virtualization

    WPF虚拟化技术分为UI 虚拟化和数据虚拟化 第一种方法被称为"UI 虚拟化".支持虚拟化用户界面的控件是足够聪明来创建只显示的是实际在屏幕上可见的数据项目所需的 UI 元素.例如 ...

  4. 【WPF】UI虚拟化之------自定义VirtualizingWrapPanel

    原文:[WPF]UI虚拟化之------自定义VirtualizingWrapPanel 前言 前几天QA报了一个关于OOM的bug,在排查的过程中发现,ListBox控件中被塞入了过多的Item,而 ...

  5. wpf 客户端【JDAgent桌面助手】开发详解(三) 瀑布流效果实现与UI虚拟化优化大数据显示

    目录区域: 业余开发的wpf 客户端终于完工了..晒晒截图 wpf 客户端[JDAgent桌面助手]开发详解-开篇 wpf 客户端[JDAgent桌面助手]详解(一)主窗口 圆形菜单... wpf 客 ...

  6. WPF ListBox/ListView/DataGrid 虚拟化时的滚动方式

    ListBox的滚动方式 分为像素滚动和列表项滚动 通过ListBox的附加属性ScrollViewer.CanContentScroll来设置.因此ListBox的默认模板中,含有ScrollVie ...

  7. WPF:间接支持虚拟化的ListBox

    /// <summary> /// 间接实现了虚拟化的ListBox /// 子项必须实现IVisible接口 /// 你可以在IsVisible发生改变时实现一系列自定义动作 /// 比 ...

  8. WPF 列表虚拟化时的滚动方式

    ListBox的滚动方式 分为像素滚动和列表项滚动 通过ListBox的附加属性ScrollViewer.CanContentScroll来设置.因此ListBox的默认模板中,含有ScrollVie ...

  9. WPF的UI虚拟化

    许多时候,我们的界面上会呈现大量的数据,如包含数千条记录的表格或包含数百张照片的相册.由于呈现UI是一件开销比较大的动作,一次性呈现数百张照片就目前的电脑性能来说是需要占用大量内存和时间的.因此需要对 ...

随机推荐

  1. 快速搭建springmvc+spring data jpa工程

    一.前言 这里简单讲述一下如何快速使用springmvc和spring data jpa搭建后台开发工程,并提供了一个简单的demo作为参考. 二.创建maven工程 http://www.cnblo ...

  2. 9、 Struts2验证(声明式验证、自定义验证器)

    1. 什么是Struts2 验证器 一个健壮的 web 应用程序必须确保用户输入是合法.有效的. Struts2 的输入验证 基于 XWork Validation Framework 的声明式验证: ...

  3. 【用户交互】APP没有退出前台但改变系统属性如何实时更新UI?监听系统广播,让用户交互更舒心~

    前日,一小伙伴问我一个问题,说它解决了半天都没解决这个问题,截图如下: 大概楼主理解如下: 如果在应用中有一个判断wifi的开关和一个当前音量大小的seekbar以及一个获取当前电量多少的按钮,想知道 ...

  4. 【HTML】Html页面跳转的5种方式

    目录结构: // contents structure [-] html实现 javascript方式实现 结合了倒数的javascript实现(IE) 解决Firefox不支持innerText的问 ...

  5. 项目自动化建构工具gradle 入门1——输出helloWorld

    先来一个简单的例子,4个步骤: 1.进入D:\work\gradle\java 目录  ,您电脑没这目录? 那辛苦自己一级一级建立起来吧 新建文件build.gradle,文件内容是: apply p ...

  6. .JavaWeb文件上传和FileUpload组件使用

    .JavaWeb文件上传 1.自定义上传 文件上传时的表单设计要符合文件提交的方式: 1.提交方式:post 2.表单中有文件上传的表单项:<input type="file" ...

  7. 纯JS打造比QQ空间更强大的图片浏览器-支持拖拽、缩放、过滤、缩略图等

    在线演示地址(打开网页后,点击商家图册): http://www.sport7.cn/cc/jiangnan/football5.html 先看一看效果图: 该图片浏览器实现的功能如下: 1. 鼠标滚 ...

  8. App开发的新趋势

    移动开发这些年,移动开发者人数越来越多,类似的培训公司发展也很快,不过伴随着的是移动应用的需求这几年发展更为旺盛.要开发好的App,纯原生开发肯定是最佳选择.但是这么多年发展,原生开发的难度并没有降低 ...

  9. zone.js - 暴力之美

    在ng2的开发过程中,Angular团队为我们带来了一个新的库 – zone.js.zone.js的设计灵感来源于Dart语言,它描述JavaScript执行过程的上下文,可以在异步任务之间进行持久性 ...

  10. 如何安装一个优秀的BUG管理平台——真的是手把手教学!

    前言 就BUG管理而言,国内的禅道做得很不错,而且持续有更新.我们来看看如何从头到尾安装禅道,各位要注意的是,不是文章深或者浅,而是文章如何在遇到问题的时候,从什么途径和用什么方法解决问题的.现在发觉 ...