[UWP] Multi-window (#2432)
authorEdwin Wachs <edwin.wachs@tecfinance.com.br>
Thu, 15 Aug 2019 17:36:38 +0000 (14:36 -0300)
committerE.Z. Hart <hartez@users.noreply.github.com>
Thu, 15 Aug 2019 17:36:38 +0000 (11:36 -0600)
* Changes to support multiple windows on UWP

* Locker on Layout.cs to prevent concurrency

* Changes on UnitTests to work with multi-window

* implemented Xamarin.Forms.Core and UAP Element.Dispatcher

* Implementation on each platform

* Implementation on each platform

* Improved Element casting for Dispatcher utilization

* Correction of the items presented in the code review

* Control Gallery for Multiple Window and Code Review

* [UnitTests]Add missing file

* Correction for Unit Tests

* Correction for Unit Tests

* Correction for Unit Tests - Removed ThreadStatic in Ticker

* removed thread static

* removed thread static into application class

* Update Control Gallery

* Code Review (Changes)

* Comment

* Adjust StackOverflow when close the app

* Performace improvements

* - fix merge and ui test performance

* Name of method and adjust on NavigationProxy

* Adjustments in the implementation of the DispatcherManager

* Updated the ListProxy method and adjust the initialization of dispacther on page.

* Remove GetDispacther method from IPlatformServices and some adjusments of code review.

* Adjust after merge on NavigationProxy

* Register IDispatcherProvider on Xamarin.Forms.Core.UnitTests

* Adjustments for correct unit tests operation

* Adjustments for correct unit tests operation

* remove spaces

* Adjust for UITests

* Remove IsInvokeRequired and adjusted de instance of s_resolutionList

* Remove lock() on ResolveLayoutChanges method

* Make IDispatcher implementations internal

* Removed Dispatcher association from Element and Page class. Removed Child Assignment in Element Class and ThreadStatic Removal from NavigationProxy Property

* Remove DispatcherManager; contain thread static to UWP implementation

* Make dispatcher lazy

* MockDispatcherProvider on Xaml.UnitTests

* Add mock Dispatcher and DispatcherProvider for XAML unit tests

* Revert "Add mock Dispatcher and DispatcherProvider for XAML unit tests"

This reverts commit 134320d348a3812e44507ae0b50459c8f43478e9.

* Add MockDispactcherProvider on Pager.UnitTests

* Revert covariance change

* Centralize dispatcher checking logic

* Add a fallback dispatcher for platforms without a registered DispatcherProvider

* Remove Dispatcher/DispatcherProvider from project

* Allow UI test pages which use ListProxy to get a dispatcher in UITest mode

* Prevent crash instantiating UITest version of Issue2004

* Removed unnecessary old codes

* Clean up whitespace changes

* Remove unused method

36 files changed:
Xamarin.Forms.ControlGallery.WindowsUniversal/App.xaml
Xamarin.Forms.ControlGallery.WindowsUniversal/SecondaryWindowService.cs
Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue1439.cs
Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue2004.cs
Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Issue2482.cs
Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/TestPages/TestPages.cs
Xamarin.Forms.Controls/CoreGallery.cs
Xamarin.Forms.Controls/Helpers/IWindowNavigation.cs [new file with mode: 0644]
Xamarin.Forms.Controls/ISecondaryWindowService.cs
Xamarin.Forms.Core.UnitTests/ListProxyTests.cs
Xamarin.Forms.Core.UnitTests/MockDispatcher.cs [new file with mode: 0644]
Xamarin.Forms.Core.UnitTests/MockDispatcherProvider.cs [new file with mode: 0644]
Xamarin.Forms.Core.UnitTests/Xamarin.Forms.Core.UnitTests.csproj
Xamarin.Forms.Core/AnimationExtensions.cs
Xamarin.Forms.Core/Application.cs
Xamarin.Forms.Core/BindableObject.cs
Xamarin.Forms.Core/BindingExpression.cs
Xamarin.Forms.Core/DispatcherExtensions.cs [new file with mode: 0644]
Xamarin.Forms.Core/Element.cs
Xamarin.Forms.Core/IDispatcher.cs [new file with mode: 0644]
Xamarin.Forms.Core/IDispatcherProvider.cs [new file with mode: 0644]
Xamarin.Forms.Core/Internals/Ticker.cs
Xamarin.Forms.Core/Layout.cs
Xamarin.Forms.Core/ListProxy.cs
Xamarin.Forms.Core/Shell/SearchHandler.cs
Xamarin.Forms.Core/TemplatedItemsList.cs
Xamarin.Forms.Core/TypedBinding.cs
Xamarin.Forms.Pages.UnitTests/Xamarin.Forms.Pages.UnitTests.csproj
Xamarin.Forms.Platform.UAP/Dispatcher.cs [new file with mode: 0644]
Xamarin.Forms.Platform.UAP/DispatcherProvider.cs [new file with mode: 0644]
Xamarin.Forms.Platform.UAP/ListViewRenderer.cs
Xamarin.Forms.Platform.UAP/Platform.cs
Xamarin.Forms.Platform.UAP/WindowsBasePage.cs
Xamarin.Forms.Platform.UAP/WindowsBasePlatformServices.cs
Xamarin.Forms.Platform.UAP/Xamarin.Forms.Platform.UAP.csproj
Xamarin.Forms.Xaml.UnitTests/Xamarin.Forms.Xaml.UnitTests.csproj

index d114d1a..d4127ea 100644 (file)
@@ -5,5 +5,12 @@
     xmlns:local="using:Xamarin.Forms.ControlGallery.WindowsUniversal"
        RequestedTheme="Light">
 
+       <Application.Resources>
+               <ResourceDictionary>
+                       <ResourceDictionary.MergedDictionaries>
+                               <ResourceDictionary Source="ms-appx:///Xamarin.Forms.Platform.UAP/Resources.xbf" />
+                       </ResourceDictionary.MergedDictionaries>
+               </ResourceDictionary>
+       </Application.Resources>
 
 </Application>
index 58e826a..a9c8b78 100644 (file)
@@ -21,23 +21,10 @@ namespace Xamarin.Forms.ControlGallery.WindowsUniversal
                        await newView.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
                        {
                                var frame = new Windows.UI.Xaml.Controls.Frame();
-                               frame.Navigate(pageType);
-                               Window.Current.Content = frame;
-                               Window.Current.Activate();
-
-                               newViewId = ApplicationView.GetForCurrentView().Id;
-                       });
-                       bool viewShown = await ApplicationViewSwitcher.TryShowAsStandaloneAsync(newViewId);
-               }
 
