392 lines
16 KiB
C#
392 lines
16 KiB
C#
|
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
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Provides methods for searching a given scope for named elements.
|
|||
|
/// </summary>
|
|||
|
public static class BindingScope {
|
|||
|
static readonly List<ChildResolver> ChildResolvers = new List<ChildResolver>();
|
|||
|
static readonly Dictionary<Type, Object> NonResolvableChildTypes = new Dictionary<Type, Object>();
|
|||
|
|
|||
|
static BindingScope()
|
|||
|
{
|
|||
|
AddChildResolver<ContentControl>(e => new[] { e.Content as DependencyObject });
|
|||
|
AddChildResolver<ItemsControl>(e => e.Items.OfType<DependencyObject>().ToArray() );
|
|||
|
#if !SILVERLIGHT && !WinRT
|
|||
|
AddChildResolver<HeaderedContentControl>(e => new[] { e.Header as DependencyObject });
|
|||
|
AddChildResolver<HeaderedItemsControl>(e => new[] { e.Header as DependencyObject });
|
|||
|
#endif
|
|||
|
#if WinRT
|
|||
|
AddChildResolver<SemanticZoom>(e => new[] { e.ZoomedInView as DependencyObject, e.ZoomedOutView as DependencyObject });
|
|||
|
AddChildResolver<ListViewBase>(e => new[] { e.Header as DependencyObject });
|
|||
|
#endif
|
|||
|
#if WinRT81
|
|||
|
AddChildResolver<ListViewBase>(e => new[] { e.Footer as DependencyObject });
|
|||
|
AddChildResolver<Hub>(ResolveHub);
|
|||
|
AddChildResolver<HubSection>(e => new[] { e.Header as DependencyObject });
|
|||
|
AddChildResolver<CommandBar>(ResolveCommandBar);
|
|||
|
AddChildResolver<Button>(e => ResolveFlyoutBase(e.Flyout));
|
|||
|
AddChildResolver<FrameworkElement>(e => ResolveFlyoutBase(FlyoutBase.GetAttachedFlyout(e)));
|
|||
|
#endif
|
|||
|
#if WINDOWS_UWP
|
|||
|
AddChildResolver<SplitView>(e => new[] { e.Pane as DependencyObject, e.Content as DependencyObject });
|
|||
|
#endif
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Searches through the list of named elements looking for a case-insensitive match.
|
|||
|
/// </summary>
|
|||
|
/// <param name="elementsToSearch">The named elements to search through.</param>
|
|||
|
/// <param name="name">The name to search for.</param>
|
|||
|
/// <returns>The named element or null if not found.</returns>
|
|||
|
public static FrameworkElement FindName(this IEnumerable<FrameworkElement> 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
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Adds a child resolver.
|
|||
|
/// </summary>
|
|||
|
/// <param name="filter">The type filter.</param>
|
|||
|
/// <param name="resolver">The resolver.</param>
|
|||
|
public static ChildResolver AddChildResolver(Func<Type, bool> filter, Func<DependencyObject, IEnumerable<DependencyObject>> 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;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Adds a child resolver.
|
|||
|
/// </summary>
|
|||
|
/// <param name="filter">The type filter.</param>
|
|||
|
/// <param name="resolver">The resolver.</param>
|
|||
|
public static ChildResolver AddChildResolver<T>(Func<T, IEnumerable<DependencyObject>> resolver) where T : DependencyObject
|
|||
|
{
|
|||
|
if (resolver == null) {
|
|||
|
throw new ArgumentNullException("resolver");
|
|||
|
}
|
|||
|
|
|||
|
NonResolvableChildTypes.Clear();
|
|||
|
|
|||
|
var childResolver = new ChildResolver<T>(resolver);
|
|||
|
|
|||
|
ChildResolvers.Add(childResolver);
|
|||
|
|
|||
|
return childResolver;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Removes a child resolver.
|
|||
|
/// </summary>
|
|||
|
/// <param name="resolver">The resolver to remove.</param>
|
|||
|
/// <returns>true, when the resolver was (found and) removed.</returns>
|
|||
|
public static bool RemoveChildResolver(ChildResolver resolver) {
|
|||
|
if (resolver == null) {
|
|||
|
throw new ArgumentNullException("resolver");
|
|||
|
}
|
|||
|
|
|||
|
return ChildResolvers.Remove(resolver);
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Gets all the <see cref="FrameworkElement"/> instances with names in the scope.
|
|||
|
/// </summary>
|
|||
|
/// <returns>Named <see cref="FrameworkElement"/> instances in the provided scope.</returns>
|
|||
|
/// <remarks>Pass in a <see cref="DependencyObject"/> and receive a list of named <see cref="FrameworkElement"/> instances in the same scope.</remarks>
|
|||
|
public static Func<DependencyObject, IEnumerable<FrameworkElement>> GetNamedElements = elementInScope => {
|
|||
|
var routeHops = FindScopeNamingRoute(elementInScope);
|
|||
|
return FindNamedDescendants(routeHops);
|
|||
|
};
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Gets the parent of the given object in the Visual Tree.
|
|||
|
/// </summary>
|
|||
|
/// <returns>The parent of the given object in the Visual Tree</returns>
|
|||
|
public static Func<DependencyObject, DependencyObject> GetVisualParent = e => VisualTreeHelper.GetParent(e);
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Finds a set of named <see cref="FrameworkElement"/> instances in each hop in a <see cref="ScopeNamingRoute"/>.
|
|||
|
/// </summary>
|
|||
|
/// <remarks>
|
|||
|
/// Searches all the elements in the <see cref="ScopeNamingRoute"/> parameter as well as the visual children of
|
|||
|
/// each of these elements, the <see cref="ContentControl.Content"/>, the <c>HeaderedContentControl.Header</c>,
|
|||
|
/// the <see cref="ItemsControl.Items"/>, or the <c>HeaderedItemsControl.Header</c>, if any are found.
|
|||
|
/// </remarks>
|
|||
|
public static Func<ScopeNamingRoute, IEnumerable<FrameworkElement>> 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<FrameworkElement>();
|
|||
|
var queue = new Queue<DependencyObject>();
|
|||
|
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<DependencyObject>())
|
|||
|
.Where(c => c != null)
|
|||
|
.Apply(queue.Enqueue);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return descendants;
|
|||
|
};
|
|||
|
|
|||
|
#if WinRT81
|
|||
|
private static IEnumerable<DependencyObject> 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<DependencyObject> 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<DependencyObject> ResolveCommandBar(CommandBar commandBar) {
|
|||
|
foreach (var child in commandBar.PrimaryCommands.OfType<DependencyObject>()) {
|
|||
|
yield return child;
|
|||
|
}
|
|||
|
|
|||
|
foreach (var child in commandBar.SecondaryCommands.OfType<DependencyObject>())
|
|||
|
{
|
|||
|
yield return child;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private static IEnumerable<DependencyObject> ResolveHub(Hub hub) {
|
|||
|
yield return hub.Header as DependencyObject;
|
|||
|
|
|||
|
foreach (var section in hub.Sections)
|
|||
|
yield return section;
|
|||
|
}
|
|||
|
#endif
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Finds a path of dependency objects which traces through visual anscestry until a root which is <see langword="null"/>,
|
|||
|
/// a <see cref="UserControl"/>, a <c>Page</c> with a dependency object <c>Page.ContentProperty</c> value,
|
|||
|
/// a dependency object with <see cref="View.IsScopeRootProperty"/> set to <see langword="true"/>. <see cref="ContentPresenter"/>
|
|||
|
/// and <see cref="ItemsPresenter"/> are included in the resulting <see cref="ScopeNamingRoute"/> in order to track which item
|
|||
|
/// in an items control we are scoped to.
|
|||
|
/// </summary>
|
|||
|
public static Func<DependencyObject, ScopeNamingRoute> 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;
|
|||
|
};
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Maintains a connection in the visual tree of dependency objects in order to record a route through it.
|
|||
|
/// </summary>
|
|||
|
public class ScopeNamingRoute {
|
|||
|
readonly Dictionary<DependencyObject, DependencyObject> path = new Dictionary<DependencyObject, DependencyObject>();
|
|||
|
DependencyObject root;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Gets or sets the starting point of the route.
|
|||
|
/// </summary>
|
|||
|
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;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Adds a segment to the route.
|
|||
|
/// </summary>
|
|||
|
/// <param name="from">The source dependency object.</param>
|
|||
|
/// <param name="to">The target dependency object.</param>
|
|||
|
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;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Tries to get a target dependency object given a source.
|
|||
|
/// </summary>
|
|||
|
/// <param name="hopSource">The possible beginning of a route segment (hop).</param>
|
|||
|
/// <param name="hopTarget">The target of a route segment (hop).</param>
|
|||
|
/// <returns><see langword="true"/> if <paramref name="hopSource"/> had a target recorded; <see langword="false"/> otherwise.</returns>
|
|||
|
public bool TryGetHop(DependencyObject hopSource, out DependencyObject hopTarget) {
|
|||
|
return path.TryGetValue(hopSource, out hopTarget);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|