#if XFORMS
namespace Caliburn.Micro.Core.Xamarin.Forms
#else
namespace Caliburn.Micro
#endif
{
using System;
using System.Linq;
using System.Collections.Generic;
using System.Reflection;
using System.Threading.Tasks;
#if XFORMS
using UIElement = global::Xamarin.Forms.Element;
using FrameworkElement = global::Xamarin.Forms.VisualElement;
using DependencyProperty = global::Xamarin.Forms.BindableProperty;
using DependencyObject = global::Xamarin.Forms.BindableObject;
#elif WinRT81
using Windows.UI.Xaml;
using Microsoft.Xaml.Interactivity;
#else
using System.Windows;
using System.Windows.Interactivity;
using Caliburn.Micro.Core;
#endif
#if WINDOWS_PHONE
using Microsoft.Phone.Controls;
#endif
///
/// Binds a view to a view model.
///
public static class ViewModelBinder {
const string AsyncSuffix = "Async";
static readonly ILog Log = LogManager.GetLog(typeof(ViewModelBinder));
///
/// Gets or sets a value indicating whether to apply conventions by default.
///
///
/// true if conventions should be applied by default; otherwise, false.
///
public static bool ApplyConventionsByDefault = true;
///
/// Indicates whether or not the conventions have already been applied to the view.
///
public static readonly DependencyProperty ConventionsAppliedProperty =
DependencyPropertyHelper.RegisterAttached(
"ConventionsApplied",
typeof(bool),
typeof(ViewModelBinder),
false
);
///
/// Determines whether a view should have conventions applied to it.
///
/// The view to check.
/// Whether or not conventions should be applied to the view.
public static bool ShouldApplyConventions(FrameworkElement view) {
var overriden = View.GetApplyConventions(view);
return overriden.GetValueOrDefault(ApplyConventionsByDefault);
}
///
/// 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) => {
var unmatchedElements = new List();
#if !XFORMS
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++) {
interpretedViewModelType = property.PropertyType;
property = interpretedViewModelType.GetPropertyCaseInsensitive(parts[i]);
}
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) {
unmatchedElements.Add(element);
Log.Warn("Binding Convention Not Applied: No conventions configured for {0}.", element.GetType());
continue;
}
var applied = convention.ApplyBinding(
interpretedViewModelType,
cleanName.Replace('_', '.'),
property,
element,
convention
);
if (applied) {
Log.Info("Binding Convention Applied: Element {0}.", element.Name);
}
else {
Log.Info("Binding Convention Not Applied: Element {0} has existing binding.", element.Name);
unmatchedElements.Add(element);
}
}
#endif
return unmatchedElements;
};
///
/// 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) => {
var unmatchedElements = namedElements.ToList();
#if !XFORMS
#if WinRT || XFORMS
var methods = viewModelType.GetRuntimeMethods();
#else
var methods = viewModelType.GetMethods();
#endif
foreach (var method in methods) {
var foundControl = unmatchedElements.FindName(method.Name);
if (foundControl == null && IsAsyncMethod(method)) {
var methodNameWithoutAsyncSuffix = method.Name.Substring(0, method.Name.Length - AsyncSuffix.Length);
foundControl = unmatchedElements.FindName(methodNameWithoutAsyncSuffix);
}
if(foundControl == null) {
Log.Info("Action Convention Not Applied: No actionable element for {0}.", method.Name);
continue;
}
unmatchedElements.Remove(foundControl);
#if WinRT81
var triggers = Interaction.GetBehaviors(foundControl);
if (triggers != null && triggers.Count > 0)
{
Log.Info("Action Convention Not Applied: Interaction.Triggers already set on {0}.", foundControl.Name);
continue;
}
#endif
var message = method.Name;
var parameters = method.GetParameters();
if (parameters.Length > 0) {
message += "(";
foreach (var parameter in parameters) {
var paramName = parameter.Name;
var specialValue = "$" + paramName.ToLower();
if (MessageBinder.SpecialValues.ContainsKey(specialValue))
paramName = specialValue;
message += paramName + ",";
}
message = message.Remove(message.Length - 1, 1);
message += ")";
}
Log.Info("Action Convention Applied: Action {0} on element {1}.", method.Name, message);
Message.SetAttach(foundControl, message);
}
#endif
return unmatchedElements;
};
static bool IsAsyncMethod(MethodInfo method) {
return typeof(Task).IsAssignableFrom(method.ReturnType) &&
method.Name.EndsWith(AsyncSuffix, StringComparison.OrdinalIgnoreCase);
}
///
/// Allows the developer to add custom handling of named elements which were not matched by any default conventions.
///
public static Action, Type> HandleUnmatchedElements = (elements, viewModelType) => { };
///
/// 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