PageRenderTime 70ms CodeModel.GetById 11ms RepoModel.GetById 1ms app.codeStats 0ms

/src/Caliburn.Micro.Silverlight/ViewModelBinder.cs

#
C# | 252 lines | 177 code | 43 blank | 32 comment | 31 complexity | 3c0065995cd99b2c25228db1742c81ce MD5 | raw file
Possible License(s): MIT
  1. namespace Caliburn.Micro {
  2. using System;
  3. using System.Linq;
  4. using System.Collections.Generic;
  5. using System.Windows;
  6. using System.Windows.Interactivity;
  7. #if WP71
  8. using Microsoft.Phone.Controls;
  9. #endif
  10. /// <summary>
  11. /// Binds a view to a view model.
  12. /// </summary>
  13. public static class ViewModelBinder {
  14. /// <summary>
  15. /// Gets or sets a value indicating whether to apply conventions by default.
  16. /// </summary>
  17. /// <value>
  18. /// <c>true</c> if conventions should be applied by default; otherwise, <c>false</c>.
  19. /// </value>
  20. public static bool ApplyConventionsByDefault = true;
  21. static readonly ILog Log = LogManager.GetLog(typeof(ViewModelBinder));
  22. /// <summary>
  23. /// Indicates whether or not the conventions have already been applied to the view.
  24. /// </summary>
  25. public static readonly DependencyProperty ConventionsAppliedProperty =
  26. DependencyProperty.RegisterAttached(
  27. "ConventionsApplied",
  28. typeof(bool),
  29. typeof(ViewModelBinder),
  30. null
  31. );
  32. /// <summary>
  33. /// Determines whether a view should have conventions applied to it.
  34. /// </summary>
  35. /// <param name="view">The view to check.</param>
  36. /// <returns>Whether or not conventions should be applied to the view.</returns>
  37. public static bool ShouldApplyConventions(FrameworkElement view) {
  38. var overriden = View.GetApplyConventions(view);
  39. return overriden.GetValueOrDefault(ApplyConventionsByDefault);
  40. }
  41. /// <summary>
  42. /// Creates data bindings on the view's controls based on the provided properties.
  43. /// </summary>
  44. /// <remarks>Parameters include named Elements to search through and the type of view model to determine conventions for. Returns unmatched elements.</remarks>
  45. public static Func<IEnumerable<FrameworkElement>, Type, IEnumerable<FrameworkElement>> BindProperties = (namedElements, viewModelType) => {
  46. var unmatchedElements = new List<FrameworkElement>();
  47. foreach(var element in namedElements) {
  48. var cleanName = element.Name.Trim('_');
  49. var parts = cleanName.Split(new[] { '_' }, StringSplitOptions.RemoveEmptyEntries);
  50. var property = viewModelType.GetPropertyCaseInsensitive(parts[0]);
  51. var interpretedViewModelType = viewModelType;
  52. for(int i = 1; i < parts.Length && property != null; i++) {
  53. interpretedViewModelType = property.PropertyType;
  54. property = interpretedViewModelType.GetPropertyCaseInsensitive(parts[i]);
  55. }
  56. if(property == null) {
  57. unmatchedElements.Add(element);
  58. Log.Info("Binding Convention Not Applied: Element {0} did not match a property.", element.Name);
  59. continue;
  60. }
  61. var convention = ConventionManager.GetElementConvention(element.GetType());
  62. if(convention == null) {
  63. unmatchedElements.Add(element);
  64. Log.Warn("Binding Convention Not Applied: No conventions configured for {0}.", element.GetType());
  65. continue;
  66. }
  67. var applied = convention.ApplyBinding(
  68. interpretedViewModelType,
  69. cleanName.Replace('_', '.'),
  70. property,
  71. element,
  72. convention
  73. );
  74. if (applied) {
  75. Log.Info("Binding Convention Applied: Element {0}.", element.Name);
  76. }
  77. else {
  78. Log.Info("Binding Convention Not Applied: Element {0} has existing binding.", element.Name);
  79. unmatchedElements.Add(element);
  80. }
  81. }
  82. return unmatchedElements;
  83. };
  84. /// <summary>
  85. /// Attaches instances of <see cref="ActionMessage"/> to the view's controls based on the provided methods.
  86. /// </summary>
  87. /// <remarks>Parameters include the named elements to search through and the type of view model to determine conventions for. Returns unmatched elements.</remarks>
  88. public static Func<IEnumerable<FrameworkElement>, Type, IEnumerable<FrameworkElement>> BindActions = (namedElements, viewModelType) => {
  89. var methods = viewModelType.GetMethods();
  90. var unmatchedElements = namedElements.ToList();
  91. foreach(var method in methods) {
  92. var foundControl = unmatchedElements.FindName(method.Name);
  93. if(foundControl == null) {
  94. Log.Info("Action Convention Not Applied: No actionable element for {0}.", method.Name);
  95. continue;
  96. }
  97. unmatchedElements.Remove(foundControl);
  98. var triggers = Interaction.GetTriggers(foundControl);
  99. if(triggers != null && triggers.Count > 0) {
  100. Log.Info("Action Convention Not Applied: Interaction.Triggers already set on {0}.", foundControl.Name);
  101. continue;
  102. }
  103. var message = method.Name;
  104. var parameters = method.GetParameters();
  105. if(parameters.Length > 0) {
  106. message += "(";
  107. foreach(var parameter in parameters) {
  108. var paramName = parameter.Name;
  109. var specialValue = "$" + paramName.ToLower();
  110. if(MessageBinder.SpecialValues.ContainsKey(specialValue))
  111. paramName = specialValue;
  112. message += paramName + ",";
  113. }
  114. message = message.Remove(message.Length - 1, 1);
  115. message += ")";
  116. }
  117. Log.Info("Action Convention Applied: Action {0} on element {1}.", method.Name, message);
  118. Message.SetAttach(foundControl, message);
  119. }
  120. return unmatchedElements;
  121. };
  122. /// <summary>
  123. /// Allows the developer to add custom handling of named elements which were not matched by any default conventions.
  124. /// </summary>
  125. public static Action<IEnumerable<FrameworkElement>, Type> HandleUnmatchedElements = (elements, viewModelType) => {};
  126. /// <summary>
  127. /// Binds the specified viewModel to the view.
  128. /// </summary>
  129. ///<remarks>Passes the the view model, view and creation context (or null for default) to use in applying binding.</remarks>
  130. public static Action<object, DependencyObject, object> Bind = (viewModel, view, context) =>{
  131. Log.Info("Binding {0} and {1}.", view, viewModel);
  132. if((bool)view.GetValue(Micro.Bind.NoContextProperty)) {
  133. Action.SetTargetWithoutContext(view, viewModel);
  134. }else {
  135. Action.SetTarget(view, viewModel);
  136. }
  137. var viewAware = viewModel as IViewAware;
  138. if (viewAware != null) {
  139. Log.Info("Attaching {0} to {1}.", view, viewAware);
  140. viewAware.AttachView(view, context);
  141. }
  142. if ((bool)view.GetValue(ConventionsAppliedProperty)) {
  143. return;
  144. }
  145. var element = View.GetFirstNonGeneratedView(view) as FrameworkElement;
  146. if (element == null) {
  147. return;
  148. }
  149. if (!ShouldApplyConventions(element)) {
  150. Log.Info("Skipping conventions for {0} and {1}.", element, viewModel);
  151. return;
  152. }
  153. var viewModelType = viewModel.GetType();
  154. var namedElements = BindingScope.GetNamedElements(element);
  155. #if SILVERLIGHT
  156. namedElements.Apply(x => x.SetValue(
  157. View.IsLoadedProperty,
  158. element.GetValue(View.IsLoadedProperty))
  159. );
  160. #endif
  161. namedElements = BindActions(namedElements, viewModelType);
  162. namedElements = BindProperties(namedElements, viewModelType);
  163. HandleUnmatchedElements(namedElements, viewModelType);
  164. #if WP71
  165. BindAppBar(view);
  166. #endif
  167. view.SetValue(ConventionsAppliedProperty, true);
  168. };
  169. #if WP71
  170. static void BindAppBar(DependencyObject view) {
  171. var page = view as PhoneApplicationPage;
  172. if (page == null || page.ApplicationBar == null) {
  173. return;
  174. }
  175. var triggers = Interaction.GetTriggers(view);
  176. foreach(var item in page.ApplicationBar.Buttons) {
  177. var button = item as AppBarButton;
  178. if (button == null) {
  179. continue;
  180. }
  181. var parsedTrigger = Parser.Parse(view, button.Message).First();
  182. var trigger = new AppBarButtonTrigger(button);
  183. var actionMessages = parsedTrigger.Actions.OfType<ActionMessage>().ToList();
  184. actionMessages.Apply(x => {
  185. x.buttonSource = button;
  186. parsedTrigger.Actions.Remove(x);
  187. trigger.Actions.Add(x);
  188. });
  189. triggers.Add(trigger);
  190. }
  191. foreach (var item in page.ApplicationBar.MenuItems) {
  192. var menuItem = item as AppBarMenuItem;
  193. if (menuItem == null) {
  194. continue;
  195. }
  196. var parsedTrigger = Parser.Parse(view, menuItem.Message).First();
  197. var trigger = new AppBarMenuItemTrigger(menuItem);
  198. var actionMessages = parsedTrigger.Actions.OfType<ActionMessage>().ToList();
  199. actionMessages.Apply(x => {
  200. x.menuItemSource = menuItem;
  201. parsedTrigger.Actions.Remove(x);
  202. trigger.Actions.Add(x);
  203. });
  204. triggers.Add(trigger);
  205. }
  206. }
  207. #endif
  208. }
  209. }