#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