/// <summary> /// Immediately cleans up any containers that have gone offscreen. Called by MeasureOverride. /// When recycling this runs before generating and measuring children; otherwise it runs after. /// </summary> private void CleanupContainers(int firstViewport, ItemsControl itemsControl) { Debug.Assert(IsVirtualizing, "Can't clean up containers if not virtualizing"); Debug.Assert(InRecyclingMode || IsPixelBased, "For backwards compat the standard virtualizing mode has its own cleanup algorithm"); Debug.Assert(itemsControl != null, "We can't cleanup if we aren't the itemshost"); // // It removes items outside of the container cache window (a logical 'window' at // least as large as the viewport). // // firstViewport is the index of first data item that will be in the viewport // at the end of Measure. This is effectively the scroll offset. // // _visibleStart is index of the first data item that was previously at the top of the viewport // At the end of a Measure pass _visibleStart == firstViewport. // // _visibleCount is the number of data items that were previously visible in the viewport. int cleanupRangeStart = -1; int cleanupCount = 0; int itemIndex = -1; // data item index used to compare with the cache window position. int lastItemIndex; IList children = RealizedChildren; int focusedChild = -1, previousFocusable = -1, nextFocusable = -1; // child indices for the focused item and before and after focus trail items bool performCleanup = false; UIElement child; object item; if (children.Count == 0) { return; // nothing to do } AdjustCacheWindow(firstViewport, itemsControl.Items.Count); if (IsKeyboardFocusWithin && !IsPixelBased) { // If we're not in a hieararchy we can find the focus trail locally; for hierarchies it has already been // precalculated. FindFocusedChild(out focusedChild, out previousFocusable, out nextFocusable); } // // Iterate over all realized children and recycle the ones that are eligible. Items NOT eligible for recycling // have one or more of the following properties // // - inside the cache window // - the item is its own container // - has keyboard focus // - is the first focusable item before or after the focused item // - the CleanupVirtualizedItem event was canceled // for (int childIndex = 0; childIndex < children.Count; childIndex++) { child = (UIElement)children[childIndex]; lastItemIndex = itemIndex; itemIndex = GetGeneratedIndex(childIndex); // itemsControl.Items can change without notifying VirtualizingStackPanel (when ItemsSource is not an ObservableCollection or does not implement INotifyCollectionChanged). // Fetch the item from the container instead of referencing from the Items collection. item = itemsControl.ItemContainerGenerator.ItemFromContainer(child); if (itemIndex - lastItemIndex != 1) { // There's a generated gap between the current item and the last. Clean up the last range of items. performCleanup = true; } if (performCleanup) { if (cleanupRangeStart >= 0 && cleanupCount > 0) { // // We've hit a non-virtualizable container or a non-contiguous section. // CleanupRange(children, Generator, cleanupRangeStart, cleanupCount); // CleanupRange just modified the _realizedChildren list. Adjust the childIndex. childIndex -= cleanupCount; focusedChild -= cleanupCount; previousFocusable -= cleanupCount; nextFocusable -= cleanupCount; cleanupCount = 0; cleanupRangeStart = -1; } performCleanup = false; } if (IsOutsideCacheWindow(itemIndex) && !((IGeneratorHost)itemsControl).IsItemItsOwnContainer(item) && childIndex != focusedChild && childIndex != previousFocusable && childIndex != nextFocusable && !IsInFocusTrail(child) && // logically the same computation as the three above; used when in a treeview. child != _bringIntoViewContainer && // the container we're going to bring into view must not be recycled NotifyCleanupItem(child, itemsControl)) { // // The container is eligible to be virtualized // if (cleanupRangeStart == -1) { cleanupRangeStart = childIndex; } cleanupCount++; // // Save off the child's desired size if we're doing pixel-based virtualization. // We need to save off the size when doing hierarchical (i.e. TreeView) virtualization, since containers will vary // greatly in size. This is required both to compute the index of the first visible item in the viewport and to Arrange // children in their proper locations. // if (IsPixelBased) { itemsControl.StoreItemValue(item, child.DesiredSize, _desiredSizeStorageIndex); } } else { // Non-recyclable container; performCleanup = true; } } if (cleanupRangeStart >= 0 && cleanupCount > 0) { CleanupRange(children, Generator, cleanupRangeStart, cleanupCount); } }
// Tells the Generator to clear out all containers for this ItemsControl. This is called by the ItemValueStorage // service when the ItemsControl this panel is a host for is about to be thrown away. This allows the VSP to save // off any properties it is interested in and results in a call to ClearContainerForItem on the ItemsControl, allowing // the Item Container Storage to do so as well. // Note: A possible perf improvement may be to make 'fast' RemoveAll on the Generator that simply calls ClearContainerForItem // for us without walking through its data structures to actually clean out items. internal void ClearAllContainers(ItemsControl itemsControl) { Debug.Assert(itemsControl == ItemsControl.GetItemsOwner(this), "We can only clear containers that this panel is a host for"); IItemContainerGenerator generator = Generator; if (IsPixelBased) { IList children = RealizedChildren; UIElement child; for (int i = 0; i < children.Count; i++) { child = (UIElement)children[i]; itemsControl.StoreItemValue(((ItemContainerGenerator)generator).ItemFromContainer(child), child.DesiredSize, _desiredSizeStorageIndex); } } if (generator != null) { generator.RemoveAll(); } }