443 lines
15 KiB
C#
443 lines
15 KiB
C#
|
#if XFORMS
|
|||
|
namespace Caliburn.Micro.Core.Xamarin.Forms
|
|||
|
#else
|
|||
|
namespace Caliburn.Micro
|
|||
|
#endif
|
|||
|
{
|
|||
|
using System;
|
|||
|
using System.Linq;
|
|||
|
#if WinRT
|
|||
|
using System.Reflection;
|
|||
|
using Windows.ApplicationModel;
|
|||
|
using Windows.UI.Xaml;
|
|||
|
using Windows.UI.Xaml.Controls;
|
|||
|
using Windows.UI.Xaml.Markup;
|
|||
|
using Windows.UI.Xaml.Media;
|
|||
|
#elif XFORMS
|
|||
|
using System.Reflection;
|
|||
|
using global::Xamarin.Forms;
|
|||
|
using UIElement = global::Xamarin.Forms.Element;
|
|||
|
using FrameworkElement = global::Xamarin.Forms.VisualElement;
|
|||
|
using DependencyProperty = global::Xamarin.Forms.BindableProperty;
|
|||
|
using DependencyObject = global::Xamarin.Forms.BindableObject;
|
|||
|
using ContentControl = global::Xamarin.Forms.ContentView;
|
|||
|
#else
|
|||
|
using System.ComponentModel;
|
|||
|
using System.Windows;
|
|||
|
using System.Windows.Controls;
|
|||
|
using System.Windows.Markup;
|
|||
|
using Caliburn.Micro.Core;
|
|||
|
#endif
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Hosts attached properties related to view models.
|
|||
|
/// </summary>
|
|||
|
public static class View {
|
|||
|
static readonly ILog Log = LogManager.GetLog(typeof(View));
|
|||
|
#if WinRT || XFORMS
|
|||
|
const string DefaultContentPropertyName = "Content";
|
|||
|
#else
|
|||
|
static readonly ContentPropertyAttribute DefaultContentProperty = new ContentPropertyAttribute("Content");
|
|||
|
#endif
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// A dependency property which allows the framework to track whether a certain element has already been loaded in certain scenarios.
|
|||
|
/// </summary>
|
|||
|
public static readonly DependencyProperty IsLoadedProperty =
|
|||
|
DependencyPropertyHelper.RegisterAttached(
|
|||
|
"IsLoaded",
|
|||
|
typeof(bool),
|
|||
|
typeof(View),
|
|||
|
false
|
|||
|
);
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// A dependency property which marks an element as a name scope root.
|
|||
|
/// </summary>
|
|||
|
public static readonly DependencyProperty IsScopeRootProperty =
|
|||
|
DependencyPropertyHelper.RegisterAttached(
|
|||
|
"IsScopeRoot",
|
|||
|
typeof(bool),
|
|||
|
typeof(View),
|
|||
|
false
|
|||
|
);
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// A dependency property which allows the override of convention application behavior.
|
|||
|
/// </summary>
|
|||
|
public static readonly DependencyProperty ApplyConventionsProperty =
|
|||
|
DependencyPropertyHelper.RegisterAttached(
|
|||
|
"ApplyConventions",
|
|||
|
typeof(bool?),
|
|||
|
typeof(View)
|
|||
|
);
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// A dependency property for assigning a context to a particular portion of the UI.
|
|||
|
/// </summary>
|
|||
|
public static readonly DependencyProperty ContextProperty =
|
|||
|
DependencyPropertyHelper.RegisterAttached(
|
|||
|
"Context",
|
|||
|
typeof(object),
|
|||
|
typeof(View),
|
|||
|
null,
|
|||
|
OnContextChanged
|
|||
|
);
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// A dependency property for attaching a model to the UI.
|
|||
|
/// </summary>
|
|||
|
public static DependencyProperty ModelProperty =
|
|||
|
DependencyPropertyHelper.RegisterAttached(
|
|||
|
"Model",
|
|||
|
typeof(object),
|
|||
|
typeof(View),
|
|||
|
null,
|
|||
|
OnModelChanged
|
|||
|
);
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Used by the framework to indicate that this element was generated.
|
|||
|
/// </summary>
|
|||
|
public static readonly DependencyProperty IsGeneratedProperty =
|
|||
|
DependencyPropertyHelper.RegisterAttached(
|
|||
|
"IsGenerated",
|
|||
|
typeof(bool),
|
|||
|
typeof(View),
|
|||
|
false
|
|||
|
);
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Executes the handler immediately if the element is loaded, otherwise wires it to the Loaded event.
|
|||
|
/// </summary>
|
|||
|
/// <param name="element">The element.</param>
|
|||
|
/// <param name="handler">The handler.</param>
|
|||
|
/// <returns>true if the handler was executed immediately; false otherwise</returns>
|
|||
|
public static bool ExecuteOnLoad(FrameworkElement element, RoutedEventHandler handler) {
|
|||
|
#if XFORMS
|
|||
|
handler(element, new RoutedEventArgs());
|
|||
|
return true;
|
|||
|
#else
|
|||
|
#if SILVERLIGHT
|
|||
|
if ((bool)element.GetValue(IsLoadedProperty)) {
|
|||
|
#elif WinRT
|
|||
|
if (IsElementLoaded(element)) {
|
|||
|
#else
|
|||
|
if(element.IsLoaded) {
|
|||
|
#endif
|
|||
|
handler(element, new RoutedEventArgs());
|
|||
|
return true;
|
|||
|
}
|
|||
|
|
|||
|
RoutedEventHandler loaded = null;
|
|||
|
loaded = (s, e) => {
|
|||
|
element.Loaded -= loaded;
|
|||
|
#if SILVERLIGHT
|
|||
|
element.SetValue(IsLoadedProperty, true);
|
|||
|
#endif
|
|||
|
handler(s, e);
|
|||
|
};
|
|||
|
element.Loaded += loaded;
|
|||
|
return false;
|
|||
|
#endif
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Executes the handler when the element is unloaded.
|
|||
|
/// </summary>
|
|||
|
/// <param name="element">The element.</param>
|
|||
|
/// <param name="handler">The handler.</param>
|
|||
|
public static void ExecuteOnUnload(FrameworkElement element, RoutedEventHandler handler) {
|
|||
|
#if !XFORMS
|
|||
|
RoutedEventHandler unloaded = null;
|
|||
|
unloaded = (s, e) => {
|
|||
|
element.Unloaded -= unloaded;
|
|||
|
handler(s, e);
|
|||
|
};
|
|||
|
element.Unloaded += unloaded;
|
|||
|
#endif
|
|||
|
}
|
|||
|
|
|||
|
#if WinRT
|
|||
|
/// <summary>
|
|||
|
/// Determines whether the specified <paramref name="element"/> is loaded.
|
|||
|
/// </summary>
|
|||
|
/// <param name="element">The element.</param>
|
|||
|
/// <returns>true if the element is loaded; otherwise, false.
|
|||
|
/// </returns>
|
|||
|
public static bool IsElementLoaded(FrameworkElement element) {
|
|||
|
try
|
|||
|
{
|
|||
|
if ((element.Parent ?? VisualTreeHelper.GetParent(element)) != null)
|
|||
|
{
|
|||
|
return true;
|
|||
|
}
|
|||
|
|
|||
|
var rootVisual = Window.Current.Content;
|
|||
|
|
|||
|
if (rootVisual != null)
|
|||
|
{
|
|||
|
return element == rootVisual;
|
|||
|
}
|
|||
|
|
|||
|
return false;
|
|||
|
|
|||
|
}
|
|||
|
catch
|
|||
|
{
|
|||
|
return false;
|
|||
|
}
|
|||
|
}
|
|||
|
#endif
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Executes the handler the next time the elements's LayoutUpdated event fires.
|
|||
|
/// </summary>
|
|||
|
/// <param name="element">The element.</param>
|
|||
|
/// <param name="handler">The handler.</param>
|
|||
|
#if WinRT
|
|||
|
public static void ExecuteOnLayoutUpdated(FrameworkElement element, EventHandler<object> handler) {
|
|||
|
EventHandler<object> onLayoutUpdate = null;
|
|||
|
#else
|
|||
|
public static void ExecuteOnLayoutUpdated(FrameworkElement element, EventHandler handler) {
|
|||
|
EventHandler onLayoutUpdate = null;
|
|||
|
#endif
|
|||
|
#if !XFORMS
|
|||
|
onLayoutUpdate = (s, e) => {
|
|||
|
element.LayoutUpdated -= onLayoutUpdate;
|
|||
|
handler(element, e);
|
|||
|
};
|
|||
|
element.LayoutUpdated += onLayoutUpdate;
|
|||
|
#endif
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Used to retrieve the root, non-framework-created view.
|
|||
|
/// </summary>
|
|||
|
/// <param name="view">The view to search.</param>
|
|||
|
/// <returns>The root element that was not created by the framework.</returns>
|
|||
|
/// <remarks>In certain instances the services create UI elements.
|
|||
|
/// For example, if you ask the window manager to show a UserControl as a dialog, it creates a window to host the UserControl in.
|
|||
|
/// The WindowManager marks that element as a framework-created element so that it can determine what it created vs. what was intended by the developer.
|
|||
|
/// Calling GetFirstNonGeneratedView allows the framework to discover what the original element was.
|
|||
|
/// </remarks>
|
|||
|
public static Func<object, object> GetFirstNonGeneratedView = view => {
|
|||
|
var dependencyObject = view as DependencyObject;
|
|||
|
if (dependencyObject == null) {
|
|||
|
return view;
|
|||
|
}
|
|||
|
|
|||
|
if ((bool)dependencyObject.GetValue(IsGeneratedProperty)) {
|
|||
|
if (dependencyObject is ContentControl) {
|
|||
|
return ((ContentControl)dependencyObject).Content;
|
|||
|
}
|
|||
|
#if WinRT || XFORMS
|
|||
|
var type = dependencyObject.GetType();
|
|||
|
var contentPropertyName = GetContentPropertyName(type);
|
|||
|
|
|||
|
return type.GetRuntimeProperty(contentPropertyName)
|
|||
|
.GetValue(dependencyObject, null);
|
|||
|
#else
|
|||
|
var type = dependencyObject.GetType();
|
|||
|
var contentProperty = type.GetAttributes<ContentPropertyAttribute>(true)
|
|||
|
.FirstOrDefault() ?? DefaultContentProperty;
|
|||
|
|
|||
|
return type.GetProperty(contentProperty.Name)
|
|||
|
.GetValue(dependencyObject, null);
|
|||
|
#endif
|
|||
|
}
|
|||
|
|
|||
|
return dependencyObject;
|
|||
|
};
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Gets the convention application behavior.
|
|||
|
/// </summary>
|
|||
|
/// <param name="d">The element the property is attached to.</param>
|
|||
|
/// <returns>Whether or not to apply conventions.</returns>
|
|||
|
public static bool? GetApplyConventions(DependencyObject d) {
|
|||
|
return (bool?)d.GetValue(ApplyConventionsProperty);
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Sets the convention application behavior.
|
|||
|
/// </summary>
|
|||
|
/// <param name="d">The element to attach the property to.</param>
|
|||
|
/// <param name="value">Whether or not to apply conventions.</param>
|
|||
|
public static void SetApplyConventions(DependencyObject d, bool? value) {
|
|||
|
d.SetValue(ApplyConventionsProperty, value);
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Sets the model.
|
|||
|
/// </summary>
|
|||
|
/// <param name="d">The element to attach the model to.</param>
|
|||
|
/// <param name="value">The model.</param>
|
|||
|
public static void SetModel(DependencyObject d, object value) {
|
|||
|
d.SetValue(ModelProperty, value);
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Gets the model.
|
|||
|
/// </summary>
|
|||
|
/// <param name="d">The element the model is attached to.</param>
|
|||
|
/// <returns>The model.</returns>
|
|||
|
public static object GetModel(DependencyObject d) {
|
|||
|
return d.GetValue(ModelProperty);
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Gets the context.
|
|||
|
/// </summary>
|
|||
|
/// <param name="d">The element the context is attached to.</param>
|
|||
|
/// <returns>The context.</returns>
|
|||
|
public static object GetContext(DependencyObject d) {
|
|||
|
return d.GetValue(ContextProperty);
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Sets the context.
|
|||
|
/// </summary>
|
|||
|
/// <param name="d">The element to attach the context to.</param>
|
|||
|
/// <param name="value">The context.</param>
|
|||
|
public static void SetContext(DependencyObject d, object value) {
|
|||
|
d.SetValue(ContextProperty, value);
|
|||
|
}
|
|||
|
|
|||
|
static void OnModelChanged(DependencyObject targetLocation, DependencyPropertyChangedEventArgs args) {
|
|||
|
if (args.OldValue == args.NewValue) {
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
if (args.NewValue != null) {
|
|||
|
var context = GetContext(targetLocation);
|
|||
|
|
|||
|
var view = ViewLocator.LocateForModel(args.NewValue, targetLocation, context);
|
|||
|
// Trialing binding before setting content in Xamarin Forms
|
|||
|
#if XFORMS
|
|||
|
ViewModelBinder.Bind(args.NewValue, view, context);
|
|||
|
#endif
|
|||
|
if (!SetContentProperty(targetLocation, view)) {
|
|||
|
|
|||
|
Log.Warn("SetContentProperty failed for ViewLocator.LocateForModel, falling back to LocateForModelType");
|
|||
|
|
|||
|
view = ViewLocator.LocateForModelType(args.NewValue.GetType(), targetLocation, context);
|
|||
|
|
|||
|
SetContentProperty(targetLocation, view);
|
|||
|
}
|
|||
|
#if !XFORMS
|
|||
|
ViewModelBinder.Bind(args.NewValue, view, context);
|
|||
|
#endif
|
|||
|
}
|
|||
|
else {
|
|||
|
SetContentProperty(targetLocation, args.NewValue);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
static void OnContextChanged(DependencyObject targetLocation, DependencyPropertyChangedEventArgs e) {
|
|||
|
if (e.OldValue == e.NewValue) {
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
var model = GetModel(targetLocation);
|
|||
|
if (model == null) {
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
var view = ViewLocator.LocateForModel(model, targetLocation, e.NewValue);
|
|||
|
|
|||
|
if (!SetContentProperty(targetLocation, view)) {
|
|||
|
|
|||
|
Log.Warn("SetContentProperty failed for ViewLocator.LocateForModel, falling back to LocateForModelType");
|
|||
|
|
|||
|
view = ViewLocator.LocateForModelType(model.GetType(), targetLocation, e.NewValue);
|
|||
|
|
|||
|
SetContentProperty(targetLocation, view);
|
|||
|
}
|
|||
|
|
|||
|
ViewModelBinder.Bind(model, view, e.NewValue);
|
|||
|
}
|
|||
|
|
|||
|
static bool SetContentProperty(object targetLocation, object view) {
|
|||
|
var fe = view as FrameworkElement;
|
|||
|
if (fe != null && fe.Parent != null) {
|
|||
|
SetContentPropertyCore(fe.Parent, null);
|
|||
|
}
|
|||
|
|
|||
|
return SetContentPropertyCore(targetLocation, view);
|
|||
|
}
|
|||
|
|
|||
|
#if WinRT || XFORMS
|
|||
|
static bool SetContentPropertyCore(object targetLocation, object view) {
|
|||
|
try {
|
|||
|
var type = targetLocation.GetType();
|
|||
|
var contentPropertyName = GetContentPropertyName(type);
|
|||
|
|
|||
|
type.GetRuntimeProperty(contentPropertyName)
|
|||
|
.SetValue(targetLocation, view, null);
|
|||
|
|
|||
|
return true;
|
|||
|
}
|
|||
|
catch (Exception e) {
|
|||
|
Log.Error(e);
|
|||
|
|
|||
|
return false;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private static string GetContentPropertyName(Type type) {
|
|||
|
var typeInfo = type.GetTypeInfo();
|
|||
|
var contentProperty = typeInfo.GetCustomAttribute<ContentPropertyAttribute>();
|
|||
|
|
|||
|
return contentProperty?.Name ?? DefaultContentPropertyName;
|
|||
|
}
|
|||
|
#else
|
|||
|
static bool SetContentPropertyCore(object targetLocation, object view) {
|
|||
|
try {
|
|||
|
var type = targetLocation.GetType();
|
|||
|
var contentProperty = type.GetAttributes<ContentPropertyAttribute>(true)
|
|||
|
.FirstOrDefault() ?? DefaultContentProperty;
|
|||
|
|
|||
|
type.GetProperty(contentProperty.Name ?? DefaultContentProperty.Name)
|
|||
|
.SetValue(targetLocation, view, null);
|
|||
|
|
|||
|
return true;
|
|||
|
}
|
|||
|
catch(Exception e) {
|
|||
|
Log.Error(e);
|
|||
|
|
|||
|
return false;
|
|||
|
}
|
|||
|
}
|
|||
|
#endif
|
|||
|
|
|||
|
private static bool? inDesignMode;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Gets a value that indicates whether the process is running in design mode.
|
|||
|
/// </summary>
|
|||
|
public static bool InDesignMode
|
|||
|
{
|
|||
|
get
|
|||
|
{
|
|||
|
if (inDesignMode == null)
|
|||
|
{
|
|||
|
#if XFORMS
|
|||
|
inDesignMode = false;
|
|||
|
#elif WinRT
|
|||
|
inDesignMode = DesignMode.DesignModeEnabled;
|
|||
|
#elif SILVERLIGHT
|
|||
|
inDesignMode = DesignerProperties.IsInDesignTool;
|
|||
|
#else
|
|||
|
var descriptor = DependencyPropertyDescriptor.FromProperty(DesignerProperties.IsInDesignModeProperty, typeof(FrameworkElement));
|
|||
|
inDesignMode = (bool)descriptor.Metadata.DefaultValue;
|
|||
|
#endif
|
|||
|
}
|
|||
|
|
|||
|
return inDesignMode.GetValueOrDefault(false);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|