Example #1
0
        public void ValidateDataContextGetsPropagated()
        {
            const string c_element1DataContext = "Element1_DataContext";

            RunOnUIThread.Execute(() =>
            {
                var data = new List <Button>()
                {
                    new Button()
                    {
                        Content     = "Element1_Content",
                        DataContext = c_element1DataContext
                    }
                };

                var elementFactory = new ElementFromElementElementFactory();

                var repeater = new ItemsRepeater()
                {
                    ItemsSource  = data,
                    ItemTemplate = elementFactory
                };

                Content = repeater;

                Content.UpdateLayout();

                // Verify that DataContext of data has propagated to the container
                var firstElement = repeater.TryGetElement(0) as Button;
                var retrievedDataContextItem1 = firstElement.DataContext as string;
                Verify.IsTrue(data[0] == firstElement.Content);
                Verify.IsTrue(retrievedDataContextItem1 == c_element1DataContext);
            });
        }
Example #2
0
        public void VerifyClearingItemsSourceClearsElements()
        {
            var data    = new ObservableCollection <string>(Enumerable.Range(0, 4).Select(i => "Item #" + i));
            var mapping = (List <ContentControl>)null;

            RunOnUIThread.Execute(() =>
            {
                mapping = Enumerable.Range(0, data.Count).Select(i => new ContentControl {
                    Width = 40, Height = 40
                }).ToList();

                var dataSource         = MockItemsSource.CreateDataSource(data, supportsUniqueIds: false);
                var elementFactory     = MockElementFactory.CreateElementFactory(mapping);
                ItemsRepeater repeater = new ItemsRepeater();
                repeater.ItemsSource   = dataSource;
                repeater.ItemTemplate  = elementFactory;
                // This was an issue only for NonVirtualizing layouts
                repeater.Layout = new MyCustomNonVirtualizingStackLayout();
                Content         = repeater;
                Content.UpdateLayout();

                repeater.ItemsSource = null;
            });

            foreach (var item in mapping)
            {
                Verify.IsNull(item.Parent);
            }
        }
Example #3
0
        public void ValidateRepeaterDefaults()
        {
            RunOnUIThread.Execute(() =>
            {
                var repeater = new ItemsRepeater()
                {
                    ItemsSource = Enumerable.Range(0, 10).Select(i => string.Format("Item #{0}", i)),
                };

                Content = new ItemsRepeaterScrollHost()
                {
                    Width        = 400,
                    Height       = 800,
                    ScrollViewer = new ScrollViewer {
                        Content = repeater
                    }
                };

                Content.UpdateLayout();

                for (int i = 0; i < 10; i++)
                {
                    var element = repeater.TryGetElement(i);
                    Verify.IsNotNull(element);
                    Verify.AreEqual(string.Format("Item #{0}", i), ((TextBlock)element).Text);
                    Verify.AreEqual(i, repeater.GetElementIndex(element));
                }

                Verify.IsNull(repeater.TryGetElement(20));
            });
        }
Example #4
0
        public void ValidateDataContextDoesNotGetOverwritten()
        {
            const string c_element1DataContext = "Element1_DataContext";

            RunOnUIThread.Execute(() =>
            {
                var data = new List <Button>()
                {
                    new Button()
                    {
                        Content     = "Element1_Content",
                        DataContext = c_element1DataContext
                    }
                };

                var elementFactory = new DataAsElementElementFactory();

                var repeater = new ItemsRepeater()
                {
                    ItemsSource  = data,
                    ItemTemplate = elementFactory
                };

                Content = repeater;

                Content.UpdateLayout();

                // Verify that DataContext is still the same
                var firstElement = repeater.TryGetElement(0) as Button;
                var retrievedDataContextItem1 = firstElement.DataContext as string;
                Verify.IsTrue(retrievedDataContextItem1 == c_element1DataContext);
            });
        }
        private void SetupRepeater(IList data, bool phased)
        {
            var repeater = new ItemsRepeater();

            repeater.ItemsSource = data;
            if (phased)
            {
                repeater.ItemTemplate = (ElementFactory)Resources["SharedElementFactoryPhased"];
            }
            else
            {
                repeater.ItemTemplate = (ElementFactory)Resources["SharedElementFactoryBinding"];
            }

            if (data is IList <Recipe> )
            {
                repeater.Layout = (VirtualizingLayout)Resources["SharedFlowLayout"];
            }
            else
            {
                repeater.Layout = (VirtualizingLayout)Resources["SharedStackLayout"];
            }

            repeater.VerticalCacheLength       = 4.0;
            repeater.XYFocusKeyboardNavigation = Windows.UI.Xaml.Input.XYFocusKeyboardNavigationMode.Enabled;
            var tracker      = new ItemsRepeaterScrollHost();
            var scrollViewer = new Windows.UI.Xaml.Controls.ScrollViewer();

            tracker.ScrollViewer   = scrollViewer;
            scrollViewer.Content   = repeater;
            scrollViewer.IsTabStop = false;
            host.Child             = tracker;

            _printChildrenCount = () => VisualTreeHelper.GetChildrenCount(repeater).ToString();
        }
