[UWP] Allow embedding Forms page in secondary window (#5658)

* Make secondary window work in UWP (fixes #2229)

* Update Xamarin.Forms.Core/Internals/Ticker.cs

Co-Authored-By: hartez <hartez@users.noreply.github.com>
This commit is contained in:
E.Z. Hart 2019-04-22 16:44:12 -06:00 коммит произвёл Samantha Houts
Родитель 0850710138
Коммит e50775037a
9 изменённых файлов: 235 добавлений и 12 удалений

Просмотреть файл

@ -0,0 +1,49 @@
using System;
using System.Threading.Tasks;
using Windows.ApplicationModel.Core;
using Windows.UI.Core;
using Windows.UI.ViewManagement;
using Windows.UI.Xaml;
using Xamarin.Forms;
using Xamarin.Forms.ControlGallery.WindowsUniversal;
using Xamarin.Forms.Controls;
using Xamarin.Forms.Platform.UWP;
[assembly: Dependency(typeof(SecondaryWindowService))]
namespace Xamarin.Forms.ControlGallery.WindowsUniversal
{
class SecondaryWindowService : ISecondaryWindowService
{
public async Task OpenSecondaryWindow(Type pageType)
{
CoreApplicationView newView = CoreApplication.CreateNewView();
int newViewId = 0;
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);
Window.Current.Content = frame;
Window.Current.Activate();
newViewId = ApplicationView.GetForCurrentView().Id;
});
bool viewShown = await ApplicationViewSwitcher.TryShowAsStandaloneAsync(newViewId);
}
}
}

Просмотреть файл

@ -124,6 +124,7 @@
<Compile Include="PlatformSpecificCoreGalleryFactory.cs" />
<Compile Include="RegistrarValidationService.cs" />
<Compile Include="SampleNativeControl.cs" />
<Compile Include="SecondaryWindowService.cs" />
<Compile Include="_2489CustomRenderer.cs" />
<Compile Include="_57114Renderer.cs" />
<Compile Include="_58406EffectRenderer.cs" />

Просмотреть файл

@ -10,6 +10,8 @@ using Xamarin.Forms.PlatformConfiguration;
using Xamarin.Forms.PlatformConfiguration.AndroidSpecific;
using Xamarin.Forms.PlatformConfiguration.iOSSpecific;
using Xamarin.Forms.Controls.GalleryPages.VisualStateManagerGalleries;
using Xamarin.Forms.Controls.Issues;
namespace Xamarin.Forms.Controls
{
[Preserve(AllMembers = true)]
@ -562,6 +564,14 @@ namespace Xamarin.Forms.Controls
}
};
var secondaryWindowService = DependencyService.Get<ISecondaryWindowService>();
if (secondaryWindowService != null)
{
var openSecondWindowButton = new Button() { Text = "Open Secondary Window" };
openSecondWindowButton.Clicked += (obj, args) => { secondaryWindowService.OpenSecondaryWindow(new Issue2482()); };
stackLayout.Children.Add(openSecondWindowButton);
}
this.SetAutomationPropertiesName("Gallery");
this.SetAutomationPropertiesHelpText("Lists all gallery pages");

Просмотреть файл

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
namespace Xamarin.Forms.Controls
{
public interface ISecondaryWindowService
{
Task OpenSecondaryWindow(Type pageType);
Task OpenSecondaryWindow(ContentPage page);
}
}

Просмотреть файл

@ -42,7 +42,22 @@ namespace Xamarin.Forms.Internals
public static Ticker Default
{
internal set { s_ticker = value; }
get { return s_ticker ?? (s_ticker = Device.PlatformServices.CreateTicker()); }
get
{
if (s_ticker == null)
{
s_ticker = Device.PlatformServices.CreateTicker();
}
return s_ticker.GetTickerInstance();
}
}
protected virtual Ticker GetTickerInstance()
{
// This method is provided so platforms can override it and return something other than
// the normal Ticker singleton
return s_ticker;
}
public virtual int Insert(Func<long, bool> timeout)
@ -123,4 +138,4 @@ namespace Xamarin.Forms.Internals
EnableTimer();
}
}
}
}

Просмотреть файл

