From 793e71349a346ce3baeffa85169324c92ee2109c Mon Sep 17 00:00:00 2001 From: Chris Pulman Date: Tue, 29 Jun 2021 22:56:16 +0100 Subject: [PATCH 1/2] Refactored RoutedViewHost and ViewModelViewHost to allow DefaultContent to be shown fixes #2815 --- .../Routing/RoutedViewHostTests.cs | 49 ++++++++++++ .../Routing/ViewModelViewHostTests.cs | 55 ++++++++++++++ .../windows-common/RoutedViewHost.cs | 74 +++++++++++-------- .../windows-common/ViewModelViewHost.cs | 42 ++++++----- 4 files changed, 172 insertions(+), 48 deletions(-) create mode 100644 src/ReactiveUI.Tests/Routing/RoutedViewHostTests.cs create mode 100644 src/ReactiveUI.Tests/Routing/ViewModelViewHostTests.cs diff --git a/src/ReactiveUI.Tests/Routing/RoutedViewHostTests.cs b/src/ReactiveUI.Tests/Routing/RoutedViewHostTests.cs new file mode 100644 index 0000000000..41d37e246d --- /dev/null +++ b/src/ReactiveUI.Tests/Routing/RoutedViewHostTests.cs @@ -0,0 +1,49 @@ +// Copyright (c) 2021 .NET Foundation and Contributors. All rights reserved. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System; +using System.Reactive.Concurrency; +using System.Windows; +using DynamicData; +using ReactiveUI.Tests.Wpf; +using Xunit; + +namespace ReactiveUI.Tests +{ + public class RoutedViewHostTests + { + [StaFact] + public void RoutedViewHostDefaultContentNotNull() + { + var uc = new RoutedViewHost + { + DefaultContent = new System.Windows.Controls.Label() + }; + var window = new WpfTestWindow(); + window.RootGrid.Children.Add(uc); + + var activation = new ActivationForViewFetcher(); + + activation.GetActivationForView(window).ToObservableChangeSet(scheduler: ImmediateScheduler.Instance).Bind(out var windowActivated).Subscribe(); + + activation.GetActivationForView(uc).ToObservableChangeSet(scheduler: ImmediateScheduler.Instance).Bind(out var controlActivated).Subscribe(); + + var loaded = new RoutedEventArgs + { + RoutedEvent = FrameworkElement.LoadedEvent + }; + + window.RaiseEvent(loaded); + uc.RaiseEvent(loaded); + + new[] { true }.AssertAreEqual(windowActivated); + new[] { true }.AssertAreEqual(controlActivated); + + Assert.NotNull(uc.Content); + + window.Dispatcher.InvokeShutdown(); + } + } +} diff --git a/src/ReactiveUI.Tests/Routing/ViewModelViewHostTests.cs b/src/ReactiveUI.Tests/Routing/ViewModelViewHostTests.cs new file mode 100644 index 0000000000..db1db2997b --- /dev/null +++ b/src/ReactiveUI.Tests/Routing/ViewModelViewHostTests.cs @@ -0,0 +1,55 @@ +// Copyright (c) 2021 .NET Foundation and Contributors. All rights reserved. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using System; +using System.Reactive.Concurrency; +using System.Windows; +using DynamicData; +using ReactiveUI.Tests.Wpf; +using Xunit; + +namespace ReactiveUI.Tests +{ + public class ViewModelViewHostTests + { + [StaFact] + public void ViewModelViewHostDefaultContentNotNull() + { + var uc = new ViewModelViewHost + { + DefaultContent = new System.Windows.Controls.Label() + }; + var window = new WpfTestWindow(); + window.RootGrid.Children.Add(uc); + + var activation = new ActivationForViewFetcher(); + + activation.GetActivationForView(window) + .ToObservableChangeSet(scheduler: ImmediateScheduler.Instance) + .Bind(out var windowActivated) + .Subscribe(); + + activation.GetActivationForView(uc) + .ToObservableChangeSet(scheduler: ImmediateScheduler.Instance) + .Bind(out var controlActivated) + .Subscribe(); + + var loaded = new RoutedEventArgs + { + RoutedEvent = FrameworkElement.LoadedEvent + }; + + window.RaiseEvent(loaded); + uc.RaiseEvent(loaded); + + new[] { true }.AssertAreEqual(windowActivated); + new[] { true }.AssertAreEqual(controlActivated); + + Assert.NotNull(uc.Content); + + window.Dispatcher.InvokeShutdown(); + } + } +} diff --git a/src/ReactiveUI/Platforms/windows-common/RoutedViewHost.cs b/src/ReactiveUI/Platforms/windows-common/RoutedViewHost.cs index 31aa5195e7..8f7720960c 100644 --- a/src/ReactiveUI/Platforms/windows-common/RoutedViewHost.cs +++ b/src/ReactiveUI/Platforms/windows-common/RoutedViewHost.cs @@ -16,12 +16,15 @@ using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; #else + using System.Windows.Controls; + #endif #if HAS_UNO namespace ReactiveUI.Uno #else + namespace ReactiveUI #endif { @@ -55,6 +58,8 @@ class RoutedViewHost : TransitioningContentControl, IActivatableView, IEnableLog public static readonly DependencyProperty ViewContractObservableProperty = DependencyProperty.Register("ViewContractObservable", typeof(IObservable), typeof(RoutedViewHost), new PropertyMetadata(Observable.Default)); + private string? _viewContract; + /// /// Initializes a new instance of the class. /// @@ -66,14 +71,8 @@ public RoutedViewHost() HorizontalContentAlignment = HorizontalAlignment.Stretch; VerticalContentAlignment = VerticalAlignment.Stretch; - if (ModeDetector.InUnitTestRunner()) - { - ViewContractObservable = Observable.Never; - return; - } - var platform = Locator.Current.GetService(); - Func platformGetter = () => default!; + Func platformGetter = () => default; if (platform == null) { @@ -86,7 +85,9 @@ public RoutedViewHost() platformGetter = () => platform.GetOrientation(); } - ViewContractObservable = Observable.FromEvent( + ViewContractObservable = ModeDetector.InUnitTestRunner() + ? Observable.Never + : Observable.FromEvent( eventHandler => { void Handler(object sender, SizeChangedEventArgs e) => eventHandler(platformGetter()); @@ -98,35 +99,19 @@ public RoutedViewHost() .StartWith(platformGetter()) .Select(x => x); - var vmAndContract = this.WhenAnyObservable(x => x.Router.CurrentViewModel!).CombineLatest( + IRoutableViewModel? currentViewModel = null; + var vmAndContract = this.WhenAnyObservable(x => x.Router.CurrentViewModel).Do(x => currentViewModel = x).CombineLatest( this.WhenAnyObservable(x => x.ViewContractObservable), - (viewModel, contract) => (viewModel, contract)); + (viewModel, contract) => (viewModel, contract)) + .StartWith((currentViewModel, ViewContract)); this.WhenActivated(d => { // NB: The DistinctUntilChanged is useful because most views in // WinRT will end up getting here twice - once for configuring // the RoutedViewHost's ViewModel, and once on load via SizeChanged - d(vmAndContract.DistinctUntilChanged().Subscribe( - x => - { - if (x.viewModel == null) - { - Content = DefaultContent; - return; - } - - var viewLocator = ViewLocator ?? ReactiveUI.ViewLocator.Current; - var view = viewLocator.ResolveView(x.viewModel, x.contract) ?? viewLocator.ResolveView(x.viewModel); - - if (view == null) - { - throw new Exception($"Couldn't find view for '{x.viewModel}'."); - } - - view.ViewModel = x.viewModel; - Content = view; - }, + d(vmAndContract.DistinctUntilChanged<(IRoutableViewModel? viewModel, string? contract)>().Subscribe( + ResolveViewForViewModel, ex => RxApp.DefaultExceptionHandler.OnNext(ex))); }); } @@ -162,6 +147,15 @@ public IObservable ViewContractObservable set => SetValue(ViewContractObservableProperty, value); } + /// + /// Gets or sets the view contract. + /// + public string? ViewContract + { + get => _viewContract; + set => ViewContractObservable = Observable.Return(value); + } + /// /// Gets or sets the view locator. /// @@ -169,5 +163,25 @@ public IObservable ViewContractObservable /// The view locator. /// public IViewLocator? ViewLocator { get; set; } + + private void ResolveViewForViewModel((IRoutableViewModel? viewModel, string? contract) x) + { + if (x.viewModel == null) + { + Content = DefaultContent; + return; + } + + var viewLocator = ViewLocator ?? ReactiveUI.ViewLocator.Current; + var view = viewLocator.ResolveView(x.viewModel, x.contract) ?? viewLocator.ResolveView(x.viewModel); + + if (view == null) + { + throw new Exception($"Couldn't find view for '{x.viewModel}'."); + } + + view.ViewModel = x.viewModel; + Content = view; + } } } diff --git a/src/ReactiveUI/Platforms/windows-common/ViewModelViewHost.cs b/src/ReactiveUI/Platforms/windows-common/ViewModelViewHost.cs index 210c9daafe..82bd945484 100644 --- a/src/ReactiveUI/Platforms/windows-common/ViewModelViewHost.cs +++ b/src/ReactiveUI/Platforms/windows-common/ViewModelViewHost.cs @@ -16,12 +16,15 @@ using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; #else + using System.Windows.Controls; + #endif #if HAS_UNO namespace ReactiveUI.Uno #else + namespace ReactiveUI #endif { @@ -70,13 +73,13 @@ public ViewModelViewHost() DefaultStyleKey = typeof(ViewModelViewHost); #endif - if (ModeDetector.InUnitTestRunner()) - { - ViewContractObservable = Observable.Never; + ////if (ModeDetector.InUnitTestRunner()) + ////{ + //// ViewContractObservable = Observable.Never; - // NB: InUnitTestRunner also returns true in Design Mode - return; - } + //// // NB: InUnitTestRunner also returns true in Design Mode + //// return; + ////} var platform = Locator.Current.GetService(); Func platformGetter = () => default; @@ -92,26 +95,29 @@ public ViewModelViewHost() platformGetter = () => platform.GetOrientation(); } + ViewContractObservable = ModeDetector.InUnitTestRunner() + ? Observable.Never + : Observable.FromEvent( + eventHandler => + { + void Handler(object? sender, SizeChangedEventArgs e) => eventHandler(platformGetter()!); + return Handler; + }, + x => SizeChanged += x, + x => SizeChanged -= x) + .StartWith(platformGetter()) + .DistinctUntilChanged(); + var contractChanged = _updateViewContract.Select(_ => ViewContractObservable).Switch(); var viewModelChanged = _updateViewModel.Select(_ => ViewModel); - var vmAndContract = contractChanged.CombineLatest(viewModelChanged, (contract, vm) => new { ViewModel = vm, Contract = contract }); + var vmAndContract = contractChanged.CombineLatest(viewModelChanged, (contract, vm) => (ViewModel: vm, Contract: contract)).StartWith((default, null)); - vmAndContract.Subscribe(x => ResolveViewForViewModel(x.ViewModel, x.Contract)); contractChanged .ObserveOn(RxApp.MainThreadScheduler) .Subscribe(x => _viewContract = x ?? string.Empty); - ViewContractObservable = Observable.FromEvent( - eventHandler => - { - void Handler(object? sender, SizeChangedEventArgs e) => eventHandler(platformGetter()!); - return Handler; - }, - x => SizeChanged += x, - x => SizeChanged -= x) - .StartWith(platformGetter()) - .DistinctUntilChanged(); + this.WhenActivated(d => d(vmAndContract.DistinctUntilChanged().Subscribe(x => ResolveViewForViewModel(x.ViewModel, x.Contract)))); } /// From d67cd712fec9d21a56b8454314132c2c964af8b1 Mon Sep 17 00:00:00 2001 From: Chris Pulman Date: Wed, 30 Jun 2021 03:29:54 +0100 Subject: [PATCH 2/2] Updated to include a ViewModel / Navigation test Refactored code to standardise, assigned _viewContract --- .../Routing/Mocks/TestView.cs | 26 ++++++ .../Routing/RoutedViewHostTests.cs | 85 +++++++++++++++++++ .../Routing/ViewModelViewHostTests.cs | 38 +++++++++ .../windows-common/RoutedViewHost.cs | 12 ++- .../windows-common/ViewModelViewHost.cs | 73 +++------------- 5 files changed, 167 insertions(+), 67 deletions(-) create mode 100644 src/ReactiveUI.Tests/Routing/Mocks/TestView.cs diff --git a/src/ReactiveUI.Tests/Routing/Mocks/TestView.cs b/src/ReactiveUI.Tests/Routing/Mocks/TestView.cs new file mode 100644 index 0000000000..4def1f624d --- /dev/null +++ b/src/ReactiveUI.Tests/Routing/Mocks/TestView.cs @@ -0,0 +1,26 @@ +// Copyright (c) 2021 .NET Foundation and Contributors. All rights reserved. +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for full license information. + +using Splat; + +namespace ReactiveUI.Tests +{ + public class TestView : ReactiveUserControl, IScreen + { +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. + + public TestView() +#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. + { + } + + public TestView(IScreen? screen = null) + { + Router = screen?.Router ?? Locator.Current.GetService()!; + } + + public RoutingState Router { get; } + } +} diff --git a/src/ReactiveUI.Tests/Routing/RoutedViewHostTests.cs b/src/ReactiveUI.Tests/Routing/RoutedViewHostTests.cs index 41d37e246d..8450d1a794 100644 --- a/src/ReactiveUI.Tests/Routing/RoutedViewHostTests.cs +++ b/src/ReactiveUI.Tests/Routing/RoutedViewHostTests.cs @@ -8,6 +8,7 @@ using System.Windows; using DynamicData; using ReactiveUI.Tests.Wpf; +using Splat; using Xunit; namespace ReactiveUI.Tests @@ -45,5 +46,89 @@ public void RoutedViewHostDefaultContentNotNull() window.Dispatcher.InvokeShutdown(); } + + [StaFact] + public void RoutedViewHostDefaultContentNotNullWithViewModelAndActivated() + { + Locator.CurrentMutable.Register(() => new(ImmediateScheduler.Instance)); + Locator.CurrentMutable.Register(() => new()); + Locator.CurrentMutable.Register>(() => new TestView()); + + var uc = new RoutedViewHost + { + DefaultContent = new System.Windows.Controls.Label(), + Router = Locator.Current.GetService()! + }; + var window = new WpfTestWindow(); + window.RootGrid.Children.Add(uc); + + var activation = new ActivationForViewFetcher(); + + activation.GetActivationForView(window).ToObservableChangeSet(scheduler: ImmediateScheduler.Instance).Bind(out var windowActivated).Subscribe(); + + activation.GetActivationForView(uc).ToObservableChangeSet(scheduler: ImmediateScheduler.Instance).Bind(out var controlActivated).Subscribe(); + + var loaded = new RoutedEventArgs + { + RoutedEvent = FrameworkElement.LoadedEvent + }; + + window.RaiseEvent(loaded); + uc.RaiseEvent(loaded); + + new[] { true }.AssertAreEqual(windowActivated); + new[] { true }.AssertAreEqual(controlActivated); + + // Default Content + Assert.IsType(uc.Content); + + // Test Navigation after activated + uc.Router.Navigate.Execute(Locator.Current.GetService()!); + Assert.IsType(uc.Content); + + window.Dispatcher.InvokeShutdown(); + } + + [StaFact] + public void RoutedViewHostDefaultContentNotNullWithViewModelAndNotActivated() + { + Locator.CurrentMutable.Register(() => new(ImmediateScheduler.Instance)); + Locator.CurrentMutable.Register(() => new()); + Locator.CurrentMutable.Register>(() => new TestView()); + + var uc = new RoutedViewHost + { + DefaultContent = new System.Windows.Controls.Label(), + Router = Locator.Current.GetService()! + }; + var window = new WpfTestWindow(); + window.RootGrid.Children.Add(uc); + + var activation = new ActivationForViewFetcher(); + + activation.GetActivationForView(window).ToObservableChangeSet(scheduler: ImmediateScheduler.Instance).Bind(out var windowActivated).Subscribe(); + + activation.GetActivationForView(uc).ToObservableChangeSet(scheduler: ImmediateScheduler.Instance).Bind(out var controlActivated).Subscribe(); + + var loaded = new RoutedEventArgs + { + RoutedEvent = FrameworkElement.LoadedEvent + }; + + // Test navigation before Activation. + uc.Router.Navigate.Execute(Locator.Current.GetService()!); + + // Activate + window.RaiseEvent(loaded); + uc.RaiseEvent(loaded); + + new[] { true }.AssertAreEqual(windowActivated); + new[] { true }.AssertAreEqual(controlActivated); + + // Test Navigation before activated + Assert.IsType(uc.Content); + + window.Dispatcher.InvokeShutdown(); + } } } diff --git a/src/ReactiveUI.Tests/Routing/ViewModelViewHostTests.cs b/src/ReactiveUI.Tests/Routing/ViewModelViewHostTests.cs index db1db2997b..62e5db4c38 100644 --- a/src/ReactiveUI.Tests/Routing/ViewModelViewHostTests.cs +++ b/src/ReactiveUI.Tests/Routing/ViewModelViewHostTests.cs @@ -8,6 +8,7 @@ using System.Windows; using DynamicData; using ReactiveUI.Tests.Wpf; +using Splat; using Xunit; namespace ReactiveUI.Tests @@ -51,5 +52,42 @@ public void ViewModelViewHostDefaultContentNotNull() window.Dispatcher.InvokeShutdown(); } + + [StaFact] + public void ViewModelViewHostContentNotNullWithViewModelAndActivated() + { + Locator.CurrentMutable.Register(() => new()); + Locator.CurrentMutable.Register>(() => new TestView()); + + var uc = new ViewModelViewHost + { + DefaultContent = new System.Windows.Controls.Label(), + ViewModel = Locator.Current.GetService() + }; + var window = new WpfTestWindow(); + window.RootGrid.Children.Add(uc); + + var activation = new ActivationForViewFetcher(); + + activation.GetActivationForView(window).ToObservableChangeSet(scheduler: ImmediateScheduler.Instance).Bind(out var windowActivated).Subscribe(); + + activation.GetActivationForView(uc).ToObservableChangeSet(scheduler: ImmediateScheduler.Instance).Bind(out var controlActivated).Subscribe(); + + var loaded = new RoutedEventArgs + { + RoutedEvent = FrameworkElement.LoadedEvent + }; + + window.RaiseEvent(loaded); + uc.RaiseEvent(loaded); + + new[] { true }.AssertAreEqual(windowActivated); + new[] { true }.AssertAreEqual(controlActivated); + + // Test IViewFor after activated + Assert.IsType(uc.Content); + + window.Dispatcher.InvokeShutdown(); + } } } diff --git a/src/ReactiveUI/Platforms/windows-common/RoutedViewHost.cs b/src/ReactiveUI/Platforms/windows-common/RoutedViewHost.cs index 8f7720960c..db427230cd 100644 --- a/src/ReactiveUI/Platforms/windows-common/RoutedViewHost.cs +++ b/src/ReactiveUI/Platforms/windows-common/RoutedViewHost.cs @@ -95,15 +95,13 @@ public RoutedViewHost() }, x => SizeChanged += x, x => SizeChanged -= x) - .DistinctUntilChanged() - .StartWith(platformGetter()) - .Select(x => x); + .StartWith(platformGetter()) + .DistinctUntilChanged(); IRoutableViewModel? currentViewModel = null; - var vmAndContract = this.WhenAnyObservable(x => x.Router.CurrentViewModel).Do(x => currentViewModel = x).CombineLatest( - this.WhenAnyObservable(x => x.ViewContractObservable), - (viewModel, contract) => (viewModel, contract)) - .StartWith((currentViewModel, ViewContract)); + var vmAndContract = this.WhenAnyObservable(x => x.Router.CurrentViewModel).Do(x => currentViewModel = x).StartWith(currentViewModel).CombineLatest( + this.WhenAnyObservable(x => x.ViewContractObservable).Do(x => _viewContract = x).StartWith(ViewContract), + (viewModel, contract) => (viewModel, contract)); this.WhenActivated(d => { diff --git a/src/ReactiveUI/Platforms/windows-common/ViewModelViewHost.cs b/src/ReactiveUI/Platforms/windows-common/ViewModelViewHost.cs index 82bd945484..94a6729792 100644 --- a/src/ReactiveUI/Platforms/windows-common/ViewModelViewHost.cs +++ b/src/ReactiveUI/Platforms/windows-common/ViewModelViewHost.cs @@ -8,13 +8,14 @@ using System.Reactive; using System.Reactive.Disposables; using System.Reactive.Linq; -using System.Reactive.Subjects; using System.Windows; using Splat; #if NETFX_CORE || HAS_UNO + using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; + #else using System.Windows.Controls; @@ -22,6 +23,7 @@ #endif #if HAS_UNO + namespace ReactiveUI.Uno #else @@ -33,13 +35,11 @@ namespace ReactiveUI /// the ViewModel property and display it. This control is very useful /// inside a DataTemplate to display the View associated with a ViewModel. /// - [SuppressMessage("Design", "CA1010:Collections should implement generic interface", Justification = "Deliberate usage")] - [SuppressMessage("Design", "CA1063: Remove IDisposable from the list of interfaces implemented", Justification = "Deliberate usage")] public #if HAS_UNO partial #endif - class ViewModelViewHost : TransitioningContentControl, IViewFor, IEnableLogger, IDisposable + class ViewModelViewHost : TransitioningContentControl, IViewFor, IEnableLogger { /// /// The default content dependency property. @@ -51,18 +51,15 @@ class ViewModelViewHost : TransitioningContentControl, IViewFor, IEnableLogger, /// The view model dependency property. /// public static readonly DependencyProperty ViewModelProperty = - DependencyProperty.Register(nameof(ViewModel), typeof(object), typeof(ViewModelViewHost), new PropertyMetadata(null, ViewModelChanged)); + DependencyProperty.Register(nameof(ViewModel), typeof(object), typeof(ViewModelViewHost), new PropertyMetadata(null)); /// /// The view contract observable dependency property. /// public static readonly DependencyProperty ViewContractObservableProperty = - DependencyProperty.Register(nameof(ViewContractObservable), typeof(IObservable), typeof(ViewModelViewHost), new PropertyMetadata(Observable.Default, ViewContractChanged)); + DependencyProperty.Register(nameof(ViewContractObservable), typeof(IObservable), typeof(ViewModelViewHost), new PropertyMetadata(Observable.Default)); - private readonly Subject _updateViewModel = new(); - private readonly Subject _updateViewContract = new(); private string? _viewContract; - private bool _isDisposed; /// /// Initializes a new instance of the class. @@ -73,14 +70,6 @@ public ViewModelViewHost() DefaultStyleKey = typeof(ViewModelViewHost); #endif - ////if (ModeDetector.InUnitTestRunner()) - ////{ - //// ViewContractObservable = Observable.Never; - - //// // NB: InUnitTestRunner also returns true in Design Mode - //// return; - ////} - var platform = Locator.Current.GetService(); Func platformGetter = () => default; @@ -96,8 +85,8 @@ public ViewModelViewHost() } ViewContractObservable = ModeDetector.InUnitTestRunner() - ? Observable.Never - : Observable.FromEvent( + ? Observable.Never + : Observable.FromEvent( eventHandler => { void Handler(object? sender, SizeChangedEventArgs e) => eventHandler(platformGetter()!); @@ -108,15 +97,16 @@ public ViewModelViewHost() .StartWith(platformGetter()) .DistinctUntilChanged(); - var contractChanged = _updateViewContract.Select(_ => ViewContractObservable).Switch(); - var viewModelChanged = _updateViewModel.Select(_ => ViewModel); - - var vmAndContract = contractChanged.CombineLatest(viewModelChanged, (contract, vm) => (ViewModel: vm, Contract: contract)).StartWith((default, null)); + var contractChanged = this.WhenAnyObservable(x => x.ViewContractObservable).Do(x => _viewContract = x).StartWith(ViewContract); + var viewModelChanged = this.WhenAnyValue(x => x.ViewModel).StartWith(ViewModel); contractChanged .ObserveOn(RxApp.MainThreadScheduler) .Subscribe(x => _viewContract = x ?? string.Empty); + var vmAndContract = contractChanged + .CombineLatest(viewModelChanged, (contract, vm) => (ViewModel: vm, Contract: contract)); + this.WhenActivated(d => d(vmAndContract.DistinctUntilChanged().Subscribe(x => ResolveViewForViewModel(x.ViewModel, x.Contract)))); } @@ -161,43 +151,6 @@ public string? ViewContract /// public IViewLocator? ViewLocator { get; set; } - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// Disposes of resources inside the class. - /// - /// If we are disposing managed resources. - protected virtual void Dispose(bool isDisposing) - { - if (_isDisposed) - { - return; - } - - if (isDisposing) - { - _updateViewModel.Dispose(); - _updateViewContract.Dispose(); - } - - _isDisposed = true; - } - - private static void ViewModelChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs) - { - ((ViewModelViewHost)dependencyObject)._updateViewModel.OnNext(Unit.Default); - } - - private static void ViewContractChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs) - { - ((ViewModelViewHost)dependencyObject)._updateViewContract.OnNext(Unit.Default); - } - private void ResolveViewForViewModel(object? viewModel, string? contract) { if (viewModel == null)