ConventionManager.cs 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627
  1. namespace Caliburn.Micro {
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. using System.Reflection;
  6. #if WinRT81
  7. using Windows.UI.Xaml;
  8. using Windows.UI.Xaml.Controls;
  9. using Windows.UI.Xaml.Controls.Primitives;
  10. using Windows.UI.Xaml.Data;
  11. using Windows.UI.Xaml.Markup;
  12. using EventTrigger = Microsoft.Xaml.Interactions.Core.EventTriggerBehavior;
  13. using Windows.UI.Xaml.Shapes;
  14. #else
  15. using System.ComponentModel;
  16. using System.Windows;
  17. using System.Windows.Controls;
  18. using System.Windows.Controls.Primitives;
  19. using System.Windows.Data;
  20. using System.Windows.Markup;
  21. using System.Windows.Shapes;
  22. using EventTrigger = System.Windows.Interactivity.EventTrigger;
  23. #endif
  24. #if !SILVERLIGHT && !WinRT
  25. using System.Windows.Documents;
  26. using Caliburn.Micro.Core;
  27. #endif
  28. /// <summary>
  29. /// Used to configure the conventions used by the framework to apply bindings and create actions.
  30. /// </summary>
  31. public static class ConventionManager {
  32. static readonly ILog Log = LogManager.GetLog(typeof(ConventionManager));
  33. /// <summary>
  34. /// Converters <see cref="bool"/> to/from <see cref="Visibility"/>.
  35. /// </summary>
  36. public static IValueConverter BooleanToVisibilityConverter = new BooleanToVisibilityConverter();
  37. /// <summary>
  38. /// Indicates whether or not static properties should be included during convention name matching.
  39. /// </summary>
  40. /// <remarks>False by default.</remarks>
  41. public static bool IncludeStaticProperties = false;
  42. /// <summary>
  43. /// Indicates whether or not the Content of ContentControls should be overwritten by conventional bindings.
  44. /// </summary>
  45. /// <remarks>False by default.</remarks>
  46. public static bool OverwriteContent = false;
  47. /// <summary>
  48. /// The default DataTemplate used for ItemsControls when required.
  49. /// </summary>
  50. public static DataTemplate DefaultItemTemplate = (DataTemplate)
  51. #if SILVERLIGHT || WinRT
  52. XamlReader.Load(
  53. #else
  54. XamlReader.Parse(
  55. #endif
  56. #if WinRT
  57. "<DataTemplate xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation' xmlns:cal='using:Caliburn.Micro'>" +
  58. "<ContentControl cal:View.Model=\"{Binding}\" VerticalContentAlignment=\"Stretch\" HorizontalContentAlignment=\"Stretch\" IsTabStop=\"False\" />" +
  59. "</DataTemplate>"
  60. #else
  61. "<DataTemplate xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation' " +
  62. "xmlns:cal='clr-namespace:Caliburn.Micro;assembly=Caliburn.Micro.Platform'> " +
  63. "<ContentControl cal:View.Model=\"{Binding}\" VerticalContentAlignment=\"Stretch\" HorizontalContentAlignment=\"Stretch\" IsTabStop=\"False\" />" +
  64. "</DataTemplate>"
  65. #endif
  66. );
  67. /// <summary>
  68. /// The default DataTemplate used for Headered controls when required.
  69. /// </summary>
  70. public static DataTemplate DefaultHeaderTemplate = (DataTemplate)
  71. #if SILVERLIGHT || WinRT
  72. XamlReader.Load(
  73. #else
  74. XamlReader.Parse(
  75. #endif
  76. "<DataTemplate xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'><TextBlock Text=\"{Binding DisplayName, Mode=TwoWay}\" /></DataTemplate>"
  77. );
  78. static readonly Dictionary<Type, ElementConvention> ElementConventions = new Dictionary<Type, ElementConvention>();
  79. /// <summary>
  80. /// Changes the provided word from a plural form to a singular form.
  81. /// </summary>
  82. public static Func<string, string> Singularize = original => {
  83. return original.EndsWith("ies")
  84. ? original.TrimEnd('s').TrimEnd('e').TrimEnd('i') + "y"
  85. : original.TrimEnd('s');
  86. };
  87. /// <summary>
  88. /// Derives the SelectedItem property name.
  89. /// </summary>
  90. public static Func<string, IEnumerable<string>> DerivePotentialSelectionNames = name => {
  91. var singular = Singularize(name);
  92. return new[] {
  93. "Active" + singular,
  94. "Selected" + singular,
  95. "Current" + singular
  96. };
  97. };
  98. /// <summary>
  99. /// Creates a binding and sets it on the element, applying the appropriate conventions.
  100. /// </summary>
  101. /// <param name="viewModelType"></param>
  102. /// <param name="path"></param>
  103. /// <param name="property"></param>
  104. /// <param name="element"></param>
  105. /// <param name="convention"></param>
  106. /// <param name="bindableProperty"></param>
  107. public static Action<Type, string, PropertyInfo, FrameworkElement, ElementConvention, DependencyProperty> SetBinding =
  108. (viewModelType, path, property, element, convention, bindableProperty) => {
  109. #if WinRT
  110. var binding = new Binding { Path = new PropertyPath(path) };
  111. #else
  112. var binding = new Binding(path);
  113. #endif
  114. ApplyBindingMode(binding, property);
  115. ApplyValueConverter(binding, bindableProperty, property);
  116. ApplyStringFormat(binding, convention, property);
  117. ApplyValidation(binding, viewModelType, property);
  118. ApplyUpdateSourceTrigger(bindableProperty, element, binding, property);
  119. BindingOperations.SetBinding(element, bindableProperty, binding);
  120. };
  121. /// <summary>
  122. /// Applies the appropriate binding mode to the binding.
  123. /// </summary>
  124. public static Action<Binding, PropertyInfo> ApplyBindingMode = (binding, property) => {
  125. #if WinRT
  126. var setMethod = property.SetMethod;
  127. binding.Mode = (property.CanWrite && setMethod != null && setMethod.IsPublic) ? BindingMode.TwoWay : BindingMode.OneWay;
  128. #else
  129. var setMethod = property.GetSetMethod();
  130. binding.Mode = (property.CanWrite && setMethod != null && setMethod.IsPublic) ? BindingMode.TwoWay : BindingMode.OneWay;
  131. #endif
  132. };
  133. /// <summary>
  134. /// Determines whether or not and what type of validation to enable on the binding.
  135. /// </summary>
  136. public static Action<Binding, Type, PropertyInfo> ApplyValidation = (binding, viewModelType, property) => {
  137. #if SILVERLIGHT || NET45
  138. if (typeof(INotifyDataErrorInfo).IsAssignableFrom(viewModelType)) {
  139. binding.ValidatesOnNotifyDataErrors = true;
  140. binding.ValidatesOnExceptions = true;
  141. }
  142. #endif
  143. #if !WinRT
  144. if (typeof(IDataErrorInfo).IsAssignableFrom(viewModelType)) {
  145. binding.ValidatesOnDataErrors = true;
  146. binding.ValidatesOnExceptions = true;
  147. }
  148. #endif
  149. };
  150. /// <summary>
  151. /// Determines whether a value converter is is needed and applies one to the binding.
  152. /// </summary>
  153. public static Action<Binding, DependencyProperty, PropertyInfo> ApplyValueConverter = (binding, bindableProperty, property) => {
  154. if (bindableProperty == UIElement.VisibilityProperty && typeof(bool).IsAssignableFrom(property.PropertyType))
  155. binding.Converter = BooleanToVisibilityConverter;
  156. };
  157. /// <summary>
  158. /// Determines whether a custom string format is needed and applies it to the binding.
  159. /// </summary>
  160. public static Action<Binding, ElementConvention, PropertyInfo> ApplyStringFormat = (binding, convention, property) => {
  161. #if !WinRT
  162. if(typeof(DateTime).IsAssignableFrom(property.PropertyType))
  163. binding.StringFormat = "{0:d}";
  164. #endif
  165. };
  166. /// <summary>
  167. /// Determines whether a custom update source trigger should be applied to the binding.
  168. /// </summary>
  169. public static Action<DependencyProperty, DependencyObject, Binding, PropertyInfo> ApplyUpdateSourceTrigger = (bindableProperty, element, binding, info) => {
  170. #if SILVERLIGHT && !SL5
  171. ApplySilverlightTriggers(
  172. element,
  173. bindableProperty,
  174. x => x.GetBindingExpression(bindableProperty),
  175. info,
  176. binding
  177. );
  178. #elif WinRT81 || NET
  179. binding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
  180. #endif
  181. };
  182. static ConventionManager() {
  183. #if WINDOWS_UWP
  184. AddElementConvention<SplitView>(SplitView.ContentProperty, "IsPaneOpen", "PaneClosing").GetBindableProperty =
  185. delegate (DependencyObject foundControl)
  186. {
  187. var element = (SplitView)foundControl;
  188. if (!OverwriteContent)
  189. return null;
  190. Log.Info("ViewModel bound on {0}.", element.Name);
  191. return View.ModelProperty;
  192. };
  193. #endif
  194. #if !WINDOWS_PHONE && !WinRT
  195. AddElementConvention<DatePicker>(DatePicker.SelectedDateProperty, "SelectedDate", "SelectedDateChanged");
  196. #endif
  197. #if WinRT81
  198. AddElementConvention<DatePicker>(DatePicker.DateProperty, "Date", "DateChanged");
  199. AddElementConvention<TimePicker>(TimePicker.TimeProperty, "Time", "TimeChanged");
  200. AddElementConvention<Hub>(Hub.HeaderProperty, "Header", "Loaded");
  201. AddElementConvention<HubSection>(HubSection.HeaderProperty, "Header", "SectionsInViewChanged");
  202. AddElementConvention<MenuFlyoutItem>(MenuFlyoutItem.TextProperty, "Text", "Click");
  203. AddElementConvention<ToggleMenuFlyoutItem>(ToggleMenuFlyoutItem.IsCheckedProperty, "IsChecked", "Click");
  204. #endif
  205. #if WinRT81 && !WP81
  206. AddElementConvention<SearchBox>(SearchBox.QueryTextProperty, "QueryText", "QuerySubmitted");
  207. #endif
  208. #if WinRT
  209. AddElementConvention<ToggleSwitch>(ToggleSwitch.IsOnProperty, "IsOn", "Toggled");
  210. AddElementConvention<ProgressRing>(ProgressRing.IsActiveProperty, "IsActive", "Loaded");
  211. AddElementConvention<Slider>(Slider.ValueProperty, "Value", "ValueChanged");
  212. AddElementConvention<RichEditBox>(RichEditBox.DataContextProperty, "DataContext", "TextChanged");
  213. #endif
  214. #if WP81 || WINDOWS_UWP
  215. AddElementConvention<Pivot>(Pivot.ItemsSourceProperty, "SelectedItem", "SelectionChanged")
  216. .ApplyBinding = (viewModelType, path, property, element, convention) =>
  217. {
  218. if (!SetBindingWithoutBindingOrValueOverwrite(viewModelType, path, property, element, convention, ItemsControl.ItemsSourceProperty))
  219. {
  220. return false;
  221. }
  222. ConfigureSelectedItem(element, Pivot.SelectedItemProperty, viewModelType, path);
  223. ApplyItemTemplate((ItemsControl)element, property);
  224. return true;
  225. };
  226. #endif
  227. #if SILVERLIGHT || WinRT
  228. AddElementConvention<HyperlinkButton>(HyperlinkButton.ContentProperty, "DataContext", "Click");
  229. AddElementConvention<PasswordBox>(PasswordBox.PasswordProperty, "Password", "PasswordChanged");
  230. #else
  231. AddElementConvention<DocumentViewer>(DocumentViewer.DocumentProperty, "DataContext", "Loaded");
  232. AddElementConvention<PasswordBox>(null, "Password", "PasswordChanged");
  233. AddElementConvention<Hyperlink>(Hyperlink.DataContextProperty, "DataContext", "Click");
  234. AddElementConvention<RichTextBox>(RichTextBox.DataContextProperty, "DataContext", "TextChanged");
  235. AddElementConvention<Menu>(Menu.ItemsSourceProperty,"DataContext", "Click");
  236. AddElementConvention<MenuItem>(MenuItem.ItemsSourceProperty, "DataContext", "Click");
  237. AddElementConvention<Label>(Label.ContentProperty, "Content", "DataContextChanged");
  238. AddElementConvention<Slider>(Slider.ValueProperty, "Value", "ValueChanged");
  239. AddElementConvention<Expander>(Expander.IsExpandedProperty, "IsExpanded", "Expanded");
  240. AddElementConvention<StatusBar>(StatusBar.ItemsSourceProperty, "DataContext", "Loaded");
  241. AddElementConvention<ToolBar>(ToolBar.ItemsSourceProperty, "DataContext", "Loaded");
  242. AddElementConvention<ToolBarTray>(ToolBarTray.VisibilityProperty, "DataContext", "Loaded");
  243. AddElementConvention<TreeView>(TreeView.ItemsSourceProperty, "SelectedItem", "SelectedItemChanged");
  244. AddElementConvention<TabControl>(TabControl.ItemsSourceProperty, "ItemsSource", "SelectionChanged")
  245. .ApplyBinding = (viewModelType, path, property, element, convention) => {
  246. var bindableProperty = convention.GetBindableProperty(element);
  247. if(!SetBindingWithoutBindingOverwrite(viewModelType, path, property, element, convention, bindableProperty))
  248. return false;
  249. var tabControl = (TabControl)element;
  250. if(tabControl.ContentTemplate == null
  251. && tabControl.ContentTemplateSelector == null
  252. && property.PropertyType.IsGenericType) {
  253. var itemType = property.PropertyType.GetGenericArguments().First();
  254. if(!itemType.IsValueType && !typeof(string).IsAssignableFrom(itemType)){
  255. tabControl.ContentTemplate = DefaultItemTemplate;
  256. Log.Info("ContentTemplate applied to {0}.", element.Name);
  257. }
  258. }
  259. ConfigureSelectedItem(element, Selector.SelectedItemProperty, viewModelType, path);
  260. if(string.IsNullOrEmpty(tabControl.DisplayMemberPath))
  261. ApplyHeaderTemplate(tabControl, TabControl.ItemTemplateProperty, TabControl.ItemTemplateSelectorProperty, viewModelType);
  262. return true;
  263. };
  264. AddElementConvention<TabItem>(TabItem.ContentProperty, "DataContext", "DataContextChanged");
  265. AddElementConvention<Window>(Window.DataContextProperty, "DataContext", "Loaded");
  266. #endif
  267. AddElementConvention<UserControl>(UserControl.VisibilityProperty, "DataContext", "Loaded");
  268. AddElementConvention<Image>(Image.SourceProperty, "Source", "Loaded");
  269. AddElementConvention<ToggleButton>(ToggleButton.IsCheckedProperty, "IsChecked", "Click");
  270. AddElementConvention<ButtonBase>(ButtonBase.ContentProperty, "DataContext", "Click");
  271. AddElementConvention<TextBox>(TextBox.TextProperty, "Text", "TextChanged");
  272. AddElementConvention<TextBlock>(TextBlock.TextProperty, "Text", "DataContextChanged");
  273. AddElementConvention<ProgressBar>(ProgressBar.ValueProperty, "Value", "ValueChanged");
  274. AddElementConvention<Selector>(Selector.ItemsSourceProperty, "SelectedItem", "SelectionChanged")
  275. .ApplyBinding = (viewModelType, path, property, element, convention) => {
  276. if (!SetBindingWithoutBindingOrValueOverwrite(viewModelType, path, property, element, convention, ItemsControl.ItemsSourceProperty)) {
  277. return false;
  278. }
  279. ConfigureSelectedItem(element, Selector.SelectedItemProperty, viewModelType, path);
  280. ApplyItemTemplate((ItemsControl)element, property);
  281. return true;
  282. };
  283. AddElementConvention<ItemsControl>(ItemsControl.ItemsSourceProperty, "DataContext", "Loaded")
  284. .ApplyBinding = (viewModelType, path, property, element, convention) => {
  285. if (!SetBindingWithoutBindingOrValueOverwrite(viewModelType, path, property, element, convention, ItemsControl.ItemsSourceProperty)) {
  286. return false;
  287. }
  288. ApplyItemTemplate((ItemsControl)element, property);
  289. return true;
  290. };
  291. AddElementConvention<ContentControl>(ContentControl.ContentProperty, "DataContext", "Loaded").GetBindableProperty =
  292. delegate(DependencyObject foundControl) {
  293. var element = (ContentControl)foundControl;
  294. if (element.Content is DependencyObject && !OverwriteContent)
  295. return null;
  296. #if SILVERLIGHT
  297. var useViewModel = element.ContentTemplate == null;
  298. #else
  299. var useViewModel = element.ContentTemplate == null && element.ContentTemplateSelector == null;
  300. #endif
  301. if (useViewModel) {
  302. Log.Info("ViewModel bound on {0}.", element.Name);
  303. return View.ModelProperty;
  304. }
  305. Log.Info("Content bound on {0}. Template or content was present.", element.Name);
  306. return ContentControl.ContentProperty;
  307. };
  308. AddElementConvention<Shape>(Shape.VisibilityProperty, "DataContext", "MouseLeftButtonUp");
  309. AddElementConvention<FrameworkElement>(FrameworkElement.VisibilityProperty, "DataContext", "Loaded");
  310. }
  311. /// <summary>
  312. /// Adds an element convention.
  313. /// </summary>
  314. /// <typeparam name="T">The type of element.</typeparam>
  315. /// <param name="bindableProperty">The default property for binding conventions.</param>
  316. /// <param name="parameterProperty">The default property for action parameters.</param>
  317. /// <param name="eventName">The default event to trigger actions.</param>
  318. public static ElementConvention AddElementConvention<T>(DependencyProperty bindableProperty, string parameterProperty, string eventName) {
  319. return AddElementConvention(new ElementConvention {
  320. ElementType = typeof(T),
  321. GetBindableProperty = element => bindableProperty,
  322. ParameterProperty = parameterProperty,
  323. CreateTrigger = () => new EventTrigger { EventName = eventName }
  324. });
  325. }
  326. /// <summary>
  327. /// Adds an element convention.
  328. /// </summary>
  329. /// <param name="convention"></param>
  330. public static ElementConvention AddElementConvention(ElementConvention convention) {
  331. return ElementConventions[convention.ElementType] = convention;
  332. }
  333. /// <summary>
  334. /// Gets an element convention for the provided element type.
  335. /// </summary>
  336. /// <param name="elementType">The type of element to locate the convention for.</param>
  337. /// <returns>The convention if found, null otherwise.</returns>
  338. /// <remarks>Searches the class hierarchy for conventions.</remarks>
  339. public static ElementConvention GetElementConvention(Type elementType) {
  340. if (elementType == null)
  341. return null;
  342. ElementConvention propertyConvention;
  343. ElementConventions.TryGetValue(elementType, out propertyConvention);
  344. #if WinRT
  345. return propertyConvention ?? GetElementConvention(elementType.GetTypeInfo().BaseType);
  346. #else
  347. return propertyConvention ?? GetElementConvention(elementType.BaseType);
  348. #endif
  349. }
  350. /// <summary>
  351. /// Determines whether a particular dependency property already has a binding on the provided element.
  352. /// </summary>
  353. public static bool HasBinding(FrameworkElement element, DependencyProperty property) {
  354. #if NET
  355. return BindingOperations.GetBindingBase(element, property) != null;
  356. #else
  357. return element.GetBindingExpression(property) != null;
  358. #endif
  359. }
  360. /// <summary>
  361. /// Creates a binding and sets it on the element, guarding against pre-existing bindings.
  362. /// </summary>
  363. public static bool SetBindingWithoutBindingOverwrite(Type viewModelType, string path, PropertyInfo property,
  364. FrameworkElement element, ElementConvention convention,
  365. DependencyProperty bindableProperty) {
  366. if (bindableProperty == null || HasBinding(element, bindableProperty)) {
  367. return false;
  368. }
  369. SetBinding(viewModelType, path, property, element, convention, bindableProperty);
  370. return true;
  371. }
  372. /// <summary>
  373. /// Creates a binding and set it on the element, guarding against pre-existing bindings and pre-existing values.
  374. /// </summary>
  375. /// <param name="viewModelType"></param>
  376. /// <param name="path"></param>
  377. /// <param name="property"></param>
  378. /// <param name="element"></param>
  379. /// <param name="convention"></param>
  380. /// <param name="bindableProperty"> </param>
  381. /// <returns></returns>
  382. public static bool SetBindingWithoutBindingOrValueOverwrite(Type viewModelType, string path,
  383. PropertyInfo property, FrameworkElement element,
  384. ElementConvention convention,
  385. DependencyProperty bindableProperty) {
  386. if (bindableProperty == null || HasBinding(element, bindableProperty)) {
  387. return false;
  388. }
  389. if (element.GetValue(bindableProperty) != null) {
  390. return false;
  391. }
  392. SetBinding(viewModelType, path, property, element, convention, bindableProperty);
  393. return true;
  394. }
  395. /// <summary>
  396. /// Attempts to apply the default item template to the items control.
  397. /// </summary>
  398. /// <param name="itemsControl">The items control.</param>
  399. /// <param name="property">The collection property.</param>
  400. public static void ApplyItemTemplate(ItemsControl itemsControl, PropertyInfo property) {
  401. if (!string.IsNullOrEmpty(itemsControl.DisplayMemberPath)
  402. || HasBinding(itemsControl, ItemsControl.DisplayMemberPathProperty)
  403. || itemsControl.ItemTemplate != null) {
  404. return;
  405. }
  406. #if !WinRT
  407. if (property.PropertyType.IsGenericType) {
  408. var itemType = property.PropertyType.GetGenericArguments().First();
  409. if (itemType.IsValueType || typeof(string).IsAssignableFrom(itemType)) {
  410. return;
  411. }
  412. }
  413. #else
  414. if (property.PropertyType.GetTypeInfo().IsGenericType) {
  415. var itemType = property.PropertyType.GenericTypeArguments.First();
  416. if (itemType.GetTypeInfo().IsValueType || typeof (string).IsAssignableFrom(itemType)) {
  417. return;
  418. }
  419. }
  420. #endif
  421. #if !SILVERLIGHT
  422. if (itemsControl.ItemTemplateSelector == null){
  423. itemsControl.ItemTemplate = DefaultItemTemplate;
  424. Log.Info("ItemTemplate applied to {0}.", itemsControl.Name);
  425. }
  426. #else
  427. itemsControl.ItemTemplate = DefaultItemTemplate;
  428. Log.Info("ItemTemplate applied to {0}.", itemsControl.Name);
  429. #endif
  430. }
  431. /// <summary>
  432. /// Configures the selected item convention.
  433. /// </summary>
  434. /// <param name="selector">The element that has a SelectedItem property.</param>
  435. /// <param name="selectedItemProperty">The SelectedItem property.</param>
  436. /// <param name="viewModelType">The view model type.</param>
  437. /// <param name="path">The property path.</param>
  438. public static Action<FrameworkElement, DependencyProperty, Type, string> ConfigureSelectedItem =
  439. (selector, selectedItemProperty, viewModelType, path) => {
  440. if (HasBinding(selector, selectedItemProperty)) {
  441. return;
  442. }
  443. var index = path.LastIndexOf('.');
  444. index = index == -1 ? 0 : index + 1;
  445. var baseName = path.Substring(index);
  446. foreach (var potentialName in DerivePotentialSelectionNames(baseName)) {
  447. if (viewModelType.GetPropertyCaseInsensitive(potentialName) != null) {
  448. var selectionPath = path.Replace(baseName, potentialName);
  449. #if WinRT
  450. var binding = new Binding { Mode = BindingMode.TwoWay, Path = new PropertyPath(selectionPath) };
  451. #else
  452. var binding = new Binding(selectionPath) { Mode = BindingMode.TwoWay };
  453. #endif
  454. var shouldApplyBinding = ConfigureSelectedItemBinding(selector, selectedItemProperty, viewModelType, selectionPath, binding);
  455. if (shouldApplyBinding) {
  456. BindingOperations.SetBinding(selector, selectedItemProperty, binding);
  457. Log.Info("SelectedItem binding applied to {0}.", selector.Name);
  458. return;
  459. }
  460. Log.Info("SelectedItem binding not applied to {0} due to 'ConfigureSelectedItemBinding' customization.", selector.Name);
  461. }
  462. }
  463. };
  464. /// <summary>
  465. /// Configures the SelectedItem binding for matched selection path.
  466. /// </summary>
  467. /// <param name="selector">The element that has a SelectedItem property.</param>
  468. /// <param name="selectedItemProperty">The SelectedItem property.</param>
  469. /// <param name="viewModelType">The view model type.</param>
  470. /// <param name="selectionPath">The property path.</param>
  471. /// <param name="binding">The binding to configure.</param>
  472. /// <returns>A bool indicating whether to apply binding</returns>
  473. public static Func<FrameworkElement, DependencyProperty, Type, string, Binding, bool> ConfigureSelectedItemBinding =
  474. (selector, selectedItemProperty, viewModelType, selectionPath, binding) => {
  475. return true;
  476. };
  477. /// <summary>
  478. /// Applies a header template based on <see cref="IHaveDisplayName"/>
  479. /// </summary>
  480. /// <param name="element"></param>
  481. /// <param name="headerTemplateProperty"></param>
  482. /// <param name="headerTemplateSelectorProperty"> </param>
  483. /// <param name="viewModelType"></param>
  484. public static void ApplyHeaderTemplate(FrameworkElement element, DependencyProperty headerTemplateProperty, DependencyProperty headerTemplateSelectorProperty, Type viewModelType) {
  485. var template = element.GetValue(headerTemplateProperty);
  486. var selector = headerTemplateSelectorProperty != null
  487. ? element.GetValue(headerTemplateSelectorProperty)
  488. : null;
  489. if (template != null || selector != null || !typeof(IHaveDisplayName).IsAssignableFrom(viewModelType)) {
  490. return;
  491. }
  492. element.SetValue(headerTemplateProperty, DefaultHeaderTemplate);
  493. Log.Info("Header template applied to {0}.", element.Name);
  494. }
  495. /// <summary>
  496. /// Gets a property by name, ignoring case and searching all interfaces.
  497. /// </summary>
  498. /// <param name="type">The type to inspect.</param>
  499. /// <param name="propertyName">The property to search for.</param>
  500. /// <returns>The property or null if not found.</returns>
  501. public static PropertyInfo GetPropertyCaseInsensitive(this Type type, string propertyName) {
  502. #if WinRT
  503. var typeInfo = type.GetTypeInfo();
  504. var typeList = new List<Type> { type };
  505. if (typeInfo.IsInterface) {
  506. typeList.AddRange(typeInfo.ImplementedInterfaces);
  507. }
  508. return typeList
  509. .Select(interfaceType => interfaceType.GetRuntimeProperty(propertyName))
  510. .FirstOrDefault(property => property != null);
  511. #else
  512. var typeList = new List<Type> { type };
  513. if (type.IsInterface) {
  514. typeList.AddRange(type.GetInterfaces());
  515. }
  516. var flags = BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance;
  517. if (IncludeStaticProperties) {
  518. flags = flags | BindingFlags.Static;
  519. }
  520. return typeList
  521. .Select(interfaceType => interfaceType.GetProperty(propertyName, flags))
  522. .FirstOrDefault(property => property != null);
  523. #endif
  524. }
  525. #if (SILVERLIGHT && !SL5)
  526. /// <summary>
  527. /// Accounts for the lack of UpdateSourceTrigger in silverlight.
  528. /// </summary>
  529. /// <param name="element">The element to wire for change events on.</param>
  530. /// <param name="dependencyProperty">The property that is being bound.</param>
  531. /// <param name="expressionSource">Gets the the binding expression that needs to be updated.</param>
  532. /// <param name="property">The property being bound to if available.</param>
  533. /// <param name="binding">The binding if available.</param>
  534. public static void ApplySilverlightTriggers(DependencyObject element, DependencyProperty dependencyProperty, Func<FrameworkElement, BindingExpression> expressionSource, PropertyInfo property, Binding binding){
  535. var textBox = element as TextBox;
  536. if (textBox != null && dependencyProperty == TextBox.TextProperty) {
  537. if (property != null) {
  538. var typeCode = Type.GetTypeCode(property.PropertyType);
  539. if (typeCode == TypeCode.Single || typeCode == TypeCode.Double || typeCode == TypeCode.Decimal) {
  540. binding.UpdateSourceTrigger = UpdateSourceTrigger.Explicit;
  541. textBox.KeyUp += delegate {
  542. var start = textBox.SelectionStart;
  543. var text = textBox.Text;
  544. expressionSource(textBox).UpdateSource();
  545. textBox.Text = text;
  546. textBox.SelectionStart = start;
  547. };
  548. return;
  549. }
  550. }
  551. textBox.TextChanged += delegate { expressionSource(textBox).UpdateSource(); };
  552. return;
  553. }
  554. var passwordBox = element as PasswordBox;
  555. if (passwordBox != null && dependencyProperty == PasswordBox.PasswordProperty) {
  556. passwordBox.PasswordChanged += delegate { expressionSource(passwordBox).UpdateSource(); };
  557. }
  558. }
  559. #endif
  560. }
  561. }