public void ValidateOneScrollerScenario()
        {
            if (!PlatformConfiguration.IsOsVersionGreaterThanOrEqual(OSVersion.Redstone5))
            {
                Log.Warning("Skipping since version is less than RS5 and effective viewport feature is not available below RS5");
                return;
            }

            var      realizationRects     = new List <Rect>();
            Scroller scroller             = null;
            var      scrollCompletedEvent = new AutoResetEvent(false);
            var      zoomCompletedEvent   = new AutoResetEvent(false);

            RunOnUIThread.Execute(() =>
            {
                var repeater = new ItemsRepeater()
                {
                    Layout = GetMonitoringLayout(new Size(500, 600), realizationRects),
                    HorizontalCacheLength = 0.0,
                    VerticalCacheLength   = 0.0
                };

                scroller = new Scroller
                {
                    Content = repeater,
                    Width   = 200,
                    Height  = 300
                };

                Content = scroller;
                Content.UpdateLayout();

                Verify.AreEqual(2, realizationRects.Count);
                Verify.AreEqual(new Rect(0, 0, 0, 0), realizationRects[0]);
                Verify.AreEqual(new Rect(0, 0, 200, 300), realizationRects[1]);
                realizationRects.Clear();

                scroller.ScrollCompleted += (Scroller sender, ScrollCompletedEventArgs args) =>
                {
                    scrollCompletedEvent.Set();
                };

                scroller.ZoomCompleted += (Scroller sender, ZoomCompletedEventArgs args) =>
                {
                    zoomCompletedEvent.Set();
                };
            });
            IdleSynchronizer.Wait();

            RunOnUIThread.Execute(() =>
            {
                scroller.ScrollTo(0.0, 100.0, new ScrollOptions(AnimationMode.Disabled, SnapPointsMode.Ignore));
            });
            Verify.IsTrue(scrollCompletedEvent.WaitOne(DefaultWaitTimeInMS));
            CompositionPropertySpy.SynchronouslyTickUIThread(1);

            RunOnUIThread.Execute(() =>
            {
                Verify.AreEqual(new Rect(0, 100, 200, 300), realizationRects.Last());
                realizationRects.Clear();

                scroller.ZoomTo(2.0f, Vector2.Zero, new ZoomOptions(AnimationMode.Disabled, SnapPointsMode.Ignore));
            });
            Verify.IsTrue(zoomCompletedEvent.WaitOne(DefaultWaitTimeInMS));
            CompositionPropertySpy.SynchronouslyTickUIThread(1);

            RunOnUIThread.Execute(() =>
            {
                Verify.AreEqual(
                    new Rect(0, 100, 100, 150),
                    realizationRects.Last());
                realizationRects.Clear();
            });
        }