-               public async Task OpenSecondaryWindow(ContentPage page)
-               {
-                       CoreApplicationView newView = CoreApplication.CreateNewView();
-                       int newViewId = 0;
-                       await newView.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
-                       {
-                               var frame = new Windows.UI.Xaml.Controls.Frame();
-                               frame.Navigate(page);
+                               //The page instance must be created inside the new UI Thread.
+                               ContentPage instance = (ContentPage)Activator.CreateInstance(pageType);
+                               frame.Navigate(instance);
                                Window.Current.Content = frame;
                                Window.Current.Activate();
 
index e3db028..1905da9 100644 (file)
@@ -33,7 +33,7 @@ namespace Xamarin.Forms.Controls.Issues
                const string lblGroup = "lblGroup";
 
                StackLayout _layout = new StackLayout { Spacing = 30, VerticalOptions = LayoutOptions.FillAndExpand };
-               ListView _listView = new ListView { VerticalOptions = LayoutOptions.Start, IsGroupingEnabled = true, RowHeight = 50, HeightRequest = 300 };
+               ListView _listView;
                Label _label1 = new Label { VerticalOptions = LayoutOptions.Start };
                Label _label2 = new Label { VerticalOptions = LayoutOptions.Start, AutomationId = lblItem };
                Label _label3 = new Label { VerticalOptions = LayoutOptions.Start, AutomationId = lblGroup };
@@ -42,6 +42,7 @@ namespace Xamarin.Forms.Controls.Issues
                {
                        BindingContext = new ViewModel();
 
+                       _listView = new ListView { VerticalOptions = LayoutOptions.Start, IsGroupingEnabled = true, RowHeight = 50, HeightRequest = 300 };
                        _listView.ItemTapped += _listView_ItemTapped;
                        _listView.SetBinding(ListView.ItemsSourceProperty, new Binding(nameof(ViewModel.Items)));
                        _listView.SetBinding(ListView.SelectedItemProperty, new Binding(nameof(ViewModel.SelectedItem)));
index a8957f8..c0af863 100644 (file)
@@ -25,6 +25,9 @@ namespace Xamarin.Forms.Controls.Issues
 #endif
        public class Issue2004 : TestContentPage
        {
+#if UITEST
+               protected override void Init(){}
+#else
                static internal NavigationPage settingsPage = new NavigationPage(new SettingsView());
                static internal NavigationPage addressesPage = new NavigationPage(new AddressListView());
                static internal NavigationPage associationsPage = new NavigationPage(new ContentPage());
@@ -289,6 +292,7 @@ namespace Xamarin.Forms.Controls.Issues
                                };
                        }
                }
+#endif
 
 #if UITEST
                [Test]
index bd69aa3..d34daeb 100644 (file)
@@ -3,6 +3,7 @@ using System.Collections.Generic;
 using System.Diagnostics;
 using System.Dynamic;
 using System.Threading;
+using System.Threading.Tasks;
 using Xamarin.Forms.CustomAttributes;
 using Xamarin.Forms.Internals;
 
@@ -62,6 +63,26 @@ namespace Xamarin.Forms.Controls.Issues
                                }
                        };
 
+                       var labelRunsBackground = new Label() { Text = "This should start updating with the time in a few seconds" };
+                       layout.Children.Add(labelRunsBackground);
+
+                       Device.StartTimer(TimeSpan.FromSeconds(1), () =>
+                       {
+                               labelRunsBackground.Dispatcher.BeginInvokeOnMainThread(() => labelRunsBackground.Text = DateTime.Now.ToString("HH:mm:ss"));
+                               return true;
+                       });
+
+                       var threadpoolButton = new Button { Text = "Update Instructions from Thread Pool" };
+                       layout.Children.Add(threadpoolButton);
+
+                       this.Dispatcher.BeginInvokeOnMainThread(() => { instructions.Text = "updated from thread pool 1"; });
+
+                       threadpoolButton.Clicked += (o, a) => {
+                               Task.Run(() => {
+                                       this.Dispatcher.BeginInvokeOnMainThread(() => { instructions.Text = "updated from thread pool 2"; });
+                               });
+                       };
+
                        layout.Children.Add(instructions);
                        layout.Children.Add(_result);
                        layout.Children.Add(button);