Example #6
0
        // [TestMethod] Issue 1018
        public void ValidateFocusMoveOnElementClearedWithUniqueIds()
        {
            ItemsRepeater     repeater   = null;
            CustomItemsSource dataSource = null;

            RunOnUIThread.Execute(() =>
            {
                dataSource = new CustomItemsSourceWithUniqueId(Enumerable.Range(0, 5).ToList());
            });

            repeater = SetupRepeater(dataSource, "<Button Content='{Binding}' Height='10' />");

            // dataSource: 0 1 2 3 4
            // Index 0 deleted, focus should be on the new element which has index 0
            SharedHelpers.RunActionsWithWait(
                new Action[]
            {
                () => { MoveFocusToIndex(repeater, 0); },
                () => { dataSource.Remove(0 /* index */, 1 /* count */, true /* reset*/); },
                () => { ValidateCurrentFocus(repeater, 0 /*expectedIndex */, "1" /* expectedContent */); }
            });

            // dataSource: 1 2 3 4
            // Last element deleted, focus should move to the previous element
            int lastIndex = dataSource.Inner.Count - 1;

            SharedHelpers.RunActionsWithWait(
                new Action[]
            {
                () => { MoveFocusToIndex(repeater, lastIndex); },
                () => { dataSource.Remove(lastIndex /* index */, 1 /* count */, true /* reset*/); },
                () => { ValidateCurrentFocus(repeater, 2 /*expectedIndex */, "3" /* expectedContent */); }
            });

            // dataSource: 1 2 3
            // Reset should keep the focused element as long as the unique id matches.
            SharedHelpers.RunActionsWithWait(
                new Action[]
            {
                () => { MoveFocusToIndex(repeater, 0); },
                () => { dataSource.Reset(); },
                () =>
                {
                    int newIndex = dataSource.Inner.IndexOf(1);
                    ValidateCurrentFocus(repeater, newIndex /*expectedIndex */, "1" /* expectedContent */);
                }
            });

            // dataSource: 1 2 3
            // Remove multiple elements
            SharedHelpers.RunActionsWithWait(
                new Action[]
            {
                () => { MoveFocusToIndex(repeater, 0); },
                () => { dataSource.Remove(0 /* index */, 2 /* count */, true /* reset*/); },
                () => { ValidateCurrentFocus(repeater, 0 /*expectedIndex */, "3" /* expectedContent */); }
            });
        }
Example #7
0
        private void ValidateCurrentFocus(ItemsRepeater repeater, int expectedIndex, string expectedContent)
        {
            var currentFocus        = FocusManager.GetFocusedElement() as ContentControl;
            var currentFocusedIndex = repeater.GetElementIndex(currentFocus);

            Log.Comment("expectedIndex: " + expectedIndex + " actual : " + currentFocusedIndex);
            Verify.AreEqual(expectedIndex, currentFocusedIndex);
            Log.Comment("expectedContent: " + expectedContent + " actual : " + currentFocus.Content.ToString());
            Verify.AreEqual(expectedContent, currentFocus.Content.ToString());
        }
Example #8
0
 public void ValidateGetSetItemsSource()
 {
     RunOnUIThread.Execute(() =>
     {
         ItemsRepeater repeater = new ItemsRepeater();
         var dataSource         = new InspectingDataSource(Enumerable.Range(0, 10).Select(i => string.Format("Item #{0}", i)));
         repeater.SetValue(ItemsRepeater.ItemsSourceProperty, dataSource);
         Verify.AreSame(dataSource, repeater.GetValue(ItemsRepeater.ItemsSourceProperty) as ItemsSourceView);
         Verify.AreSame(dataSource, repeater.ItemsSourceView);
     });
 }
Example #9
0
        private ItemsRepeater CreateRepeater(object dataSource, object elementFactory)
        {
            var repeater = new ItemsRepeater
            {
                ItemsSource  = dataSource,
                ItemTemplate = elementFactory
            };

            repeater.Layout = CreateLayout(repeater);
            return(repeater);
        }
Example #10
0
        public void ValidateGetSetBackground()
        {
            RunOnUIThread.Execute(() =>
            {
                ItemsRepeater repeater = new ItemsRepeater();
                var redBrush           = new SolidColorBrush(Colors.Red);
                repeater.SetValue(ItemsRepeater.BackgroundProperty, redBrush);
                Verify.AreSame(redBrush, repeater.GetValue(ItemsRepeater.BackgroundProperty) as Brush);
                Verify.AreSame(redBrush, repeater.Background);

                var blueBrush       = new SolidColorBrush(Colors.Blue);
                repeater.Background = blueBrush;
                Verify.AreSame(blueBrush, repeater.Background);
            });
        }
