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); } }
public void ValidateChildrenPeers() { RunOnUIThread.Execute(() => { var data = new ObservableCollection <string>(Enumerable.Range(0, 3).Select(i => string.Format("Item #{0}", i))); var dataSource = MockItemsSource.CreateDataSource(data, supportsUniqueIds: false); // StackPanel doesn't have an automation peer, but we still need // to report its children's peer. var group = new StackPanel(); group.Children.Add(new ListViewItem()); group.Children.Add(new ListViewItem()); var mapping = new Dictionary <int, UIElement> { { 0, new ListViewItem() }, { 1, new ListViewItem() }, { 2, group }, }; var elementFactory = MockElementFactory.CreateElementFactory(mapping); var layout = new MockVirtualizingLayout { MeasureLayoutFunc = (availableSize, context) => { var ctx = (VirtualizingLayoutContext)context; // Realize elements 0, 1, 2 in a random order. ctx.GetOrCreateElementAt(2); ctx.GetOrCreateElementAt(0); var element1 = ctx.GetOrCreateElementAt(1); // Clear element 1 because we will be validating // that we ignore unrealized elements. ctx.RecycleElement(element1); return(default(Size)); } }; var repeater = CreateRepeater(dataSource, elementFactory, layout); Content = repeater; repeater.UpdateLayout(); var peer = FrameworkElementAutomationPeer.CreatePeerForElement(repeater); var children = peer.GetChildren().Select(p => ((FrameworkElementAutomationPeer)p).Owner).ToList(); Verify.AreEqual(3, children.Count); Verify.AreEqual(mapping[0], children[0]); Verify.AreEqual(group.Children[0], children[1]); Verify.AreEqual(group.Children[1], children[2]); }); }
public void CanQueryElementFactory() { RunOnUIThread.Execute(() => { var data = new ObservableCollection <string>(); var dataSource = MockItemsSource.CreateDataSource(data, supportsUniqueIds: false); var elementFactory = MockElementFactory.CreateElementFactory(new List <UIElement> { new ContentControl() }); var repeater = CreateRepeater(dataSource, elementFactory); Content = repeater; repeater.UpdateLayout(); // Our layout will query the size though once for every layout pass. // The first layout pass is a bit special because we don't have a viewport and // we will invalidate measure when we get one after the first arrange pass. dataSource.ValidateGetSizeCalls(1); // GetSize calls are cached by ItemsSourceView. elementFactory.ValidateRecycleElementCalls(); data.Add("Item #1"); repeater.UpdateLayout(); var item1 = (UIElement)VisualTreeHelper.GetChild(repeater, 0); // One GetSize invocation from the layout, another one from the view manager. dataSource.ValidateGetSizeCalls(1 /* No invocation from the view manager on WPF */); // GetSize calls are cached by ItemsSourceView dataSource.ValidateGetAtCalls(new MockItemsSource.GetAtCallInfo(0)); elementFactory.ValidateGetElementCalls(new MockElementFactory.GetElementCallInfo(0, repeater)); elementFactory.ValidateRecycleElementCalls(); Verify.AreEqual(item1, repeater.TryGetElement(0)); data.RemoveAt(0); repeater.UpdateLayout(); dataSource.ValidateGetAtCalls(); dataSource.ValidateGetSizeCalls(1); // GetSize calls are cached by ItemsSourceView // Whenever we get an element from the view generator, we call HasKeyIndexMapping to see if we should // store its unique id or not. dataSource.ValidateGetItemIdCalls(); elementFactory.ValidateGetElementCalls(); elementFactory.ValidateRecycleElementCalls(new MockElementFactory.RecycleElementCallInfo(item1, repeater)); }); }
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 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); } }); }
// [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); }); }