index 3a5c9b4..94fbbaa 100644 (file)
@@ -386,6 +386,9 @@ namespace Xamarin.Forms.Controls
                public IApp RunningApp => AppSetup.RunningApp;
 
                protected virtual bool Isolate => false;
+
+               IDispatcher _dispatcher = new FallbackDispatcher();
+               public override IDispatcher Dispatcher { get => _dispatcher; }
 #endif
 
                protected TestCarouselPage()
@@ -527,6 +530,9 @@ namespace Xamarin.Forms.Controls
                public IApp RunningApp => AppSetup.RunningApp;
 
                protected virtual bool Isolate => false;
+
+               IDispatcher _dispatcher = new FallbackDispatcher();
+               public override IDispatcher Dispatcher { get => _dispatcher; }
 #endif
 
                protected TestTabbedPage()
index 72cc79f..870bc64 100644 (file)
@@ -563,7 +563,6 @@ namespace Xamarin.Forms.Controls
                                                        GC.Collect ();
                                                })
                                        }
-
                                }
                        };
 
@@ -571,7 +570,7 @@ namespace Xamarin.Forms.Controls
                        if (secondaryWindowService != null)
                        {
                                var openSecondWindowButton = new Button() { Text = "Open Secondary Window" };
-                               openSecondWindowButton.Clicked += (obj, args) => { secondaryWindowService.OpenSecondaryWindow(new Issue2482()); };
+                               openSecondWindowButton.Clicked += (obj, args) => { secondaryWindowService.OpenSecondaryWindow(typeof(Issue2482)); };
                                stackLayout.Children.Add(openSecondWindowButton);
                        }
 
diff --git a/Xamarin.Forms.Controls/Helpers/IWindowNavigation.cs b/Xamarin.Forms.Controls/Helpers/IWindowNavigation.cs
new file mode 100644 (file)
index 0000000..8ad4ff6
--- /dev/null
@@ -0,0 +1,10 @@
+using System.Threading.Tasks;
+
+namespace Xamarin.Forms.Controls
+{
+    public interface IWindowNavigation
+    {
+               Task OpenNewWindowAsync();
+               void NavigateToAnotherPage(Page page);  
+       }
+}
index b39b55e..45e882e 100644 (file)
@@ -8,6 +8,5 @@ namespace Xamarin.Forms.Controls
        public interface ISecondaryWindowService
        {
                Task OpenSecondaryWindow(Type pageType);
-               Task OpenSecondaryWindow(ContentPage page);
        }
 }
index cd94e71..f63594c 100644 (file)
@@ -257,7 +257,7 @@ namespace Xamarin.Forms.Core.UnitTests
                public void SynchronizedCollectionAdd()
                {
                        bool invoked = false;
-                       Device.PlatformServices  = new MockPlatformServices (invokeOnMainThread: action => {
+                       Device.PlatformServices  = new MockPlatformServices (isInvokeRequired:true, invokeOnMainThread: action => {
                                invoked = true;
                                action();
                        });
diff --git a/Xamarin.Forms.Core.UnitTests/MockDispatcher.cs b/Xamarin.Forms.Core.UnitTests/MockDispatcher.cs
new file mode 100644 (file)
index 0000000..b00efbb
--- /dev/null
@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Xamarin.Forms.Core.UnitTests
+{
+       public class MockDispatcher : IDispatcher
+       {
+               public void BeginInvokeOnMainThread(Action action)
+               {
+                       Device.BeginInvokeOnMainThread(action);
+               }
+
+               bool IDispatcher.IsInvokeRequired => Device.IsInvokeRequired;
+       }
+}
diff --git a/Xamarin.Forms.Core.UnitTests/MockDispatcherProvider.cs b/Xamarin.Forms.Core.UnitTests/MockDispatcherProvider.cs
new file mode 100644 (file)
index 0000000..801213c
--- /dev/null
@@ -0,0 +1,19 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Xamarin.Forms;
+using Xamarin.Forms.Core.UnitTests;
+
+[assembly: Dependency(typeof(MockDispatcherProvider))]
+namespace Xamarin.Forms.Core.UnitTests
+{
+       public class MockDispatcherProvider : IDispatcherProvider
+       {
+               public IDispatcher GetDispatcher(object context)
+               {
+                       return new MockDispatcher();
+               }
+       }
+}
index 1a994b3..080768d 100644 (file)
@@ -73,6 +73,8 @@
     <Compile Include="CommandSourceTests.cs" />
     <Compile Include="CommandTests.cs" />
     <Compile Include="DependencyResolutionTests.cs" />
+    <Compile Include="MockDispatcherProvider.cs" />
+    <Compile Include="MockDispatcher.cs" />
     <Compile Include="DeviceUnitTests.cs" />
     <Compile Include="EffectiveFlowDirectionExtensions.cs" />
     <Compile Include="ShellTestBase.cs" />
index 5e69c43..700c603 100644 (file)
@@ -56,14 +56,7 @@ namespace Xamarin.Forms
                                AbortKinetic(key);
                        };
 
-                       if (Device.IsInvokeRequired)
-                       {
-                               Device.BeginInvokeOnMainThread(abort);
-                       }
-                       else
-                       {
-                               abort();
-                       }
+                       DoAction(self, abort);
 
                        return true;
                }
@@ -109,30 +102,14 @@ namespace Xamarin.Forms
                                throw new ArgumentNullException(nameof(self));
 
                        Action animate = () => AnimateInternal(self, name, transform, callback, rate, length, easing, finished, repeat);
-
-                       if (Device.IsInvokeRequired)
-                       {
-                               Device.BeginInvokeOnMainThread(animate);
-                       }
-                       else
-                       {
-                               animate();
-                       }
+                       DoAction(self, animate);
                }
 
 
                public static void AnimateKinetic(this IAnimatable self, string name, Func<double, double, bool> callback, double velocity, double drag, Action finished = null)
                {
                        Action animate = () => AnimateKineticInternal(self, name, callback, velocity, drag, finished);
-
-                       if (Device.IsInvokeRequired)
-                       {
-                               Device.BeginInvokeOnMainThread(animate);
-                       }
-                       else
-                       {
-                               animate();
-                       }
+                       DoAction(self, animate);
                }
 
                public static bool AnimationIsRunning(this IAnimatable self, string handle)
