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); }); }
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 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); }); }
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)); }); }
// 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"); }); }
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); }); }
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])); } }); }
// [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); }); }