Browse Source

Add ViewContract to ViewModelViewHost

pull/7266/head
Sergey Volkov 5 years ago
parent
commit
fe42fbfe6b
  1. 52
      src/Avalonia.ReactiveUI/ViewModelViewHost.cs
  2. 72
      tests/Avalonia.ReactiveUI.UnitTests/ViewModelViewHostTest.cs

52
src/Avalonia.ReactiveUI/ViewModelViewHost.cs

@ -3,7 +3,7 @@ using System.Reactive.Disposables;
using ReactiveUI;
using Splat;
namespace Avalonia.ReactiveUI
namespace Avalonia.ReactiveUI
{
/// <summary>
/// This content control will automatically load the View associated with
@ -18,6 +18,12 @@ namespace Avalonia.ReactiveUI
public static readonly AvaloniaProperty<object?> ViewModelProperty =
AvaloniaProperty.Register<ViewModelViewHost, object?>(nameof(ViewModel));
/// <summary>
/// <see cref="AvaloniaProperty"/> for the <see cref="ViewContract"/> property.
/// </summary>
public static readonly StyledProperty<string?> ViewContractProperty =
AvaloniaProperty.Register<ViewModelViewHost, string?>(nameof(ViewContract));
/// <summary>
/// Initializes a new instance of the <see cref="ViewModelViewHost"/> class.
/// </summary>
@ -25,8 +31,8 @@ namespace Avalonia.ReactiveUI
{
this.WhenActivated(disposables =>
{
this.WhenAnyValue(x => x.ViewModel)
.Subscribe(NavigateToViewModel)
this.WhenAnyValue(x => x.ViewModel, x => x.ViewContract)
.Subscribe(tuple => NavigateToViewModel(tuple.Item1, tuple.Item2))
.DisposeWith(disposables);
});
}
@ -39,7 +45,16 @@ namespace Avalonia.ReactiveUI
get => GetValue(ViewModelProperty);
set => SetValue(ViewModelProperty, value);
}
/// <summary>
/// Gets or sets the view contract.
/// </summary>
public string? ViewContract
{
get => GetValue(ViewContractProperty);
set => SetValue(ViewContractProperty, value);
}
/// <summary>
/// Gets or sets the view locator.
/// </summary>
@ -49,7 +64,8 @@ namespace Avalonia.ReactiveUI
/// Invoked when ReactiveUI router navigates to a view model.
/// </summary>
/// <param name="viewModel">ViewModel to which the user navigates.</param>
private void NavigateToViewModel(object? viewModel)
/// <param name="contract">The contract for view resolution.</param>
private void NavigateToViewModel(object? viewModel, string? contract)
{
if (viewModel == null)
{
@ -57,17 +73,33 @@ namespace Avalonia.ReactiveUI
Content = DefaultContent;
return;
}
var viewLocator = ViewLocator ?? global::ReactiveUI.ViewLocator.Current;
var viewInstance = viewLocator.ResolveView(viewModel);
var viewInstance = viewLocator.ResolveView(viewModel, contract);
if (viewInstance == null)
{
this.Log().Warn($"Couldn't find view for '{viewModel}'. Is it registered? Falling back to default content.");
if (contract == null)
{
this.Log().Warn($"Couldn't find view for '{viewModel}'. Is it registered? Falling back to default content.");
}
else
{
this.Log().Warn($"Couldn't find view with contract '{contract}' for '{viewModel}'. Is it registered? Falling back to default content.");
}
Content = DefaultContent;
return;
}
this.Log().Info($"Ready to show {viewInstance} with autowired {viewModel}.");
if (contract == null)
{
this.Log().Info($"Ready to show {viewInstance} with autowired {viewModel}.");
}
else
{
this.Log().Info($"Ready to show {viewInstance} with autowired {viewModel} and contract '{contract}'.");
}
viewInstance.ViewModel = viewModel;
if (viewInstance is IStyledElement styled)
styled.DataContext = viewModel;

72
tests/Avalonia.ReactiveUI.UnitTests/ViewModelViewHostTest.cs

@ -12,15 +12,23 @@ namespace Avalonia.ReactiveUI.UnitTests
public class FirstView : ReactiveUserControl<FirstViewModel> { }
public class AlternativeFirstView : ReactiveUserControl<FirstViewModel> { }
public class SecondViewModel : ReactiveObject { }
public class SecondView : ReactiveUserControl<SecondViewModel> { }
public class AlternativeSecondView : ReactiveUserControl<SecondViewModel> { }
public static string AlternativeViewContract => "AlternativeView";
public ViewModelViewHostTest()
{
Locator.CurrentMutable.RegisterConstant(new AvaloniaActivationForViewFetcher(), typeof(IActivationForViewFetcher));
Locator.CurrentMutable.Register(() => new FirstView(), typeof(IViewFor<FirstViewModel>));
Locator.CurrentMutable.Register(() => new SecondView(), typeof(IViewFor<SecondViewModel>));
Locator.CurrentMutable.Register(() => new AlternativeFirstView(), typeof(IViewFor<FirstViewModel>), AlternativeViewContract);
Locator.CurrentMutable.Register(() => new AlternativeSecondView(), typeof(IViewFor<SecondViewModel>), AlternativeViewContract);
}
[Fact]
@ -67,5 +75,67 @@ namespace Avalonia.ReactiveUI.UnitTests
Assert.Equal(first, ((FirstView)host.Content).DataContext);
Assert.Equal(first, ((FirstView)host.Content).ViewModel);
}
[Fact]
public void ViewModelViewHost_View_Should_Stay_In_Sync_With_ViewModel_And_Contract()
{
var defaultContent = new TextBlock();
var host = new ViewModelViewHost
{
DefaultContent = defaultContent,
PageTransition = null
};
var root = new TestRoot
{
Child = host
};
Assert.NotNull(host.Content);
Assert.Equal(typeof(TextBlock), host.Content.GetType());
Assert.Equal(defaultContent, host.Content);
var first = new FirstViewModel();
host.ViewModel = first;
host.ViewContract = null;
Assert.NotNull(host.Content);
Assert.Equal(typeof(FirstView), host.Content.GetType());
Assert.Equal(first, ((FirstView)host.Content).DataContext);
Assert.Equal(first, ((FirstView)host.Content).ViewModel);
host.ViewContract = AlternativeViewContract;
Assert.NotNull(host.Content);
Assert.Equal(typeof(AlternativeFirstView), host.Content.GetType());
Assert.Equal(first, ((AlternativeFirstView)host.Content).DataContext);
Assert.Equal(first, ((AlternativeFirstView)host.Content).ViewModel);
var second = new SecondViewModel();
host.ViewModel = second;
host.ViewContract = null;
Assert.NotNull(host.Content);
Assert.Equal(typeof(SecondView), host.Content.GetType());
Assert.Equal(second, ((SecondView)host.Content).DataContext);
Assert.Equal(second, ((SecondView)host.Content).ViewModel);
host.ViewContract = AlternativeViewContract;
Assert.NotNull(host.Content);
Assert.Equal(typeof(AlternativeSecondView), host.Content.GetType());
Assert.Equal(second, ((AlternativeSecondView)host.Content).DataContext);
Assert.Equal(second, ((AlternativeSecondView)host.Content).ViewModel);
host.ViewModel = null;
host.ViewContract = null;
Assert.NotNull(host.Content);
Assert.Equal(typeof(TextBlock), host.Content.GetType());
Assert.Equal(defaultContent, host.Content);
host.ViewContract = AlternativeViewContract;
Assert.NotNull(host.Content);
Assert.Equal(typeof(TextBlock), host.Content.GetType());
Assert.Equal(defaultContent, host.Content);
}
}
}
}

Loading…
Cancel
Save