@ -83,7 +83,7 @@ namespace Xamarin.Forms
return FlowDirection.MatchParent;
}
static Windows.UI.Xaml.ResourceDictionary GetTabletResources()
internal static Windows.UI.Xaml.ResourceDictionary GetTabletResources()
{
return new Windows.UI.Xaml.ResourceDictionary {
Source = new Uri("ms-appx:///Xamarin.Forms.Platform.UAP/Resources.xbf")

Просмотреть файл

@ -67,9 +67,16 @@ namespace Xamarin.Forms.Platform.UWP
_page = page;
var current = Windows.UI.Xaml.Application.Current;
if (!current.Resources.ContainsKey("RootContainerStyle"))
{
Windows.UI.Xaml.Application.Current.Resources.MergedDictionaries.Add(Forms.GetTabletResources());
}
_container = new Canvas
{
Style = (Windows.UI.Xaml.Style)Windows.UI.Xaml.Application.Current.Resources["RootContainerStyle"]
Style = (Windows.UI.Xaml.Style)current.Resources["RootContainerStyle"]
};
_page.Content = _container;

Просмотреть файл

@ -26,19 +26,24 @@ namespace Xamarin.Forms.Platform.UWP
{
internal abstract class WindowsBasePlatformServices : IPlatformServices
{
const string WrongThreadError = "RPC_E_WRONG_THREAD";
readonly CoreDispatcher _dispatcher;
protected WindowsBasePlatformServices(CoreDispatcher dispatcher)
{
if (dispatcher == null)
throw new ArgumentNullException(nameof(dispatcher));
_dispatcher = dispatcher;
_dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher));
}
public void BeginInvokeOnMainThread(Action action)
public async void BeginInvokeOnMainThread(Action action)
{
_dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => action()).WatchForError();
if (CoreApplication.Views.Count == 1)
{
// This is the normal scenario - one window only
_dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => action()).WatchForError();
return;
}
await TryAllDispatchers(action);
}
public Ticker CreateTicker()
@ -115,7 +120,23 @@ namespace Xamarin.Forms.Platform.UWP
return new WindowsIsolatedStorage(ApplicationData.Current.LocalFolder);
}
public bool IsInvokeRequired => !_dispatcher.HasThreadAccess;
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 string RuntimePlatform => Device.UWP;
@ -152,5 +173,90 @@ namespace Xamarin.Forms.Platform.UWP
{
return Platform.GetNativeSize(view, widthConstraint, heightConstraint);
}
async Task TryAllDispatchers(Action action)
{
// Our best bet is Window.Current; most of the time, that's the Dispatcher we need
var currentWindow = Window.Current;
if (currentWindow?.Dispatcher != null)
{
try
{
await TryDispatch(currentWindow.Dispatcher, action);
return;
}
catch (Exception ex) when (ex.Message.Contains(WrongThreadError))
{
// The current window is not the one we need
}
}
// Either Window.Current was the wrong Dispatcher, or Window.Current was null because we're on a
// non-UI thread (e.g., one from the thread pool). So now it's time to try all the available Dispatchers
var views = CoreApplication.Views;
for (int n = 0; n < views.Count; n++)
{
var dispatcher = views[n].Dispatcher;
if (dispatcher == null || dispatcher == currentWindow?.Dispatcher)
{
// Obviously null Dispatchers are no good, and we already tried the one from currentWindow
continue;
}
// We need to ignore Deactivated/Never Activated windows, but it's possible we can't access their
// properties from this thread. So we'll check those using the Dispatcher
bool activated = false;
await TryDispatch(dispatcher, () => {
var mode = views[n].CoreWindow.ActivationMode;
activated = (mode == CoreWindowActivationMode.ActivatedInForeground
|| mode == CoreWindowActivationMode.ActivatedNotForeground);
});
if (!activated)
{
// This is a deactivated (or not yet activated) window; move on
continue;
}
try
{
await TryDispatch(dispatcher, action);
return;
}
catch (Exception ex) when (ex.Message.Contains(WrongThreadError))
{
// This was the incorrect dispatcher; move on to try another one
}
}
}
async Task<bool> TryDispatch(CoreDispatcher dispatcher, Action action)
{
if (dispatcher == null)
{
throw new ArgumentNullException(nameof(dispatcher));
}
var taskCompletionSource = new TaskCompletionSource<bool>();
await dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => {
try
{
action();
taskCompletionSource.SetResult(true);
}
catch (Exception ex)
{
taskCompletionSource.SetException(ex);
}
});
return await taskCompletionSource.Task;
}
}
}

Просмотреть файл

@ -1,10 +1,15 @@
using Windows.UI.Xaml.Media;
using Windows.ApplicationModel.Core;
using System;
using Windows.UI.Xaml.Media;
using Xamarin.Forms.Internals;
namespace Xamarin.Forms.Platform.UWP
{
internal class WindowsTicker : Ticker
{
[ThreadStatic]
static Ticker s_ticker;
protected override void DisableTimer()
{
CompositionTarget.Rendering -= RenderingFrameEventHandler;
@ -19,5 +24,22 @@ namespace Xamarin.Forms.Platform.UWP
{
SendSignals();
}
protected override Ticker GetTickerInstance()
{
if (CoreApplication.Views.Count > 1)
{
// We've got multiple windows open, we'll need to use the local ThreadStatic Ticker instead of the
// singleton in the base class
if (s_ticker == null)
{
s_ticker = new WindowsTicker();
}
return s_ticker;
}
return base.GetTickerInstance();
}
}
}