* 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
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>
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();
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 };
{
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)));
#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());
};
}
}
+#endif
#if UITEST
[Test]
using System.Diagnostics;
using System.Dynamic;
using System.Threading;
+using System.Threading.Tasks;
using Xamarin.Forms.CustomAttributes;
using Xamarin.Forms.Internals;
}
};
+ 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);
public IApp RunningApp => AppSetup.RunningApp;
protected virtual bool Isolate => false;
+
+ IDispatcher _dispatcher = new FallbackDispatcher();
+ public override IDispatcher Dispatcher { get => _dispatcher; }
#endif
protected TestCarouselPage()
public IApp RunningApp => AppSetup.RunningApp;
protected virtual bool Isolate => false;
+
+ IDispatcher _dispatcher = new FallbackDispatcher();
+ public override IDispatcher Dispatcher { get => _dispatcher; }
#endif
protected TestTabbedPage()
GC.Collect ();
})
}
-
}
};
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);
}
--- /dev/null
+using System.Threading.Tasks;
+
+namespace Xamarin.Forms.Controls
+{
+ public interface IWindowNavigation
+ {
+ Task OpenNewWindowAsync();
+ void NavigateToAnotherPage(Page page);
+ }
+}
public interface ISecondaryWindowService
{
Task OpenSecondaryWindow(Type pageType);
- Task OpenSecondaryWindow(ContentPage page);
}
}
public void SynchronizedCollectionAdd()
{
bool invoked = false;
- Device.PlatformServices = new MockPlatformServices (invokeOnMainThread: action => {
+ Device.PlatformServices = new MockPlatformServices (isInvokeRequired:true, invokeOnMainThread: action => {
invoked = true;
action();
});
--- /dev/null
+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;
+ }
+}
--- /dev/null
+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();
+ }
+ }
+}
<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" />
AbortKinetic(key);
};
- if (Device.IsInvokeRequired)
- {
- Device.BeginInvokeOnMainThread(abort);
- }
- else
- {
- abort();
- }
+ DoAction(self, abort);
return true;
}
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)
}
}
+ 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;
{
Task<IDictionary<string, object>> _propertiesTask;
readonly Lazy<PlatformConfigurationRegistry<Application>> _platformConfigurationRegistry;
+
+ public override IDispatcher Dispatcher => this.GetDispatcher();
+
IAppIndexingProvider _appIndexProvider;
ReadOnlyCollection<Element> _logicalChildren;
Page _mainPage;
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));
public async Task SavePropertiesAsync()
{
- if (Device.IsInvokeRequired)
+ if (Dispatcher.IsInvokeRequired)
{
- Device.BeginInvokeOnMainThread(SaveProperties);
+ Dispatcher.BeginInvokeOnMainThread(SaveProperties);
}
else
{
// 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
{
{
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;
}
}
- 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)
--- /dev/null
+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);
+ }
+ }
+}
{
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)
--- /dev/null
+using System;
+
+namespace Xamarin.Forms
+{
+ public interface IDispatcher
+ {
+ void BeginInvokeOnMainThread(Action action);
+ bool IsInvokeRequired { get; }
+ }
+}
--- /dev/null
+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);
+ }
+}
{
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();
// 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);
+ }
}
}
{
internal sealed class ListProxy : IReadOnlyList<object>, IListProxy, INotifyCollectionChanged
{
+ IDispatcher _dispatcher;
readonly ICollection _collection;
readonly IList _list;
readonly int _windowSize;
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;
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);
}
}
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)
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)
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
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));
{
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));
}
}
<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>
--- /dev/null
+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;
+ }
+}
--- /dev/null
+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();
+ }
+ }
+}
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)
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
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)
{
public abstract class WindowsBasePage : Windows.UI.Xaml.Controls.Page
{
+
+ Application _application;
+
public WindowsBasePage()
{
if (!Windows.ApplicationModel.DesignMode.DesignModeEnabled)
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)
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;
<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" />
<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>