public UIElement GetElement(int index, bool forceCreate, bool suppressAutoRecycle) { UIElement element = forceCreate ? null : GetElementIfAlreadyHeldByLayout(index); if (element == null) { // check if this is the anchor made through repeater in preparation // for a bring into view. var madeAnchor = m_owner.MadeAnchor; if (madeAnchor != null) { var anchorVirtInfo = ItemsRepeater.TryGetVirtualizationInfo(madeAnchor); if (anchorVirtInfo.Index == index) { element = madeAnchor; } } } if (element == null) { element = GetElementFromUniqueIdResetPool(index); } if (element == null) { element = GetElementFromPinnedElements(index); } if (element == null) { element = GetElementFromElementFactory(index); } var virtInfo = ItemsRepeater.TryGetVirtualizationInfo(element); if (suppressAutoRecycle) { virtInfo.AutoRecycleCandidate = false; REPEATER_TRACE_INFO("%* GetElement: %d Not AutoRecycleCandidate: \n", m_owner.Indent(), virtInfo.Index); } else { virtInfo.AutoRecycleCandidate = true; virtInfo.KeepAlive = true; REPEATER_TRACE_INFO("%* GetElement: %d AutoRecycleCandidate: \n", m_owner.Indent(), virtInfo.Index); } return(element); }
// We optimize for the case where index is not realized to return null as quickly as we can. // Flow layouts manage containers on their own and will never ask for an index that is already realized. // If an index that is realized is requested by the layout, we unfortunately have to walk the // children. Not ideal, but a reasonable default to provide consistent behavior between virtualizing // and non-virtualizing hosts. UIElement GetElementIfAlreadyHeldByLayout(int index) { UIElement element = null; bool cachedFirstLastIndicesInvalid = m_firstRealizedElementIndexHeldByLayout == FirstRealizedElementIndexDefault; MUX_ASSERT(!cachedFirstLastIndicesInvalid || m_lastRealizedElementIndexHeldByLayout == LastRealizedElementIndexDefault); bool isRequestedIndexInRealizedRange = (m_firstRealizedElementIndexHeldByLayout <= index && index <= m_lastRealizedElementIndexHeldByLayout); if (cachedFirstLastIndicesInvalid || isRequestedIndexInRealizedRange) { // Both First and Last indices need to be valid or default. MUX_ASSERT((m_firstRealizedElementIndexHeldByLayout == FirstRealizedElementIndexDefault && m_lastRealizedElementIndexHeldByLayout == LastRealizedElementIndexDefault) || (m_firstRealizedElementIndexHeldByLayout != FirstRealizedElementIndexDefault && m_lastRealizedElementIndexHeldByLayout != LastRealizedElementIndexDefault)); var children = m_owner.Children; for (int i = 0; i < children.Count; ++i) { var child = children[i]; var virtInfo = ItemsRepeater.TryGetVirtualizationInfo(child); if (virtInfo != null && virtInfo.IsHeldByLayout) { // Only give back elements held by layout. If someone else is holding it, they will be served by other methods. int childIndex = virtInfo.Index; m_firstRealizedElementIndexHeldByLayout = Math.Min(m_firstRealizedElementIndexHeldByLayout, childIndex); m_lastRealizedElementIndexHeldByLayout = Math.Max(m_lastRealizedElementIndexHeldByLayout, childIndex); if (virtInfo.Index == index) { element = child; // If we have valid first/last indices, we don't have to walk the rest, but if we // do not, then we keep walking through the entire children collection to get accurate // indices once. if (!cachedFirstLastIndicesInvalid) { break; } } } } } return(element); }
// There are several cases handled here with respect to which element gets returned and when DataContext is modified. // // 1. If there is no ItemTemplate: // 1.1 If data is a UIElement . the data is returned // 1.2 If data is not a UIElement . a default DataTemplate is used to fetch element and DataContext is set to data** // // 2. If there is an ItemTemplate: // 2.1 If data is not a FrameworkElement . Element is fetched from ElementFactory and DataContext is set to the data** // 2.2 If data is a FrameworkElement: // 2.2.1 If Element returned by the ElementFactory is the same as the data . Element (a.k.a. data) is returned as is // 2.2.2 If Element returned by the ElementFactory is not the same as the data // . Element that is fetched from the ElementFactory is returned and // DataContext is set to the data's DataContext (if it exists), otherwise it is set to the data itself** // // **data context is set only if no x:Bind was used. ie. No data template component on the root. UIElement GetElementFromElementFactory(int index) { // The view generator is the provider of last resort. var data = m_owner.ItemsSourceView.GetAt(index); UIElement GetElement() { var elementFactory = m_owner.ItemTemplateShim; if (elementFactory == null) { if (data is UIElement dataAsElement) { return(dataAsElement); } else { // If no ItemTemplate was provided, use a default //var factory = XamlReader.Load("<DataTemplate xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'><TextBlock Text='{Binding}'/></DataTemplate>") as DataTemplate; var factory = new DataTemplate(() => { var tb = new TextBlock(); tb.SetBinding(TextBlock.TextProperty, new Binding()); return(tb); }); m_owner.ItemTemplate = factory; elementFactory = m_owner.ItemTemplateShim; } } if (m_ElementFactoryGetArgs == null) { m_ElementFactoryGetArgs = new ElementFactoryGetArgs(); } var args = m_ElementFactoryGetArgs; using var scopeGuard = Disposable.Create(() => { args.Data = null; args.Parent = null; }); args.Data = data; args.Parent = m_owner; args.Index = index; return(elementFactory.GetElement(args)); }; var element = GetElement(); var virtInfo = ItemsRepeater.TryGetVirtualizationInfo(element); if (virtInfo == null) { virtInfo = ItemsRepeater.CreateAndInitializeVirtualizationInfo(element); REPEATER_TRACE_PERF("ElementCreated"); } else { // View obtained from ElementFactory already has a VirtualizationInfo attached to it // which means that the element has been recycled and not created from scratch. REPEATER_TRACE_PERF("ElementRecycled"); } if (data != element) { // Prepare the element // If we are phasing, run phase 0 before setting DataContext. If phase 0 is not // run before setting DataContext, when setting DataContext all the phases will be // run in the OnDataContextChanged handler in code generated by the xaml compiler (code-gen). var extension = CachedVisualTreeHelpers.GetDataTemplateComponent(element); if (extension != null) { // Clear out old data. extension.Recycle(); int nextPhase = VirtualizationInfo.PhaseReachedEnd; // Run Phase 0 extension.ProcessBindings(data, index, 0 /* currentPhase */, out nextPhase); // Setup phasing information, so that Phaser can pick up any pending phases left. // Update phase on virtInfo. Set data and templateComponent only if x:Phase was used. virtInfo.UpdatePhasingInfo(nextPhase, nextPhase > 0 ? data : null, nextPhase > 0 ? extension : null); } else if (element is FrameworkElement elementAsFE) { // Set data context only if no x:Bind was used. ie. No data template component on the root. // If the passed in data is a UIElement and is different from the element returned by // the template factory then we need to propagate the DataContext. // Otherwise just set the DataContext on the element as the data. var elementDataContext = data; if (data is FrameworkElement dataAsElement) { var dataDataContext = dataAsElement.DataContext; if (dataDataContext != null) { elementDataContext = dataDataContext; } } elementAsFE.DataContext = elementDataContext; } else { MUX_ASSERT(false, "Element returned by factory is not a FrameworkElement!"); } } virtInfo.MoveOwnershipToLayoutFromElementFactory( index, /* uniqueId: */ m_owner.ItemsSourceView.HasKeyIndexMapping ? m_owner.ItemsSourceView.KeyFromIndex(index) : null); // The view generator is the only provider that prepares the element. var repeater = m_owner; #if IS_UNO //TODO: Uno specific - remove when #4689 is fixed repeater.OnUnoBeforeElementPrepared(element, index); #endif // Add the element to the children collection here before raising OnElementPrepared so // that handlers can walk up the tree in case they want to find their IndexPath in the // nested case. var children = repeater.Children; if (CachedVisualTreeHelpers.GetParent(element) != repeater) { children.Add(element); } repeater.AnimationManager.OnElementPrepared(element); repeater.OnElementPrepared(element, index); if (data != element) { m_phaser.PhaseElement(element, virtInfo); } // Update realized indices m_firstRealizedElementIndexHeldByLayout = Math.Min(m_firstRealizedElementIndexHeldByLayout, index); m_lastRealizedElementIndexHeldByLayout = Math.Max(m_lastRealizedElementIndexHeldByLayout, index); return(element); }
Control FindFocusCandidate(int clearedIndex, out UIElement focusedChild) { // Walk through all the children and find elements with index before and after the cleared index. // Note that during a delete the next element would now have the same index. int previousIndex = int.MinValue; int nextIndex = int.MaxValue; UIElement nextElement = null; UIElement previousElement = null; var children = m_owner.Children; for (int i = 0; i < children.Count; ++i) { var child = children[i]; var virtInfo = ItemsRepeater.TryGetVirtualizationInfo(child); if (virtInfo != null && virtInfo.IsHeldByLayout) { int currentIndex = virtInfo.Index; if (currentIndex < clearedIndex) { if (currentIndex > previousIndex) { previousIndex = currentIndex; previousElement = child; } } else if (currentIndex >= clearedIndex) { // Note that we use >= above because if we deleted the focused element, // the next element would have the same index now. if (currentIndex < nextIndex) { nextIndex = currentIndex; nextElement = child; } } } } // Find the next element if one exists, if not use the previous element. // If the container itself is not focusable, find a descendent that is. Control focusCandidate = null; focusedChild = null; if (nextElement != null) { focusedChild = nextElement as UIElement; focusCandidate = nextElement as Control; if (focusCandidate == null) { var firstFocus = FocusManager.FindFirstFocusableElement(nextElement); if (firstFocus != null) { focusCandidate = firstFocus as Control; } } } if (focusCandidate == null && previousElement != null) { focusedChild = previousElement as UIElement; focusCandidate = previousElement as Control; if (previousElement == null) { var lastFocus = FocusManager.FindLastFocusableElement(previousElement); if (lastFocus != null) { focusCandidate = lastFocus as Control; } } } return(focusCandidate); }