diff --git a/src/Avalonia.ReactiveUI/ReactiveUserControl.cs b/src/Avalonia.ReactiveUI/ReactiveUserControl.cs index 31f4691c90..f4824ade0a 100644 --- a/src/Avalonia.ReactiveUI/ReactiveUserControl.cs +++ b/src/Avalonia.ReactiveUI/ReactiveUserControl.cs @@ -28,11 +28,7 @@ namespace Avalonia.ReactiveUI // This WhenActivated block calls ViewModel's WhenActivated // block if the ViewModel implements IActivatableViewModel. this.WhenActivated(disposables => { }); - - this.ObservableForProperty(x => x.ViewModel, false, true) - .Subscribe(args => DataContext = args.Value); - this.ObservableForProperty(x => x.DataContext, false, true) - .Subscribe(args => ViewModel = args.Value as TViewModel); + this.GetObservable(ViewModelProperty).Subscribe(OnViewModelChanged); } /// @@ -49,5 +45,22 @@ namespace Avalonia.ReactiveUI get => ViewModel; set => ViewModel = (TViewModel)value; } + + protected override void OnDataContextChanged(EventArgs e) + { + ViewModel = DataContext as TViewModel; + } + + private void OnViewModelChanged(object value) + { + if (value == null) + { + ClearValue(DataContextProperty); + } + else if (DataContext != value) + { + DataContext = value; + } + } } } diff --git a/src/Avalonia.ReactiveUI/ReactiveWindow.cs b/src/Avalonia.ReactiveUI/ReactiveWindow.cs index 1204266b63..758a807bfc 100644 --- a/src/Avalonia.ReactiveUI/ReactiveWindow.cs +++ b/src/Avalonia.ReactiveUI/ReactiveWindow.cs @@ -28,11 +28,8 @@ namespace Avalonia.ReactiveUI // This WhenActivated block calls ViewModel's WhenActivated // block if the ViewModel implements IActivatableViewModel. this.WhenActivated(disposables => { }); - - this.ObservableForProperty(x => x.ViewModel, false, true) - .Subscribe(args => DataContext = args.Value); - this.ObservableForProperty(x => x.DataContext, false, true) - .Subscribe(args => ViewModel = args.Value as TViewModel); + this.GetObservable(DataContextProperty).Subscribe(OnDataContextChanged); + this.GetObservable(ViewModelProperty).Subscribe(OnViewModelChanged); } /// @@ -49,5 +46,29 @@ namespace Avalonia.ReactiveUI get => ViewModel; set => ViewModel = (TViewModel)value; } + + private void OnDataContextChanged(object value) + { + if (value is TViewModel viewModel) + { + ViewModel = viewModel; + } + else + { + ViewModel = null; + } + } + + private void OnViewModelChanged(object value) + { + if (value == null) + { + ClearValue(DataContextProperty); + } + else if (DataContext != value) + { + DataContext = value; + } + } } } diff --git a/tests/Avalonia.ReactiveUI.UnitTests/ReactiveUserControlTest.cs b/tests/Avalonia.ReactiveUI.UnitTests/ReactiveUserControlTest.cs index 5d257f75f2..4dd60e21c5 100644 --- a/tests/Avalonia.ReactiveUI.UnitTests/ReactiveUserControlTest.cs +++ b/tests/Avalonia.ReactiveUI.UnitTests/ReactiveUserControlTest.cs @@ -1,4 +1,5 @@ using System.Reactive.Disposables; +using Avalonia.Controls; using Avalonia.UnitTests; using ReactiveUI; using Splat; @@ -100,5 +101,75 @@ namespace Avalonia.ReactiveUI.UnitTests Assert.NotNull(view.DataContext); Assert.False(view.ViewModel.IsActive); } + + [Fact] + public void Should_Inherit_DataContext() + { + var vm1 = new ExampleViewModel(); + var vm2 = new ExampleViewModel(); + var view = new ExampleView(); + var root = new TestRoot(view); + + Assert.Null(view.DataContext); + Assert.Null(view.ViewModel); + + root.DataContext = vm1; + + Assert.Same(vm1, view.DataContext); + Assert.Same(vm1, view.ViewModel); + + root.DataContext = null; + + Assert.Null(view.DataContext); + Assert.Null(view.ViewModel); + + root.DataContext = vm2; + + Assert.Same(vm2, view.DataContext); + Assert.Same(vm2, view.ViewModel); + } + + [Fact] + public void Should_Not_Overlap_Change_Notifications() + { + var vm1 = new ExampleViewModel(); + var vm2 = new ExampleViewModel(); + + var view1 = new ExampleView(); + var view2 = new ExampleView(); + + Assert.Null(view1.DataContext); + Assert.Null(view2.DataContext); + Assert.Null(view1.ViewModel); + Assert.Null(view2.ViewModel); + + view1.DataContext = vm1; + + Assert.Same(vm1, view1.DataContext); + Assert.Same(vm1, view1.ViewModel); + Assert.Null(view2.DataContext); + Assert.Null(view2.ViewModel); + + view2.DataContext = vm2; + + Assert.Same(vm1, view1.DataContext); + Assert.Same(vm1, view1.ViewModel); + Assert.Same(vm2, view2.DataContext); + Assert.Same(vm2, view2.ViewModel); + + view1.ViewModel = null; + + Assert.Null(view1.DataContext); + Assert.Null(view1.ViewModel); + Assert.Same(vm2, view2.DataContext); + Assert.Same(vm2, view2.ViewModel); + + view2.ViewModel = null; + + Assert.Null(view1.DataContext); + Assert.Null(view2.DataContext); + Assert.Null(view1.ViewModel); + Assert.Null(view2.ViewModel); + } } }