View.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443
  1. #if XFORMS
  2. namespace Caliburn.Micro.Core.Xamarin.Forms
  3. #else
  4. namespace Caliburn.Micro
  5. #endif
  6. {
  7. using System;
  8. using System.Linq;
  9. #if WinRT
  10. using System.Reflection;
  11. using Windows.ApplicationModel;
  12. using Windows.UI.Xaml;
  13. using Windows.UI.Xaml.Controls;
  14. using Windows.UI.Xaml.Markup;
  15. using Windows.UI.Xaml.Media;
  16. #elif XFORMS
  17. using System.Reflection;
  18. using global::Xamarin.Forms;
  19. using UIElement = global::Xamarin.Forms.Element;
  20. using FrameworkElement = global::Xamarin.Forms.VisualElement;
  21. using DependencyProperty = global::Xamarin.Forms.BindableProperty;
  22. using DependencyObject = global::Xamarin.Forms.BindableObject;
  23. using ContentControl = global::Xamarin.Forms.ContentView;
  24. #else
  25. using System.ComponentModel;
  26. using System.Windows;
  27. using System.Windows.Controls;
  28. using System.Windows.Markup;
  29. using Caliburn.Micro.Core;
  30. #endif
  31. /// <summary>
  32. /// Hosts attached properties related to view models.
  33. /// </summary>
  34. public static class View {
  35. static readonly ILog Log = LogManager.GetLog(typeof(View));
  36. #if WinRT || XFORMS
  37. const string DefaultContentPropertyName = "Content";
  38. #else
  39. static readonly ContentPropertyAttribute DefaultContentProperty = new ContentPropertyAttribute("Content");
  40. #endif
  41. /// <summary>
  42. /// A dependency property which allows the framework to track whether a certain element has already been loaded in certain scenarios.
  43. /// </summary>
  44. public static readonly DependencyProperty IsLoadedProperty =
  45. DependencyPropertyHelper.RegisterAttached(
  46. "IsLoaded",
  47. typeof(bool),
  48. typeof(View),
  49. false
  50. );
  51. /// <summary>
  52. /// A dependency property which marks an element as a name scope root.
  53. /// </summary>
  54. public static readonly DependencyProperty IsScopeRootProperty =
  55. DependencyPropertyHelper.RegisterAttached(
  56. "IsScopeRoot",
  57. typeof(bool),
  58. typeof(View),
  59. false
  60. );
  61. /// <summary>
  62. /// A dependency property which allows the override of convention application behavior.
  63. /// </summary>
  64. public static readonly DependencyProperty ApplyConventionsProperty =
  65. DependencyPropertyHelper.RegisterAttached(
  66. "ApplyConventions",
  67. typeof(bool?),
  68. typeof(View)
  69. );
  70. /// <summary>
  71. /// A dependency property for assigning a context to a particular portion of the UI.
  72. /// </summary>
  73. public static readonly DependencyProperty ContextProperty =
  74. DependencyPropertyHelper.RegisterAttached(
  75. "Context",
  76. typeof(object),
  77. typeof(View),
  78. null,
  79. OnContextChanged
  80. );
  81. /// <summary>
  82. /// A dependency property for attaching a model to the UI.
  83. /// </summary>
  84. public static DependencyProperty ModelProperty =
  85. DependencyPropertyHelper.RegisterAttached(
  86. "Model",
  87. typeof(object),
  88. typeof(View),
  89. null,
  90. OnModelChanged
  91. );
  92. /// <summary>
  93. /// Used by the framework to indicate that this element was generated.
  94. /// </summary>
  95. public static readonly DependencyProperty IsGeneratedProperty =
  96. DependencyPropertyHelper.RegisterAttached(
  97. "IsGenerated",
  98. typeof(bool),
  99. typeof(View),
  100. false
  101. );
  102. /// <summary>
  103. /// Executes the handler immediately if the element is loaded, otherwise wires it to the Loaded event.
  104. /// </summary>
  105. /// <param name="element">The element.</param>
  106. /// <param name="handler">The handler.</param>
  107. /// <returns>true if the handler was executed immediately; false otherwise</returns>
  108. public static bool ExecuteOnLoad(FrameworkElement element, RoutedEventHandler handler) {
  109. #if XFORMS
  110. handler(element, new RoutedEventArgs());
  111. return true;
  112. #else
  113. #if SILVERLIGHT
  114. if ((bool)element.GetValue(IsLoadedProperty)) {
  115. #elif WinRT
  116. if (IsElementLoaded(element)) {
  117. #else
  118. if(element.IsLoaded) {
  119. #endif
  120. handler(element, new RoutedEventArgs());
  121. return true;
  122. }
  123. RoutedEventHandler loaded = null;
  124. loaded = (s, e) => {
  125. element.Loaded -= loaded;
  126. #if SILVERLIGHT
  127. element.SetValue(IsLoadedProperty, true);
  128. #endif
  129. handler(s, e);
  130. };
  131. element.Loaded += loaded;
  132. return false;
  133. #endif
  134. }
  135. /// <summary>
  136. /// Executes the handler when the element is unloaded.
  137. /// </summary>
  138. /// <param name="element">The element.</param>
  139. /// <param name="handler">The handler.</param>
  140. public static void ExecuteOnUnload(FrameworkElement element, RoutedEventHandler handler) {
  141. #if !XFORMS
  142. RoutedEventHandler unloaded = null;
  143. unloaded = (s, e) => {
  144. element.Unloaded -= unloaded;
  145. handler(s, e);
  146. };
  147. element.Unloaded += unloaded;
  148. #endif
  149. }
  150. #if WinRT
  151. /// <summary>
  152. /// Determines whether the specified <paramref name="element"/> is loaded.
  153. /// </summary>
  154. /// <param name="element">The element.</param>
  155. /// <returns>true if the element is loaded; otherwise, false.
  156. /// </returns>
  157. public static bool IsElementLoaded(FrameworkElement element) {
  158. try
  159. {
  160. if ((element.Parent ?? VisualTreeHelper.GetParent(element)) != null)
  161. {
  162. return true;
  163. }
  164. var rootVisual = Window.Current.Content;
  165. if (rootVisual != null)
  166. {
  167. return element == rootVisual;
  168. }
  169. return false;
  170. }
  171. catch
  172. {
  173. return false;
  174. }
  175. }
  176. #endif
  177. /// <summary>
  178. /// Executes the handler the next time the elements's LayoutUpdated event fires.
  179. /// </summary>
  180. /// <param name="element">The element.</param>
  181. /// <param name="handler">The handler.</param>
  182. #if WinRT
  183. public static void ExecuteOnLayoutUpdated(FrameworkElement element, EventHandler<object> handler) {
  184. EventHandler<object> onLayoutUpdate = null;
  185. #else
  186. public static void ExecuteOnLayoutUpdated(FrameworkElement element, EventHandler handler) {
  187. EventHandler onLayoutUpdate = null;
  188. #endif
  189. #if !XFORMS
  190. onLayoutUpdate = (s, e) => {
  191. element.LayoutUpdated -= onLayoutUpdate;
  192. handler(element, e);
  193. };
  194. element.LayoutUpdated += onLayoutUpdate;
  195. #endif
  196. }
  197. /// <summary>
  198. /// Used to retrieve the root, non-framework-created view.
  199. /// </summary>
  200. /// <param name="view">The view to search.</param>
  201. /// <returns>The root element that was not created by the framework.</returns>
  202. /// <remarks>In certain instances the services create UI elements.
  203. /// For example, if you ask the window manager to show a UserControl as a dialog, it creates a window to host the UserControl in.
  204. /// 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.
  205. /// Calling GetFirstNonGeneratedView allows the framework to discover what the original element was.
  206. /// </remarks>
  207. public static Func<object, object> GetFirstNonGeneratedView = view => {
  208. var dependencyObject = view as DependencyObject;
  209. if (dependencyObject == null) {
  210. return view;
  211. }
  212. if ((bool)dependencyObject.GetValue(IsGeneratedProperty)) {
  213. if (dependencyObject is ContentControl) {
  214. return ((ContentControl)dependencyObject).Content;
  215. }
  216. #if WinRT || XFORMS
  217. var type = dependencyObject.GetType();
  218. var contentPropertyName = GetContentPropertyName(type);
  219. return type.GetRuntimeProperty(contentPropertyName)
  220. .GetValue(dependencyObject, null);
  221. #else
  222. var type = dependencyObject.GetType();
  223. var contentProperty = type.GetAttributes<ContentPropertyAttribute>(true)
  224. .FirstOrDefault() ?? DefaultContentProperty;
  225. return type.GetProperty(contentProperty.Name)
  226. .GetValue(dependencyObject, null);
  227. #endif
  228. }
  229. return dependencyObject;
  230. };
  231. /// <summary>
  232. /// Gets the convention application behavior.
  233. /// </summary>
  234. /// <param name="d">The element the property is attached to.</param>
  235. /// <returns>Whether or not to apply conventions.</returns>
  236. public static bool? GetApplyConventions(DependencyObject d) {
  237. return (bool?)d.GetValue(ApplyConventionsProperty);
  238. }
  239. /// <summary>
  240. /// Sets the convention application behavior.
  241. /// </summary>
  242. /// <param name="d">The element to attach the property to.</param>
  243. /// <param name="value">Whether or not to apply conventions.</param>
  244. public static void SetApplyConventions(DependencyObject d, bool? value) {
  245. d.SetValue(ApplyConventionsProperty, value);
  246. }
  247. /// <summary>
  248. /// Sets the model.
  249. /// </summary>
  250. /// <param name="d">The element to attach the model to.</param>
  251. /// <param name="value">The model.</param>
  252. public static void SetModel(DependencyObject d, object value) {
  253. d.SetValue(ModelProperty, value);
  254. }
  255. /// <summary>
  256. /// Gets the model.
  257. /// </summary>
  258. /// <param name="d">The element the model is attached to.</param>
  259. /// <returns>The model.</returns>
  260. public static object GetModel(DependencyObject d) {
  261. return d.GetValue(ModelProperty);
  262. }
  263. /// <summary>
  264. /// Gets the context.
  265. /// </summary>
  266. /// <param name="d">The element the context is attached to.</param>
  267. /// <returns>The context.</returns>
  268. public static object GetContext(DependencyObject d) {
  269. return d.GetValue(ContextProperty);
  270. }
  271. /// <summary>
  272. /// Sets the context.
  273. /// </summary>
  274. /// <param name="d">The element to attach the context to.</param>
  275. /// <param name="value">The context.</param>
  276. public static void SetContext(DependencyObject d, object value) {
  277. d.SetValue(ContextProperty, value);
  278. }
  279. static void OnModelChanged(DependencyObject targetLocation, DependencyPropertyChangedEventArgs args) {
  280. if (args.OldValue == args.NewValue) {
  281. return;
  282. }
  283. if (args.NewValue != null) {
  284. var context = GetContext(targetLocation);
  285. var view = ViewLocator.LocateForModel(args.NewValue, targetLocation, context);
  286. // Trialing binding before setting content in Xamarin Forms
  287. #if XFORMS
  288. ViewModelBinder.Bind(args.NewValue, view, context);
  289. #endif
  290. if (!SetContentProperty(targetLocation, view)) {
  291. Log.Warn("SetContentProperty failed for ViewLocator.LocateForModel, falling back to LocateForModelType");
  292. view = ViewLocator.LocateForModelType(args.NewValue.GetType(), targetLocation, context);
  293. SetContentProperty(targetLocation, view);
  294. }
  295. #if !XFORMS
  296. ViewModelBinder.Bind(args.NewValue, view, context);
  297. #endif
  298. }
  299. else {
  300. SetContentProperty(targetLocation, args.NewValue);
  301. }
  302. }
  303. static void OnContextChanged(DependencyObject targetLocation, DependencyPropertyChangedEventArgs e) {
  304. if (e.OldValue == e.NewValue) {
  305. return;
  306. }
  307. var model = GetModel(targetLocation);
  308. if (model == null) {
  309. return;
  310. }
  311. var view = ViewLocator.LocateForModel(model, targetLocation, e.NewValue);
  312. if (!SetContentProperty(targetLocation, view)) {
  313. Log.Warn("SetContentProperty failed for ViewLocator.LocateForModel, falling back to LocateForModelType");
  314. view = ViewLocator.LocateForModelType(model.GetType(), targetLocation, e.NewValue);
  315. SetContentProperty(targetLocation, view);
  316. }
  317. ViewModelBinder.Bind(model, view, e.NewValue);
  318. }
  319. static bool SetContentProperty(object targetLocation, object view) {
  320. var fe = view as FrameworkElement;
  321. if (fe != null && fe.Parent != null) {
  322. SetContentPropertyCore(fe.Parent, null);
  323. }
  324. return SetContentPropertyCore(targetLocation, view);
  325. }
  326. #if WinRT || XFORMS
  327. static bool SetContentPropertyCore(object targetLocation, object view) {
  328. try {
  329. var type = targetLocation.GetType();
  330. var contentPropertyName = GetContentPropertyName(type);
  331. type.GetRuntimeProperty(contentPropertyName)
  332. .SetValue(targetLocation, view, null);
  333. return true;
  334. }
  335. catch (Exception e) {
  336. Log.Error(e);
  337. return false;
  338. }
  339. }
  340. private static string GetContentPropertyName(Type type) {
  341. var typeInfo = type.GetTypeInfo();
  342. var contentProperty = typeInfo.GetCustomAttribute<ContentPropertyAttribute>();
  343. return contentProperty?.Name ?? DefaultContentPropertyName;
  344. }
  345. #else
  346. static bool SetContentPropertyCore(object targetLocation, object view) {
  347. try {
  348. var type = targetLocation.GetType();
  349. var contentProperty = type.GetAttributes<ContentPropertyAttribute>(true)
  350. .FirstOrDefault() ?? DefaultContentProperty;
  351. type.GetProperty(contentProperty.Name ?? DefaultContentProperty.Name)
  352. .SetValue(targetLocation, view, null);
  353. return true;
  354. }
  355. catch(Exception e) {
  356. Log.Error(e);
  357. return false;
  358. }
  359. }
  360. #endif
  361. private static bool? inDesignMode;
  362. /// <summary>
  363. /// Gets a value that indicates whether the process is running in design mode.
  364. /// </summary>
  365. public static bool InDesignMode
  366. {
  367. get
  368. {
  369. if (inDesignMode == null)
  370. {
  371. #if XFORMS
  372. inDesignMode = false;
  373. #elif WinRT
  374. inDesignMode = DesignMode.DesignModeEnabled;
  375. #elif SILVERLIGHT
  376. inDesignMode = DesignerProperties.IsInDesignTool;
  377. #else
  378. var descriptor = DependencyPropertyDescriptor.FromProperty(DesignerProperties.IsInDesignModeProperty, typeof(FrameworkElement));
  379. inDesignMode = (bool)descriptor.Metadata.DefaultValue;
  380. #endif
  381. }
  382. return inDesignMode.GetValueOrDefault(false);
  383. }
  384. }
  385. }
  386. }