@@ -296,6 +273,32 @@ namespace Xamarin.Forms
                        }
                }
 
+               static void DoAction(IAnimatable self, Action action)
+               {
+                       if (self is BindableObject element)
+                       {
+                               if (element.Dispatcher.IsInvokeRequired)
+                               {
+                                       element.Dispatcher.BeginInvokeOnMainThread(action);
+                               }
+                               else
+                               {
+                                       action();
+                               }
+
+                               return;
+                       }
+
+                       if (Device.IsInvokeRequired)
+                       {
+                               Device.BeginInvokeOnMainThread(action);
+                       }
+                       else
+                       {
+                               action();
+                       }
+               }
+
                class Info
                {
                        public Action<double> Callback;
index 2b9e2b6..673d5d7 100644 (file)
@@ -14,6 +14,9 @@ namespace Xamarin.Forms
        {
                Task<IDictionary<string, object>> _propertiesTask;
                readonly Lazy<PlatformConfigurationRegistry<Application>> _platformConfigurationRegistry;
+
+               public override IDispatcher Dispatcher => this.GetDispatcher();
+
                IAppIndexingProvider _appIndexProvider;
                ReadOnlyCollection<Element> _logicalChildren;
                Page _mainPage;
@@ -28,9 +31,9 @@ namespace Xamarin.Forms
                        var f = false;
                        if (f)
                                Loader.Load();
-                       NavigationProxy = new NavigationImpl(this);
-                       SetCurrentApplication(this);
 
+                       SetCurrentApplication(this);
+                       NavigationProxy = new NavigationImpl(this);
                        SystemResources = DependencyService.Get<ISystemResourcesProvider>().GetSystemResources();
                        SystemResources.ValuesChanged += OnParentResourcesChanged;
                        _platformConfigurationRegistry = new Lazy<PlatformConfigurationRegistry<Application>>(() => new PlatformConfigurationRegistry<Application>(this));
@@ -177,9 +180,9 @@ namespace Xamarin.Forms
 
                public async Task SavePropertiesAsync()
                {
-                       if (Device.IsInvokeRequired)
+                       if (Dispatcher.IsInvokeRequired)
                        {
-                               Device.BeginInvokeOnMainThread(SaveProperties);
+                               Dispatcher.BeginInvokeOnMainThread(SaveProperties);
                        }
                        else
                        {
@@ -190,9 +193,9 @@ namespace Xamarin.Forms
                // Don't use this unless there really is no better option
                internal void SavePropertiesAsFireAndForget()
                {
-                       if (Device.IsInvokeRequired)
+                       if (Dispatcher.IsInvokeRequired)
                        {
-                               Device.BeginInvokeOnMainThread(SaveProperties);
+                               Dispatcher.BeginInvokeOnMainThread(SaveProperties);
                        }
                        else
                        {
index 3416464..7adcfff 100644 (file)
@@ -9,6 +9,24 @@ namespace Xamarin.Forms
 {
        public abstract class BindableObject : INotifyPropertyChanged, IDynamicResourceHandler
        {
+               IDispatcher _dispatcher;
+               public virtual IDispatcher Dispatcher
+               {
+                       get
+                       {
+                               if (_dispatcher == null)
+                               {
+                                       _dispatcher = this.GetDispatcher();
+                               }
+
+                               return _dispatcher;
+                       }
+                       internal set
+                       {
+                               _dispatcher = value;
+                       }
+               }
+
                readonly Dictionary<BindableProperty, BindablePropertyContext> _properties = new Dictionary<BindableProperty, BindablePropertyContext>(4);
                bool _applying;
                object _inheritedContext;
index ebea375..e1a8093 100644 (file)
@@ -727,14 +727,20 @@ namespace Xamarin.Forms
                                        }
                                }
 
-                               if (Device.IsInvokeRequired)
+                               Action action = () => _expression.Apply();
+                               if (_expression._weakTarget.TryGetTarget(out BindableObject obj) && obj.Dispatcher != null && obj.Dispatcher.IsInvokeRequired)
                                {
-                                       Device.BeginInvokeOnMainThread(() => _expression.Apply());
+                                       obj.Dispatcher.BeginInvokeOnMainThread(action);
+                               }
+                               else if(Device.IsInvokeRequired)
+                               {
+                                       Device.BeginInvokeOnMainThread(action);
                                }
                                else
                                {
-                                       _expression.Apply();
+                                       action();
                                }
+
                        }
 
                        public bool TryGetValue(object source, out object value)
diff --git a/Xamarin.Forms.Core/DispatcherExtensions.cs b/Xamarin.Forms.Core/DispatcherExtensions.cs
new file mode 100644 (file)
index 0000000..f5de449
--- /dev/null
@@ -0,0 +1,68 @@
+using System;
+
+namespace Xamarin.Forms
+{
+       internal static class DispatcherExtensions
+       {
+               static IDispatcherProvider s_current;
+               static IDispatcher s_default;
+
+               public static IDispatcher GetDispatcher(this BindableObject bindableObject)
+               {
+                       if (s_default != null)
+                       {
+                               // If we're already using the fallback dispatcher, keep using it
+                               return s_default;
+                       }
+
+                       // See if the current platform has a DispatcherProvider for us
+                       s_current = s_current ?? DependencyService.Get<IDispatcherProvider>();
+
+                       if (s_current == null)
+                       {
+                               // No DispatcherProvider available, use the fallback dispatcher
+                               s_default = new FallbackDispatcher();
+                               return s_default;
+                       }
+
+                       // Use the DispatcherProvider to retrieve an appropriate dispatcher for this BindableObject
+                       return s_current.GetDispatcher(bindableObject);
+               }
+
+               public static void Dispatch(this IDispatcher dispatcher, Action action)
+               {
+                       if (dispatcher != null)
+                       {
+                               if (dispatcher.IsInvokeRequired)
+                               {
+                                       dispatcher.BeginInvokeOnMainThread(action);
+                               }
+                               else
+                               {
+                                       action();
+                               }
+                       }
+                       else
+                       {
+                               if (Device.IsInvokeRequired)
+                               {
+                                       Device.BeginInvokeOnMainThread(action);
+                               }
+                               else
+                               {
+                                       action();
+                               }
+                       }
+               }
+       }
+
+       internal class FallbackDispatcher : IDispatcher
+       {
+               public bool IsInvokeRequired => Device.IsInvokeRequired;
+
+               public void BeginInvokeOnMainThread(Action action)
+               {
+                       Device.BeginInvokeOnMainThread(action);
+               }
+       }
+}
index bc2d9e6..ca812b9 100644 (file)
@@ -10,7 +10,6 @@ namespace Xamarin.Forms
 {
        public abstract partial class Element : BindableObject, IElement, INameScope, IElementController
        {
-
                public static readonly BindableProperty MenuProperty = BindableProperty.CreateAttached(nameof(Menu), typeof(Menu), typeof(Element), null);
 
                public static Menu GetMenu(BindableObject bindable)
diff --git a/Xamarin.Forms.Core/IDispatcher.cs b/Xamarin.Forms.Core/IDispatcher.cs
new file mode 100644 (file)
index 0000000..cd0c271
--- /dev/null
@@ -0,0 +1,10 @@
+using System;
+
+namespace Xamarin.Forms
+{
+       public interface IDispatcher
+       { 
+               void BeginInvokeOnMainThread(Action action);
+               bool IsInvokeRequired { get; }
+       }
+}
diff --git a/Xamarin.Forms.Core/IDispatcherProvider.cs b/Xamarin.Forms.Core/IDispatcherProvider.cs
new file mode 100644 (file)
index 0000000..1c4ad45
--- /dev/null
@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Xamarin.Forms
+{
+       [EditorBrowsable(EditorBrowsableState.Never)]
+       public interface IDispatcherProvider
+       {
+               IDispatcher GetDispatcher(object context);
+       }
+}
index be48a17..be24d7a 100644 (file)
@@ -84,16 +84,29 @@ namespace Xamarin.Forms.Internals
                {
                        Device.BeginInvokeOnMainThread(() =>
                        {
-                               _timeouts.RemoveAll(t => t.Item1 == handle);
+                               RemoveTimeout(handle);
+                       });
+               }
 
-                               if (_timeouts.Count == 0)
-                               {
-                                       _enabled = false;
-                                       Disable();
-                               }
+               public virtual void Remove(int handle, IDispatcher dispatcher)
+               {
+                       dispatcher.BeginInvokeOnMainThread(() =>
+                       {
+                               RemoveTimeout(handle);
                        });
                }
 
+               void RemoveTimeout(int handle)
+               {
+                       _timeouts.RemoveAll(t => t.Item1 == handle);
+
+                       if (_timeouts.Count == 0)
+                       {
+                               _enabled = false;
+                               Disable();
+                       }
+               }
+
                protected abstract void DisableTimer();
 
                protected abstract void EnableTimer();
index 91ef664..64e7d5b 100644 (file)
@@ -365,23 +365,32 @@ namespace Xamarin.Forms
                                // This avoids a lot of unnecessary layout operations if something is triggering many property
                                // changes at once (e.g., a BindingContext change)
 
-                               Device.BeginInvokeOnMainThread(() =>
+                               if (Dispatcher != null)
                                {
-                                       // if thread safety mattered we would need to lock this and compareexchange above
-                                       IList<KeyValuePair<Layout, int>> copy = s_resolutionList;
-                                       s_resolutionList = new List<KeyValuePair<Layout, int>>();
-                                       s_relayoutInProgress = false;
-
-                                       foreach (KeyValuePair<Layout, int> kvp in copy.OrderBy(kvp => kvp.Value))
-                                       {
-                                               Layout layout = kvp.Key;
-                                               double width = layout.Width, height = layout.Height;
-                                               if (!layout._allocatedFlag && width >= 0 && height >= 0)
-                                               {
-                                                       layout.SizeAllocated(width, height);
-                                               }
-                                       }
-                               });
+                                       Dispatcher.BeginInvokeOnMainThread(ResolveLayoutChanges);
+                               }
+                               else
+                               {
+                                       Device.BeginInvokeOnMainThread(ResolveLayoutChanges);
+                               }                       
+                       }
+               }
+
+               internal void ResolveLayoutChanges()
+               {
+                       // if thread safety mattered we would need to lock this and compareexchange above
+                       IList<KeyValuePair<Layout, int>> copy = s_resolutionList;
+                       s_resolutionList = new List<KeyValuePair<Layout, int>>();
+                       s_relayoutInProgress = false;
+
+                       foreach (KeyValuePair<Layout, int> kvp in copy)
+                       {
+                               Layout layout = kvp.Key;
+                               double width = layout.Width, height = layout.Height;
+                               if (!layout._allocatedFlag && width >= 0 && height >= 0)
+                               {
+                                       layout.SizeAllocated(width, height);
+                               }
                        }
                }
 
index 031b971..a3ff67b 100644 (file)
@@ -10,6 +10,7 @@ namespace Xamarin.Forms
 {
        internal sealed class ListProxy : IReadOnlyList<object>, IListProxy, INotifyCollectionChanged
        {
+               IDispatcher _dispatcher;
                readonly ICollection _collection;
                readonly IList _list;
                readonly int _windowSize;
@@ -26,8 +27,9 @@ namespace Xamarin.Forms
 
                int _windowIndex;
 
-               internal ListProxy(IEnumerable enumerable, int windowSize = int.MaxValue)
+               internal ListProxy(IEnumerable enumerable, int windowSize = int.MaxValue, IDispatcher dispatcher = null)
                {
+                       _dispatcher = dispatcher;
                        _windowSize = windowSize;
 
                        ProxiedEnumerable = enumerable;
@@ -213,16 +215,13 @@ namespace Xamarin.Forms
                                sync.Callback(ProxiedEnumerable, sync.Context, () =>
                                {
                                        e = e.WithCount(Count);
-                                       Device.BeginInvokeOnMainThread(action);
+                                       _dispatcher.Dispatch(action);
                                }, false);
                        }
                        else
                        {
                                e = e.WithCount(Count);
-                               if (Device.IsInvokeRequired)
-                                       Device.BeginInvokeOnMainThread(action);
-                               else
-                                       action();
+                               _dispatcher.Dispatch(action);
                        }
                }
 
index e7d2afd..028d257 100644 (file)
@@ -532,7 +532,7 @@ namespace Xamarin.Forms
                        if (newValue == null)
                                self.ListProxy = null;
                        else
-                               self.ListProxy = new ListProxy((IEnumerable)newValue);
+                               self.ListProxy = new ListProxy((IEnumerable)newValue, dispatcher: self.Dispatcher);
                }
 
                static void OnQueryChanged(BindableObject bindable, object oldValue, object newValue)
index 24e02de..deff582 100644 (file)
@@ -62,9 +62,9 @@ namespace Xamarin.Forms.Internals
 
                        IEnumerable source = GetItemsViewSource();
                        if (source != null)
-                               ListProxy = new ListProxy(source);
+                               ListProxy = new ListProxy(source, dispatcher: _itemsView.Dispatcher);
                        else
-                               ListProxy = new ListProxy(new object[0]);
+                               ListProxy = new ListProxy(new object[0], dispatcher: _itemsView.Dispatcher);
                }
 
                internal TemplatedItemsList(TemplatedItemsList<TView, TItem> parent, IEnumerable itemSource, TView itemsView, BindableProperty itemTemplateProperty, int windowSize = int.MaxValue)
@@ -82,11 +82,11 @@ namespace Xamarin.Forms.Internals
 
                        if (itemSource != null)
                        {
-                               ListProxy = new ListProxy(itemSource, windowSize);
+                               ListProxy = new ListProxy(itemSource, windowSize, _itemsView.Dispatcher);
                                ListProxy.CollectionChanged += OnProxyCollectionChanged;
                        }
                        else
-                               ListProxy = new ListProxy(new object[0]);
+                               ListProxy = new ListProxy(new object[0], dispatcher: _itemsView.Dispatcher);
                }
 
                event PropertyChangedEventHandler ITemplatedItemsList<TItem>.PropertyChanged
@@ -937,9 +937,9 @@ namespace Xamarin.Forms.Internals
 
                        IEnumerable itemSource = GetItemsViewSource();
                        if (itemSource == null)
-                               ListProxy = new ListProxy(new object[0]);
+                               ListProxy = new ListProxy(new object[0], dispatcher: _itemsView.Dispatcher);
                        else
-                               ListProxy = new ListProxy(itemSource);
+                               ListProxy = new ListProxy(itemSource, dispatcher: _itemsView.Dispatcher);
 
                        ListProxy.CollectionChanged += OnProxyCollectionChanged;
                        OnProxyCollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
index 77f3a8e..3d8519c 100644 (file)
@@ -272,7 +272,9 @@ namespace Xamarin.Forms.Internals
                        {
                                if (!string.IsNullOrEmpty(e.PropertyName) && string.CompareOrdinal(e.PropertyName, PropertyName) != 0)
                                        return;
-                               Device.BeginInvokeOnMainThread(() => _binding.Apply(false));
+
+                               IDispatcher dispatcher = (sender as BindableObject)?.Dispatcher;
+                               dispatcher.Dispatch(() => _binding.Apply(false));
                        }
                }
 
index 4db3aaa..b45905f 100644 (file)
     <PackageReference Include="NUnitTestAdapter" Version="2.1.1" />
   </ItemGroup>
   <ItemGroup>
+    <Compile Include="..\Xamarin.Forms.Core.UnitTests\MockDispatcherProvider.cs">
+      <Link>MockDispatcherProvider.cs</Link>
+    </Compile>
+       <Compile Include="..\Xamarin.Forms.Core.UnitTests\MockDispatcher.cs">
+      <Link>MockDispatcher.cs</Link>
+    </Compile>
     <Compile Include="..\Xamarin.Forms.Core.UnitTests\MockPlatformServices.cs">
       <Link>MockPlatformServices.cs</Link>
     </Compile>
diff --git a/Xamarin.Forms.Platform.UAP/Dispatcher.cs b/Xamarin.Forms.Platform.UAP/Dispatcher.cs
new file mode 100644 (file)
index 0000000..fbc9189
--- /dev/null
@@ -0,0 +1,25 @@
+using System;
+using Windows.ApplicationModel.Core;
+using Windows.UI.Core;
+using Xamarin.Forms;
+using Xamarin.Forms.Platform.UWP;
+
+namespace Xamarin.Forms.Platform.UWP
+{
+       internal class Dispatcher : IDispatcher
+       {
+               readonly CoreDispatcher _coreDispatcher;
+
+               public void BeginInvokeOnMainThread(Action action)
+               {
+                       _coreDispatcher.RunAsync(CoreDispatcherPriority.Normal, () => action()).WatchForError();
+               }
+
+               public Dispatcher()
+               {
+                       _coreDispatcher = CoreApplication.GetCurrentView().Dispatcher;
+               }
+
+               bool IDispatcher.IsInvokeRequired => Device.IsInvokeRequired;
+       }
+}
diff --git a/Xamarin.Forms.Platform.UAP/DispatcherProvider.cs b/Xamarin.Forms.Platform.UAP/DispatcherProvider.cs
new file mode 100644 (file)
index 0000000..549a705
--- /dev/null
@@ -0,0 +1,23 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Xamarin.Forms;
+using Xamarin.Forms.Internals;
+using Xamarin.Forms.Platform.UWP;
+
+[assembly: Dependency(typeof(DispatcherProvider))]
+namespace Xamarin.Forms.Platform.UWP
+{
+       internal class DispatcherProvider : IDispatcherProvider
+       {
+               [ThreadStatic]
+               static Dispatcher s_current;
+
+               public IDispatcher GetDispatcher(object context)
+               {
+                       return s_current = s_current ?? new Dispatcher();
+               }
+       }
+}
index 695b8d7..ca31c39 100644 (file)
@@ -207,7 +207,10 @@ namespace Xamarin.Forms.Platform.UWP
                                ReloadData();
                        }
 