Example #2
0
        public void ValidatePhaseInvokeAndOrdering()
        {
            if (!PlatformConfiguration.IsOsVersionGreaterThan(OSVersion.Redstone2))
            {
                Log.Warning("Skipping: GetAvailableSize API is only available in RS3 and above.");
                return;
            }

            ItemsRepeater    repeater           = null;
            int              numPhases          = 6; // 0 to 5
            ManualResetEvent buildTreeCompleted = new ManualResetEvent(false);

            RunOnUIThread.Execute(() =>
            {
                var itemTemplate = (DataTemplate)XamlReader.Load(
                    @"<DataTemplate  xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'>
							<Button Width='100' Height='100'/>
						</DataTemplate>"                        );
                repeater = new ItemsRepeater()
                {
                    ItemsSource  = Enumerable.Range(0, 10),
                    ItemTemplate = new CustomElementFactory(numPhases),
                    Layout       = new StackLayout(),
                };

                repeater.ElementPrepared += (sender, args) =>
                {
                    if (args.Index == expectedLastRealizedIndex)
                    {
                        Log.Comment("Item 8 Created!");
                        RepeaterTestHooks.BuildTreeCompleted += (sender1, args1) =>
                        {
                            buildTreeCompleted.Set();
                        };
                    }
                };

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

                // CompositionTarget.Rendering += (sender, args) => { Log.Comment("Rendering"); }; // debugging aid
            });

            if (buildTreeCompleted.WaitOne(TimeSpan.FromMilliseconds(2000)))
            {
                RunOnUIThread.Execute(() =>
                {
                    var calls = ElementPhasingManager.ProcessedCalls;

                    Verify.AreEqual(9, calls.Count);
                    calls[0].RemoveAt(0);                     // Remove the create we did for first measure.
                    foreach (var index in calls.Keys)
                    {
                        var phases = calls[index];
                        Verify.AreEqual(6, phases.Count);
                        for (int i = 0; i < phases.Count; i++)
                        {
                            Verify.AreEqual(i, phases[i]);
                        }
                    }

                    ElementPhasingManager.ProcessedCalls.Clear();
                });
            }
            else
            {
                Verify.Fail("Failed on waiting on build tree.");
            }
        }
        public void VerifyCurrentAnchor()
        {
            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();

                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);
                });
            }
        }
        private void SetupRepeaterAnchoringUI(
            Scroller scroller,
            AutoResetEvent scrollerLoadedEvent)
        {
            Log.Comment("Setting up ItemsRepeater anchoring UI with Scroller and ItemsRepeater");

            TestDataSource dataSource = new TestDataSource(
                Enumerable.Range(0, c_defaultAnchoringUIRepeaterChildrenCount).Select(i => string.Format("Item #{0}", i)).ToList());

            RecyclingElementFactory elementFactory = new RecyclingElementFactory();

            elementFactory.RecyclePool       = new RecyclePool();
            elementFactory.Templates["Item"] = XamlReader.Load(
                @"<DataTemplate xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'>
                        <Border BorderThickness='3' BorderBrush='Chartreuse' Width='100' Height='100' Margin='3' Background='BlanchedAlmond'>
                          <TextBlock Text='{Binding}' HorizontalAlignment='Center' VerticalAlignment='Center'/>
                        </Border>
                      </DataTemplate>") as DataTemplate;

            ItemsRepeater repeater = new ItemsRepeater()
            {
                Name        = "repeater",
                ItemsSource = dataSource,
#if BUILD_WINDOWS
                ItemTemplate = (Windows.UI.Xaml.IElementFactory)elementFactory,
#else
                ItemTemplate = elementFactory,
#endif
                Layout = new UniformGridLayout()
                {
                    MinItemWidth     = 125,
                    MinItemHeight    = 125,
                    MinRowSpacing    = 10,
                    MinColumnSpacing = 10
                },
                Margin = new Thickness(30)
            };

            Border border = new Border()
            {
                Name            = "border",
                BorderThickness = new Thickness(3),
                BorderBrush     = new SolidColorBrush(Colors.Chartreuse),
                Margin          = new Thickness(15),
                Background      = new SolidColorBrush(Colors.Beige),
                Child           = repeater
            };

            Verify.IsNotNull(scroller);
            scroller.Name = "scroller";
            scroller.ContentOrientation = ContentOrientation.Vertical;
            scroller.Width      = 400;
            scroller.Height     = 600;
            scroller.Background = new SolidColorBrush(Colors.AliceBlue);
            scroller.Content    = border;

            if (scrollerLoadedEvent != null)
            {
                scroller.Loaded += (object sender, RoutedEventArgs e) =>
                {
                    Log.Comment("Scroller.Loaded event handler");
                    scrollerLoadedEvent.Set();
                };
            }

            scroller.AnchorRequested += (Scroller sender, ScrollerAnchorRequestedEventArgs args) =>
            {
                Log.Comment("Scroller.AnchorRequested event handler");

                Verify.IsNull(args.AnchorElement);
                Verify.IsGreaterThan(args.AnchorCandidates.Count, 0);
            };

            Log.Comment("Setting window content");
            MUXControlsTestApp.App.TestContentRoot = scroller;
        }
Example #5
0
 private void Repeater_ElementPrepared(ItemsRepeater sender, ItemsRepeaterElementPreparedEventArgs args)
 {
     (args.Element as RepeaterItem).RepeatedIndex = args.Index;
 }
        public void ValidateBasicScrollViewerScenario()
        {
            if (!PlatformConfiguration.IsOsVersionGreaterThanOrEqual(OSVersion.Redstone5))
            {
                Log.Warning("Skipping since version is less than RS5 and effective viewport is not available below RS5");
                return;
            }

            var              realizationRects         = new List <Rect>();
            var              viewChangeCompletedEvent = new AutoResetEvent(false);
            ScrollViewer     scrollViewer             = null;
            ManualResetEvent viewChanged    = new ManualResetEvent(false);
            ManualResetEvent layoutMeasured = new ManualResetEvent(false);

            RunOnUIThread.Execute(() =>
            {
                var repeater = new ItemsRepeater()
                {
                    Layout = GetMonitoringLayout(new Size(500, 600), realizationRects, layoutMeasured),
                    HorizontalCacheLength = 0.0,
                    VerticalCacheLength   = 0.0
                };

                scrollViewer = new ScrollViewer {
                    Content = repeater,
                    Width   = 200,
                    Height  = 300,
                    HorizontalScrollBarVisibility = ScrollBarVisibility.Hidden,
                    VerticalScrollBarVisibility   = ScrollBarVisibility.Hidden,
                };

                scrollViewer.ViewChanged += (sender, args) =>
                {
                    if (!args.IsIntermediate)
                    {
                        Log.Comment("ViewChanged " + scrollViewer.HorizontalOffset + ":" + scrollViewer.VerticalOffset);
                        viewChanged.Set();
                    }
                };

                Content = scrollViewer;
            });

            Verify.IsTrue(layoutMeasured.WaitOne(), "Did not receive measure on layout");

            RunOnUIThread.Execute(() =>
            {
                // First layout pass will invalidate measure during the first arrange
                // so that we can get a viewport during the second measure/arrange pass.
                Verify.AreEqual(new Rect(0, 0, 0, 0), realizationRects[0]);
                Verify.AreEqual(new Rect(0, 0, 200, 300), realizationRects[1]);
                realizationRects.Clear();

                viewChanged.Reset();
                layoutMeasured.Reset();
                scrollViewer.ChangeView(null, 100.0, 1.0f, disableAnimation: true);
            });

            IdleSynchronizer.Wait();
            Verify.IsTrue(viewChanged.WaitOne(), "Did not receive view changed event");
            Verify.IsTrue(layoutMeasured.WaitOne(), "Did not receive measure on layout");
            viewChanged.Reset();
            layoutMeasured.Reset();

            RunOnUIThread.Execute(() =>
            {
                Verify.AreEqual(new Rect(0, 100, 200, 300), realizationRects.Last());
                realizationRects.Clear();
                viewChangeCompletedEvent.Reset();

                // Max viewport offset is (300, 400). Horizontal viewport offset
                // is expected to get coerced from 400 to 300.
                scrollViewer.ChangeView(400, 100.0, 1.0f, disableAnimation: true);
            });

            IdleSynchronizer.Wait();
            Verify.IsTrue(viewChanged.WaitOne(), "Did not receive view changed event");
            Verify.IsTrue(layoutMeasured.WaitOne(), "Did not receive measure on layout");
            viewChanged.Reset();
            layoutMeasured.Reset();

            RunOnUIThread.Execute(() =>
            {
                Verify.AreEqual(new Rect(300, 100, 200, 300), realizationRects.Last());
                realizationRects.Clear();
                viewChangeCompletedEvent.Reset();

                scrollViewer.ChangeView(null, null, 2.0f, disableAnimation: true);
            });

            IdleSynchronizer.Wait();
            Verify.IsTrue(viewChanged.WaitOne(), "Did not receive view changed event");
            Verify.IsTrue(layoutMeasured.WaitOne(), "Did not receive measure on layout");
            viewChanged.Reset();
            layoutMeasured.Reset();

            RunOnUIThread.Execute(() =>
            {
                Verify.AreEqual(new Rect(150, 50, 100, 150), realizationRects.Last());
                realizationRects.Clear();
            });
        }
Example #7
0
        public void NestedRepeaterWithDataTemplateScenario()
        {
            ItemsRepeater    rootRepeater = null;
            ScrollViewer     scrollViewer = null;
            ManualResetEvent viewChanged  = new ManualResetEvent(false);

            RunOnUIThread.Execute(() =>
            {
                var anchorProvider = (ScrollAnchorProvider)XamlReader.Load(
                    @"<controls:ScrollAnchorProvider 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:ScrollAnchorProvider.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:ScrollAnchorProvider.Resources>
                    <ScrollViewer x:Name='scrollviewer'>
                        <controls:ItemsRepeater x:Name='rootRepeater' ItemTemplate='{StaticResource GroupTemplate}' VerticalCacheLength='0' />
                    </ScrollViewer>
                </controls:ScrollAnchorProvider>");

                rootRepeater              = (ItemsRepeater)anchorProvider.FindName("rootRepeater");
                scrollViewer              = (ScrollViewer)anchorProvider.FindName("scrollviewer");
                scrollViewer.ViewChanged += (sender, args) =>
                {
                    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(() =>
                {
                    scrollViewer.ChangeView(null, i * 200, null);
                });

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

                RunOnUIThread.Execute(() =>
                {
                    Verify.AreEqual(i * 200, scrollViewer.VerticalOffset);
                });
            }
        }
        private void MoveFocusToIndex(ItemsRepeater repeater, int index)
        {
            var element = repeater.TryGetElement(index) as Control;

            element.Focus(FocusState.Programmatic);
        }
        // [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]));
                }
            });
        }
        public void ValidateOneScrollerScenario()
        {
            if (!PlatformConfiguration.IsOsVersionGreaterThanOrEqual(OSVersion.Redstone5))
            {
                Log.Warning("Skipping since version is less than RS5 and effective viewport feature is not available below RS5");
                return;
            }

            var      realizationRects         = new List <Rect>();
            Scroller scroller                 = null;
            var      viewChangeCompletedEvent = new AutoResetEvent(false);

            RunOnUIThread.Execute(() =>
            {
                var repeater = new ItemsRepeater()
                {
                    Layout = GetMonitoringLayout(new Size(500, 600), realizationRects),
                    HorizontalCacheLength = 0.0,
                    VerticalCacheLength   = 0.0
                };

                scroller = new Scroller
                {
                    Child  = repeater,
                    Width  = 200,
                    Height = 300
                };

                Content = scroller;
                Content.UpdateLayout();

                Verify.AreEqual(2, realizationRects.Count);
                Verify.AreEqual(new Rect(0, 0, 0, 0), realizationRects[0]);
                Verify.AreEqual(new Rect(0, 0, 200, 300), realizationRects[1]);
                realizationRects.Clear();

                scroller.ViewChangeCompleted += (Scroller sender, ScrollerViewChangeCompletedEventArgs args) =>
                {
                    viewChangeCompletedEvent.Set();
                };
            });
            IdleSynchronizer.Wait();

            RunOnUIThread.Execute(() =>
            {
                scroller.ChangeOffsets(new ScrollerChangeOffsetsOptions(0.0, 100.0, ScrollerViewKind.Absolute, ScrollerViewChangeKind.DisableAnimation, ScrollerViewChangeSnapPointRespect.IgnoreSnapPoints));
            });
            Verify.IsTrue(viewChangeCompletedEvent.WaitOne(DefaultWaitTimeInMS));

            RunOnUIThread.Execute(() =>
            {
                Verify.AreEqual(new Rect(0, 100, 200, 300), realizationRects.Last());
                realizationRects.Clear();

                viewChangeCompletedEvent.Reset();
                scroller.ChangeZoomFactor(new ScrollerChangeZoomFactorOptions(2.0f, ScrollerViewKind.Absolute, Vector2.Zero, ScrollerViewChangeKind.DisableAnimation, ScrollerViewChangeSnapPointRespect.IgnoreSnapPoints));
            });
            Verify.IsTrue(viewChangeCompletedEvent.WaitOne(DefaultWaitTimeInMS));

            RunOnUIThread.Execute(() =>
            {
                Verify.AreEqual(
                    new Rect(0, 100, 100, 150),
                    realizationRects.Last());
                realizationRects.Clear();
            });
        }
        // [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(FocusState.Keyboard);
            });
            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], FocusManager.GetFocusedElement());
            });
        }
        public void ValidateTwoScrollersScenario()
        {
            if (!PlatformConfiguration.IsOsVersionGreaterThanOrEqual(OSVersion.Redstone5))
            {
                Log.Warning("Skipping since version is less than RS5 and effective viewport feature is not available below RS5");
                return;
            }

            var      realizationRects   = new List <Rect>();
            Scroller horizontalScroller = null;
            Scroller verticalScroller   = null;
            var      horizontalViewChangeCompletedEvent = new AutoResetEvent(false);
            var      verticalViewChangeCompletedEvent   = new AutoResetEvent(false);

            RunOnUIThread.Execute(() =>
            {
                var repeater = new ItemsRepeater()
                {
                    Layout = GetMonitoringLayout(new Size(500, 500), realizationRects),
                    HorizontalCacheLength = 0.0,
                    VerticalCacheLength   = 0.0
                };

                horizontalScroller = new Scroller
                {
                    Child = repeater,
                    IsChildAvailableHeightConstrained = true
                };

                // Placing a Grid in between two Scroller controls to avoid
                // unsupported combined use of facades and ElementCompositionPreview.
                var grid = new Grid();
                grid.Children.Add(horizontalScroller);

                verticalScroller = new Scroller
                {
                    Child  = grid,
                    Width  = 200,
                    Height = 200,
                    IsChildAvailableWidthConstrained = true
                };

                Content = verticalScroller;
                Content.UpdateLayout();

                Verify.AreEqual(2, realizationRects.Count);
                Verify.AreEqual(new Rect(0, 0, 0, 0), realizationRects[0]);
                Verify.AreEqual(new Rect(0, 0, 200, 200), realizationRects[1]);
                realizationRects.Clear();

                horizontalScroller.ViewChangeCompleted += (Scroller sender, ScrollerViewChangeCompletedEventArgs args) =>
                {
                    horizontalViewChangeCompletedEvent.Set();
                };

                verticalScroller.ViewChangeCompleted += (Scroller sender, ScrollerViewChangeCompletedEventArgs args) =>
                {
                    verticalViewChangeCompletedEvent.Set();
                };
            });
            IdleSynchronizer.Wait();

            RunOnUIThread.Execute(() =>
            {
                verticalScroller.ChangeOffsets(new ScrollerChangeOffsetsOptions(0.0, 100.0, ScrollerViewKind.Absolute, ScrollerViewChangeKind.DisableAnimation, ScrollerViewChangeSnapPointRespect.IgnoreSnapPoints));
            });
            Verify.IsTrue(verticalViewChangeCompletedEvent.WaitOne(DefaultWaitTimeInMS));

            RunOnUIThread.Execute(() =>
            {
                Verify.AreEqual(new Rect(0, 100, 200, 200), realizationRects.Last());
                realizationRects.Clear();

                // Max viewport offset is (300, 300). Horizontal viewport offset
                // is expected to get coerced from 400 to 300.
                horizontalScroller.ChangeOffsets(new ScrollerChangeOffsetsOptions(400.0, 100.0, ScrollerViewKind.Absolute, ScrollerViewChangeKind.DisableAnimation, ScrollerViewChangeSnapPointRespect.IgnoreSnapPoints));
            });
            Verify.IsTrue(horizontalViewChangeCompletedEvent.WaitOne(DefaultWaitTimeInMS));

            RunOnUIThread.Execute(() =>
            {
                Verify.AreEqual(new Rect(300, 100, 200, 200), realizationRects.Last());
                realizationRects.Clear();
            });
        }
