зеркало из https://github.com/DeGsoft/maui-linux.git
[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:
Родитель
0850710138
Коммит
e50775037a
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче