Files
Shrlalgo.RvKits/WPFluent/Controls/NavigationView/NavigationViewActivator.cs

162 lines
6.3 KiB
C#

using WPFluent.Abstractions;
using WPFluent.Designer;
using System.Reflection;
using System.Windows.Controls;
// ReSharper disable once CheckNamespace
namespace WPFluent.Controls;
/// <summary>
/// Internal activator for creating content instances of the navigation view items.
/// </summary>
internal static class NavigationViewActivator
{
private static ConstructorInfo? FindParameterlessConstructor(Type? tPage)
{ return tPage?.GetConstructor(Type.EmptyTypes); }
private static FrameworkElement? InvokeElementConstructor(Type tPage, object? dataContext)
{
ConstructorInfo? ctor = dataContext is null
? tPage.GetConstructor(Type.EmptyTypes)
: tPage.GetConstructor(new[] { dataContext.GetType() });
return ctor?.Invoke(new[] { dataContext }) as FrameworkElement;
}
private static FrameworkElement? InvokeParameterlessConstructor(Type? tPage)
{ return FindParameterlessConstructor(tPage)?.Invoke(null) as FrameworkElement; }
private static void SetDataContext(FrameworkElement? element, object? dataContext)
{
if(element != null && dataContext != null)
{
element.DataContext = dataContext;
}
}
/// <summary>
/// Creates new instance of type derived from <see cref="FrameworkElement"/>.
/// </summary>
/// <param name="pageType"><see cref="FrameworkElement"/> to instantiate.</param>
/// <param name="dataContext">Additional context to set.</param>
/// <returns>Instance of the <see cref="FrameworkElement"/> object or <see langword="null"/>.</returns>
public static FrameworkElement? CreateInstance(Type pageType, object? dataContext = null)
{
if(!typeof(FrameworkElement).IsAssignableFrom(pageType))
{
throw new InvalidCastException(
$"PageType of the ${typeof(INavigationViewItem)} must be derived from {typeof(FrameworkElement)}. {pageType} is not.");
}
if(DesignerHelper.IsInDesignMode)
{
return new Page
{
Content =
new TextBlock
{
Text = "Pages are not rendered while using the Designer. Edit the page template directly.",
},
};
}
FrameworkElement? instance;
#if NET48_OR_GREATER || NETCOREAPP3_0_OR_GREATER
if(ControlsServices.ControlsServiceProvider != null)
{
ConstructorInfo[] pageConstructors = pageType.GetConstructors();
var parameterlessCount = pageConstructors.Count(ctor => ctor.GetParameters().Length == 0);
var parameterfullCount = pageConstructors.Length - parameterlessCount;
if(parameterlessCount == 1)
{
instance = InvokeParameterlessConstructor(pageType);
}
else if(parameterlessCount == 0 && parameterfullCount > 0)
{
ConstructorInfo? selectedCtor =
FitBestConstructor(pageConstructors, dataContext) ??
throw new InvalidOperationException(
$"The {pageType} page does not have a parameterless constructor or the required services have not been configured for dependency injection. Use the static {nameof(ControlsServices)} class to initialize the GUI library with your service provider. If you are using {typeof(INavigationViewPageProvider)} do not navigate initially and don't use Cache or Precache.");
instance = InvokeElementConstructor(selectedCtor, dataContext);
SetDataContext(instance, dataContext);
return instance;
}
}
else if(dataContext != null)
#else
if (dataContext != null)
#endif
{
instance = InvokeElementConstructor(pageType, dataContext);
if(instance != null)
{
return instance;
}
}
ConstructorInfo emptyConstructor =
FindParameterlessConstructor(pageType) ??
throw new InvalidOperationException(
$"The {pageType} page does not have a parameterless constructor. If you are using {typeof(INavigationViewPageProvider)} do not navigate initially and don't use Cache or Precache.");
instance = emptyConstructor.Invoke(null) as FrameworkElement;
SetDataContext(instance, dataContext);
return instance;
}
#if NET48_OR_GREATER || NETCOREAPP3_0_OR_GREATER
private static object? ResolveConstructorParameter(Type tParam, object? dataContext)
{
if(dataContext != null && dataContext.GetType() == tParam)
{
return dataContext;
}
return ControlsServices.ControlsServiceProvider?.GetService(tParam);
}
/// <summary>
/// Picks the constructor with the highest number of satisfiable parameters based on the provided context.
/// </summary>
/// <param name="parameterfullCtors">Array of constructors to evaluate.</param>
/// <param name="dataContext">Context used to determine parameter satisfaction.</param>
/// <returns>The constructor with the most satisfiable arguments, or null if none are fully satisfiable.</returns>
private static ConstructorInfo? FitBestConstructor(ConstructorInfo[] parameterfullCtors, object? dataContext)
{
return parameterfullCtors
.Select(
ctor =>
{
ParameterInfo[] parameters = ctor.GetParameters();
int score = parameters.Aggregate(
0,
(acc, prm) => acc +
(ResolveConstructorParameter(prm.ParameterType, dataContext) != null ? 1 : 0));
score = score != parameters.Length ? 0 : score;
return new { Constructor = ctor, Score = score };
})
.Where(cs => cs.Score != 0)
.OrderByDescending(cs => cs.Score)
.FirstOrDefault()
?.Constructor;
}
private static FrameworkElement? InvokeElementConstructor(ConstructorInfo ctor, object? dataContext)
{
IEnumerable<object?> args = ctor.GetParameters()
.Select(prm => ResolveConstructorParameter(prm.ParameterType, dataContext));
return ctor.Invoke(args.ToArray()) as FrameworkElement;
}
#endif
}