Example #13
0
        public void ValidateTabNavigation()
        {
            if (!PlatformConfiguration.IsOsVersionGreaterThan(OSVersion.Redstone2))
            {
                Log.Warning("Test is disabled on anything lower than RS3 because the GetChildrenInTabFocusOrder API is not available on previous versions.");
                return;
            }

            ItemsRepeater repeater         = null;
            ScrollViewer  scrollViewer     = null;
            var           data             = new ObservableCollection <string>(Enumerable.Range(0, 50).Select(i => "Item #" + i));
            var           viewChangedEvent = new AutoResetEvent(false);

            RunOnUIThread.Execute(() =>
            {
                var itemTemplate = (DataTemplate)XamlReader.Load(
                    @"<DataTemplate  xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'>
                            <Button Content='{Binding}' Height='100' />
                        </DataTemplate>");

                var elementFactory = new RecyclingElementFactory()
                {
                    RecyclePool = new RecyclePool(),
                    Templates   =
                    {
                        { "itemTemplate", itemTemplate },
                    }
                };

                Content = CreateAndInitializeRepeater
                          (
                    data,
                    new StackLayout(),
                    elementFactory,
                    ref repeater,
                    ref scrollViewer
                          );

                scrollViewer.ViewChanged += (s, e) =>
                {
                    if (!e.IsIntermediate)
                    {
                        viewChangedEvent.Set();
                    }
                };

                repeater.TabFocusNavigation = KeyboardNavigationMode.Local;
                Content.UpdateLayout();
                ValidateTabNavigationOrder(repeater);
            });

            IdleSynchronizer.Wait();

            RunOnUIThread.Execute(() =>
            {
                // Move window - disconnected
                scrollViewer.ChangeView(null, 2000, null, true);
            });

            Verify.IsTrue(viewChangedEvent.WaitOne(DefaultWaitTimeInMS), "Waiting for final ViewChanged event.");
            IdleSynchronizer.Wait();

            RunOnUIThread.Execute(() =>
            {
                ValidateTabNavigationOrder(repeater);

                // Delete a couple of elements and validate we
                // ignore unrealized elements.
                // First visible element is expected to be index 20.
                while (data.Count > 21)
                {
                    data.RemoveAt(21);
                }

                repeater.UpdateLayout();
                ValidateTabNavigationOrder(repeater);
            });
        }
        public void ValidateElementAnimator()
        {
            ItemsRepeater          repeater = null;
            ElementAnimatorDerived animator = null;
            var data           = new ObservableCollection <string>(Enumerable.Range(0, 10).Select(i => string.Format("Item #{0}", i)));
            var renderingEvent = new ManualResetEvent(false);

            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>");

                CompositionTarget.Rendering += (sender, args) =>
                {
                    renderingEvent.Set();
                };

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

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

            IdleSynchronizer.Wait();
            Verify.IsTrue(renderingEvent.WaitOne(), "Waiting for rendering event");

            List <CallInfo> showCalls         = new List <CallInfo>();
            List <CallInfo> hideCalls         = new List <CallInfo>();
            List <CallInfo> boundsChangeCalls = new List <CallInfo>();

            RunOnUIThread.Execute(() =>
            {
                animator = new ElementAnimatorDerived()
                {
                    HasShowAnimationValue         = true,
                    HasHideAnimationValue         = true,
                    HasBoundsChangeAnimationValue = true,
                    StartShowAnimationFunc        = (UIElement element, AnimationContext context) =>
                    {
                        showCalls.Add(new CallInfo(repeater.GetElementIndex(element), context));
                    },

                    StartHideAnimationFunc = (UIElement element, AnimationContext context) =>
                    {
                        hideCalls.Add(new CallInfo(repeater.GetElementIndex(element), context));
                    },

                    StartBoundsChangeAnimationFunc = (UIElement element, AnimationContext context, Rect oldBounds, Rect newBounds) =>
                    {
                        boundsChangeCalls.Add(new CallInfo(repeater.GetElementIndex(element), context, oldBounds, newBounds));
                    }
                };
                repeater.Animator = animator;

                renderingEvent.Reset();
                data.Insert(0, "new item");
                data.RemoveAt(2);
            });

            Verify.IsTrue(renderingEvent.WaitOne(), "Waiting for rendering event");
            IdleSynchronizer.Wait();

            Verify.AreEqual(1, showCalls.Count);
            var call = showCalls[0];

            Verify.AreEqual(0, call.Index);
            Verify.AreEqual(AnimationContext.CollectionChangeAdd, call.Context);

            Verify.AreEqual(1, hideCalls.Count);
            call = hideCalls[0];
            Verify.AreEqual(-1, call.Index); // not in the repeater anymore
            Verify.AreEqual(AnimationContext.CollectionChangeRemove, call.Context);

            Verify.AreEqual(1, boundsChangeCalls.Count);
            call = boundsChangeCalls[0];
            Verify.AreEqual(1, call.Index);
            Verify.AreEqual(AnimationContext.CollectionChangeAdd | AnimationContext.CollectionChangeRemove, call.Context);
            Verify.AreEqual(0, call.OldBounds.Y);
            Verify.AreEqual(50, call.NewBounds.Y);

            showCalls.Clear();
            hideCalls.Clear();
            boundsChangeCalls.Clear();

            // Hookup just for show animations and validate.
            RunOnUIThread.Execute(() =>
            {
                animator.HasShowAnimationValue         = true;
                animator.HasHideAnimationValue         = false;
                animator.HasBoundsChangeAnimationValue = false;

                renderingEvent.Reset();
                data.Insert(0, "new item");
                data.RemoveAt(2);
            });

            Verify.IsTrue(renderingEvent.WaitOne(), "Waiting for rendering event");
            IdleSynchronizer.Wait();

            Verify.AreEqual(1, showCalls.Count);
            call = showCalls[0];
            Verify.AreEqual(0, call.Index);
            Verify.AreEqual(AnimationContext.CollectionChangeAdd, call.Context);

            Verify.AreEqual(0, hideCalls.Count);
            Verify.AreEqual(0, boundsChangeCalls.Count);
        }
        public void CanGrowCacheBufferWithScrollViewer()
        {
            if (!PlatformConfiguration.IsOsVersionGreaterThanOrEqual(OSVersion.Redstone5))
            {
                Log.Warning("Skipping since version is less than RS5 and effective viewport is not available below RS5");
                return;
            }

            ScrollViewer  scroller = null;
            ItemsRepeater repeater = null;
            var           measureRealizationRects = new List <Rect>();
            var           arrangeRealizationRects = new List <Rect>();
            var           fullCacheEvent          = new ManualResetEvent(initialState: false);

            RunOnUIThread.Execute(() =>
            {
                Log.Comment("Preparing the visual tree...");

                scroller = new ScrollViewer
                {
                    Width  = 400,
                    Height = 400
                };

                var layout = new MockVirtualizingLayout
                {
                    MeasureLayoutFunc = (availableSize, context) =>
                    {
                        var ctx = (VirtualizingLayoutContext)context;
                        Log.Comment("MeasureLayout - Rect:" + ctx.RealizationRect);
                        if (measureRealizationRects.Count == 0 || measureRealizationRects.Last() != ctx.RealizationRect)
                        {
                            measureRealizationRects.Add(ctx.RealizationRect);
                        }

                        return(new Size(1000, 2000));
                    },

                    ArrangeLayoutFunc = (finalSize, context) =>
                    {
                        var ctx = (VirtualizingLayoutContext)context;
                        Log.Comment("ArrangeLayout - Rect:" + ctx.RealizationRect);
                        if (arrangeRealizationRects.Count == 0 || arrangeRealizationRects.Last() != ctx.RealizationRect)
                        {
                            arrangeRealizationRects.Add(ctx.RealizationRect);
                        }

                        if (ctx.RealizationRect.Height == scroller.Height * (repeater.VerticalCacheLength + 1))
                        {
                            fullCacheEvent.Set();
                        }

                        return(finalSize);
                    }
                };

                repeater = new ItemsRepeater()
                {
                    Layout = layout
                };

                scroller.Content = repeater;
                Content          = scroller;
            });

            if (!fullCacheEvent.WaitOne(DefaultWaitTimeInMS))
            {
                Verify.Fail("Cache full size never reached.");
            }
            IdleSynchronizer.Wait();

            RunOnUIThread.Execute(() =>
            {
                var cacheLength = repeater.VerticalCacheLength;
                var expectedRealizationWindow = new Rect(
                    -cacheLength / 2 * scroller.Width,
                    -cacheLength / 2 * scroller.Height,
                    (1 + cacheLength) * scroller.Width,
                    (1 + cacheLength) * scroller.Height);

                Log.Comment("Validate that the realization window reached full size.");
                Verify.AreEqual(expectedRealizationWindow, measureRealizationRects.Last());
                Verify.AreEqual(expectedRealizationWindow, arrangeRealizationRects.Last());

                Log.Comment("Validate that the realization window grew by 40 pixels each time during the process.");
                for (int i = 2; i < measureRealizationRects.Count; ++i)
                {
                    Verify.AreEqual(-40, measureRealizationRects[i].X - measureRealizationRects[i - 1].X);
                    Verify.AreEqual(-40, measureRealizationRects[i].Y - measureRealizationRects[i - 1].Y);
                    Verify.AreEqual(80, measureRealizationRects[i].Width - measureRealizationRects[i - 1].Width);
                    Verify.AreEqual(80, measureRealizationRects[i].Height - measureRealizationRects[i - 1].Height);

                    Verify.AreEqual(-40, arrangeRealizationRects[i].X - arrangeRealizationRects[i - 1].X);
                    Verify.AreEqual(-40, arrangeRealizationRects[i].Y - arrangeRealizationRects[i - 1].Y);
                    Verify.AreEqual(80, arrangeRealizationRects[i].Width - arrangeRealizationRects[i - 1].Width);
                    Verify.AreEqual(80, arrangeRealizationRects[i].Height - arrangeRealizationRects[i - 1].Height);
                }
            });
        }
Example #16
0
        private async void OnElementPrepared(Microsoft.UI.Xaml.Controls.ItemsRepeater sender, Microsoft.UI.Xaml.Controls.ItemsRepeaterElementPreparedEventArgs args)
        {
            var button  = args.Element as Button;
            var content = button.Content as Grid;

            var title    = content.Children[1] as TextBlock;
            var subtitle = content.Children[2] as TextBlock;

            if (button.DataContext is MessageInteractionCounters counters)
            {
                var photo = content.Children[0] as Image;

                var views  = content.Children[3] as TextBlock;
                var shares = content.Children[4] as TextBlock;

                var caption = counters.Message.GetCaption();
                if (string.IsNullOrEmpty(caption?.Text))
                {
                    var message = counters.Message;
                    if (message.Content is MessageVoiceNote)
                    {
                        title.Text = Strings.Resources.AttachAudio;
                    }
                    else if (message.Content is MessageVideo)
                    {
                        title.Text = Strings.Resources.AttachVideo;
                    }
                    else if (message.Content is MessageAnimation)
                    {
                        title.Text = Strings.Resources.AttachGif;
                    }
                    else if (message.Content is MessageAudio audio)
                    {
                        var performer = string.IsNullOrEmpty(audio.Audio.Performer) ? null : audio.Audio.Performer;
                        var titloe    = string.IsNullOrEmpty(audio.Audio.Title) ? null : audio.Audio.Title;

                        if (performer == null || titloe == null)
                        {
                            title.Text = Strings.Resources.AttachMusic;
                        }
                        else
                        {
                            title.Text = $"\uD83C\uDFB5 {performer} - {titloe}";
                        }
                    }
                    else if (message.Content is MessageDocument document)
                    {
                        if (string.IsNullOrEmpty(document.Document.FileName))
                        {
                            title.Text = Strings.Resources.AttachDocument;
                        }
                        else
                        {
                            title.Text = document.Document.FileName;
                        }
                    }
                    else if (message.Content is MessageInvoice invoice)
                    {
                        title.Text = invoice.Title;
                    }
                    else if (message.Content is MessageContact)
                    {
                        title.Text = Strings.Resources.AttachContact;
                    }
                    else if (message.Content is MessageLocation location)
                    {
                        title.Text = location.LivePeriod > 0 ? Strings.Resources.AttachLiveLocation : Strings.Resources.AttachLocation;
                    }
                    else if (message.Content is MessageVenue)
                    {
                        title.Text = Strings.Resources.AttachLocation;
                    }
                    else if (message.Content is MessagePhoto)
                    {
                        title.Text = Strings.Resources.AttachPhoto;
                    }
                    else if (message.Content is MessagePoll poll)
                    {
                        title.Text = "\uD83D\uDCCA " + poll.Poll.Question;
                    }
                    else if (message.Content is MessageCall call)
                    {
                        title.Text = call.ToOutcomeText(message.IsOutgoing);
                    }
                    else if (message.Content is MessageUnsupported)
                    {
                        title.Text = Strings.Resources.UnsupportedAttachment;
                    }
                }
                else
                {
                    title.Text = caption.Text.Replace('\n', ' ');
                }

                subtitle.Text = Converter.DateAt(counters.Message.Date);

                views.Text  = Locale.Declension("Views", counters.ViewCount);
                shares.Text = Locale.Declension("Shares", counters.ForwardCount);

                var thumbnail = counters.Message.GetMinithumbnail();
                if (thumbnail != null)
                {
                    double ratioX = (double)36 / thumbnail.Width;
                    double ratioY = (double)36 / thumbnail.Height;
                    double ratio  = Math.Max(ratioX, ratioY);

                    var width  = (int)(thumbnail.Width * ratio);
                    var height = (int)(thumbnail.Height * ratio);

                    var bitmap = new BitmapImage {
                        DecodePixelWidth = width, DecodePixelHeight = height, DecodePixelType = DecodePixelType.Logical
                    };
                    var bytes = thumbnail.Data.ToArray();

                    using (var stream = new System.IO.MemoryStream(bytes))
                    {
                        var random = System.IO.WindowsRuntimeStreamExtensions.AsRandomAccessStream(stream);
                        await bitmap.SetSourceAsync(random);
                    }

                    photo.Source     = bitmap;
                    photo.Visibility = Visibility.Visible;
                }
                else
                {
                    photo.Visibility = Visibility.Collapsed;
                    photo.Source     = null;
                }

                button.CommandParameter = counters.Message;
                button.Command          = ViewModel.OpenPostCommand;
            }
            else
            {
                var photo = content.Children[0] as ProfilePicture;

                if (button.DataContext is ChatStatisticsMessageSenderInfo senderInfo)
                {
                    var user = ViewModel.CacheService.GetUser(senderInfo.UserId);
                    if (user == null)
                    {
                        return;
                    }

                    var stringBuilder = new StringBuilder();
                    if (senderInfo.SentMessageCount > 0)
                    {
                        stringBuilder.Append(Locale.Declension("messages", senderInfo.SentMessageCount));
                    }

                    if (senderInfo.AverageCharacterCount > 0)
                    {
                        if (stringBuilder.Length > 0)
                        {
                            stringBuilder.Append(", ");
                        }
                        stringBuilder.AppendFormat(Strings.Resources.CharactersPerMessage, Locale.Declension("Characters", senderInfo.AverageCharacterCount));
                    }

                    title.Text    = user.GetFullName();
                    subtitle.Text = stringBuilder.ToString();
                    photo.Source  = PlaceholderHelper.GetUser(ViewModel.ProtoService, user, 36);

                    button.CommandParameter = senderInfo.UserId;
                    button.Command          = ViewModel.OpenProfileCommand;
                }
                else if (button.DataContext is ChatStatisticsAdministratorActionsInfo adminInfo)
                {
                    var user = ViewModel.CacheService.GetUser(adminInfo.UserId);
                    if (user == null)
                    {
                        return;
                    }

                    var stringBuilder = new StringBuilder();
                    if (adminInfo.DeletedMessageCount > 0)
                    {
                        stringBuilder.Append(Locale.Declension("Deletions", adminInfo.DeletedMessageCount));
                    }

                    if (adminInfo.BannedUserCount > 0)
                    {
                        if (stringBuilder.Length > 0)
                        {
                            stringBuilder.Append(", ");
                        }

                        stringBuilder.Append(Locale.Declension("Bans", adminInfo.BannedUserCount));
                    }

                    if (adminInfo.RestrictedUserCount > 0)
                    {
                        if (stringBuilder.Length > 0)
                        {
                            stringBuilder.Append(", ");
                        }

                        stringBuilder.Append(Locale.Declension("Restrictions", adminInfo.RestrictedUserCount));
                    }

                    title.Text    = user.GetFullName();
                    subtitle.Text = stringBuilder.ToString();
                    photo.Source  = PlaceholderHelper.GetUser(ViewModel.ProtoService, user, 36);

                    button.CommandParameter = adminInfo.UserId;
                    button.Command          = ViewModel.OpenProfileCommand;
                }
                else if (button.DataContext is ChatStatisticsInviterInfo inviterInfo)
                {
                    var user = ViewModel.CacheService.GetUser(inviterInfo.UserId);
                    if (user == null)
                    {
                        return;
                    }

                    if (inviterInfo.AddedMemberCount > 0)
                    {
                        subtitle.Text = Locale.Declension("Invitations", inviterInfo.AddedMemberCount);
                    }
                    else
                    {
                        subtitle.Text = string.Empty;
                    }

                    title.Text   = user.GetFullName();
                    photo.Source = PlaceholderHelper.GetUser(ViewModel.ProtoService, user, 36);

                    button.CommandParameter = inviterInfo.UserId;
                    button.Command          = ViewModel.OpenProfileCommand;
                }
            }
        }
        public void CanBringIntoViewElements()
        {
            if (!PlatformConfiguration.IsOsVersionGreaterThanOrEqual(OSVersion.Redstone5))
            {
                Log.Warning("Skipping since version is less than RS5 and effective viewport is not available below RS5");
                return;
            }

            if (!PlatformConfiguration.IsOsVersionGreaterThan(OSVersion.Redstone3))
            {
                Log.Warning("Skipping CanBringIntoViewElements because UIElement.BringIntoViewRequested was added in RS4.");
                return;
            }

            ScrollViewer  scroller        = null;
            ItemsRepeater repeater        = null;
            var           rootLoadedEvent = new AutoResetEvent(initialState: false);
            var           effectiveViewChangeCompletedEvent = new AutoResetEvent(initialState: false);
            var           viewChangeCompletedEvent          = new AutoResetEvent(initialState: false);

            var viewChangedOffsets = new List <double>();

            RunOnUIThread.Execute(() =>
            {
                var lorem = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam laoreet erat vel massa rutrum, eget mollis massa vulputate. Vivamus semper augue leo, eget faucibus nulla mattis nec. Donec scelerisque lacus at dui ultricies, eget auctor ipsum placerat. Integer aliquet libero sed nisi eleifend, nec rutrum arcu lacinia. Sed a sem et ante gravida congue sit amet ut augue. Donec quis pellentesque urna, non finibus metus. Proin sed ornare tellus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam laoreet erat vel massa rutrum, eget mollis massa vulputate. Vivamus semper augue leo, eget faucibus nulla mattis nec. Donec scelerisque lacus at dui ultricies, eget auctor ipsum placerat. Integer aliquet libero sed nisi eleifend, nec rutrum arcu lacinia. Sed a sem et ante gravida congue sit amet ut augue. Donec quis pellentesque urna, non finibus metus. Proin sed ornare tellus.";
                var root  = (Grid)XamlReader.Load(TestUtilities.ProcessTestXamlForRepo(
                                                      @"<Grid 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'> 
                         <Grid.Resources>
                           <controls:StackLayout x:Name='VerticalStackLayout' />
                           <controls:RecyclingElementFactory x:Key='ElementFactory'>
                             <controls:RecyclingElementFactory.RecyclePool>
                               <controls:RecyclePool />
                             </controls:RecyclingElementFactory.RecyclePool>
                             <DataTemplate x:Key='ItemTemplate'>
                               <Border Background='LightGray' Margin ='5'>
                                 <TextBlock Text='{Binding}' TextWrapping='WrapWholeWords' />
                               </Border>
                             </DataTemplate>
                           </controls:RecyclingElementFactory>
                         </Grid.Resources>
                         <ScrollViewer x:Name='Scroller' Width='400' Height='600' Background='Gray'>
                           <controls:ItemsRepeater
                             x:Name='ItemsRepeater'
                             ItemTemplate='{StaticResource ElementFactory}'
                             Layout='{StaticResource VerticalStackLayout}'
                             HorizontalCacheLength='0'
                             VerticalCacheLength='0' />
                         </ScrollViewer>
                       </Grid>"));

                var elementFactory = (RecyclingElementFactory)root.Resources["ElementFactory"];
                scroller           = (ScrollViewer)root.FindName("Scroller");
                repeater           = (ItemsRepeater)root.FindName("ItemsRepeater");

                var items = Enumerable.Range(0, 400).Select(i => string.Format("{0}: {1}", i, lorem.Substring(0, 250)));

                repeater.ItemsSource = items;

                scroller.ViewChanged += (o, e) =>
                {
                    Log.Comment("ViewChanged: " + scroller.VerticalOffset);
                    viewChangedOffsets.Add(scroller.VerticalOffset);
                    if (!e.IsIntermediate)
                    {
                        viewChangeCompletedEvent.Set();
                    }
                };

                scroller.EffectiveViewportChanged += (o, args) =>
                {
                    effectiveViewChangeCompletedEvent.Set();
                };

                Content = root;

                root.Loaded += delegate
                {
                    rootLoadedEvent.Set();
                };
            });
            Verify.IsTrue(rootLoadedEvent.WaitOne(DefaultWaitTimeInMS));
            IdleSynchronizer.Wait();

            RunOnUIThread.Execute(() =>
            {
                repeater.GetOrCreateElement(100).StartBringIntoView();
                repeater.UpdateLayout();
            });

            Verify.IsTrue(viewChangeCompletedEvent.WaitOne(DefaultWaitTimeInMS));
            IdleSynchronizer.Wait();
            Verify.AreEqual(1, viewChangedOffsets.Count);
            viewChangedOffsets.Clear();

            ValidateRealizedRange(repeater, 99, 106);

            RunOnUIThread.Execute(() =>
            {
                Log.Comment("Scroll into view item 105 (already realized) w/ animation.");
                repeater.TryGetElement(105).StartBringIntoView(new BringIntoViewOptions
                {
                    VerticalAlignmentRatio = 0.5,
                    AnimationDesired       = true
                });
                repeater.UpdateLayout();
            });

            Verify.IsTrue(viewChangeCompletedEvent.WaitOne(DefaultWaitTimeInMS));
            IdleSynchronizer.Wait();
            Verify.IsLessThanOrEqual(1, viewChangedOffsets.Count);
            viewChangedOffsets.Clear();
            ValidateRealizedRange(repeater, 99, 106);

            RunOnUIThread.Execute(() =>
            {
                Log.Comment("Scroll item 0 to the top w/ animation and 0.5 vertical alignment.");
                repeater.GetOrCreateElement(0).StartBringIntoView(new BringIntoViewOptions
                {
                    VerticalAlignmentRatio = 0.5,
                    AnimationDesired       = true
                });
                repeater.UpdateLayout();
            });

            Verify.IsTrue(viewChangeCompletedEvent.WaitOne(DefaultWaitTimeInMS));
            IdleSynchronizer.Wait();
            viewChangedOffsets.Clear();
            ValidateRealizedRange(repeater, 0, 6);

            RunOnUIThread.Execute(() =>
            {
                // You can't align the first group in the middle obviously.
                Verify.AreEqual(0, scroller.VerticalOffset);

                Log.Comment("Scroll to item 20.");
                repeater.GetOrCreateElement(20).StartBringIntoView(new BringIntoViewOptions
                {
                    VerticalAlignmentRatio = 0.0
                });
                repeater.UpdateLayout();
            });

            Verify.IsTrue(viewChangeCompletedEvent.WaitOne(DefaultWaitTimeInMS));
            IdleSynchronizer.Wait();
            ValidateRealizedRange(repeater, 19, 26);
        }
Example #18
0
        public void ValidateNonVirtualLayoutDoesNotGetMeasuredForViewportChanges()
        {
            RunOnUIThread.Execute(() =>
            {
                int measureCount = 0;
                int arrangeCount = 0;
                var repeater     = new ItemsRepeater();

                // with a non virtualizing layout, repeater will just
                // run layout once.
                repeater.Layout = new MockNonVirtualizingLayout()
                {
                    MeasureLayoutFunc = (size, context) =>
                    {
                        measureCount++;
                        return(new Size(100, 800));
                    },
                    ArrangeLayoutFunc = (size, context) =>
                    {
                        arrangeCount++;
                        return(new Size(100, 800));
                    }
                };

                repeater.ItemsSource  = Enumerable.Range(0, 10);
                repeater.ItemTemplate = (DataTemplate)XamlReader.Load(
                    @"<DataTemplate  xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'>
                         <Button Content='{Binding}' Height='100' />
                    </DataTemplate>");

                Content = new ScrollViewer()
                {
                    Content = repeater
                };
                Content.UpdateLayout();

                Verify.AreEqual(1, measureCount);
                Verify.AreEqual(1, arrangeCount);

                measureCount = 0;
                arrangeCount = 0;

                // Once we switch to a virtualizing layout we should
                // get at least two passes to update the viewport.
                repeater.Layout = new MockVirtualizingLayout()
                {
                    MeasureLayoutFunc = (size, context) =>
                    {
                        measureCount++;
                        return(new Size(100, 800));
                    },
                    ArrangeLayoutFunc = (size, context) =>
                    {
                        arrangeCount++;
                        return(new Size(100, 800));
                    }
                };

                Content.UpdateLayout();

                Verify.IsGreaterThan(measureCount, 1);
                Verify.IsGreaterThan(arrangeCount, 1);
            });
        }
        public void AnchoringAtRepeaterMiddle()
        {
            using (ScrollerTestHooksHelper scrollerTestHooksHelper = new ScrollerTestHooksHelper(
                       enableAnchorNotifications: true,
                       enableInteractionSourcesNotifications: true,
                       enableExpressionAnimationStatusNotifications: false))
            {
                using (PrivateLoggingHelper privateLoggingHelper = new PrivateLoggingHelper("Scroller"))
                {
                    Scroller       scroller                 = null;
                    AutoResetEvent scrollerLoadedEvent      = new AutoResetEvent(false);
                    AutoResetEvent scrollerViewChangedEvent = new AutoResetEvent(false);

                    RunOnUIThread.Execute(() =>
                    {
                        scroller = new Scroller();

                        SetupRepeaterAnchoringUI(scroller, scrollerLoadedEvent);

                        scroller.HorizontalAnchorRatio = double.NaN;
                        scroller.VerticalAnchorRatio   = 0.5;
                    });

                    WaitForEvent("Waiting for Loaded event", scrollerLoadedEvent);

                    ZoomTo(scroller, 2.0f, 0.0f, 0.0f, AnimationMode.Enabled, SnapPointsMode.Ignore);
                    ScrollTo(scroller, 0.0, 250.0, AnimationMode.Enabled, SnapPointsMode.Ignore, false /*hookViewChanged*/);

                    ItemsRepeater  repeater   = null;
                    TestDataSource dataSource = null;

                    RunOnUIThread.Execute(() =>
                    {
                        repeater   = (scroller.Content as Border).Child as ItemsRepeater;
                        dataSource = repeater.ItemsSource as TestDataSource;

                        scroller.ViewChanged += delegate(Scroller sender, object args) {
                            scrollerViewChangedEvent.Set();
                        };

                        Log.Comment("Inserting items at the beginning");
                        dataSource.Insert(0 /*index*/, 2 /*count*/);
                    });

                    WaitForEvent("Waiting for Scroller.ViewChanged event", scrollerViewChangedEvent);

                    RunOnUIThread.Execute(() =>
                    {
                        Log.Comment("Scroller offset change expected");
                        Verify.AreEqual(520.0, scroller.VerticalOffset);
                    });

                    RunOnUIThread.Execute(() =>
                    {
                        scrollerViewChangedEvent.Reset();

                        Log.Comment("Removing items from the beginning");
                        dataSource.Remove(0 /*index*/, 2 /*count*/);
                    });

                    WaitForEvent("Waiting for Scroller.ViewChanged event", scrollerViewChangedEvent);

                    RunOnUIThread.Execute(() =>
                    {
                        Log.Comment("Scroller offset change expected");
                        Verify.AreEqual(250.0, scroller.VerticalOffset);
                    });
                }
            }
        }
Example #20
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);
                    }
                });
            }
        }
        public void AnchoringAtRepeaterMiddle()
        {
            using (ScrollerTestHooksHelper scrollerTestHooksHelper = new ScrollerTestHooksHelper())
            {
                using (PrivateLoggingHelper privateLoggingHelper = new PrivateLoggingHelper("Scroller"))
                {
                    Scroller       scroller                 = null;
                    AutoResetEvent scrollerLoadedEvent      = new AutoResetEvent(false);
                    AutoResetEvent scrollerViewChangedEvent = new AutoResetEvent(false);

                    RunOnUIThread.Execute(() =>
                    {
                        scroller = new Scroller();

                        SetupRepeaterAnchoringUI(scroller, scrollerLoadedEvent);

                        scroller.HorizontalAnchorRatio = double.NaN;
                        scroller.VerticalAnchorRatio   = 0.5;
                    });

                    WaitForEvent("Waiting for Loaded event", scrollerLoadedEvent);

                    ChangeZoomFactor(scroller, 2.0f, 0.0f, 0.0f, ScrollerViewKind.Absolute, ScrollerViewChangeKind.AllowAnimation);
                    ChangeOffsets(scroller, 0.0, 250.0, ScrollerViewKind.Absolute, ScrollerViewChangeKind.AllowAnimation, ScrollerViewChangeSnapPointRespect.IgnoreSnapPoints, false /*hookViewChanged*/);

                    ItemsRepeater  repeater   = null;
                    TestDataSource dataSource = null;

                    RunOnUIThread.Execute(() =>
                    {
                        repeater   = (scroller.Content as Border).Child as ItemsRepeater;
                        dataSource = repeater.ItemsSource as TestDataSource;

                        scroller.ViewChanged += delegate(Scroller sender, object args) {
                            scrollerViewChangedEvent.Set();
                        };

                        Log.Comment("Inserting items at the beginning");
                        dataSource.Insert(0 /*index*/, 2 /*count*/);
                    });

                    WaitForEvent("Waiting for Scroller.ViewChanged event", scrollerViewChangedEvent);

                    RunOnUIThread.Execute(() =>
                    {
                        Log.Comment("Scroller offset change expected");
                        Verify.AreEqual(scroller.VerticalOffset, 520.0);
                    });

                    RunOnUIThread.Execute(() =>
                    {
                        scrollerViewChangedEvent.Reset();

                        Log.Comment("Removing items from the beginning");
                        dataSource.Remove(0 /*index*/, 2 /*count*/);
                    });

                    WaitForEvent("Waiting for Scroller.ViewChanged event", scrollerViewChangedEvent);

                    RunOnUIThread.Execute(() =>
                    {
                        Log.Comment("Scroller offset change expected");
                        Verify.AreEqual(scroller.VerticalOffset, 250.0);
                    });
                }
            }
        }
