#if XFORMS namespace Caliburn.Micro.Core.Xamarin.Forms #else namespace Caliburn.Micro #endif { using System; using System.Linq; using System.Reflection; using System.Text.RegularExpressions; using System.Collections.Generic; #if XFORMS using global::Xamarin.Forms; using UIElement = global::Xamarin.Forms.Element; using TextBlock = global::Xamarin.Forms.Label; using DependencyObject = global::Xamarin.Forms.BindableObject; #elif !WinRT using System.Windows; using System.Windows.Controls; #else using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; #endif #if !SILVERLIGHT && !WinRT && !XFORMS using System.Windows.Interop; using Caliburn.Micro.Core; #endif /// /// A strategy for determining which view to use for a given model. /// public static class ViewLocator { static readonly ILog Log = LogManager.GetLog(typeof(ViewLocator)); static Dictionary ViewList = new Dictionary(); //These fields are used for configuring the default type mappings. They can be changed using ConfigureTypeMappings(). static string defaultSubNsViews; static string defaultSubNsViewModels; static bool useNameSuffixesInMappings; static string nameFormat; static string viewModelSuffix; static readonly List ViewSuffixList = new List(); static bool includeViewSuffixInVmNames; /// /// Used to transform names. /// public static NameTransformer NameTransformer = new NameTransformer(); /// /// Separator used when resolving View names for context instances. /// public static string ContextSeparator = "."; static ViewLocator() { ConfigureTypeMappings(new TypeMappingConfiguration()); } /// /// Specifies how type mappings are created, including default type mappings. Calling this method will /// clear all existing name transformation rules and create new default type mappings according to the /// configuration. /// /// An instance of TypeMappingConfiguration that provides the settings for configuration public static void ConfigureTypeMappings(TypeMappingConfiguration config) { if (String.IsNullOrEmpty(config.DefaultSubNamespaceForViews)) { throw new ArgumentException("DefaultSubNamespaceForViews field cannot be blank."); } if (String.IsNullOrEmpty(config.DefaultSubNamespaceForViewModels)) { throw new ArgumentException("DefaultSubNamespaceForViewModels field cannot be blank."); } if (String.IsNullOrEmpty(config.NameFormat)) { throw new ArgumentException("NameFormat field cannot be blank."); } NameTransformer.Clear(); ViewSuffixList.Clear(); defaultSubNsViews = config.DefaultSubNamespaceForViews; defaultSubNsViewModels = config.DefaultSubNamespaceForViewModels; nameFormat = config.NameFormat; useNameSuffixesInMappings = config.UseNameSuffixesInMappings; viewModelSuffix = config.ViewModelSuffix; ViewSuffixList.AddRange(config.ViewSuffixList); includeViewSuffixInVmNames = config.IncludeViewSuffixInViewModelNames; SetAllDefaults(); } private static void SetAllDefaults() { if (useNameSuffixesInMappings) { //Add support for all view suffixes ViewSuffixList.Apply(AddDefaultTypeMapping); } else { AddSubNamespaceMapping(defaultSubNsViewModels, defaultSubNsViews); } } /// /// Adds a default type mapping using the standard namespace mapping convention /// /// Suffix for type name. Should be "View" or synonym of "View". (Optional) public static void AddDefaultTypeMapping(string viewSuffix = "View") { if (!useNameSuffixesInMappings) { return; } //Check for . construct AddNamespaceMapping(String.Empty, String.Empty, viewSuffix); //Check for .ViewModels.. construct AddSubNamespaceMapping(defaultSubNsViewModels, defaultSubNsViews, viewSuffix); } /// /// This method registers a View suffix or synonym so that View Context resolution works properly. /// It is automatically called internally when calling AddNamespaceMapping(), AddDefaultTypeMapping(), /// or AddTypeMapping(). It should not need to be called explicitly unless a rule that handles synonyms /// is added directly through the NameTransformer. /// /// Suffix for type name. Should be "View" or synonym of "View". public static void RegisterViewSuffix(string viewSuffix) { if (ViewSuffixList.Count(s => s == viewSuffix) == 0) { ViewSuffixList.Add(viewSuffix); } } /// /// Adds a standard type mapping based on namespace RegEx replace and filter patterns /// /// RegEx replace pattern for source namespace /// RegEx filter pattern for source namespace /// Array of RegEx replace values for target namespaces /// Suffix for type name. Should be "View" or synonym of "View". (Optional) public static void AddTypeMapping(string nsSourceReplaceRegEx, string nsSourceFilterRegEx, string[] nsTargetsRegEx, string viewSuffix = "View") { RegisterViewSuffix(viewSuffix); var replist = new List(); var repsuffix = useNameSuffixesInMappings ? viewSuffix : String.Empty; const string basegrp = "${basename}"; foreach (var t in nsTargetsRegEx) { replist.Add(t + String.Format(nameFormat, basegrp, repsuffix)); } var rxbase = RegExHelper.GetNameCaptureGroup("basename"); var suffix = String.Empty; if (useNameSuffixesInMappings) { suffix = viewModelSuffix; if (!viewModelSuffix.Contains(viewSuffix) && includeViewSuffixInVmNames) { suffix = viewSuffix + suffix; } } var rxsrcfilter = String.IsNullOrEmpty(nsSourceFilterRegEx) ? null : String.Concat(nsSourceFilterRegEx, String.Format(nameFormat, RegExHelper.NameRegEx, suffix), "$"); var rxsuffix = RegExHelper.GetCaptureGroup("suffix", suffix); NameTransformer.AddRule( String.Concat(nsSourceReplaceRegEx, String.Format(nameFormat, rxbase, rxsuffix), "$"), replist.ToArray(), rxsrcfilter ); } /// /// Adds a standard type mapping based on namespace RegEx replace and filter patterns /// /// RegEx replace pattern for source namespace /// RegEx filter pattern for source namespace /// RegEx replace value for target namespace /// Suffix for type name. Should be "View" or synonym of "View". (Optional) public static void AddTypeMapping(string nsSourceReplaceRegEx, string nsSourceFilterRegEx, string nsTargetRegEx, string viewSuffix = "View") { AddTypeMapping(nsSourceReplaceRegEx, nsSourceFilterRegEx, new[] { nsTargetRegEx }, viewSuffix); } /// /// Adds a standard type mapping based on simple namespace mapping /// /// Namespace of source type /// Namespaces of target type as an array /// Suffix for type name. Should be "View" or synonym of "View". (Optional) public static void AddNamespaceMapping(string nsSource, string[] nsTargets, string viewSuffix = "View") { //need to terminate with "." in order to concatenate with type name later var nsencoded = RegExHelper.NamespaceToRegEx(nsSource + "."); //Start pattern search from beginning of string ("^") //unless original string was blank (i.e. special case to indicate "append target to source") if (!String.IsNullOrEmpty(nsSource)) { nsencoded = "^" + nsencoded; } //Capture namespace as "origns" in case we need to use it in the output in the future var nsreplace = RegExHelper.GetCaptureGroup("origns", nsencoded); var nsTargetsRegEx = nsTargets.Select(t => t + ".").ToArray(); AddTypeMapping(nsreplace, null, nsTargetsRegEx, viewSuffix); } /// /// Adds a standard type mapping based on simple namespace mapping /// /// Namespace of source type /// Namespace of target type /// Suffix for type name. Should be "View" or synonym of "View". (Optional) public static void AddNamespaceMapping(string nsSource, string nsTarget, string viewSuffix = "View") { AddNamespaceMapping(nsSource, new[] { nsTarget }, viewSuffix); } /// /// Adds a standard type mapping by substituting one subnamespace for another /// /// Subnamespace of source type /// Subnamespaces of target type as an array /// Suffix for type name. Should be "View" or synonym of "View". (Optional) public static void AddSubNamespaceMapping(string nsSource, string[] nsTargets, string viewSuffix = "View") { //need to terminate with "." in order to concatenate with type name later var nsencoded = RegExHelper.NamespaceToRegEx(nsSource + "."); string rxbeforetgt, rxaftersrc, rxaftertgt; var rxbeforesrc = rxbeforetgt = rxaftersrc = rxaftertgt = String.Empty; if (!String.IsNullOrEmpty(nsSource)) { if (!nsSource.StartsWith("*")) { rxbeforesrc = RegExHelper.GetNamespaceCaptureGroup("nsbefore"); rxbeforetgt = @"${nsbefore}"; } if (!nsSource.EndsWith("*")) { rxaftersrc = RegExHelper.GetNamespaceCaptureGroup("nsafter"); rxaftertgt = "${nsafter}"; } } var rxmid = RegExHelper.GetCaptureGroup("subns", nsencoded); var nsreplace = String.Concat(rxbeforesrc, rxmid, rxaftersrc); var nsTargetsRegEx = nsTargets.Select(t => String.Concat(rxbeforetgt, t, ".", rxaftertgt)).ToArray(); AddTypeMapping(nsreplace, null, nsTargetsRegEx, viewSuffix); } /// /// Adds a standard type mapping by substituting one subnamespace for another /// /// Subnamespace of source type /// Subnamespace of target type /// Suffix for type name. Should be "View" or synonym of "View". (Optional) public static void AddSubNamespaceMapping(string nsSource, string nsTarget, string viewSuffix = "View") { AddSubNamespaceMapping(nsSource, new[] { nsTarget }, viewSuffix); } /// /// Retrieves the view from the IoC container or tries to create it if not found. /// /// /// Pass the type of view as a parameter and recieve an instance of the view. /// public static Func GetOrCreateViewType = viewType => { var view = IoC.GetAllInstances(viewType) .FirstOrDefault() as UIElement; if (view != null) { InitializeComponent(view); return view; } #if !WinRT && !XFORMS if(viewType.IsInterface || viewType.IsAbstract || !typeof(UIElement).IsAssignableFrom(viewType)) return new TextBlock { Text = string.Format("Cannot create {0}.", viewType.FullName) }; #else var viewTypeInfo = viewType.GetTypeInfo(); var uiElementInfo = typeof(UIElement).GetTypeInfo(); if (viewTypeInfo.IsInterface || viewTypeInfo.IsAbstract || !uiElementInfo.IsAssignableFrom(viewTypeInfo)) return new TextBlock { Text = string.Format("Cannot create {0}.", viewType.FullName) }; #endif view = (UIElement)System.Activator.CreateInstance(viewType); InitializeComponent(view); return view; }; /// /// Modifies the name of the type to be used at design time. /// public static Func ModifyModelTypeAtDesignTime = modelTypeName => { if (modelTypeName.StartsWith("_")) { var index = modelTypeName.IndexOf('.'); modelTypeName = modelTypeName.Substring(index + 1); index = modelTypeName.IndexOf('.'); modelTypeName = modelTypeName.Substring(index + 1); } return modelTypeName; }; /// /// Transforms a ViewModel type name into all of its possible View type names. Optionally accepts an instance /// of context object /// /// Enumeration of transformed names /// Arguments: /// typeName = The name of the ViewModel type being resolved to its companion View. /// context = An instance of the context or null. /// public static Func> TransformName = (typeName, context) => { Func getReplaceString; if (context == null) { getReplaceString = r => r; return NameTransformer.Transform(typeName, getReplaceString); } var contextstr = ContextSeparator + context; string grpsuffix = String.Empty; if (useNameSuffixesInMappings) { //Create RegEx for matching any of the synonyms registered var synonymregex = "(" + String.Join("|", ViewSuffixList.ToArray()) + ")"; grpsuffix = RegExHelper.GetCaptureGroup("suffix", synonymregex); } const string grpbase = @"\${basename}"; var patternregex = String.Format(nameFormat, grpbase, grpsuffix) + "$"; //Strip out any synonym by just using contents of base capture group with context string var replaceregex = "${basename}" + contextstr; //Strip out the synonym getReplaceString = r => Regex.Replace(r, patternregex, replaceregex); //Return only the names for the context return NameTransformer.Transform(typeName, getReplaceString).Where(n => n.EndsWith(contextstr)); }; /// /// Locates the view type based on the specified model type. /// /// The view. /// /// Pass the model type, display location (or null) and the context instance (or null) as parameters and receive a view type. /// public static Func LocateTypeForModelType = (modelType, displayLocation, context) => { var viewTypeName = modelType.FullName; if (View.InDesignMode) { viewTypeName = ModifyModelTypeAtDesignTime(viewTypeName); } viewTypeName = viewTypeName.Substring( 0, viewTypeName.IndexOf('`') < 0 ? viewTypeName.Length : viewTypeName.IndexOf('`') ); var viewTypeList = TransformName(viewTypeName, context); var viewType = AssemblySource.FindTypeByNames(viewTypeList); if (viewType == null) { Log.Warn("View not found. Searched: {0}.", string.Join(", ", viewTypeList.ToArray())); } return viewType; }; /// /// Locates the view for the specified model type. /// /// The view. /// /// Pass the model type, display location (or null) and the context instance (or null) as parameters and receive a view instance. /// public static Func LocateForModelType = (modelType, displayLocation, context) => { var viewType = LocateTypeForModelType(modelType, displayLocation, context); return viewType == null ? new TextBlock { Text = string.Format("Cannot find view for {0}.", modelType) } : GetOrCreateViewType(viewType); }; /// /// Locates the view for the specified model instance. /// /// The view. /// /// Pass the model instance, display location (or null) and the context (or null) as parameters and receive a view instance. /// public static Func LocateForModel = (model, displayLocation, context) => { var viewAware = model as IViewAware; if (viewAware != null) { var view = viewAware.GetView(context) as UIElement; if (view != null) { #if !SILVERLIGHT && !WinRT && !XFORMS var windowCheck = view as Window; if (windowCheck == null || (!windowCheck.IsLoaded && !(new WindowInteropHelper(windowCheck).Handle == IntPtr.Zero))) { Log.Info("Using cached view for {0}.", model); return view; } #else Log.Info("Using cached view for {0}.", model); return view; #endif } } return LocateForModelType(model.GetType(), displayLocation, context); }; /// /// Transforms a view type into a pack uri. /// public static Func DeterminePackUriFromType = (viewModelType, viewType) => { #if !WinRT && !XFORMS var assemblyName = viewType.Assembly.GetAssemblyName(); var applicationAssemblyName = Application.Current.GetType().Assembly.GetAssemblyName(); #else var assemblyName = viewType.GetTypeInfo().Assembly.GetAssemblyName(); var applicationAssemblyName = Application.Current.GetType().GetTypeInfo().Assembly.GetAssemblyName(); #endif var viewTypeName = viewType.FullName; if (viewTypeName.StartsWith(assemblyName)) viewTypeName = viewTypeName.Substring(assemblyName.Length); var uri = viewTypeName.Replace(".", "/") + ".xaml"; if(!applicationAssemblyName.Equals(assemblyName)) { return "/" + assemblyName + ";component" + uri; } return uri; }; /// /// When a view does not contain a code-behind file, we need to automatically call InitializeCompoent. /// /// The element to initialize public static void InitializeComponent(object element) { #if XFORMS return; #elif !WinRT var method = element.GetType() .GetMethod("InitializeComponent", BindingFlags.Public | BindingFlags.Instance); method.Invoke(element, null); #else var method = element.GetType().GetTypeInfo() .GetDeclaredMethods("InitializeComponent") .SingleOrDefault(m => m.GetParameters().Length == 0); method?.Invoke(element, null); #endif } } }