public void ValidateStackLayoutDisabledVirtualizationWithItemsRepeater() { RunOnUIThread.Execute(() => { var repeater = new ItemsRepeater(); var stackLayout = new StackLayout(); stackLayout.DisableVirtualization = true; repeater.Layout = stackLayout; repeater.ItemsSource = Enumerable.Range(0, 10); repeater.ItemTemplate = (DataTemplate)XamlReader.Parse( @"<DataTemplate xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'> <Button Content='{Binding}' Height='100' /> </DataTemplate>"); var scrollViewer = new ScrollViewer() { Content = repeater }; scrollViewer.Height = 100; Content = scrollViewer; Content.UpdateLayout(); for (int i = 0; i < repeater.ItemsSourceView.Count; i++) { var child = repeater.TryGetElement(i) as Button; Verify.IsNotNull(child); } }); }
private ItemsRepeaterScrollHost CreateAndInitializeRepeater( object itemsSource, VirtualizingLayout layout, object elementFactory, ref ItemsRepeater repeater, ref ScrollViewer scrollViewer) { repeater = new ItemsRepeater() { ItemsSource = itemsSource, Layout = layout, ItemTemplate = elementFactory, HorizontalCacheLength = 0, VerticalCacheLength = 0, }; scrollViewer = new ScrollViewer() { Content = repeater }; return(new ItemsRepeaterScrollHost() { Width = 400, Height = 400, ScrollViewer = scrollViewer }); }
public void ValidateStackLayoutDoesNotRetainIncorrectMinorWidth() { RunOnUIThread.Execute(() => { var repeater = new ItemsRepeater() { ItemsSource = Enumerable.Range(0, 1) }; Content = new ScrollViewer() { Content = repeater, Width = 400, }; Content.UpdateLayout(); // Measure with large width. repeater.Measure(new Size(600, 100)); Verify.AreEqual(600, repeater.DesiredSize.Width); // Measure with smaller width again before arrange. // StackLayout has to pick up the smaller width for its extent. repeater.Measure(new Size(300, 100)); Verify.AreEqual(300, repeater.DesiredSize.Width); Content.UpdateLayout(); Verify.AreEqual(400, repeater.ActualWidth); }); }
public void CanRemoveItemsStartingBeforeRealizedRange() { CustomItemsSource dataSource = null; RunOnUIThread.Execute(() => dataSource = new CustomItemsSource(Enumerable.Range(0, 20).ToList())); ScrollViewer scrollViewer = null; ItemsRepeater repeater = null; var viewChangedEvent = new ManualResetEvent(false); RunOnUIThread.Execute(() => { repeater = SetupRepeater(dataSource, ref scrollViewer); scrollViewer.ScrollChanged += (sender, args) => { if (args.HorizontalChange != 0 || args.VerticalChange != 0) { viewChangedEvent.Set(); } }; scrollViewer.ChangeView(null, 600, null, true); }); Verify.IsTrue(viewChangedEvent.WaitOne(DefaultWaitTime), "Waiting for ViewChanged."); IdleSynchronizer.Wait(); RunOnUIThread.Execute(() => { Log.Comment("Remove before realized range: start:(0)beforeView end:(1)beforeView."); dataSource.Remove(index: 0, count: 2, reset: false); repeater.UpdateLayout(); var realized = VerifyRealizedRange(repeater, dataSource); Verify.AreEqual(4, realized); Log.Comment("Remove before realized range: start:(0)beforeView end:(4)inview."); dataSource.Remove(index: 0, count: 5, reset: false); repeater.UpdateLayout(); realized = VerifyRealizedRange(repeater, dataSource); Verify.AreEqual(3, realized); Log.Comment("Insert before realized range: Inserting 10 items at index 0"); dataSource.Insert(index: 0, count: 10, reset: false); repeater.UpdateLayout(); VerifyRealizedRange(repeater, dataSource); Log.Comment("Insert after realized range: Inserting 10 items at index 19"); dataSource.Insert(index: 19, count: 10, reset: false); repeater.UpdateLayout(); VerifyRealizedRange(repeater, dataSource); Log.Comment("Remove before realized range: start:(5)beforeView end:(25)afterView."); dataSource.Remove(index: 5, count: 20, reset: false); repeater.UpdateLayout(); realized = VerifyRealizedRange(repeater, dataSource); Verify.IsLessThanOrEqual(3, realized); }); }
private int VerifyRealizedRange(ItemsRepeater repeater, CustomItemsSource dataSource) { int numRealized = 0; // trace Log.Comment("index:ItemsSourceView:Item"); for (int i = 0; i < dataSource.Inner.Count; i++) { var element = repeater.TryGetElement(i); if (element != null) { var data = ((TextBlock)element).Text; Log.Comment("{0} {1}:[{2}]", i, dataSource.GetAt(i), data); numRealized++; } else { Log.Comment(i + ":[null]"); } } // verify for (int i = 0; i < dataSource.Inner.Count; i++) { var element = repeater.TryGetElement(i); if (element != null) { var data = ((TextBlock)element).Text; Verify.AreEqual(dataSource.GetAt(i).ToString(), data); } } return(numRealized); }
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); }); }
public void ValidateNonVirtualLayoutWithItemsRepeater() { RunOnUIThread.Execute(() => { var repeater = new ItemsRepeater(); repeater.Layout = new NonVirtualStackLayout(); repeater.ItemsSource = Enumerable.Range(0, 10); repeater.ItemTemplate = (DataTemplate)XamlReader.Parse( @"<DataTemplate xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'> <Button Content='{Binding}' Height='100' /> </DataTemplate>"); Content = repeater; Content.UpdateLayout(); double expectedYOffset = 0; for (int i = 0; i < repeater.ItemsSourceView.Count; i++) { var child = repeater.TryGetElement(i) as Button; Verify.IsNotNull(child); var layoutBounds = LayoutInformation.GetLayoutSlot(child); Verify.AreEqual(expectedYOffset, layoutBounds.Y); Verify.AreEqual(i, child.Content); expectedYOffset += 100; } }); }
private ItemsRepeater SetupRepeater(CustomItemsSource dataSource, ElementFactory elementFactory, ref ScrollViewer scrollViewer, VirtualizingLayout layout) { var repeater = new ItemsRepeater() { ItemsSource = dataSource, ItemTemplate = elementFactory, Layout = layout, VerticalCacheLength = 0, HorizontalCacheLength = 0 }; scrollViewer = new ScrollViewer { Content = repeater }; Content = new ItemsRepeaterScrollHost() { Width = 200, Height = 200, ScrollViewer = scrollViewer }; Content.UpdateLayout(); if (dataSource.Count > 0) { int realized = VerifyRealizedRange(repeater, dataSource); Verify.IsGreaterThan(realized, 0); } return(repeater); }
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); }); }
// [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 */); } }); }
private void ValidateCurrentFocus(ItemsRepeater repeater, int expectedIndex, string expectedContent) { var currentFocus = Keyboard.FocusedElement 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()); }
private ItemsRepeater CreateRepeater(object dataSource, object elementFactory) { var repeater = new ItemsRepeater { ItemsSource = dataSource, ItemTemplate = elementFactory }; repeater.Layout = CreateLayout(repeater); return(repeater); }
private ItemsRepeater CreateRepeater(object dataSource, object elementFactory, VirtualizingLayout layout = null) { var repeater = new ItemsRepeater { ItemsSource = dataSource, ItemTemplate = elementFactory, }; repeater.Layout = layout ?? CreateLayout(repeater); return(repeater); }
public void CanMoveItem() { CustomItemsSource dataSource = null; RunOnUIThread.Execute(() => dataSource = new CustomItemsSource(Enumerable.Range(0, 10).ToList())); ScrollViewer scrollViewer = null; ItemsRepeater repeater = null; var viewChangedEvent = new ManualResetEvent(false); RunOnUIThread.Execute(() => { repeater = SetupRepeater(dataSource, ref scrollViewer); scrollViewer.ScrollChanged += (sender, args) => { if (args.HorizontalChange != 0 || args.VerticalChange != 0) { viewChangedEvent.Set(); } }; scrollViewer.ChangeView(null, 400, null, true); }); Verify.IsTrue(viewChangedEvent.WaitOne(DefaultWaitTime), "Waiting for ViewChanged."); IdleSynchronizer.Wait(); RunOnUIThread.Execute(() => { var realized = VerifyRealizedRange(repeater, dataSource); Verify.AreEqual(4, realized); Log.Comment("Move before realized range."); dataSource.Move(oldIndex: 0, newIndex: 1, count: 2, reset: false); repeater.UpdateLayout(); realized = VerifyRealizedRange(repeater, dataSource); Verify.AreEqual(4, realized); Log.Comment("Move in realized range."); dataSource.Move(oldIndex: 3, newIndex: 5, count: 2, reset: false); repeater.UpdateLayout(); realized = VerifyRealizedRange(repeater, dataSource); Verify.AreEqual(4, realized); Log.Comment("Move after realized range"); dataSource.Move(oldIndex: 7, newIndex: 8, count: 2, reset: false); repeater.UpdateLayout(); realized = VerifyRealizedRange(repeater, dataSource); Verify.AreEqual(4, realized); }); }
public void ValidateMappingAndAutoRecycling() { ItemsRepeater repeater = null; ScrollViewer scrollViewer = null; RunOnUIThread.Execute(() => { var layout = new MockVirtualizingLayout() { MeasureLayoutFunc = (availableSize, context) => { var element0 = context.GetOrCreateElementAt(index: 0); // lookup - repeater will give back the same element and note that this element will not // be pinned - i.e it will be auto recycled after a measure pass where GetElementAt(0) is not called. var element0lookup = context.GetOrCreateElementAt(index: 0, options: ElementRealizationOptions.None); var element1 = context.GetOrCreateElementAt(index: 1, options: ElementRealizationOptions.ForceCreate | ElementRealizationOptions.SuppressAutoRecycle); // forcing a new element for index 1 that will be pinned (not auto recycled). This will be // a completely new element. Repeater does not do the mapping/lookup when forceCreate is true. var element1Clone = context.GetOrCreateElementAt(index: 1, options: ElementRealizationOptions.ForceCreate | ElementRealizationOptions.SuppressAutoRecycle); Verify.AreSame(element0, element0lookup); Verify.AreNotSame(element1, element1Clone); element0.Measure(availableSize); element1.Measure(availableSize); element1Clone.Measure(availableSize); return(new Size(100, 100)); }, }; Content = CreateAndInitializeRepeater( itemsSource: Enumerable.Range(0, 5), layout: layout, elementFactory: GetDataTemplate("<Button>Hello</Button>"), repeater: ref repeater, scrollViewer: ref scrollViewer); Content.UpdateLayout(); Verify.IsNotNull(repeater.TryGetElement(0)); Verify.IsNotNull(repeater.TryGetElement(1)); layout.MeasureLayoutFunc = null; repeater.InvalidateMeasure(); Content.UpdateLayout(); Verify.IsNull(repeater.TryGetElement(0)); // not pinned, should be auto recycled. Verify.IsNotNull(repeater.TryGetElement(1)); // pinned, should stay alive }); }
private void OnRecipeDeleted(object sender, RoutedEventArgs e) { if (_flatData != null) { // walk up the tree to find the container and repeater. var container = (FrameworkElement)sender; while (!(container.Parent is ItemsRepeater)) { container = (FrameworkElement)container.Parent; } ItemsRepeater repeater = (ItemsRepeater)container.Parent; int index = repeater.GetElementIndex(container); _flatData.RemoveAt(index); } }
public void VerifyElement0OwnershipInUniformGridLayout() { CustomItemsSource dataSource = null; RunOnUIThread.Execute(() => dataSource = new CustomItemsSource(new List <int>())); ItemsRepeater repeater = null; int elementsCleared = 0; int elementsPrepared = 0; RunOnUIThread.Execute(() => { repeater = SetupRepeater(dataSource, new UniformGridLayout()); repeater.ElementPrepared += (sender, args) => { elementsPrepared++; }; repeater.ElementClearing += (sender, args) => { elementsCleared++; }; }); IdleSynchronizer.Wait(); RunOnUIThread.Execute(() => { Log.Comment("Add two item"); dataSource.Insert(index: 0, count: 1, reset: false); dataSource.Insert(index: 1, count: 1, reset: false); repeater.UpdateLayout(); var realized = VerifyRealizedRange(repeater, dataSource); Verify.AreEqual(2, realized); Log.Comment("replace the first item"); dataSource.Replace(index: 0, oldCount: 1, newCount: 1, reset: false); repeater.UpdateLayout(); realized = VerifyRealizedRange(repeater, dataSource); Verify.AreEqual(2, realized); Log.Comment("Remove two items"); dataSource.Remove(index: 1, count: 1, reset: false); dataSource.Remove(index: 0, count: 1, reset: false); repeater.UpdateLayout(); realized = VerifyRealizedRange(repeater, dataSource); Verify.AreEqual(0, realized); Verify.AreEqual(elementsPrepared, elementsCleared); }); }
private void OnRecipeInserted(object sender, RoutedEventArgs e) { if (_flatData != null) { // walk up the tree to find the container and repeater. var container = (FrameworkElement)sender; while (!(container.Parent is ItemsRepeater)) { container = (FrameworkElement)container.Parent; } ItemsRepeater repeater = (ItemsRepeater)container.Parent; int index = repeater.GetElementIndex(container) + 1; _flatData.Insert(index, new Recipe() { ImageUri = new Uri("pack://application:,,,/Images/recipe0.png"), Description = "added recipe", }); } }
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)); }); }
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(); }); IdleSynchronizer.Wait(); RunOnUIThread.Execute(() => { data.Reset(); }); IdleSynchronizer.Wait(); RunOnUIThread.Execute(() => { // Still focused. Verify.AreEqual(focusedElement, Keyboard.FocusedElement); // Change focused element. focusedElement = (Control)repeater.TryGetElement(1); focusedElement.Focus(); }); IdleSynchronizer.Wait(); RunOnUIThread.Execute(() => { // Focus is on the new element. Verify.AreEqual(focusedElement, Keyboard.FocusedElement); }); }
public void ValidateNoScrollingSurfaceScenario() { RunOnUIThread.Execute(() => { var realizationRects = new List <Rect>(); var repeater = new ItemsRepeater() { Layout = GetMonitoringLayout(new Size(500, 500), realizationRects), HorizontalCacheLength = 0.0, VerticalCacheLength = 0.0 }; Content = repeater; Content.UpdateLayout(); Verify.AreEqual(2, realizationRects.Count); Verify.AreEqual(new Rect(0, 0, 0, 0), realizationRects[0]); //if (!PlatformConfiguration.IsOsVersionGreaterThanOrEqual(OSVersion.Redstone5)) { Verify.AreEqual(new Rect(0, 0, float.MaxValue, float.MaxValue), realizationRects[1]); } /*else * { * // Using Effective Viewport * Verify.AreEqual(0, realizationRects[1].X); * // 32 pixel title bar and some tolerance for borders * Verify.IsLessThan(2.0, Math.Abs(realizationRects[1].Y - 32)); * // Width/Height depends on the window size, so just * // validating something reasonable here to avoid flakiness. * Verify.IsLessThan(500.0, realizationRects[1].Width); * Verify.IsLessThan(500.0, realizationRects[1].Height); * }*/ realizationRects.Clear(); }); }
// 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"); }); }
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.Parse( @"<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); }
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 = repeater.TryGetElement(i) ?? context.GetOrCreateElementAt(i); element.Measure(availableSize); children.Add(element); } return(new Size(10, 10)); }; return(layout); }
public void EnsureReplaceOfAnchorDoesNotResetAllContainers() { CustomItemsSource dataSource = null; RunOnUIThread.Execute(() => dataSource = new CustomItemsSource(Enumerable.Range(0, 10).ToList())); ScrollViewer scrollViewer = null; ItemsRepeater repeater = null; var viewChangedEvent = new ManualResetEvent(false); int elementsCleared = 0; int elementsPrepared = 0; RunOnUIThread.Execute(() => { repeater = SetupRepeater(dataSource, ref scrollViewer); repeater.ElementPrepared += (sender, args) => { elementsPrepared++; }; repeater.ElementClearing += (sender, args) => { elementsCleared++; }; }); IdleSynchronizer.Wait(); RunOnUIThread.Execute(() => { var realized = VerifyRealizedRange(repeater, dataSource); Verify.AreEqual(3, realized); Log.Comment("Replace anchor element 0"); elementsPrepared = 0; elementsCleared = 0; dataSource.Replace(index: 0, oldCount: 1, newCount: 1, reset: false); repeater.UpdateLayout(); Verify.AreEqual(1, elementsPrepared); Verify.AreEqual(1, elementsCleared); realized = VerifyRealizedRange(repeater, dataSource); Verify.AreEqual(3, realized); }); }
private void MoveFocusToIndex(ItemsRepeater repeater, int index) { var element = repeater.TryGetElement(index) as Control; element.Focus(); }
// [TestMethod] Issue 1018 public void CanReuseElementsDuringUniqueIdReset() { var data = new WinRTCollection(Enumerable.Range(0, 2).Select(i => string.Format("Item #{0}", i))); List <UIElement> mapping = null; ItemsRepeater repeater = null; MockElementFactory elementFactory = null; ContentControl focusedElement = null; RunOnUIThread.Execute(() => { mapping = new List <UIElement> { new ContentControl(), new ContentControl() }; repeater = CreateRepeater( MockItemsSource.CreateDataSource(data, supportsUniqueIds: true), MockElementFactory.CreateElementFactory(mapping)); elementFactory = (MockElementFactory)repeater.ItemTemplate; Content = repeater; repeater.UpdateLayout(); focusedElement = (ContentControl)repeater.TryGetElement(1); focusedElement.Focus(); }); IdleSynchronizer.Wait(); RunOnUIThread.Execute(() => { elementFactory.ValidateGetElementCalls( new MockElementFactory.GetElementCallInfo(0, repeater), new MockElementFactory.GetElementCallInfo(1, repeater)); elementFactory.ValidateRecycleElementCalls(); data.ResetWith(new[] { data[0], "New item" }); Verify.AreEqual(0, repeater.GetElementIndex(mapping[0])); Verify.AreEqual(1, repeater.GetElementIndex(mapping[1])); Verify.IsNull(repeater.TryGetElement(0)); Verify.IsNull(repeater.TryGetElement(1)); elementFactory.ValidateGetElementCalls(/* GetElement should not be called */); elementFactory.ValidateRecycleElementCalls(/* RecycleElement should not be called */); mapping[1] = new ContentControl(); // For "New Item" repeater.UpdateLayout(); Verify.AreEqual(0, repeater.GetElementIndex(mapping[0])); Verify.AreEqual(1, repeater.GetElementIndex(mapping[1])); Verify.AreEqual(mapping[0], repeater.TryGetElement(0)); Verify.AreEqual(mapping[1], repeater.TryGetElement(1)); elementFactory.ValidateGetElementCalls( new MockElementFactory.GetElementCallInfo(1, repeater)); elementFactory.ValidateRecycleElementCalls( new MockElementFactory.RecycleElementCallInfo(focusedElement, repeater)); // If the focused element survived the reset, we will keep focus on it. If not, we // try to find one based on the index. In this case, the focused element (index 1) // got recycled, and we still have index 1 after the stable reset, so the new index 1 // will get focused. Note that recycling the elements to view generator in the case of // stable reset happens during the arrange, so by that time we will have pulled elements // from the stable reset pool and maybe created some new elements as well. int index = repeater.GetElementIndex(focusedElement); Log.Comment("focused index " + index); Verify.AreEqual(mapping[1], Keyboard.FocusedElement); }); }
public void CanReplaceSingleItem() { CustomItemsSource dataSource = null; RunOnUIThread.Execute(() => dataSource = new CustomItemsSource(Enumerable.Range(0, 10).ToList())); ScrollViewer scrollViewer = null; ItemsRepeater repeater = null; var viewChangedEvent = new ManualResetEvent(false); int elementsCleared = 0; int elementsPrepared = 0; RunOnUIThread.Execute(() => { repeater = SetupRepeater(dataSource, ref scrollViewer); scrollViewer.ScrollChanged += (sender, args) => { if (args.HorizontalChange != 0 || args.VerticalChange != 0) { viewChangedEvent.Set(); } }; repeater.ElementPrepared += (sender, args) => { elementsPrepared++; }; repeater.ElementClearing += (sender, args) => { elementsCleared++; }; scrollViewer.ChangeView(null, 200, null, true); }); Verify.IsTrue(viewChangedEvent.WaitOne(DefaultWaitTime), "Waiting for ViewChanged."); IdleSynchronizer.Wait(); RunOnUIThread.Execute(() => { var realized = VerifyRealizedRange(repeater, dataSource); Verify.AreEqual(4, realized); Log.Comment("Replace before realized range."); dataSource.Replace(index: 0, oldCount: 1, newCount: 1, reset: false); repeater.UpdateLayout(); realized = VerifyRealizedRange(repeater, dataSource); Verify.AreEqual(4, realized); Log.Comment("Replace in realized range."); elementsPrepared = 0; elementsCleared = 0; dataSource.Replace(index: 2, oldCount: 1, newCount: 1, reset: false); repeater.UpdateLayout(); Verify.AreEqual(1, elementsPrepared); Verify.AreEqual(1, elementsCleared); realized = VerifyRealizedRange(repeater, dataSource); Verify.AreEqual(4, realized); Log.Comment("Replace after realized range"); dataSource.Replace(index: 8, oldCount: 1, newCount: 1, reset: false); repeater.UpdateLayout(); realized = VerifyRealizedRange(repeater, dataSource); Verify.AreEqual(4, realized); }); }
// [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(); }); 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(); }); 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])); } }); }
public void ValidateRecycledElementOwnerAffinity() { RunOnUIThread.Execute(() => { ItemsRepeater repeater1 = null; ItemsRepeater repeater2 = null; const int numItems = 10; var dataCollection = new ObservableCollection <int>(Enumerable.Range(0, numItems)); const string recycleKey = "key"; var dataSource = MockItemsSource.CreateDataSource <int>(dataCollection, true); var layout = new StackLayout(); var recyclePool = new RecyclePool(); var itemTemplate = (DataTemplate)XamlReader.Parse( @"<DataTemplate xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'> <TextBlock Text='{Binding}' /> </DataTemplate>"); repeater1 = new ItemsRepeater() { ItemsSource = dataSource, Layout = layout, ItemTemplate = new RecyclingElementFactoryDerived() { Templates = { { "key", itemTemplate } }, RecyclePool = recyclePool, SelectTemplateIdFunc = (object data, UIElement owner) => recycleKey } }; repeater2 = new ItemsRepeater() { ItemsSource = dataSource, Layout = layout, ItemTemplate = new RecyclingElementFactoryDerived() { Templates = { { "key", itemTemplate } }, RecyclePool = recyclePool, SelectTemplateIdFunc = (object data, UIElement owner) => recycleKey } }; var root = new StackPanel(); root.Children.Add(repeater1); root.Children.Add(repeater2); Content = new ItemsRepeaterScrollHost() { Width = 400, Height = 400, ScrollViewer = new ScrollViewer() { Content = root } }; Content.UpdateLayout(); Verify.AreEqual(numItems, VisualTreeHelper.GetChildrenCount(repeater1)); Verify.AreEqual(numItems, VisualTreeHelper.GetChildrenCount(repeater2)); // Throw all the elements into the recycle pool dataCollection.Clear(); Content.UpdateLayout(); for (int i = 0; i < numItems; i++) { var element1 = (FrameworkElement)recyclePool.TryGetElement(recycleKey, repeater1); Verify.AreSame(repeater1, element1.Parent); var element2 = (FrameworkElement)recyclePool.TryGetElement(recycleKey, repeater2); Verify.AreSame(repeater2, element2.Parent); } }); }