RoutedViewHost.cs 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. // Copyright (c) The Avalonia Project. All rights reserved.
  2. // Licensed under the MIT license. See licence.md file in the project root for full license information.
  3. using System;
  4. using System.Reactive.Disposables;
  5. using System.Reactive.Linq;
  6. using Avalonia.Animation;
  7. using Avalonia.Controls;
  8. using Avalonia.Styling;
  9. using Avalonia;
  10. using ReactiveUI;
  11. using Splat;
  12. namespace Avalonia.ReactiveUI
  13. {
  14. /// <summary>
  15. /// This control hosts the View associated with ReactiveUI RoutingState,
  16. /// and will display the View and wire up the ViewModel whenever a new
  17. /// ViewModel is navigated to. Nested routing is also supported.
  18. /// </summary>
  19. /// <remarks>
  20. /// <para>
  21. /// ReactiveUI routing consists of an IScreen that contains current
  22. /// RoutingState, several IRoutableViewModels, and a platform-specific
  23. /// XAML control called RoutedViewHost.
  24. /// </para>
  25. /// <para>
  26. /// RoutingState manages the ViewModel navigation stack and allows
  27. /// ViewModels to navigate to other ViewModels. IScreen is the root of
  28. /// a navigation stack; despite the name, its views don't have to occupy
  29. /// the whole screen. RoutedViewHost monitors an instance of RoutingState,
  30. /// responding to any changes in the navigation stack by creating and
  31. /// embedding the appropriate view.
  32. /// </para>
  33. /// <para>
  34. /// Place this control to a view containing your ViewModel that implements
  35. /// IScreen, and bind IScreen.Router property to RoutedViewHost.Router property.
  36. /// <code>
  37. /// <![CDATA[
  38. /// <rxui:RoutedViewHost
  39. /// HorizontalAlignment="Stretch"
  40. /// VerticalAlignment="Stretch"
  41. /// Router="{Binding Router}">
  42. /// <rxui:RoutedViewHost.DefaultContent>
  43. /// <TextBlock Text="Default Content"/>
  44. /// </rxui:RoutedViewHost.DefaultContent>
  45. /// </rxui:RoutedViewHost>
  46. /// ]]>
  47. /// </code>
  48. /// </para>
  49. /// <para>
  50. /// See <see href="https://reactiveui.net/docs/handbook/routing/">
  51. /// ReactiveUI routing documentation website</see> for more info.
  52. /// </para>
  53. /// </remarks>
  54. public class RoutedViewHost : TransitioningContentControl, IActivatableView, IEnableLogger
  55. {
  56. /// <summary>
  57. /// <see cref="AvaloniaProperty"/> for the <see cref="Router"/> property.
  58. /// </summary>
  59. public static readonly AvaloniaProperty<RoutingState> RouterProperty =
  60. AvaloniaProperty.Register<RoutedViewHost, RoutingState>(nameof(Router));
  61. /// <summary>
  62. /// Initializes a new instance of the <see cref="RoutedViewHost"/> class.
  63. /// </summary>
  64. public RoutedViewHost()
  65. {
  66. this.WhenActivated(disposables =>
  67. {
  68. this.WhenAnyObservable(x => x.Router.CurrentViewModel)
  69. .DistinctUntilChanged()
  70. .Subscribe(NavigateToViewModel)
  71. .DisposeWith(disposables);
  72. });
  73. }
  74. /// <summary>
  75. /// Gets or sets the <see cref="RoutingState"/> of the view model stack.
  76. /// </summary>
  77. public RoutingState Router
  78. {
  79. get => GetValue(RouterProperty);
  80. set => SetValue(RouterProperty, value);
  81. }
  82. /// <summary>
  83. /// Gets or sets the ReactiveUI view locator used by this router.
  84. /// </summary>
  85. public IViewLocator ViewLocator { get; set; }
  86. /// <summary>
  87. /// Invoked when ReactiveUI router navigates to a view model.
  88. /// </summary>
  89. /// <param name="viewModel">ViewModel to which the user navigates.</param>
  90. private void NavigateToViewModel(object viewModel)
  91. {
  92. if (viewModel == null)
  93. {
  94. this.Log().Info("ViewModel is null. Falling back to default content.");
  95. Content = DefaultContent;
  96. return;
  97. }
  98. var viewLocator = ViewLocator ?? global::ReactiveUI.ViewLocator.Current;
  99. var viewInstance = viewLocator.ResolveView(viewModel);
  100. if (viewInstance == null)
  101. {
  102. this.Log().Warn($"Couldn't find view for '{viewModel}'. Is it registered? Falling back to default content.");
  103. Content = DefaultContent;
  104. return;
  105. }
  106. this.Log().Info($"Ready to show {viewInstance} with autowired {viewModel}.");
  107. viewInstance.ViewModel = viewModel;
  108. if (viewInstance is IStyledElement styled)
  109. styled.DataContext = viewModel;
  110. Content = viewInstance;
  111. }
  112. }
  113. }