diff --git a/src/Caliburn.Micro.Platform/ViewModelBinder.cs b/src/Caliburn.Micro.Platform/ViewModelBinder.cs index 49032030..3c8ad649 100644 --- a/src/Caliburn.Micro.Platform/ViewModelBinder.cs +++ b/src/Caliburn.Micro.Platform/ViewModelBinder.cs @@ -11,6 +11,7 @@ namespace Caliburn.Micro using System.Collections.Generic; using System.Reflection; using System.Threading.Tasks; + using System.Text; #if XFORMS using UIElement = global::Xamarin.Forms.Element; using FrameworkElement = global::Xamarin.Forms.VisualElement; @@ -41,7 +42,8 @@ namespace Caliburn.Micro /// /// Binds a view to a view model. /// - public static class ViewModelBinder { + public static class ViewModelBinder + { const string AsyncSuffix = "Async"; static readonly ILog Log = LogManager.GetLog(typeof(ViewModelBinder)); @@ -75,7 +77,8 @@ public static class ViewModelBinder { /// /// The view to check. /// Whether or not conventions should be applied to the view. - public static bool ShouldApplyConventions(FrameworkElement view) { + public static bool ShouldApplyConventions(FrameworkElement view) + { var overriden = View.GetApplyConventions(view); return overriden.GetValueOrDefault(ApplyConventionsByDefault); } @@ -84,30 +87,35 @@ public static bool ShouldApplyConventions(FrameworkElement view) { /// Creates data bindings on the view's controls based on the provided properties. /// /// Parameters include named Elements to search through and the type of view model to determine conventions for. Returns unmatched elements. - public static Func, Type, IEnumerable> BindProperties = (namedElements, viewModelType) => { + public static Func, Type, IEnumerable> BindProperties = (namedElements, viewModelType) => + { var unmatchedElements = new List(); #if !XFORMS && !MAUI - foreach (var element in namedElements) { + foreach (var element in namedElements) + { var cleanName = element.Name.Trim('_'); var parts = cleanName.Split(new[] { '_' }, StringSplitOptions.RemoveEmptyEntries); var property = viewModelType.GetPropertyCaseInsensitive(parts[0]); var interpretedViewModelType = viewModelType; - for (int i = 1; i < parts.Length && property != null; i++) { + for (int i = 1; i < parts.Length && property != null; i++) + { interpretedViewModelType = property.PropertyType; property = interpretedViewModelType.GetPropertyCaseInsensitive(parts[i]); } - if (property == null) { + if (property == null) + { unmatchedElements.Add(element); Log.Info("Binding Convention Not Applied: Element {0} did not match a property.", element.Name); continue; } var convention = ConventionManager.GetElementConvention(element.GetType()); - if (convention == null) { + if (convention == null) + { unmatchedElements.Add(element); Log.Warn("Binding Convention Not Applied: No conventions configured for {0}.", element.GetType()); continue; @@ -121,10 +129,12 @@ public static bool ShouldApplyConventions(FrameworkElement view) { convention ); - if (applied) { + if (applied) + { Log.Info("Binding Convention Applied: Element {0}.", element.Name); } - else { + else + { Log.Info("Binding Convention Not Applied: Element {0} has existing binding.", element.Name); unmatchedElements.Add(element); } @@ -138,7 +148,8 @@ public static bool ShouldApplyConventions(FrameworkElement view) { /// Attaches instances of to the view's controls based on the provided methods. /// /// Parameters include the named elements to search through and the type of view model to determine conventions for. Returns unmatched elements. - public static Func, Type, IEnumerable> BindActions = (namedElements, viewModelType) => { + public static Func, Type, IEnumerable> BindActions = (namedElements, viewModelType) => + { var unmatchedElements = namedElements.ToList(); #if !XFORMS && !MAUI #if WINDOWS_UWP || XFORMS || MAUI @@ -146,21 +157,24 @@ public static bool ShouldApplyConventions(FrameworkElement view) { #else var methods = viewModelType.GetMethods(); #endif - - foreach (var method in methods) { - Log.Info($"Searching for methods control {method.Name} unmatchedElements count {unmatchedElements.Count}"); + + foreach (var method in methods) + { + Log.Info($"Searching for methods control {method.Name} unmatchedElements count {unmatchedElements.Count}"); var foundControl = unmatchedElements.FindName(method.Name); - if (foundControl == null && IsAsyncMethod(method)) { + if (foundControl == null && IsAsyncMethod(method)) + { var methodNameWithoutAsyncSuffix = method.Name.Substring(0, method.Name.Length - AsyncSuffix.Length); foundControl = unmatchedElements.FindName(methodNameWithoutAsyncSuffix); } - if(foundControl == null) { + if (foundControl == null) + { Log.Info("Action Convention Not Applied: No actionable element for {0}. {1}", method.Name, unmatchedElements.Count); - foreach(var element in unmatchedElements) + foreach (var element in unmatchedElements) { - Log.Info($"Unnamed element {element.Name}"); + Log.Info($"Unnamed element {element.Name}"); } continue; } @@ -176,25 +190,31 @@ public static bool ShouldApplyConventions(FrameworkElement view) { } #endif - var message = method.Name; var parameters = method.GetParameters(); + var messageBuilder = new StringBuilder(method.Name); - if (parameters.Length > 0) { - message += "("; + if (parameters.Length > 0) + { + messageBuilder.Append("("); - foreach (var parameter in parameters) { + foreach (var parameter in parameters) + { var paramName = parameter.Name; var specialValue = "$" + paramName.ToLower(); if (MessageBinder.SpecialValues.ContainsKey(specialValue)) paramName = specialValue; - message += paramName + ","; + messageBuilder.Append(paramName).Append(","); } - message = message.Remove(message.Length - 1, 1); - message += ")"; + // Remove the trailing comma + if (parameters.Length > 0) + messageBuilder.Length -= 1; + messageBuilder.Append(")"); + } + var message = messageBuilder.ToString(); Log.Info("Action Convention Applied: Action {0} on element {1}.", method.Name, message); Message.SetAttach(foundControl, message); @@ -203,7 +223,8 @@ public static bool ShouldApplyConventions(FrameworkElement view) { return unmatchedElements; }; - static bool IsAsyncMethod(MethodInfo method) { + static bool IsAsyncMethod(MethodInfo method) + { return typeof(Task).GetTypeInfo().IsAssignableFrom(method.ReturnType.GetTypeInfo()) && method.Name.EndsWith(AsyncSuffix, StringComparison.OrdinalIgnoreCase); } @@ -217,13 +238,16 @@ static bool IsAsyncMethod(MethodInfo method) { /// Binds the specified viewModel to the view. /// ///Passes the the view model, view and creation context (or null for default) to use in applying binding. - public static Action Bind = (viewModel, view, context) => { + public static Action Bind = (viewModel, view, context) => + { #if !WINDOWS_UWP && !XFORMS && !MAUI // when using d:DesignInstance, Blend tries to assign the DesignInstanceExtension class as the DataContext, // so here we get the actual ViewModel which is in the Instance property of DesignInstanceExtension - if (View.InDesignMode) { + if (View.InDesignMode) + { var vmType = viewModel.GetType(); - if (vmType.FullName == "Microsoft.Expression.DesignModel.InstanceBuilders.DesignInstanceExtension") { + if (vmType.FullName == "Microsoft.Expression.DesignModel.InstanceBuilders.DesignInstanceExtension") + { var propInfo = vmType.GetProperty("Instance", BindingFlags.Instance | BindingFlags.NonPublic); viewModel = propInfo.GetValue(viewModel, null); } @@ -240,29 +264,35 @@ static bool IsAsyncMethod(MethodInfo method) { var noContext = Caliburn.Micro.Bind.NoContextProperty; #endif - if ((bool)view.GetValue(noContext)) { + if ((bool)view.GetValue(noContext)) + { Action.SetTargetWithoutContext(view, viewModel); } - else { + else + { Action.SetTarget(view, viewModel); } var viewAware = viewModel as IViewAware; - if (viewAware != null) { + if (viewAware != null) + { Log.Info("Attaching {0} to {1}.", view, viewAware); viewAware.AttachView(view, context); } - if ((bool)view.GetValue(ConventionsAppliedProperty)) { + if ((bool)view.GetValue(ConventionsAppliedProperty)) + { return; } var element = View.GetFirstNonGeneratedView(view) as FrameworkElement; - if (element == null) { + if (element == null) + { return; } - if (!ShouldApplyConventions(element)) { + if (!ShouldApplyConventions(element)) + { Log.Info("Skipping conventions for {0} and {1}.", element, viewModel); return; }