-                       Device.BeginInvokeOnMainThread(() => List?.UpdateLayout());
+                       if (Element.Dispatcher == null)
+                               Device.BeginInvokeOnMainThread(() => List?.UpdateLayout());
+                       else
+                               Element.Dispatcher.BeginInvokeOnMainThread(() => List?.UpdateLayout());
                }
 
                protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
index 1cd8609..3243b9d 100644 (file)
@@ -289,8 +289,10 @@ namespace Xamarin.Forms.Platform.UWP
 
                async void SetCurrent(Page newPage, bool popping = false, Action completedCallback = null)
                {
-                       if (newPage == _currentPage)
-                               return;
+                       try
+                       {
+                               if (newPage == _currentPage)
+                                       return;
 
 #pragma warning disable CS0618 // Type or member is obsolete
                        // The Platform property is no longer necessary, but we have to set it because some third-party
@@ -304,30 +306,41 @@ namespace Xamarin.Forms.Platform.UWP
                                IVisualElementRenderer previousRenderer = GetRenderer(previousPage);
                                _container.Children.Remove(previousRenderer.ContainerElement);
 
-                               if (popping)
-                               {
-                                       previousPage.Cleanup();
-                                       // Un-parent the page; otherwise the Resources Changed Listeners won't be unhooked and the 
-                                       // page will leak 
-                                       previousPage.Parent = null;
+                                       if (popping)
+                                       {
+                                               previousPage.Cleanup();
+                                               // Un-parent the page; otherwise the Resources Changed Listeners won't be unhooked and the 
+                                               // page will leak 
+                                               previousPage.Parent = null;
+                                       }
                                }
-                       }
 
-                       newPage.Layout(ContainerBounds);
+                               newPage.Layout(ContainerBounds);
 
-                       IVisualElementRenderer pageRenderer = newPage.GetOrCreateRenderer();
-                       _container.Children.Add(pageRenderer.ContainerElement);
+                               IVisualElementRenderer pageRenderer = newPage.GetOrCreateRenderer();
+                               _container.Children.Add(pageRenderer.ContainerElement);
 
-                       pageRenderer.ContainerElement.Width = _container.ActualWidth;
-                       pageRenderer.ContainerElement.Height = _container.ActualHeight;
+                               pageRenderer.ContainerElement.Width = _container.ActualWidth;
+                               pageRenderer.ContainerElement.Height = _container.ActualHeight;
 
-                       completedCallback?.Invoke();
+                               completedCallback?.Invoke();
 
-                       _currentPage = newPage;
+                               _currentPage = newPage;
 
-                       UpdateToolbarTracker();
+                               UpdateToolbarTracker();
 
-                       await UpdateToolbarItems();
+                               await UpdateToolbarItems();
+                       }
+                       catch(Exception error)
+                       {
+                               //This exception prevents the Main Page from being changed in a child 
+                               //window or a different thread, except on the Main thread. 
+                               //HEX 0x8001010E 
+                               if (error.HResult == -2147417842)
+                                       throw new InvalidOperationException("Changing the current page is only allowed if it's being called from the same UI thread." +
+                                               "Please ensure that the new page is in the same UI thread as the current page.");
+                               throw error;
+                       }
                }
 
                async void OnToolbarItemsChanged(object sender, EventArgs e)
