namespace Caliburn.Micro {
using System;
using System.Collections.Generic;
using System.Linq;
#if WinRT
using System.ServiceModel;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Media;
#else
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Media3D;
using Caliburn.Micro.Core;
#endif
///
/// Provides methods for searching a given scope for named elements.
///
public static class BindingScope {
static readonly List ChildResolvers = new List();
static readonly Dictionary NonResolvableChildTypes = new Dictionary();
static BindingScope()
{
AddChildResolver(e => new[] { e.Content as DependencyObject });
AddChildResolver(e => e.Items.OfType().ToArray() );
#if !SILVERLIGHT && !WinRT
AddChildResolver(e => new[] { e.Header as DependencyObject });
AddChildResolver(e => new[] { e.Header as DependencyObject });
#endif
#if WinRT
AddChildResolver(e => new[] { e.ZoomedInView as DependencyObject, e.ZoomedOutView as DependencyObject });
AddChildResolver(e => new[] { e.Header as DependencyObject });
#endif
#if WinRT81
AddChildResolver(e => new[] { e.Footer as DependencyObject });
AddChildResolver(ResolveHub);
AddChildResolver(e => new[] { e.Header as DependencyObject });
AddChildResolver(ResolveCommandBar);
AddChildResolver(e => ResolveFlyoutBase(e.Flyout));
AddChildResolver(e => ResolveFlyoutBase(FlyoutBase.GetAttachedFlyout(e)));
#endif
#if WINDOWS_UWP
AddChildResolver(e => new[] { e.Pane as DependencyObject, e.Content as DependencyObject });
#endif
}
///
/// Searches through the list of named elements looking for a case-insensitive match.
///
/// The named elements to search through.
/// The name to search for.
/// The named element or null if not found.
public static FrameworkElement FindName(this IEnumerable elementsToSearch, string name) {
#if WinRT
return elementsToSearch.FirstOrDefault(x => x.Name.Equals(name, StringComparison.OrdinalIgnoreCase));
#else
return elementsToSearch.FirstOrDefault(x => x.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase));
#endif
}
///
/// Adds a child resolver.
///
/// The type filter.
/// The resolver.
public static ChildResolver AddChildResolver(Func filter, Func> resolver) {
if (filter == null) {
throw new ArgumentNullException("filter");
}
if (resolver == null) {
throw new ArgumentNullException("resolver");
}
NonResolvableChildTypes.Clear();
var childResolver = new ChildResolver(filter, resolver);
ChildResolvers.Add(childResolver);
return childResolver;
}
///
/// Adds a child resolver.
///
/// The type filter.
/// The resolver.
public static ChildResolver AddChildResolver(Func> resolver) where T : DependencyObject
{
if (resolver == null) {
throw new ArgumentNullException("resolver");
}
NonResolvableChildTypes.Clear();
var childResolver = new ChildResolver(resolver);
ChildResolvers.Add(childResolver);
return childResolver;
}
///
/// Removes a child resolver.
///
/// The resolver to remove.
/// true, when the resolver was (found and) removed.
public static bool RemoveChildResolver(ChildResolver resolver) {
if (resolver == null) {
throw new ArgumentNullException("resolver");
}
return ChildResolvers.Remove(resolver);
}
///
/// Gets all the instances with names in the scope.
///
/// Named instances in the provided scope.
/// Pass in a and receive a list of named instances in the same scope.
public static Func> GetNamedElements = elementInScope => {
var routeHops = FindScopeNamingRoute(elementInScope);
return FindNamedDescendants(routeHops);
};
///
/// Gets the parent of the given object in the Visual Tree.
///
/// The parent of the given object in the Visual Tree
public static Func GetVisualParent = e => VisualTreeHelper.GetParent(e);
///
/// Finds a set of named instances in each hop in a .
///
///
/// Searches all the elements in the parameter as well as the visual children of
/// each of these elements, the , the HeaderedContentControl.Header ,
/// the , or the HeaderedItemsControl.Header , if any are found.
///
public static Func> FindNamedDescendants = routeHops => {
if (routeHops == null) {
throw new ArgumentNullException("routeHops");
}
if (routeHops.Root == null) {
throw new ArgumentException(String.Format("Root is null on the given {0}", typeof (ScopeNamingRoute)));
}
var descendants = new List();
var queue = new Queue();
queue.Enqueue(routeHops.Root);
while (queue.Count > 0) {
var current = queue.Dequeue();
var currentElement = current as FrameworkElement;
if (currentElement != null && !string.IsNullOrEmpty(currentElement.Name))
descendants.Add(currentElement);
if (current is UserControl && !ReferenceEquals(current, routeHops.Root))
continue;
DependencyObject hopTarget;
if (routeHops.TryGetHop(current, out hopTarget)) {
queue.Enqueue(hopTarget);
continue;
}
#if NET
var childCount = (current is Visual || current is Visual3D)
? VisualTreeHelper.GetChildrenCount(current) : 0;
#else
var childCount = (current is UIElement)
? VisualTreeHelper.GetChildrenCount(current) : 0;
#endif
if (childCount > 0) {
for (var i = 0; i < childCount; i++) {
var childDo = VisualTreeHelper.GetChild(current, i);
queue.Enqueue(childDo);
}
#if WinRT
var page = current as Page;
if (page != null) {
if (page.BottomAppBar != null)
queue.Enqueue(page.BottomAppBar);
if (page.TopAppBar != null)
queue.Enqueue(page.TopAppBar);
}
#endif
}
else {
var currentType = current.GetType();
if (!NonResolvableChildTypes.ContainsKey(currentType)) {
var resolvers = ChildResolvers.Where(r => r.CanResolve(currentType)).ToArray();
if (!resolvers.Any()) {
NonResolvableChildTypes[currentType] = null;
}
else {
resolvers
.SelectMany(r => r.Resolve(current) ?? Enumerable.Empty())
.Where(c => c != null)
.Apply(queue.Enqueue);
}
}
}
}
return descendants;
};
#if WinRT81
private static IEnumerable ResolveFlyoutBase(FlyoutBase flyoutBase) {
if (flyoutBase == null)
yield break;
var flyout = flyoutBase as Flyout;
if (flyout != null && flyout.Content != null)
yield return flyout.Content;
var menuFlyout = flyoutBase as MenuFlyout;
if (menuFlyout != null && menuFlyout.Items != null) {
foreach (var item in menuFlyout.Items) {
foreach (var subItem in ResolveMenuFlyoutItems(item)) {
yield return subItem;
}
}
}
}
private static IEnumerable ResolveMenuFlyoutItems(MenuFlyoutItemBase item) {
yield return item;
#if WINDOWS_UWP
var subItem = item as MenuFlyoutSubItem;
if (subItem != null && subItem.Items != null) {
foreach (var subSubItem in subItem.Items) {
yield return subSubItem;
}
}
#endif
}
private static IEnumerable ResolveCommandBar(CommandBar commandBar) {
foreach (var child in commandBar.PrimaryCommands.OfType()) {
yield return child;
}
foreach (var child in commandBar.SecondaryCommands.OfType())
{
yield return child;
}
}
private static IEnumerable ResolveHub(Hub hub) {
yield return hub.Header as DependencyObject;
foreach (var section in hub.Sections)
yield return section;
}
#endif
///
/// Finds a path of dependency objects which traces through visual anscestry until a root which is ,
/// a , a Page with a dependency object Page.ContentProperty value,
/// a dependency object with set to .
/// and are included in the resulting in order to track which item
/// in an items control we are scoped to.
///
public static Func FindScopeNamingRoute = elementInScope => {
var root = elementInScope;
var previous = elementInScope;
DependencyObject contentPresenter = null;
var routeHops = new ScopeNamingRoute();
while (true) {
if (root == null) {
root = previous;
break;
}
if (root is UserControl)
break;
#if !SILVERLIGHT
if (root is Page) {
root = ((Page) root).Content as DependencyObject ?? root;
break;
}
#endif
if ((bool) root.GetValue(View.IsScopeRootProperty))
break;
#if WinRT
if (root is AppBar) {
var frame = Window.Current.Content as Frame;
var page = (frame != null) ? frame.Content as Page : null;
if (page != null && (root == page.TopAppBar || root == page.BottomAppBar)) {
root = page;
break;
}
}
#endif
if (root is ContentPresenter)
contentPresenter = root;
else if (root is ItemsPresenter && contentPresenter != null) {
routeHops.AddHop(root, contentPresenter);
contentPresenter = null;
}
previous = root;
root = GetVisualParent(previous);
}
routeHops.Root = root;
return routeHops;
};
///
/// Maintains a connection in the visual tree of dependency objects in order to record a route through it.
///
public class ScopeNamingRoute {
readonly Dictionary path = new Dictionary();
DependencyObject root;
///
/// Gets or sets the starting point of the route.
///
public DependencyObject Root {
get { return root; }
set {
if (path.ContainsValue(value)) {
throw new ArgumentException("Value is a target of some route hop; cannot be a root.");
}
root = value;
}
}
///
/// Adds a segment to the route.
///
/// The source dependency object.
/// The target dependency object.
public void AddHop(DependencyObject from, DependencyObject to) {
if (@from == null) {
throw new ArgumentNullException("from");
}
if (to == null) {
throw new ArgumentNullException("to");
}
if (path.Count > 0 &&
!path.ContainsKey(from) &&
!path.ContainsKey(to) &&
!path.ContainsValue(from) &&
!path.ContainsValue(from)) {
throw new ArgumentException("Hop pair not part of existing route.");
}
if (path.ContainsKey(to)) {
throw new ArgumentException("Cycle detected when adding hop.");
}
path[from] = to;
}
///
/// Tries to get a target dependency object given a source.
///
/// The possible beginning of a route segment (hop).
/// The target of a route segment (hop).
/// if had a target recorded; otherwise.
public bool TryGetHop(DependencyObject hopSource, out DependencyObject hopTarget) {
return path.TryGetValue(hopSource, out hopTarget);
}
}
}
}