diff --git a/src/Avalonia.ReactiveUI/ReactiveUserControl.cs b/src/Avalonia.ReactiveUI/ReactiveUserControl.cs
index fc48a8853d..31f4691c90 100644
--- a/src/Avalonia.ReactiveUI/ReactiveUserControl.cs
+++ b/src/Avalonia.ReactiveUI/ReactiveUserControl.cs
@@ -1,3 +1,6 @@
+using System;
+using System.Reactive.Disposables;
+using System.Reactive.Linq;
using Avalonia;
using Avalonia.VisualTree;
using Avalonia.Controls;
@@ -6,8 +9,10 @@ using ReactiveUI;
namespace Avalonia.ReactiveUI
{
///
- /// A ReactiveUI UserControl that implements
- /// and will activate your ViewModel automatically if it supports activation.
+ /// A ReactiveUI that implements the interface and
+ /// will activate your ViewModel automatically if the view model implements .
+ /// When the DataContext property changes, this class will update the ViewModel property with the new DataContext
+ /// value, and vice versa.
///
/// ViewModel type.
public class ReactiveUserControl : UserControl, IViewFor where TViewModel : class
@@ -20,7 +25,14 @@ namespace Avalonia.ReactiveUI
///
public ReactiveUserControl()
{
- DataContextChanged += (sender, args) => ViewModel = DataContext as TViewModel;
+ // 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);
}
///
diff --git a/src/Avalonia.ReactiveUI/ReactiveWindow.cs b/src/Avalonia.ReactiveUI/ReactiveWindow.cs
index 5695a727af..1204266b63 100644
--- a/src/Avalonia.ReactiveUI/ReactiveWindow.cs
+++ b/src/Avalonia.ReactiveUI/ReactiveWindow.cs
@@ -1,3 +1,6 @@
+using System;
+using System.Reactive.Disposables;
+using System.Reactive.Linq;
using Avalonia;
using Avalonia.VisualTree;
using Avalonia.Controls;
@@ -6,8 +9,10 @@ using ReactiveUI;
namespace Avalonia.ReactiveUI
{
///
- /// A ReactiveUI Window that implements
- /// and will activate your ViewModel automatically if it supports activation.
+ /// A ReactiveUI that implements the interface and will
+ /// activate your ViewModel automatically if the view model implements . When
+ /// the DataContext property changes, this class will update the ViewModel property with the new DataContext value,
+ /// and vice versa.
///
/// ViewModel type.
public class ReactiveWindow : Window, IViewFor where TViewModel : class
@@ -20,7 +25,14 @@ namespace Avalonia.ReactiveUI
///
public ReactiveWindow()
{
- DataContextChanged += (sender, args) => ViewModel = DataContext as TViewModel;
+ // 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);
}
///
diff --git a/tests/Avalonia.ReactiveUI.UnitTests/AvaloniaActivationForViewFetcherTest.cs b/tests/Avalonia.ReactiveUI.UnitTests/AvaloniaActivationForViewFetcherTest.cs
index fda8503135..c66a3c4ba1 100644
--- a/tests/Avalonia.ReactiveUI.UnitTests/AvaloniaActivationForViewFetcherTest.cs
+++ b/tests/Avalonia.ReactiveUI.UnitTests/AvaloniaActivationForViewFetcherTest.cs
@@ -74,44 +74,26 @@ namespace Avalonia.ReactiveUI.UnitTests
{
public ActivatableWindow()
{
- InitializeComponent();
- Assert.IsType(Content);
+ Content = new Border();
this.WhenActivated(disposables => { });
}
-
- private void InitializeComponent()
- {
- AvaloniaRuntimeXamlLoader.Load(@"
-
-
-", null, this);
- }
}
public class ActivatableUserControl : ReactiveUserControl
{
public ActivatableUserControl()
{
- InitializeComponent();
- Assert.IsType(Content);
+ Content = new Border();
this.WhenActivated(disposables => { });
}
-
- private void InitializeComponent()
- {
- AvaloniaRuntimeXamlLoader.Load(@"
-
-
-", null, this);
- }
}
- public AvaloniaActivationForViewFetcherTest()
- {
- Locator.CurrentMutable.RegisterConstant(
- new AvaloniaActivationForViewFetcher(),
- typeof(IActivationForViewFetcher));
- }
+ public AvaloniaActivationForViewFetcherTest() =>
+ Locator
+ .CurrentMutable
+ .RegisterConstant(
+ new AvaloniaActivationForViewFetcher(),
+ typeof(IActivationForViewFetcher));
[Fact]
public void Visual_Element_Is_Activated_And_Deactivated()
diff --git a/tests/Avalonia.ReactiveUI.UnitTests/ReactiveUserControlTest.cs b/tests/Avalonia.ReactiveUI.UnitTests/ReactiveUserControlTest.cs
index b57bb242bd..5d257f75f2 100644
--- a/tests/Avalonia.ReactiveUI.UnitTests/ReactiveUserControlTest.cs
+++ b/tests/Avalonia.ReactiveUI.UnitTests/ReactiveUserControlTest.cs
@@ -1,4 +1,4 @@
-using Avalonia.Controls;
+using System.Reactive.Disposables;
using Avalonia.UnitTests;
using ReactiveUI;
using Splat;
@@ -8,14 +8,37 @@ namespace Avalonia.ReactiveUI.UnitTests
{
public class ReactiveUserControlTest
{
- public class ExampleViewModel : ReactiveObject { }
+ public class ExampleViewModel : ReactiveObject, IActivatableViewModel
+ {
+ public bool IsActive { get; private set; }
+
+ public ViewModelActivator Activator { get; } = new ViewModelActivator();
+
+ public ExampleViewModel() => this.WhenActivated(disposables =>
+ {
+ IsActive = true;
+ Disposable
+ .Create(() => IsActive = false)
+ .DisposeWith(disposables);
+ });
+ }
public class ExampleView : ReactiveUserControl { }
+ public ReactiveUserControlTest() =>
+ Locator
+ .CurrentMutable
+ .RegisterConstant(
+ new AvaloniaActivationForViewFetcher(),
+ typeof(IActivationForViewFetcher));
+
[Fact]
public void Data_Context_Should_Stay_In_Sync_With_Reactive_User_Control_View_Model()
{
+ var root = new TestRoot();
var view = new ExampleView();
+ root.Child = view;
+
var viewModel = new ExampleViewModel();
Assert.Null(view.ViewModel);
@@ -26,6 +49,56 @@ namespace Avalonia.ReactiveUI.UnitTests
view.DataContext = null;
Assert.Null(view.ViewModel);
Assert.Null(view.DataContext);
+
+ view.ViewModel = viewModel;
+ Assert.Equal(viewModel, view.ViewModel);
+ Assert.Equal(viewModel, view.DataContext);
+
+ view.ViewModel = null;
+ Assert.Null(view.ViewModel);
+ Assert.Null(view.DataContext);
+ }
+
+ [Fact]
+ public void Should_Start_With_NotNull_Activated_ViewModel()
+ {
+ var root = new TestRoot();
+ var view = new ExampleView {ViewModel = new ExampleViewModel()};
+
+ Assert.False(view.ViewModel.IsActive);
+
+ root.Child = view;
+
+ Assert.NotNull(view.ViewModel);
+ Assert.NotNull(view.DataContext);
+ Assert.True(view.ViewModel.IsActive);
+
+ root.Child = null;
+
+ Assert.NotNull(view.ViewModel);
+ Assert.NotNull(view.DataContext);
+ Assert.False(view.ViewModel.IsActive);
+ }
+
+ [Fact]
+ public void Should_Start_With_NotNull_Activated_DataContext()
+ {
+ var root = new TestRoot();
+ var view = new ExampleView {DataContext = new ExampleViewModel()};
+
+ Assert.False(view.ViewModel.IsActive);
+
+ root.Child = view;
+
+ Assert.NotNull(view.ViewModel);
+ Assert.NotNull(view.DataContext);
+ Assert.True(view.ViewModel.IsActive);
+
+ root.Child = null;
+
+ Assert.NotNull(view.ViewModel);
+ Assert.NotNull(view.DataContext);
+ Assert.False(view.ViewModel.IsActive);
}
}
-}
\ No newline at end of file
+}
diff --git a/tests/Avalonia.ReactiveUI.UnitTests/ReactiveWindowTest.cs b/tests/Avalonia.ReactiveUI.UnitTests/ReactiveWindowTest.cs
index 3a5c562a59..18a8a33f09 100644
--- a/tests/Avalonia.ReactiveUI.UnitTests/ReactiveWindowTest.cs
+++ b/tests/Avalonia.ReactiveUI.UnitTests/ReactiveWindowTest.cs
@@ -1,4 +1,4 @@
-using Avalonia.Controls;
+using System.Reactive.Disposables;
using Avalonia.UnitTests;
using ReactiveUI;
using Splat;
@@ -8,10 +8,30 @@ namespace Avalonia.ReactiveUI.UnitTests
{
public class ReactiveWindowTest
{
- public class ExampleViewModel : ReactiveObject { }
+ public class ExampleViewModel : ReactiveObject, IActivatableViewModel
+ {
+ public bool IsActive { get; private set; }
+
+ public ViewModelActivator Activator { get; } = new ViewModelActivator();
+
+ public ExampleViewModel() => this.WhenActivated(disposables =>
+ {
+ IsActive = true;
+ Disposable
+ .Create(() => IsActive = false)
+ .DisposeWith(disposables);
+ });
+ }
public class ExampleWindow : ReactiveWindow { }
+ public ReactiveWindowTest() =>
+ Locator
+ .CurrentMutable
+ .RegisterConstant(
+ new AvaloniaActivationForViewFetcher(),
+ typeof(IActivationForViewFetcher));
+
[Fact]
public void Data_Context_Should_Stay_In_Sync_With_Reactive_Window_View_Model()
{
@@ -19,16 +39,73 @@ namespace Avalonia.ReactiveUI.UnitTests
{
var view = new ExampleWindow();
var viewModel = new ExampleViewModel();
+ view.Show();
+
Assert.Null(view.ViewModel);
+ Assert.Null(view.DataContext);
view.DataContext = viewModel;
- Assert.Equal(view.ViewModel, viewModel);
- Assert.Equal(view.DataContext, viewModel);
+ Assert.Equal(viewModel, view.ViewModel);
+ Assert.Equal(viewModel, view.DataContext);
view.DataContext = null;
Assert.Null(view.ViewModel);
Assert.Null(view.DataContext);
+
+ view.ViewModel = viewModel;
+ Assert.Equal(viewModel, view.ViewModel);
+ Assert.Equal(viewModel, view.DataContext);
+
+ view.ViewModel = null;
+ Assert.Null(view.ViewModel);
+ Assert.Null(view.DataContext);
+ }
+ }
+
+ [Fact]
+ public void Should_Start_With_NotNull_Activated_ViewModel()
+ {
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ var view = new ExampleWindow { ViewModel = new ExampleViewModel() };
+
+ Assert.False(view.ViewModel.IsActive);
+
+ view.Show();
+
+ Assert.NotNull(view.ViewModel);
+ Assert.NotNull(view.DataContext);
+ Assert.True(view.ViewModel.IsActive);
+
+ view.Close();
+
+ Assert.NotNull(view.ViewModel);
+ Assert.NotNull(view.DataContext);
+ Assert.False(view.ViewModel.IsActive);
+ }
+ }
+
+ [Fact]
+ public void Should_Start_With_NotNull_Activated_DataContext()
+ {
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ var view = new ExampleWindow { DataContext = new ExampleViewModel() };
+
+ Assert.False(view.ViewModel.IsActive);
+
+ view.Show();
+
+ Assert.NotNull(view.ViewModel);
+ Assert.NotNull(view.DataContext);
+ Assert.True(view.ViewModel.IsActive);
+
+ view.Close();
+
+ Assert.NotNull(view.ViewModel);
+ Assert.NotNull(view.DataContext);
+ Assert.False(view.ViewModel.IsActive);
}
}
}
-}
\ No newline at end of file
+}