Example #11
0
        public void VerifyUIElementsInItemsSource()
        {
            ItemsRepeater repeater = null;

            RunOnUIThread.Execute(() =>
            {
                var scrollhost = (ItemsRepeaterScrollHost)XamlReader.Load(
                    @"<controls:ItemsRepeaterScrollHost  
					 xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'
					 xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
					 xmlns:local='using:MUXControlsTestApp.Samples'
					 xmlns:controls='using:Microsoft.UI.Xaml.Controls'>
						<ScrollViewer>
							<controls:ItemsRepeater x:Name='repeater'>
								<controls:ItemsRepeater.ItemsSource>
									<local:UICollection>
										<Button>0</Button>
										<Button>1</Button>
										<Button>2</Button>
										<Button>3</Button>
										<Button>4</Button>
										<Button>5</Button>
										<Button>6</Button>
										<Button>7</Button>
										<Button>8</Button>
										<Button>9</Button>
									</local:UICollection>
								</controls:ItemsRepeater.ItemsSource>
							</controls:ItemsRepeater>
						</ScrollViewer>
					</controls:ItemsRepeaterScrollHost>"                    );

                repeater = (ItemsRepeater)scrollhost.FindName("repeater");
                Content  = scrollhost;
            });

            IdleSynchronizer.Wait();

            RunOnUIThread.Execute(() =>
            {
                for (int i = 0; i < 10; i++)
                {
                    var element = repeater.TryGetElement(i) as Button;
                    Verify.AreEqual(i.ToString(), element.Content);
                }
            });
        }
Example #12
0
        public void ValidateElementToIndexMapping()
        {
            ItemsRepeater repeater = null;

            RunOnUIThread.Execute(() =>
            {
                var elementFactory               = new RecyclingElementFactory();
                elementFactory.RecyclePool       = new RecyclePool();
                elementFactory.Templates["Item"] = (DataTemplate)XamlReader.Load(
                    @"<DataTemplate xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'> 
						  <TextBlock Text='{Binding}' Height='50' />
					  </DataTemplate>"                    );

                repeater = new ItemsRepeater()
                {
                    ItemsSource  = Enumerable.Range(0, 10).Select(i => string.Format("Item #{0}", i)),
                    ItemTemplate = elementFactory,
                    // Default is StackLayout, so do not have to explicitly set.
                    // Layout = new StackLayout(),
                };

                Content = new ItemsRepeaterScrollHost()
                {
                    Width        = 400,
                    Height       = 800,
                    ScrollViewer = new ScrollViewer {
                        Content = repeater
                    }
                };

                Content.UpdateLayout();

                for (int i = 0; i < 10; i++)
                {
                    var element = repeater.TryGetElement(i);
                    Verify.IsNotNull(element);
                    Verify.AreEqual(string.Format("Item #{0}", i), ((TextBlock)element).Text);
                    Verify.AreEqual(i, repeater.GetElementIndex(element));
                }

                Verify.IsNull(repeater.TryGetElement(20));
            });
        }
Example #13
0
        public static ImplicitAnimationCollection CreateReorderAnimationCollection(MUXC.ItemsRepeater itemsRepeater)
        {
            Compositor compositor = ElementCompositionPreview.GetElementVisual(itemsRepeater).Compositor;
            Vector3KeyFrameAnimation offsetAnimation = compositor.CreateVector3KeyFrameAnimation();

            offsetAnimation.InsertExpressionKeyFrame(1.0f, "this.FinalValue");
            offsetAnimation.Duration = TimeSpan.FromMilliseconds(ReorderAnimationDuration);
            offsetAnimation.Target   = nameof(Visual.Offset);

            CompositionAnimationGroup animationGroup = compositor.CreateAnimationGroup();

            animationGroup.Add(offsetAnimation);

            ImplicitAnimationCollection animationCollection = compositor.CreateImplicitAnimationCollection();

            animationCollection[nameof(Visual.Offset)] = animationGroup;

            return(animationCollection);
        }
Example #14
0
        public void CanResetLayoutAfterUniqueIdReset()
        {
            var    data       = new WinRTCollection(Enumerable.Range(0, 2).Select(i => string.Format("Item #{0}", i)));
            object dataSource = null;

            RunOnUIThread.Execute(() => dataSource = MockItemsSource.CreateDataSource(data, supportsUniqueIds: true));
            ItemsRepeater repeater = SetupRepeater(dataSource);

            RunOnUIThread.Execute(() =>
            {
                var range           = new UIElement[] { repeater.TryGetElement(0), repeater.TryGetElement(1) };
                var clearedElements = new List <UIElement>();

                repeater.ElementClearing += (s, e) =>
                {
                    clearedElements.Add(e.Element);
                };

                // The realized elements will be sent to the unique id reset pool.
                // They haven't been cleared yet.
                data.Reset();
                Verify.AreEqual(0, clearedElements.Count);

                // This also cause elements to be sent to the unique id reset pool.
                // We are validating here that we are smart enough not send them there twice.
                // Doing so will cause an exception to be thrown.
                repeater.Layout = null;
                Verify.AreEqual(0, clearedElements.Count);

                repeater.UpdateLayout();

                // Layout runs. The elements in the reset pool are not used.
                // They should be cleared back to the view generator at this point.
                Verify.AreEqual(2, clearedElements.Count);
                Verify.AreEqual(range[0], clearedElements[0]);
                Verify.AreEqual(range[1], clearedElements[1]);
                Verify.IsNull(repeater.TryGetElement(0));
                Verify.IsNull(repeater.TryGetElement(1));
            });
        }
Example #15
0
        public void CanSetItemsSource()
        {
            // In bug 12042052, we crash when we set ItemsSource to null because we try to subscribe to
            // the DataSourceChanged event on a null instance.
            RunOnUIThread.Execute(() =>
            {
                {
                    var repeater         = new ItemsRepeater();
                    repeater.ItemsSource = null;
                    repeater.ItemsSource = Enumerable.Range(0, 5).Select(i => string.Format("Item #{0}", i));
                }

                {
                    var repeater         = new ItemsRepeater();
                    repeater.ItemsSource = Enumerable.Range(0, 5).Select(i => string.Format("Item #{0}", i));
                    repeater.ItemsSource = Enumerable.Range(5, 5).Select(i => string.Format("Item #{0}", i));
                    repeater.ItemsSource = null;
                    repeater.ItemsSource = Enumerable.Range(10, 5).Select(i => string.Format("Item #{0}", i));
                    repeater.ItemsSource = null;
                }
            });
        }
Example #16
0
        public void CanChangeFocusAfterUniqueIdReset()
        {
            var    data       = new WinRTCollection(Enumerable.Range(0, 2).Select(i => string.Format("Item #{0}", i)));
            object dataSource = null;

            RunOnUIThread.Execute(() => dataSource = MockItemsSource.CreateDataSource(data, supportsUniqueIds: true));
            ItemsRepeater repeater       = SetupRepeater(dataSource);
            Control       focusedElement = null;

            RunOnUIThread.Execute(() =>
            {
                focusedElement = (Control)repeater.TryGetElement(0);
                focusedElement.Focus(FocusState.Keyboard);
            });

            IdleSynchronizer.Wait();
            RunOnUIThread.Execute(() =>
            {
                data.Reset();
            });

            IdleSynchronizer.Wait();
            RunOnUIThread.Execute(() =>
            {
                // Still focused.
                Verify.AreEqual(focusedElement, FocusManager.GetFocusedElement());

                // Change focused element.
                focusedElement = (Control)repeater.TryGetElement(1);
                focusedElement.Focus(FocusState.Keyboard);
            });

            IdleSynchronizer.Wait();
            RunOnUIThread.Execute(() =>
            {
                // Focus is on the new element.
                Verify.AreEqual(focusedElement, FocusManager.GetFocusedElement());
            });
        }
Example #17
0
        // Why does this test work?
        // When the elements get created from the RecyclingElementFactory, we get already "existing" data templates.
        // However, the reason for the crash in #2384 is that those "empty" data templates actually still had their data context
        // If that data context is not null, that means it did not get cleared when the element was recycled, which is the wrong behavior.
        // To check if the clearing is working correctly, we are checking this inside the ElementFactory's RecycleElement function.
        public void ValidateElementClearingClearsDataContext()
        {
            ItemsRepeater      repeater       = null;
            MockElementFactory elementFactory = null;
            int elementClearingRaisedCount    = 0;

            Log.Comment("Initialize ItemsRepeater");
            RunOnUIThread.Execute(() =>
            {
                elementFactory = new MockElementFactory()
                {
                    GetElementFunc = delegate(int index, UIElement owner) {
                        return(new Button()
                        {
                            Content = index
                        });
                    },

                    ClearElementFunc = delegate(UIElement element, UIElement owner) {
                        elementClearingRaisedCount++;
                        Verify.IsNull((element as FrameworkElement).DataContext);
                    }
                };

                repeater = CreateRepeater(Enumerable.Range(0, 100),
                                          elementFactory);

                repeater.Layout = new StackLayout();

                Content = repeater;
                repeater.UpdateLayout();

                repeater.ItemsSource = null;

                Log.Comment("Verify ItemsRepeater cleared data contexts correctly");
                Verify.IsTrue(elementClearingRaisedCount > 0, "ItemsRepeater should have cleared some elements");
            });
        }
Example #18
0
        private ItemsRepeater SetupRepeater(object dataSource, VirtualizingLayout layout, string itemContent, out ScrollViewer scrollViewer)
        {
            ItemsRepeater repeater = null;
            ScrollViewer  sv       = null;

            RunOnUIThread.Execute(() =>
            {
                var elementFactory               = new RecyclingElementFactory();
                elementFactory.RecyclePool       = new RecyclePool();
                elementFactory.Templates["Item"] = (DataTemplate)XamlReader.Load(
                    @"<DataTemplate xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'> " + itemContent + @"</DataTemplate>");

                repeater = new ItemsRepeater()
                {
                    ItemsSource           = dataSource,
                    ItemTemplate          = elementFactory,
                    Layout                = layout,
                    HorizontalCacheLength = 0.0,
                    VerticalCacheLength   = 0.0
                };

                sv = new ScrollViewer
                {
                    Content = repeater
                };

                Content = new ItemsRepeaterScrollHost()
                {
                    Width        = 200,
                    Height       = 200,
                    ScrollViewer = sv
                };
            });

            IdleSynchronizer.Wait();
            scrollViewer = sv;
            return(repeater);
        }
Example #19
0
        private VirtualizingLayout CreateLayout(ItemsRepeater repeater)
        {
            var layout   = new MockVirtualizingLayout();
            var children = new List <UIElement>();

            layout.MeasureLayoutFunc = (availableSize, context) =>
            {
                repeater.Tag = repeater.Tag ?? context;
                children.Clear();
                var itemCount = context.ItemCount;

                for (int i = 0; i < itemCount; ++i)
                {
                    var element = context.GetOrCreateElementAt(i);
                    element.Measure(availableSize);
                    children.Add(element);
                }

                return(new Size(10, 10));
            };

            return(layout);
        }
Example #20
0
        public ViewportManagerWithPlatformFeatures(ItemsRepeater owner)
        {
            // ItemsRepeater is not fully constructed yet. Don't interact with it.

            m_owner = owner;
        }
Example #21
0
        public void VerifyStoreScenarioCache()
        {
            ItemsRepeater rootRepeater = null;

            RunOnUIThread.Execute(() =>
            {
                var scrollhost = (ItemsRepeaterScrollHost)XamlReader.Load(
                    @" <controls:ItemsRepeaterScrollHost Width='400' Height='200'
						xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'
						xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
						xmlns:controls='using:Microsoft.UI.Xaml.Controls'>
						<controls:ItemsRepeaterScrollHost.Resources>
							<DataTemplate x:Key='ItemTemplate' >
								<TextBlock Text='{Binding}' Height='100' Width='100'/>
							</DataTemplate>
							<DataTemplate x:Key='GroupTemplate'>
								<StackPanel>
									<TextBlock Text='{Binding}' />
									<controls:ItemsRepeaterScrollHost>
										<ScrollViewer HorizontalScrollMode='Enabled' VerticalScrollMode='Disabled' HorizontalScrollBarVisibility='Auto' VerticalScrollBarVisibility='Hidden'>
											<controls:ItemsRepeater ItemTemplate='{StaticResource ItemTemplate}' ItemsSource='{Binding}'>
												<controls:ItemsRepeater.Layout>
													<controls:StackLayout Orientation='Horizontal' />
												</controls:ItemsRepeater.Layout>
											</controls:ItemsRepeater>
										</ScrollViewer>
									</controls:ItemsRepeaterScrollHost>
								</StackPanel>
							</DataTemplate>
						</controls:ItemsRepeaterScrollHost.Resources>
						<ScrollViewer x:Name='scrollviewer'>
							<controls:ItemsRepeater x:Name='rootRepeater' ItemTemplate='{StaticResource GroupTemplate}'/>
						</ScrollViewer>
					</controls:ItemsRepeaterScrollHost>"                    );

                rootRepeater = (ItemsRepeater)scrollhost.FindName("rootRepeater");

                List <List <int> > items = new List <List <int> >();
                for (int i = 0; i < 100; i++)
                {
                    items.Add(Enumerable.Range(0, 4).ToList());
                }
                rootRepeater.ItemsSource = items;
                Content = scrollhost;
            });

            IdleSynchronizer.Wait();

            // Verify that first items outside the visible range but in the realized range
            // for the inner of the nested repeaters are realized.
            RunOnUIThread.Execute(() =>
            {
                // Group2 will be outside the visible range but within the realized range.
                var group2 = rootRepeater.TryGetElement(2) as StackPanel;
                Verify.IsNotNull(group2);

                var group2Repeater = ((ItemsRepeaterScrollHost)group2.Children[1]).ScrollViewer.Content as ItemsRepeater;
                Verify.IsNotNull(group2Repeater);

                Verify.IsNotNull(group2Repeater.TryGetElement(0));
            });
        }
Example #22
0
        // [TestMethod] Issue #1018
        public void CanPinFocusedElements()
        {
            // Setup a grouped repeater scenario with two groups each containing two items.
            var data = new ObservableCollection <ObservableCollection <string> >(Enumerable
                                                                                 .Range(0, 2)
                                                                                 .Select(i => new ObservableCollection <string>(Enumerable
                                                                                                                                .Range(0, 2)
                                                                                                                                .Select(j => string.Format("Item #{0}.{1}", i, j)))));

            List <ContentControl>[] itemElements   = null;
            ItemsRepeater[]         innerRepeaters = null;
            List <StackPanel>       groupElements  = null;
            ItemsRepeater           rootRepeater   = null;
            var gotFocus = new ManualResetEvent(false);

            RunOnUIThread.Execute(() =>
            {
                itemElements = new[] {
                    Enumerable.Range(0, 2).Select(i => new ContentControl()).ToList(),
                    Enumerable.Range(0, 2).Select(i => new ContentControl()).ToList()
                };

                itemElements[0][0].GotFocus += delegate { gotFocus.Set(); };

                innerRepeaters = Enumerable.Range(0, 2).Select(i => CreateRepeater(
                                                                   MockItemsSource.CreateDataSource(data[i], supportsUniqueIds: false),
                                                                   MockElementFactory.CreateElementFactory(itemElements[i]))).ToArray();

                groupElements = Enumerable.Range(0, 2).Select(i =>
                {
                    var panel = new StackPanel();
                    panel.Children.Add(new ContentControl());
                    panel.Children.Add(innerRepeaters[i]);
                    return(panel);
                }).ToList();

                rootRepeater = CreateRepeater(
                    MockItemsSource.CreateDataSource(data, supportsUniqueIds: false),
                    MockElementFactory.CreateElementFactory(groupElements));
                Content = rootRepeater;
                rootRepeater.UpdateLayout();

                itemElements[0][0].Focus(FocusState.Keyboard);
            });

            Verify.IsTrue(gotFocus.WaitOne(DefaultWaitTimeInMS), "Waiting for focus event on the first element of the first group.");
            IdleSynchronizer.Wait();

            RunOnUIThread.Execute(() =>
            {
                Log.Comment("Recycle focused element 0.0 and validate it's still realized because it is pinned.");
                {
                    var ctx = (VirtualizingLayoutContext)innerRepeaters[0].Tag;
                    ctx.RecycleElement(itemElements[0][0]);
                    Verify.AreEqual(0, innerRepeaters[0].GetElementIndex(itemElements[0][0]));
                }

                Log.Comment("Recycle element 0.1 and validate it's no longer realized because it is not pinned.");
                {
                    var ctx = (VirtualizingLayoutContext)innerRepeaters[0].Tag;
                    ctx.RecycleElement(itemElements[0][1]);
                    Verify.AreEqual(-1, innerRepeaters[0].GetElementIndex(itemElements[0][1]));
                }

                Log.Comment("Recycle group 0 and validate it's still realized because one of its items is pinned.");
                {
                    var ctx = (VirtualizingLayoutContext)rootRepeater.Tag;
                    ctx.RecycleElement(groupElements[0]);
                    Verify.AreEqual(0, rootRepeater.GetElementIndex(groupElements[0]));
                }

                itemElements[1][1].GotFocus += delegate { gotFocus.Set(); };
                itemElements[1][1].Focus(FocusState.Keyboard);
            });

            Verify.IsTrue(gotFocus.WaitOne(DefaultWaitTimeInMS), "Waiting for focus event on the second element of the second group.");
            IdleSynchronizer.Wait();

            RunOnUIThread.Execute(() =>
            {
                Log.Comment(@"Move focus to item 1.1 and validate item 0.0 and group 0 are recycled because 
				 the only thing keeping them around is the fact that item 0.0 was focus pinned"                );
                {
                    ((VirtualizingLayoutContext)rootRepeater.Tag).RecycleElement(groupElements[0]);
                    ((VirtualizingLayoutContext)innerRepeaters[0].Tag).RecycleElement(itemElements[0][0]);

                    Verify.AreEqual(-1, rootRepeater.GetElementIndex(groupElements[0]));
                    Verify.AreEqual(-1, innerRepeaters[0].GetElementIndex(itemElements[0][0]));
                    Verify.AreEqual(1, innerRepeaters[0].GetElementIndex(itemElements[1][1]));
                }

                Log.Comment(@"Delete item 1.1 from the data. This will force the element to get recycled even if it's pinned.");
                {
                    data[1].RemoveAt(1);
                    rootRepeater.UpdateLayout();

                    Verify.AreEqual(-1, innerRepeaters[1].GetElementIndex(itemElements[1][1]));
                }
            });
        }
Example #23
0
        public void VerifyCorrectionsInNonScrollableDirection()
        {
            ItemsRepeater           rootRepeater = null;
            ScrollViewer            scrollViewer = null;
            ItemsRepeaterScrollHost scrollhost   = null;
            ManualResetEvent        viewChanged  = new ManualResetEvent(false);

            RunOnUIThread.Execute(() =>
            {
                scrollhost = (ItemsRepeaterScrollHost)XamlReader.Load(
                    @"<controls:ItemsRepeaterScrollHost Width='400' Height='600'
					 xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'
					 xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
					 xmlns:controls='using:Microsoft.UI.Xaml.Controls'>
					<ScrollViewer Width='400' Height='400' x:Name='scrollviewer'>
						<controls:ItemsRepeater x:Name='repeater'>
							<DataTemplate>
								<StackPanel>
									<controls:ItemsRepeater ItemsSource='{Binding}'>
										<controls:ItemsRepeater.Layout>
											<controls:StackLayout Orientation='Horizontal' />
										</controls:ItemsRepeater.Layout>
									</controls:ItemsRepeater>
								</StackPanel>
							</DataTemplate>
						</controls:ItemsRepeater>
					</ScrollViewer>
				</controls:ItemsRepeaterScrollHost>"                );

                rootRepeater              = (ItemsRepeater)scrollhost.FindName("repeater");
                scrollViewer              = (ScrollViewer)scrollhost.FindName("scrollviewer");
                scrollViewer.ViewChanged += (sender, args) =>
                {
                    if (!args.IsIntermediate)
                    {
                        viewChanged.Set();
                    }
                };

                List <List <int> > items = new List <List <int> >();
                for (int i = 0; i < 100; i++)
                {
                    items.Add(Enumerable.Range(0, 4).ToList());
                }
                rootRepeater.ItemsSource = items;
                Content = scrollhost;
            });

            // scroll down several times and validate no crash
            for (int i = 1; i < 5; i++)
            {
                IdleSynchronizer.Wait();
                RunOnUIThread.Execute(() =>
                {
                    scrollViewer.ChangeView(null, i * 200, null);
                });

                Verify.IsTrue(viewChanged.WaitOne(DefaultWaitTimeInMS));
                viewChanged.Reset();
            }
        }
Example #24
0
        public void VerifyFocusedItemIsRecycledOnCollectionReset()
        {
            List <Layout> layouts = new List <Layout>();

            RunOnUIThread.Execute(() =>
            {
                layouts.Add(new MyCustomNonVirtualizingStackLayout());
                layouts.Add(new StackLayout());
            });

            foreach (var layout in layouts)
            {
                List <string> items = new List <string> {
                    "item0", "item1", "item2", "item3", "item4", "item5", "item6", "item7", "item8", "item9"
                };
                const int     targetIndex = 4;
                string        targetItem  = items[targetIndex];
                ItemsRepeater repeater    = null;

                RunOnUIThread.Execute(() =>
                {
                    repeater = new ItemsRepeater()
                    {
                        ItemsSource  = items,
                        ItemTemplate = CreateDataTemplateWithContent(@"<Button Content='{Binding}'/>"),
                        Layout       = layout
                    };
                    Content = repeater;
                });

                IdleSynchronizer.Wait();

                RunOnUIThread.Execute(() =>
                {
                    Log.Comment("Setting Focus on item " + targetIndex);
                    Button toFocus = (Button)repeater.TryGetElement(targetIndex);
                    Verify.AreEqual(targetItem, toFocus.Content as string);
                    toFocus.Focus(FocusState.Keyboard);
                });

                IdleSynchronizer.Wait();

                RunOnUIThread.Execute(() =>
                {
                    Log.Comment("Removing focused element from collection");
                    items.Remove(targetItem);

                    Log.Comment("Reset the collection with an empty list");
                    repeater.ItemsSource = new List <string>();
                });

                IdleSynchronizer.Wait();

                RunOnUIThread.Execute(() =>
                {
                    Log.Comment("Verify new elements");
                    for (int i = 0; i < items.Count; i++)
                    {
                        Button currentButton = (Button)repeater.TryGetElement(i);
                        Verify.IsNull(currentButton);
                    }
                });
            }
        }
Example #25
0
        private void NestedRepeaterWithDataTemplateScenario(bool disableAnimation)
        {
            if (!disableAnimation && PlatformConfiguration.IsOsVersionGreaterThanOrEqual(OSVersion.Redstone5))
            {
                Log.Warning("This test is showing consistent issues with not scrolling enough on RS5 and 19H1 when animations are enabled, tracked by microsoft-ui-xaml#779");
                return;
            }

            // Example of how to include debug tracing in an ApiTests.ItemsRepeater test's output.
            // using (PrivateLoggingHelper privateLoggingHelper = new PrivateLoggingHelper("Repeater"))
            // {
            ItemsRepeater    rootRepeater = null;
            ScrollViewer     scrollViewer = null;
            ManualResetEvent viewChanged  = new ManualResetEvent(false);

            RunOnUIThread.Execute(() =>
            {
                var anchorProvider = (ItemsRepeaterScrollHost)XamlReader.Load(
                    @"<controls:ItemsRepeaterScrollHost Width='400' Height='600'
						xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'
						xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
						xmlns:controls='using:Microsoft.UI.Xaml.Controls'>
					<controls:ItemsRepeaterScrollHost.Resources>
						<DataTemplate x:Key='ItemTemplate' >
							<TextBlock Text='{Binding}' />
						</DataTemplate>
						<DataTemplate x:Key='GroupTemplate'>
							<StackPanel>
								<TextBlock Text='{Binding}' />
								<controls:ItemsRepeater ItemTemplate='{StaticResource ItemTemplate}' ItemsSource='{Binding}' VerticalCacheLength='0'/>
							</StackPanel>
						</DataTemplate>
					</controls:ItemsRepeaterScrollHost.Resources>
					<ScrollViewer x:Name='scrollviewer'>
						<controls:ItemsRepeater x:Name='rootRepeater' ItemTemplate='{StaticResource GroupTemplate}' VerticalCacheLength='0' />
					</ScrollViewer>
				</controls:ItemsRepeaterScrollHost>"                );

                rootRepeater              = (ItemsRepeater)anchorProvider.FindName("rootRepeater");
                rootRepeater.SizeChanged += (sender, args) =>
                {
                    Log.Comment($"SizeChanged: Size=({rootRepeater.ActualWidth} x {rootRepeater.ActualHeight})");
                };

                scrollViewer = (ScrollViewer)anchorProvider.FindName("scrollviewer");
                scrollViewer.ViewChanging += (sender, args) =>
                {
                    Log.Comment($"ViewChanging: Next VerticalOffset={args.NextView.VerticalOffset}, Final VerticalOffset={args.FinalView.VerticalOffset}");
                };
                scrollViewer.ViewChanged += (sender, args) =>
                {
                    Log.Comment($"ViewChanged: VerticalOffset={scrollViewer.VerticalOffset}, IsIntermediate={args.IsIntermediate}");

                    if (!args.IsIntermediate)
                    {
                        viewChanged.Set();
                    }
                };

                var itemsSource = new ObservableCollection <ObservableCollection <int> >();
                for (int i = 0; i < 100; i++)
                {
                    itemsSource.Add(new ObservableCollection <int>(Enumerable.Range(0, 5)));
                }
                ;

                rootRepeater.ItemsSource = itemsSource;
                Content = anchorProvider;
            });

            // scroll down several times to cause recycling of elements
            for (int i = 1; i < 10; i++)
            {
                IdleSynchronizer.Wait();
                RunOnUIThread.Execute(() =>
                {
                    Log.Comment($"Size=({rootRepeater.ActualWidth} x {rootRepeater.ActualHeight})");
                    Log.Comment($"ChangeView(VerticalOffset={i * 200})");
                    scrollViewer.ChangeView(null, i * 200, null, disableAnimation);
                });

                Log.Comment("Waiting for view change completion...");
                Verify.IsTrue(viewChanged.WaitOne(DefaultWaitTimeInMS));
                viewChanged.Reset();
                Log.Comment("View change completed");

                RunOnUIThread.Execute(() =>
                {
                    Verify.AreEqual(i * 200, scrollViewer.VerticalOffset);
                });
            }
            // }
        }
Example #26
0
        public void VerifyCurrentAnchor()
        {
            //if (PlatformConfiguration.IsDebugBuildConfiguration())
            //{
            //	// Test is failing in chk configuration due to:
            //	// Bug #1726 Test Failure: RepeaterTests.VerifyCurrentAnchor
            //	Log.Warning("Skipping test for Debug builds.");
            //	return;
            //}

            ItemsRepeater           rootRepeater = null;
            ScrollViewer            scrollViewer = null;
            ItemsRepeaterScrollHost scrollhost   = null;
            ManualResetEvent        viewChanged  = new ManualResetEvent(false);

            RunOnUIThread.Execute(() =>
            {
                scrollhost = (ItemsRepeaterScrollHost)XamlReader.Load(
                    @"<controls:ItemsRepeaterScrollHost Width='400' Height='600'
					 xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'
					 xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
					 xmlns:controls='using:Microsoft.UI.Xaml.Controls'>
					<controls:ItemsRepeaterScrollHost.Resources>
						<DataTemplate x:Key='ItemTemplate' >
							<TextBlock Text='{Binding}' Height='50'/>
						</DataTemplate>
					</controls:ItemsRepeaterScrollHost.Resources>
					<ScrollViewer x:Name='scrollviewer'>
						<controls:ItemsRepeater x:Name='rootRepeater' ItemTemplate='{StaticResource ItemTemplate}' VerticalCacheLength='0' />
					</ScrollViewer>
				</controls:ItemsRepeaterScrollHost>"                );

                rootRepeater              = (ItemsRepeater)scrollhost.FindName("rootRepeater");
                scrollViewer              = (ScrollViewer)scrollhost.FindName("scrollviewer");
                scrollViewer.ViewChanged += (sender, args) =>
                {
                    if (!args.IsIntermediate)
                    {
                        viewChanged.Set();
                    }
                };

                rootRepeater.ItemsSource = Enumerable.Range(0, 500);
                Content = scrollhost;
            });

            // scroll down several times and validate current anchor
            for (int i = 1; i < 10; i++)
            {
                IdleSynchronizer.Wait();
                RunOnUIThread.Execute(() =>
                {
                    scrollViewer.ChangeView(null, i * 200, null);
                });

                Verify.IsTrue(viewChanged.WaitOne(DefaultWaitTimeInMS));
                viewChanged.Reset();
                IdleSynchronizer.Wait();

                RunOnUIThread.Execute(() =>
                {
                    Verify.AreEqual(i * 200, scrollViewer.VerticalOffset);
                    var anchor = PlatformConfiguration.IsOSVersionLessThan(OSVersion.Redstone5) ?
                                 scrollhost.CurrentAnchor :
                                 scrollViewer.CurrentAnchor;
                    var anchorIndex = rootRepeater.GetElementIndex(anchor);
                    Log.Comment("CurrentAnchor: " + anchorIndex);
                    Verify.AreEqual(i * 4, anchorIndex);
                });
            }
        }
Example #27
0
        Control FindFocusCandidate(int clearedIndex, 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;

            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;
                    }
                }
            }

            // TODO UNO: Case declared as useless by intellisense
            //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);
        }
Example #28
0
 public PinnedElementInfo(UIElement element)
 {
     m_pinnedElement = element;
     m_virtInfo      = ItemsRepeater.GetVirtualizationInfo(element);
 }
Example #29
0
        public void OnItemsSourceChanged(object _, NotifyCollectionChangedEventArgs args)
        {
            // Note: For items that have been removed, the index will not be touched. It will hold
            // the old index before it was removed. It is not valid anymore.
            switch (args.Action)
            {
            case NotifyCollectionChangedAction.Add:
            {
                var newIndex = args.NewStartingIndex;
                var newCount = args.NewItems.Count;
                EnsureFirstLastRealizedIndices();
                if (newIndex <= m_lastRealizedElementIndexHeldByLayout)
                {
                    m_lastRealizedElementIndexHeldByLayout += newCount;
                    var children   = m_owner.Children;
                    var childCount = children.Count;
                    for (int i = 0; i < childCount; ++i)
                    {
                        var element   = children[i];
                        var virtInfo  = ItemsRepeater.GetVirtualizationInfo(element);
                        var dataIndex = virtInfo.Index;

                        if (virtInfo.IsRealized && dataIndex >= newIndex)
                        {
                            UpdateElementIndex(element, virtInfo, dataIndex + newCount);
                        }
                    }
                }
                else
                {
                    // Indices held by layout are not affected
                    // We could still have items in the pinned elements that need updates. This is usually a very small vector.
                    for (int i = 0; i < m_pinnedPool.Count; ++i)
                    {
                        var elementInfo = m_pinnedPool[i];
                        var virtInfo    = elementInfo.VirtualizationInfo;
                        var dataIndex   = virtInfo.Index;

                        if (virtInfo.IsRealized && dataIndex >= newIndex)
                        {
                            var element = elementInfo.PinnedElement;
                            UpdateElementIndex(element, virtInfo, dataIndex + newCount);
                        }
                    }
                }

                break;
            }

            case NotifyCollectionChangedAction.Replace:
            {
                // Requirement: oldStartIndex == newStartIndex. It is not a replace if this is not true.
                // Two cases here
                // case 1: oldCount == newCount
                //         indices are not affected. nothing to do here.
                // case 2: oldCount != newCount
                //         Replaced with less or more items. This is like an insert or remove
                //         depending on the counts.
                var oldStartIndex    = args.OldStartingIndex;
                var newStartingIndex = args.NewStartingIndex;
                var oldCount         = (int)(args.OldItems.Count);
                var newCount         = (int)(args.NewItems.Count);
                if (oldStartIndex != newStartingIndex)
                {
                    throw new InvalidOperationException("Replace is only allowed with OldStartingIndex equals to NewStartingIndex.");
                }

                if (oldCount == 0)
                {
                    throw new InvalidOperationException("Replace notification with args.OldItemsCount value of 0 is not allowed. Use Insert action instead.");
                }

                if (newCount == 0)
                {
                    throw new InvalidOperationException("Replace notification with args.NewItemCount value of 0 is not allowed. Use Remove action instead.");
                }

                int countChange = newCount - oldCount;
                if (countChange != 0)
                {
                    // countChange > 0 : countChange items were added
                    // countChange < 0 : -countChange  items were removed
                    var children = m_owner.Children;
                    for (int i = 0; i < children.Count; ++i)
                    {
                        var element   = children[i];
                        var virtInfo  = ItemsRepeater.GetVirtualizationInfo(element);
                        var dataIndex = virtInfo.Index;

                        if (virtInfo.IsRealized)
                        {
                            if (dataIndex >= oldStartIndex + oldCount)
                            {
                                UpdateElementIndex(element, virtInfo, dataIndex + countChange);
                            }
                        }
                    }

                    EnsureFirstLastRealizedIndices();
                    m_lastRealizedElementIndexHeldByLayout += countChange;
                }

                break;
            }

            case NotifyCollectionChangedAction.Remove:
            {
                var oldStartIndex = args.OldStartingIndex;
                var oldCount      = (int)(args.OldItems.Count);
                var children      = m_owner.Children;
                for (int i = 0; i < children.Count; ++i)
                {
                    var element   = children[i];
                    var virtInfo  = ItemsRepeater.GetVirtualizationInfo(element);
                    var dataIndex = virtInfo.Index;

                    if (virtInfo.IsRealized)
                    {
                        if (virtInfo.AutoRecycleCandidate && oldStartIndex <= dataIndex && dataIndex < oldStartIndex + oldCount)
                        {
                            // If we are doing the mapping, remove the element who's data was removed.
                            m_owner.ClearElementImpl(element);
                        }
                        else if (dataIndex >= (oldStartIndex + oldCount))
                        {
                            UpdateElementIndex(element, virtInfo, dataIndex - oldCount);
                        }
                    }
                }

                InvalidateRealizedIndicesHeldByLayout();
                break;
            }

            case NotifyCollectionChangedAction.Reset:
                // If we get multiple resets back to back before
                // running layout, we dont have to clear all the elements again.
                if (!m_isDataSourceStableResetPending)
                {
#if DEBUG
                    // There should be no elements in the reset pool at this time.
                    MUX_ASSERT(m_resetPool.IsEmpty);
#endif

                    if (m_owner.ItemsSourceView.HasKeyIndexMapping)
                    {
                        m_isDataSourceStableResetPending = true;
                    }

                    // Walk through all the elements and make sure they are cleared, they will go into
                    // the stable id reset pool.
                    var children = m_owner.Children;
                    for (int i = 0; i < children.Count; ++i)
                    {
                        var element  = children[i];
                        var virtInfo = ItemsRepeater.GetVirtualizationInfo(element);
                        if (virtInfo.IsRealized && virtInfo.AutoRecycleCandidate)
                        {
                            m_owner.ClearElementImpl(element);
                        }
                    }
                }

                InvalidateRealizedIndicesHeldByLayout();

                break;
            }
        }
Example #30
0
        // 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);
        }