From c404dcf1c70357d07c5529212b4e8f492be6f491 Mon Sep 17 00:00:00 2001 From: artyom Date: Wed, 28 Oct 2020 22:36:56 +0300 Subject: [PATCH 1/3] Use GetObservable and AvaProperty.UnSet --- .../ReactiveUserControl.cs | 27 +++++-- src/Avalonia.ReactiveUI/ReactiveWindow.cs | 27 +++++-- .../ReactiveUserControlTest.cs | 71 +++++++++++++++++++ 3 files changed, 117 insertions(+), 8 deletions(-) diff --git a/src/Avalonia.ReactiveUI/ReactiveUserControl.cs b/src/Avalonia.ReactiveUI/ReactiveUserControl.cs index 31f4691c90..416f0a2165 100644 --- a/src/Avalonia.ReactiveUI/ReactiveUserControl.cs +++ b/src/Avalonia.ReactiveUI/ReactiveUserControl.cs @@ -29,10 +29,29 @@ namespace Avalonia.ReactiveUI // 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(value => + { + if (value is TViewModel viewModel) + { + ViewModel = viewModel; + } + else + { + ViewModel = null; + } + }); + + this.GetObservable(ViewModelProperty).Subscribe(value => + { + if (value == null) + { + DataContext = AvaloniaProperty.UnsetValue; + } + else + { + DataContext = value; + } + }); } /// diff --git a/src/Avalonia.ReactiveUI/ReactiveWindow.cs b/src/Avalonia.ReactiveUI/ReactiveWindow.cs index 1204266b63..fcef0a019f 100644 --- a/src/Avalonia.ReactiveUI/ReactiveWindow.cs +++ b/src/Avalonia.ReactiveUI/ReactiveWindow.cs @@ -29,10 +29,29 @@ namespace Avalonia.ReactiveUI // 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(value => + { + if (value is TViewModel viewModel) + { + ViewModel = viewModel; + } + else + { + ViewModel = null; + } + }); + + this.GetObservable(ViewModelProperty).Subscribe(value => + { + if (value == null) + { + DataContext = AvaloniaProperty.UnsetValue; + } + else + { + 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); + } } } From a6d83bb7ce306388d18bee75c338f2d7f535ea3b Mon Sep 17 00:00:00 2001 From: artyom Date: Wed, 28 Oct 2020 23:11:10 +0300 Subject: [PATCH 2/3] Correct change notification listeners --- .../ReactiveUserControl.cs | 50 ++++++++++--------- src/Avalonia.ReactiveUI/ReactiveWindow.cs | 50 ++++++++++--------- 2 files changed, 52 insertions(+), 48 deletions(-) diff --git a/src/Avalonia.ReactiveUI/ReactiveUserControl.cs b/src/Avalonia.ReactiveUI/ReactiveUserControl.cs index 416f0a2165..40d9666c3e 100644 --- a/src/Avalonia.ReactiveUI/ReactiveUserControl.cs +++ b/src/Avalonia.ReactiveUI/ReactiveUserControl.cs @@ -28,30 +28,8 @@ namespace Avalonia.ReactiveUI // This WhenActivated block calls ViewModel's WhenActivated // block if the ViewModel implements IActivatableViewModel. this.WhenActivated(disposables => { }); - - this.GetObservable(DataContextProperty).Subscribe(value => - { - if (value is TViewModel viewModel) - { - ViewModel = viewModel; - } - else - { - ViewModel = null; - } - }); - - this.GetObservable(ViewModelProperty).Subscribe(value => - { - if (value == null) - { - DataContext = AvaloniaProperty.UnsetValue; - } - else - { - DataContext = value; - } - }); + this.GetObservable(DataContextProperty).Subscribe(OnDataContextChanged); + this.GetObservable(ViewModelProperty).Subscribe(OnViewModelChanged); } /// @@ -68,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/src/Avalonia.ReactiveUI/ReactiveWindow.cs b/src/Avalonia.ReactiveUI/ReactiveWindow.cs index fcef0a019f..758a807bfc 100644 --- a/src/Avalonia.ReactiveUI/ReactiveWindow.cs +++ b/src/Avalonia.ReactiveUI/ReactiveWindow.cs @@ -28,30 +28,8 @@ namespace Avalonia.ReactiveUI // This WhenActivated block calls ViewModel's WhenActivated // block if the ViewModel implements IActivatableViewModel. this.WhenActivated(disposables => { }); - - this.GetObservable(DataContextProperty).Subscribe(value => - { - if (value is TViewModel viewModel) - { - ViewModel = viewModel; - } - else - { - ViewModel = null; - } - }); - - this.GetObservable(ViewModelProperty).Subscribe(value => - { - if (value == null) - { - DataContext = AvaloniaProperty.UnsetValue; - } - else - { - DataContext = value; - } - }); + this.GetObservable(DataContextProperty).Subscribe(OnDataContextChanged); + this.GetObservable(ViewModelProperty).Subscribe(OnViewModelChanged); } /// @@ -68,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; + } + } } } From 2afd29e9c879ce0693f96d85dcd373ceda21d043 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 31 Oct 2020 16:07:23 +0100 Subject: [PATCH 3/3] Use existing OnDataContextChanged method. --- src/Avalonia.ReactiveUI/ReactiveUserControl.cs | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/Avalonia.ReactiveUI/ReactiveUserControl.cs b/src/Avalonia.ReactiveUI/ReactiveUserControl.cs index 40d9666c3e..f4824ade0a 100644 --- a/src/Avalonia.ReactiveUI/ReactiveUserControl.cs +++ b/src/Avalonia.ReactiveUI/ReactiveUserControl.cs @@ -28,7 +28,6 @@ namespace Avalonia.ReactiveUI // This WhenActivated block calls ViewModel's WhenActivated // block if the ViewModel implements IActivatableViewModel. this.WhenActivated(disposables => { }); - this.GetObservable(DataContextProperty).Subscribe(OnDataContextChanged); this.GetObservable(ViewModelProperty).Subscribe(OnViewModelChanged); } @@ -47,16 +46,9 @@ namespace Avalonia.ReactiveUI set => ViewModel = (TViewModel)value; } - private void OnDataContextChanged(object value) + protected override void OnDataContextChanged(EventArgs e) { - if (value is TViewModel viewModel) - { - ViewModel = viewModel; - } - else - { - ViewModel = null; - } + ViewModel = DataContext as TViewModel; } private void OnViewModelChanged(object value)