BindingScope.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  1. namespace Caliburn.Micro {
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. #if WinRT
  6. using System.ServiceModel;
  7. using Windows.UI.Xaml;
  8. using Windows.UI.Xaml.Controls;
  9. using Windows.UI.Xaml.Controls.Primitives;
  10. using Windows.UI.Xaml.Media;
  11. #else
  12. using System.Windows;
  13. using System.Windows.Controls;
  14. using System.Windows.Media;
  15. using System.Windows.Media.Media3D;
  16. using Caliburn.Micro.Core;
  17. #endif
  18. /// <summary>
  19. /// Provides methods for searching a given scope for named elements.
  20. /// </summary>
  21. public static class BindingScope {
  22. static readonly List<ChildResolver> ChildResolvers = new List<ChildResolver>();
  23. static readonly Dictionary<Type, Object> NonResolvableChildTypes = new Dictionary<Type, Object>();
  24. static BindingScope()
  25. {
  26. AddChildResolver<ContentControl>(e => new[] { e.Content as DependencyObject });
  27. AddChildResolver<ItemsControl>(e => e.Items.OfType<DependencyObject>().ToArray() );
  28. #if !SILVERLIGHT && !WinRT
  29. AddChildResolver<HeaderedContentControl>(e => new[] { e.Header as DependencyObject });
  30. AddChildResolver<HeaderedItemsControl>(e => new[] { e.Header as DependencyObject });
  31. #endif
  32. #if WinRT
  33. AddChildResolver<SemanticZoom>(e => new[] { e.ZoomedInView as DependencyObject, e.ZoomedOutView as DependencyObject });
  34. AddChildResolver<ListViewBase>(e => new[] { e.Header as DependencyObject });
  35. #endif
  36. #if WinRT81
  37. AddChildResolver<ListViewBase>(e => new[] { e.Footer as DependencyObject });
  38. AddChildResolver<Hub>(ResolveHub);
  39. AddChildResolver<HubSection>(e => new[] { e.Header as DependencyObject });
  40. AddChildResolver<CommandBar>(ResolveCommandBar);
  41. AddChildResolver<Button>(e => ResolveFlyoutBase(e.Flyout));
  42. AddChildResolver<FrameworkElement>(e => ResolveFlyoutBase(FlyoutBase.GetAttachedFlyout(e)));
  43. #endif
  44. #if WINDOWS_UWP
  45. AddChildResolver<SplitView>(e => new[] { e.Pane as DependencyObject, e.Content as DependencyObject });
  46. #endif
  47. }
  48. /// <summary>
  49. /// Searches through the list of named elements looking for a case-insensitive match.
  50. /// </summary>
  51. /// <param name="elementsToSearch">The named elements to search through.</param>
  52. /// <param name="name">The name to search for.</param>
  53. /// <returns>The named element or null if not found.</returns>
  54. public static FrameworkElement FindName(this IEnumerable<FrameworkElement> elementsToSearch, string name) {
  55. #if WinRT
  56. return elementsToSearch.FirstOrDefault(x => x.Name.Equals(name, StringComparison.OrdinalIgnoreCase));
  57. #else
  58. return elementsToSearch.FirstOrDefault(x => x.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase));
  59. #endif
  60. }
  61. /// <summary>
  62. /// Adds a child resolver.
  63. /// </summary>
  64. /// <param name="filter">The type filter.</param>
  65. /// <param name="resolver">The resolver.</param>
  66. public static ChildResolver AddChildResolver(Func<Type, bool> filter, Func<DependencyObject, IEnumerable<DependencyObject>> resolver) {
  67. if (filter == null) {
  68. throw new ArgumentNullException("filter");
  69. }
  70. if (resolver == null) {
  71. throw new ArgumentNullException("resolver");
  72. }
  73. NonResolvableChildTypes.Clear();
  74. var childResolver = new ChildResolver(filter, resolver);
  75. ChildResolvers.Add(childResolver);
  76. return childResolver;
  77. }
  78. /// <summary>
  79. /// Adds a child resolver.
  80. /// </summary>
  81. /// <param name="filter">The type filter.</param>
  82. /// <param name="resolver">The resolver.</param>
  83. public static ChildResolver AddChildResolver<T>(Func<T, IEnumerable<DependencyObject>> resolver) where T : DependencyObject
  84. {
  85. if (resolver == null) {
  86. throw new ArgumentNullException("resolver");
  87. }
  88. NonResolvableChildTypes.Clear();
  89. var childResolver = new ChildResolver<T>(resolver);
  90. ChildResolvers.Add(childResolver);
  91. return childResolver;
  92. }
  93. /// <summary>
  94. /// Removes a child resolver.
  95. /// </summary>
  96. /// <param name="resolver">The resolver to remove.</param>
  97. /// <returns>true, when the resolver was (found and) removed.</returns>
  98. public static bool RemoveChildResolver(ChildResolver resolver) {
  99. if (resolver == null) {
  100. throw new ArgumentNullException("resolver");
  101. }
  102. return ChildResolvers.Remove(resolver);
  103. }
  104. /// <summary>
  105. /// Gets all the <see cref="FrameworkElement"/> instances with names in the scope.
  106. /// </summary>
  107. /// <returns>Named <see cref="FrameworkElement"/> instances in the provided scope.</returns>
  108. /// <remarks>Pass in a <see cref="DependencyObject"/> and receive a list of named <see cref="FrameworkElement"/> instances in the same scope.</remarks>
  109. public static Func<DependencyObject, IEnumerable<FrameworkElement>> GetNamedElements = elementInScope => {
  110. var routeHops = FindScopeNamingRoute(elementInScope);
  111. return FindNamedDescendants(routeHops);
  112. };
  113. /// <summary>
  114. /// Gets the parent of the given object in the Visual Tree.
  115. /// </summary>
  116. /// <returns>The parent of the given object in the Visual Tree</returns>
  117. public static Func<DependencyObject, DependencyObject> GetVisualParent = e => VisualTreeHelper.GetParent(e);
  118. /// <summary>
  119. /// Finds a set of named <see cref="FrameworkElement"/> instances in each hop in a <see cref="ScopeNamingRoute"/>.
  120. /// </summary>
  121. /// <remarks>
  122. /// Searches all the elements in the <see cref="ScopeNamingRoute"/> parameter as well as the visual children of
  123. /// each of these elements, the <see cref="ContentControl.Content"/>, the <c>HeaderedContentControl.Header</c>,
  124. /// the <see cref="ItemsControl.Items"/>, or the <c>HeaderedItemsControl.Header</c>, if any are found.
  125. /// </remarks>
  126. public static Func<ScopeNamingRoute, IEnumerable<FrameworkElement>> FindNamedDescendants = routeHops => {
  127. if (routeHops == null) {
  128. throw new ArgumentNullException("routeHops");
  129. }
  130. if (routeHops.Root == null) {
  131. throw new ArgumentException(String.Format("Root is null on the given {0}", typeof (ScopeNamingRoute)));
  132. }
  133. var descendants = new List<FrameworkElement>();
  134. var queue = new Queue<DependencyObject>();
  135. queue.Enqueue(routeHops.Root);
  136. while (queue.Count > 0) {
  137. var current = queue.Dequeue();
  138. var currentElement = current as FrameworkElement;
  139. if (currentElement != null && !string.IsNullOrEmpty(currentElement.Name))
  140. descendants.Add(currentElement);
  141. if (current is UserControl && !ReferenceEquals(current, routeHops.Root))
  142. continue;
  143. DependencyObject hopTarget;
  144. if (routeHops.TryGetHop(current, out hopTarget)) {
  145. queue.Enqueue(hopTarget);
  146. continue;
  147. }
  148. #if NET
  149. var childCount = (current is Visual || current is Visual3D)
  150. ? VisualTreeHelper.GetChildrenCount(current) : 0;
  151. #else
  152. var childCount = (current is UIElement)
  153. ? VisualTreeHelper.GetChildrenCount(current) : 0;
  154. #endif
  155. if (childCount > 0) {
  156. for (var i = 0; i < childCount; i++) {
  157. var childDo = VisualTreeHelper.GetChild(current, i);
  158. queue.Enqueue(childDo);
  159. }
  160. #if WinRT
  161. var page = current as Page;
  162. if (page != null) {
  163. if (page.BottomAppBar != null)
  164. queue.Enqueue(page.BottomAppBar);
  165. if (page.TopAppBar != null)
  166. queue.Enqueue(page.TopAppBar);
  167. }
  168. #endif
  169. }
  170. else {
  171. var currentType = current.GetType();
  172. if (!NonResolvableChildTypes.ContainsKey(currentType)) {
  173. var resolvers = ChildResolvers.Where(r => r.CanResolve(currentType)).ToArray();
  174. if (!resolvers.Any()) {
  175. NonResolvableChildTypes[currentType] = null;
  176. }
  177. else {
  178. resolvers
  179. .SelectMany(r => r.Resolve(current) ?? Enumerable.Empty<DependencyObject>())
  180. .Where(c => c != null)
  181. .Apply(queue.Enqueue);
  182. }
  183. }
  184. }
  185. }
  186. return descendants;
  187. };
  188. #if WinRT81
  189. private static IEnumerable<DependencyObject> ResolveFlyoutBase(FlyoutBase flyoutBase) {
  190. if (flyoutBase == null)
  191. yield break;
  192. var flyout = flyoutBase as Flyout;
  193. if (flyout != null && flyout.Content != null)
  194. yield return flyout.Content;
  195. var menuFlyout = flyoutBase as MenuFlyout;
  196. if (menuFlyout != null && menuFlyout.Items != null) {
  197. foreach (var item in menuFlyout.Items) {
  198. foreach (var subItem in ResolveMenuFlyoutItems(item)) {
  199. yield return subItem;
  200. }
  201. }
  202. }
  203. }
  204. private static IEnumerable<DependencyObject> ResolveMenuFlyoutItems(MenuFlyoutItemBase item) {
  205. yield return item;
  206. #if WINDOWS_UWP
  207. var subItem = item as MenuFlyoutSubItem;
  208. if (subItem != null && subItem.Items != null) {
  209. foreach (var subSubItem in subItem.Items) {
  210. yield return subSubItem;
  211. }
  212. }
  213. #endif
  214. }
  215. private static IEnumerable<DependencyObject> ResolveCommandBar(CommandBar commandBar) {
  216. foreach (var child in commandBar.PrimaryCommands.OfType<DependencyObject>()) {
  217. yield return child;
  218. }
  219. foreach (var child in commandBar.SecondaryCommands.OfType<DependencyObject>())
  220. {
  221. yield return child;
  222. }
  223. }
  224. private static IEnumerable<DependencyObject> ResolveHub(Hub hub) {
  225. yield return hub.Header as DependencyObject;
  226. foreach (var section in hub.Sections)
  227. yield return section;
  228. }
  229. #endif
  230. /// <summary>
  231. /// Finds a path of dependency objects which traces through visual anscestry until a root which is <see langword="null"/>,
  232. /// a <see cref="UserControl"/>, a <c>Page</c> with a dependency object <c>Page.ContentProperty</c> value,
  233. /// a dependency object with <see cref="View.IsScopeRootProperty"/> set to <see langword="true"/>. <see cref="ContentPresenter"/>
  234. /// and <see cref="ItemsPresenter"/> are included in the resulting <see cref="ScopeNamingRoute"/> in order to track which item
  235. /// in an items control we are scoped to.
  236. /// </summary>
  237. public static Func<DependencyObject, ScopeNamingRoute> FindScopeNamingRoute = elementInScope => {
  238. var root = elementInScope;
  239. var previous = elementInScope;
  240. DependencyObject contentPresenter = null;
  241. var routeHops = new ScopeNamingRoute();
  242. while (true) {
  243. if (root == null) {
  244. root = previous;
  245. break;
  246. }
  247. if (root is UserControl)
  248. break;
  249. #if !SILVERLIGHT
  250. if (root is Page) {
  251. root = ((Page) root).Content as DependencyObject ?? root;
  252. break;
  253. }
  254. #endif
  255. if ((bool) root.GetValue(View.IsScopeRootProperty))
  256. break;
  257. #if WinRT
  258. if (root is AppBar) {
  259. var frame = Window.Current.Content as Frame;
  260. var page = (frame != null) ? frame.Content as Page : null;
  261. if (page != null && (root == page.TopAppBar || root == page.BottomAppBar)) {
  262. root = page;
  263. break;
  264. }
  265. }
  266. #endif
  267. if (root is ContentPresenter)
  268. contentPresenter = root;
  269. else if (root is ItemsPresenter && contentPresenter != null) {
  270. routeHops.AddHop(root, contentPresenter);
  271. contentPresenter = null;
  272. }
  273. previous = root;
  274. root = GetVisualParent(previous);
  275. }
  276. routeHops.Root = root;
  277. return routeHops;
  278. };
  279. /// <summary>
  280. /// Maintains a connection in the visual tree of dependency objects in order to record a route through it.
  281. /// </summary>
  282. public class ScopeNamingRoute {
  283. readonly Dictionary<DependencyObject, DependencyObject> path = new Dictionary<DependencyObject, DependencyObject>();
  284. DependencyObject root;
  285. /// <summary>
  286. /// Gets or sets the starting point of the route.
  287. /// </summary>
  288. public DependencyObject Root {
  289. get { return root; }
  290. set {
  291. if (path.ContainsValue(value)) {
  292. throw new ArgumentException("Value is a target of some route hop; cannot be a root.");
  293. }
  294. root = value;
  295. }
  296. }
  297. /// <summary>
  298. /// Adds a segment to the route.
  299. /// </summary>
  300. /// <param name="from">The source dependency object.</param>
  301. /// <param name="to">The target dependency object.</param>
  302. public void AddHop(DependencyObject from, DependencyObject to) {
  303. if (@from == null) {
  304. throw new ArgumentNullException("from");
  305. }
  306. if (to == null) {
  307. throw new ArgumentNullException("to");
  308. }
  309. if (path.Count > 0 &&
  310. !path.ContainsKey(from) &&
  311. !path.ContainsKey(to) &&
  312. !path.ContainsValue(from) &&
  313. !path.ContainsValue(from)) {
  314. throw new ArgumentException("Hop pair not part of existing route.");
  315. }
  316. if (path.ContainsKey(to)) {
  317. throw new ArgumentException("Cycle detected when adding hop.");
  318. }
  319. path[from] = to;
  320. }
  321. /// <summary>
  322. /// Tries to get a target dependency object given a source.
  323. /// </summary>
  324. /// <param name="hopSource">The possible beginning of a route segment (hop).</param>
  325. /// <param name="hopTarget">The target of a route segment (hop).</param>
  326. /// <returns><see langword="true"/> if <paramref name="hopSource"/> had a target recorded; <see langword="false"/> otherwise.</returns>
  327. public bool TryGetHop(DependencyObject hopSource, out DependencyObject hopTarget) {
  328. return path.TryGetValue(hopSource, out hopTarget);
  329. }
  330. }
  331. }
  332. }