PageRenderTime 44ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/Main/Cookbook/Caliburn.Micro.Silverlight/ViewModelBinder.cs

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