Example #22
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 #23
0
 private void Repeater_ElementIndexChanged(ItemsRepeater sender, ItemsRepeaterElementIndexChangedEventArgs args)
 {
     (args.Element as RepeaterItem).RepeatedIndex = args.NewIndex;
 }
Example #24
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 #25
0
        public void ValidateXBindWithoutPhasing()
        {
            ItemsRepeater    repeater  = null;
            int              numPhases = 1; // Just Phase 0 for x:Bind
            ManualResetEvent ElementLoadedCompleted = new ManualResetEvent(false);

            RunOnUIThread.Execute(() =>
            {
                var itemTemplate = (DataTemplate)XamlReader.Load(
                    @"<DataTemplate  xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'>
							<Button Width='100' Height='100'/>
						</DataTemplate>"                        );
                repeater = new ItemsRepeater()
                {
                    ItemsSource  = Enumerable.Range(0, 10),
                    ItemTemplate = new CustomElementFactory(numPhases),
                    Layout       = new StackLayout(),
                };

                repeater.ElementPrepared += (sender, args) =>
                {
                    if (args.Index == expectedLastRealizedIndex)
                    {
                        Log.Comment("Item 8 Created!");
                        ElementLoadedCompleted.Set();
                    }
                };

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

            if (ElementLoadedCompleted.WaitOne(TimeSpan.FromMilliseconds(2000)))
            {
                RunOnUIThread.Execute(() =>
                {
                    var calls = ElementPhasingManager.ProcessedCalls;

                    Verify.AreEqual(calls.Count, 9);
                    calls[0].RemoveAt(0);                     // Remove the create we did for first measure.
                    foreach (var index in calls.Keys)
                    {
                        var phases = calls[index];
                        Verify.AreEqual(1, phases.Count);                         // Just phase 0
                    }

                    ElementPhasingManager.ProcessedCalls.Clear();
                });
            }
            else
            {
                Verify.Fail("Failed on waiting on build tree.");
            }
        }
        public void ValidateOneScrollViewerScenario()
        {
            if (!PlatformConfiguration.IsOsVersionGreaterThanOrEqual(OSVersion.Redstone5))
            {
                Log.Warning("Skipping since version is less than RS5 and effective viewport is not available below RS5");
                return;
            }

            var          realizationRects         = new List <Rect>();
            ScrollViewer scrollViewer             = null;
            var          viewChangeCompletedEvent = new AutoResetEvent(false);

            RunOnUIThread.Execute(() =>
            {
                var repeater = new ItemsRepeater()
                {
                    Layout = GetMonitoringLayout(new Size(500, 600), realizationRects),
                    HorizontalCacheLength = 0.0,
                    VerticalCacheLength   = 0.0
                };

                scrollViewer = new ScrollViewer
                {
                    Content = repeater,
                    Width   = 200,
                    Height  = 300
                };

                Content = scrollViewer;
                Content.UpdateLayout();

                Verify.AreEqual(2, realizationRects.Count);
                Verify.AreEqual(new Rect(0, 0, 0, 0), realizationRects[0]);
                Verify.AreEqual(new Rect(0, 0, 200, 300), realizationRects[1]);
                realizationRects.Clear();

                scrollViewer.ViewChanged += (Object sender, ScrollViewerViewChangedEventArgs args) =>
                {
                    if (!args.IsIntermediate)
                    {
                        viewChangeCompletedEvent.Set();
                    }
                };
            });
            IdleSynchronizer.Wait();

            RunOnUIThread.Execute(() =>
            {
                scrollViewer.ChangeView(0.0, 100.0, null, true);
            });
            Verify.IsTrue(viewChangeCompletedEvent.WaitOne(DefaultWaitTimeInMS));

            RunOnUIThread.Execute(() =>
            {
                Verify.AreEqual(new Rect(0, 100, 200, 300), realizationRects.Last());
                realizationRects.Clear();

                viewChangeCompletedEvent.Reset();
                scrollViewer.ChangeView(null, null, 2.0f, true);
            });
            Verify.IsTrue(viewChangeCompletedEvent.WaitOne(DefaultWaitTimeInMS));

            RunOnUIThread.Execute(() =>
            {
                Verify.AreEqual(
                    new Rect(0, 50, 100, 150),
                    realizationRects.Last());
                realizationRects.Clear();
            });
        }
        private void BtnStartBringIntoView_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                txtInnerStockOffsetsChangeDuration.Text = string.Empty;
                txtOuterStockOffsetsChangeDuration.Text = string.Empty;

                FrameworkElement targetElement = null;
                ItemsRepeater    repeater      = null;

                switch (cmbNestingCombination.SelectedIndex)
                {
                case 0:     //ScrollerInScroller (1)
                    repeater = repeater1;
                    break;

                case 2:     //ScrollerInScrollViewer
                    repeater = repeater2;
                    break;

                case 3:     //ScrollViewerInScroller
                    repeater = repeater3;
                    break;

                case 4:     //ScrollViewerInScrollViewer
                    repeater = repeater4;
                    break;
                }

                Border border = null;

                if (repeater != null)
                {
                    border = repeater.TryGetElement(Convert.ToInt16(txtTargetElement.Text)) as Border;
                }
                else
                {
                    // case ScrollerInScroller (2)
                    border = (innerScroller3.Content as StackPanel).Children[Convert.ToInt16(txtTargetElement.Text)] as Border;
                }

                if (border != null)
                {
                    targetElement = border.Child as FrameworkElement;

                    if (targetElement != null)
                    {
                        BringIntoViewOptions options = new BringIntoViewOptions();

                        options.AnimationDesired         = cmbAnimationDesired.SelectedIndex == 0;
                        options.HorizontalAlignmentRatio = Convert.ToDouble(txtHorizontalAlignmentRatio.Text);
                        options.VerticalAlignmentRatio   = Convert.ToDouble(txtVerticalAlignmentRatio.Text);
                        options.HorizontalOffset         = Convert.ToDouble(txtHorizontalOffset.Text);
                        options.VerticalOffset           = Convert.ToDouble(txtVerticalOffset.Text);

                        if (chkIgnoreSnapPoints.IsChecked == false)
                        {
                            Scroller scroller1 = null;
                            Scroller scroller2 = null;

                            switch (cmbNestingCombination.SelectedIndex)
                            {
                            case 0:     //ScrollerInScroller (1)
                                scroller1 = outerScroller;
                                scroller2 = innerScroller;
                                break;

                            case 1:     //ScrollerInScroller (2)
                                scroller1 = outerScroller3;
                                scroller2 = innerScroller3;
                                break;

                            case 2:     //ScrollerInScrollViewer
                                scroller1 = innerScroller2;
                                break;

                            case 3:     //ScrollViewerInScroller
                                scroller1 = outerScroller2;
                                break;
                            }

                            if (scroller1 != null)
                            {
                                RefreshSnapPoints(scroller1, scroller1.Content as StackPanel);
                            }

                            if (scroller2 != null)
                            {
                                RefreshSnapPoints(scroller2, scroller2.Content as StackPanel);
                            }
                        }

                        if (chkLogBringIntoViewRequestedEvents.IsChecked == true)
                        {
                            AppendAsyncEventMessage("Invoking StartBringIntoView on " + targetElement.Name);
                        }
                        targetElement.StartBringIntoView(options);
                    }
                }

                if (targetElement == null && chkLogBringIntoViewRequestedEvents.IsChecked == true)
                {
                    AppendAsyncEventMessage("TargetElement not found");
                }
            }
            catch (Exception ex)
            {
                txtExceptionReport.Text = ex.ToString();
                lstScrollerEvents.Items.Add(ex.ToString());
            }
        }
        public void ValidateTwoScrollViewerScenario()
        {
            if (!PlatformConfiguration.IsOsVersionGreaterThanOrEqual(OSVersion.Redstone5))
            {
                Log.Warning("Skipping since version is less than RS5 and effective viewport is not available below RS5");
                return;
            }

            var          realizationRects   = new List <Rect>();
            ScrollViewer horizontalScroller = null;
            ScrollViewer verticalScroller   = null;
            var          horizontalViewChangeCompletedEvent = new AutoResetEvent(false);
            var          verticalViewChangeCompletedEvent   = new AutoResetEvent(false);

            RunOnUIThread.Execute(() =>
            {
                var repeater = new ItemsRepeater()
                {
                    Layout = GetMonitoringLayout(new Size(500, 500), realizationRects),
                    HorizontalCacheLength = 0.0,
                    VerticalCacheLength   = 0.0
                };

                horizontalScroller = new ScrollViewer
                {
                    Content = repeater,
                    HorizontalScrollBarVisibility = ScrollBarVisibility.Auto,
                    VerticalScrollBarVisibility   = ScrollBarVisibility.Disabled,
                    HorizontalScrollMode          = ScrollMode.Enabled,
                    VerticalScrollMode            = ScrollMode.Disabled
                };

                verticalScroller = new ScrollViewer
                {
                    Content = horizontalScroller,
                    Width   = 200,
                    Height  = 200
                };

                Content = verticalScroller;
                Content.UpdateLayout();

                Verify.AreEqual(2, realizationRects.Count);
                Verify.AreEqual(new Rect(0, 0, 0, 0), realizationRects[0]);
                Verify.AreEqual(new Rect(0, 0, 200, 200), realizationRects[1]);
                realizationRects.Clear();

                horizontalScroller.ViewChanged += (Object sender, ScrollViewerViewChangedEventArgs args) =>
                {
                    if (!args.IsIntermediate)
                    {
                        horizontalViewChangeCompletedEvent.Set();
                    }
                };

                verticalScroller.ViewChanged += (Object sender, ScrollViewerViewChangedEventArgs args) =>
                {
                    if (!args.IsIntermediate)
                    {
                        verticalViewChangeCompletedEvent.Set();
                    }
                };
            });
            IdleSynchronizer.Wait();

            RunOnUIThread.Execute(() =>
            {
                verticalScroller.ChangeView(0.0, 100.0, null, true);
            });
            Verify.IsTrue(verticalViewChangeCompletedEvent.WaitOne(DefaultWaitTimeInMS));

            RunOnUIThread.Execute(() =>
            {
                Verify.AreEqual(new Rect(0, 100, 200, 200), realizationRects.Last());
                realizationRects.Clear();

                // Max viewport offset is (300, 300). Horizontal viewport offset
                // is expected to get coerced from 400 to 300.
                horizontalScroller.ChangeView(400.0, 100.0, null, true);
            });
            Verify.IsTrue(horizontalViewChangeCompletedEvent.WaitOne(DefaultWaitTimeInMS));

            RunOnUIThread.Execute(() =>
            {
                Verify.AreEqual(new Rect(300, 100, 200, 200), realizationRects.Last());
                realizationRects.Clear();
            });
        }
        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);
                });
            }
            // }
        }
        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.ViewChanged += (sender, args) =>
                {
                    if (!args.IsIntermediate)
                    {
                        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);
            });
        }