private void OnItemRemoved(object item, int position) { IGeneratorHost host = this; DependencyObject container = this.ItemsHost.Children[position]; this.ItemContainerGenerator.INTERNAL_TryUnregisterContainer(container, item); this.ItemsHost.Children.RemoveAt(position); host.ClearContainerForItem(container, item); }
internal void Refresh(bool reuseContainers) { IGeneratorHost host = this; DependencyObject[] oldContainers = new DependencyObject[Math.Max(this.ItemsHost.Children.Count, this.Items.CountInternal)]; // First we need to get the containers and their associated item, // or they will be lost before being able to clear them. int containersCount = this.ItemsHost.Children.Count; object[] oldItems = new object[containersCount]; for (int i = 0; i < containersCount; ++i) { DependencyObject container = this.ItemsHost.InternalChildren[i]; oldContainers[i] = container; oldItems[i] = this.ItemContainerGenerator.ItemFromContainer(container); } if (!reuseContainers) { this.ItemsHost.InternalChildren.Clear(); this.ItemContainerGenerator.INTERNAL_Clear(); for (int i = 0; i < containersCount; ++i) { host.ClearContainerForItem(oldContainers[i], oldItems[i]); } } int count = this.Items.CountInternal; for (int i = 0; i < count; ++i) { object item = this.Items[i]; DependencyObject recycledContainer = reuseContainers ? oldContainers[i] : null; DependencyObject container = host.GetContainerForItem(item, recycledContainer); if (!reuseContainers) { this.ItemContainerGenerator.INTERNAL_RegisterContainer(container, item); if (container != item) { container.SetValue(FrameworkElement.DataContextProperty, item); } this.ItemsHost.Children.Add((UIElement)container); } host.PrepareItemContainer(container, item); } }
internal static void UnlinkContainerFromItem(DependencyObject container, object item, IGeneratorHost host) { // When a container is removed from the tree, its future takes one of // two forms: // a) [normal mode] the container becomes eligible for GC // b) [recycling mode] the container joins the recycled list, and // possibly re-enters the tree at some point, usually with a // different item. // // As Dev10 bug 452669 and some "subtle issues" that arose in the // container recycling work illustrate, it's important that the container // and its subtree sever their connection to the data item. Otherwise // you can get aliasing - a dead container reacting to the same item as a live // container. Even without aliasing, it's a perf waste for a dead container // to continue reacting to its former data item. // // On the other hand, it's a perf waste to spend too much effort cleaning // up the container and its subtree, since they will often just get GC'd // in the near future. // // WPF initially did a full cleanup of the container, removing all properties // that were set in PrepareContainerForItem. This avoided aliasing, but // was deemed too expensive, especially for scrolling. For Windows OS Bug // 1445288, all this cleanup work was removed. This sped up scrolling, but // introduced the problems cited in Dev10 452669 and the recycling "subtle // issues". A compromise is needed. // // The compromise is tell the container to attach to a sentinel item // BindingExpressionBase.DisconnectedItem. We allow this to propagate into the // conainer's subtree through properties like DataContext and // ContentControl.Content that are normally set by PrepareItemForContainer. // A Binding that sees the sentinel as the data item will disconnect its // event listeners from the former data item, but will not change its // own value or invalidate its target property. This avoids the cost // of re-measuring most of the subtree. container.ClearValue(ItemForItemContainerProperty); // TreeView virtualization requires that we call ClearContainer before setting // the DataContext to "Disconnected". This gives the TreeViewItems a chance // to save "Item values" in the look-aside table, before that table is // discarded. (See Dev10 628778) host.ClearContainerForItem(container, item); if (container != item) { DependencyProperty dp = FrameworkElement.DataContextProperty; #if DEBUG // Some ancient code at this point handled the case when DataContext // was set via an Expression (presumably a binding). I don't think // this actually happens any more. Just in case... EntryIndex entryIndex = container.LookupEntry(dp.GlobalIndex); Debug.Assert(!container.HasExpression(entryIndex, dp), "DataContext set by expression (unexpectedly)"); #endif container.SetValue(dp, BindingExpressionBase.DisconnectedItem); } }
private void Remove(GeneratedItemContainer container) { generatedContainers.Remove(container); host.ClearContainerForItem(container.Item, container.Container); }