using WPFluent.Abstractions; using WPFluent.Designer; using System.Reflection; using System.Windows.Controls; // ReSharper disable once CheckNamespace namespace WPFluent.Controls; /// /// Internal activator for creating content instances of the navigation view items. /// 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; } } /// /// Creates new instance of type derived from . /// /// to instantiate. /// Additional context to set. /// Instance of the object or . 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); } /// /// Picks the constructor with the highest number of satisfiable parameters based on the provided context. /// /// Array of constructors to evaluate. /// Context used to determine parameter satisfaction. /// The constructor with the most satisfiable arguments, or null if none are fully satisfiable. 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 args = ctor.GetParameters() .Select(prm => ResolveConstructorParameter(prm.ParameterType, dataContext)); return ctor.Invoke(args.ToArray()) as FrameworkElement; } #endif }