index 3f8616a..bfcdd8d 100644 (file)
@@ -6,6 +6,9 @@ namespace Xamarin.Forms.Platform.UWP
 {
        public abstract class WindowsBasePage : Windows.UI.Xaml.Controls.Page
        {
+
+               Application _application;
+
                public WindowsBasePage()
                {
                        if (!Windows.ApplicationModel.DesignMode.DesignModeEnabled)
@@ -24,19 +27,28 @@ namespace Xamarin.Forms.Platform.UWP
                        if (application == null)
                                throw new ArgumentNullException("application");
 
+                       _application = application;
                        Application.SetCurrentApplication(application);
-                       Platform = CreatePlatform();
-                       if (Application.Current.MainPage != null)
-                               Platform.SetPage(Application.Current.MainPage);
+                       if (_application.MainPage != null)
+                               RegisterWindow(_application.MainPage);
                        application.PropertyChanged += OnApplicationPropertyChanged;
 
-                       Application.Current.SendStart();
+                       _application.SendStart();
+               }
+
+               protected void RegisterWindow(Page page)
+               {
+                       if (page == null)
+                               throw new ArgumentNullException("page");
+
+                       Platform = CreatePlatform();
+                       Platform.SetPage(page);
                }
 
                void OnApplicationPropertyChanged(object sender, PropertyChangedEventArgs e)
                {
                        if (e.PropertyName == "MainPage")
-                               Platform.SetPage(Application.Current.MainPage);
+                               Platform.SetPage(_application.MainPage);
                }
 
                void OnApplicationResuming(object sender, object e)
index e07415d..54da64d 100644 (file)
@@ -124,23 +124,7 @@ namespace Xamarin.Forms.Platform.UWP
                        return new WindowsIsolatedStorage(ApplicationData.Current.LocalFolder);
                }
 
-               public bool IsInvokeRequired
-               {
-                       get
-                       {
-                               if (CoreApplication.Views.Count == 1)
-                               {
-                                       return !_dispatcher.HasThreadAccess;
-                               }
-
-                               if (Window.Current?.Dispatcher != null)
-                               {
-                                       return !Window.Current.Dispatcher.HasThreadAccess;
-                               }
-
-                               return true;
-                       }
-               }
+               public bool IsInvokeRequired => !_dispatcher?.HasThreadAccess ?? true;
 
                public string RuntimePlatform => Device.UWP;
 
index 50a7d38..5193420 100644 (file)
     <Compile Include="AccessibilityExtensions.cs" />
     <Compile Include="CollectionView\ItemsViewRenderer.cs" />
     <Compile Include="CollectionView\SelectableItemsViewRenderer.cs" />
+    <Compile Include="ColorExtensions.cs" />
+    <Compile Include="DispatcherProvider.cs" />
     <Compile Include="Extensions\ImageExtensions.cs" />
     <Compile Include="FormsCheckBox.cs" />
     <Compile Include="IImageVisualElementRenderer.cs" />
     <Compile Include="ImageButtonRenderer.cs" />
     <Compile Include="CollectionView\CollectionViewRenderer.cs" />
+    <Compile Include="Dispatcher.cs" />
     <Compile Include="FormsCancelButton.cs" />
     <Compile Include="AlertDialog.cs" />
     <Compile Include="IDontGetFocus.cs" />
     <Compile Include="CellControl.cs" />
     <Compile Include="CollapseWhenEmptyConverter.cs" />
     <Compile Include="ColorConverter.cs" />
-    <Compile Include="ColorExtensions.cs" />
     <Compile Include="DatePickerRenderer.cs" />
     <Compile Include="DefaultRenderer.cs" />
     <Compile Include="EditorRenderer.cs" />
index df79154..0d0fc1d 100644 (file)
     <PackageReference Include="Microsoft.Build.Locator" Version="1.0.31" />
   </ItemGroup>
   <ItemGroup>
+    <Compile Include="..\Xamarin.Forms.Core.UnitTests\MockDispatcherProvider.cs">
+      <Link>MockDispatcherProvider.cs</Link>
+    </Compile>
+    <Compile Include="..\Xamarin.Forms.Core.UnitTests\MockDispatcher.cs">
+      <Link>MockDispatcher.cs</Link>
+    </Compile>
     <Compile Include="..\Xamarin.Forms.Core.UnitTests\MockPlatformServices.cs">
       <Link>MockPlatformServices.cs</Link>
     </Compile>