Browse Source

Merge pull request #4473 from worldbeater/rxui-updates

Fix issues related to RoutedViewHost and AutoDataTemplateBindingHook
pull/4482/head
Steven Kirk 6 years ago
committed by GitHub
parent
commit
f5cbe636f1
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      build/ReactiveUI.props
  2. 4
      src/Avalonia.ReactiveUI/AutoDataTemplateBindingHook.cs
  3. 22
      src/Avalonia.ReactiveUI/RoutedViewHost.cs
  4. 81
      tests/Avalonia.ReactiveUI.UnitTests/AutoDataTemplateBindingHookTest.cs
  5. 76
      tests/Avalonia.ReactiveUI.UnitTests/RoutedViewHostTest.cs

2
build/ReactiveUI.props

@ -1,5 +1,5 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup> <ItemGroup>
<PackageReference Include="ReactiveUI" Version="10.3.6" /> <PackageReference Include="ReactiveUI" Version="11.5.17" />
</ItemGroup> </ItemGroup>
</Project> </Project>

4
src/Avalonia.ReactiveUI/AutoDataTemplateBindingHook.cs

@ -48,6 +48,10 @@ namespace Avalonia.ReactiveUI
if (itemsControl.ItemTemplate != null) if (itemsControl.ItemTemplate != null)
return true; return true;
if (itemsControl.DataTemplates != null &&
itemsControl.DataTemplates.Count > 0)
return true;
itemsControl.ItemTemplate = DefaultItemTemplate; itemsControl.ItemTemplate = DefaultItemTemplate;
return true; return true;
} }

22
src/Avalonia.ReactiveUI/RoutedViewHost.cs

@ -65,8 +65,15 @@ namespace Avalonia.ReactiveUI
{ {
this.WhenActivated(disposables => this.WhenActivated(disposables =>
{ {
this.WhenAnyObservable(x => x.Router.CurrentViewModel) var routerRemoved = this
.DistinctUntilChanged() .WhenAnyValue(x => x.Router)
.Where(router => router == null)
.Cast<object>();
this.WhenAnyValue(x => x.Router)
.Where(router => router != null)
.SelectMany(router => router.CurrentViewModel)
.Merge(routerRemoved)
.Subscribe(NavigateToViewModel) .Subscribe(NavigateToViewModel)
.DisposeWith(disposables); .DisposeWith(disposables);
}); });
@ -92,6 +99,13 @@ namespace Avalonia.ReactiveUI
/// <param name="viewModel">ViewModel to which the user navigates.</param> /// <param name="viewModel">ViewModel to which the user navigates.</param>
private void NavigateToViewModel(object viewModel) private void NavigateToViewModel(object viewModel)
{ {
if (Router == null)
{
this.Log().Warn("Router property is null. Falling back to default content.");
Content = DefaultContent;
return;
}
if (viewModel == null) if (viewModel == null)
{ {
this.Log().Info("ViewModel is null. Falling back to default content."); this.Log().Info("ViewModel is null. Falling back to default content.");
@ -110,8 +124,8 @@ namespace Avalonia.ReactiveUI
this.Log().Info($"Ready to show {viewInstance} with autowired {viewModel}."); this.Log().Info($"Ready to show {viewInstance} with autowired {viewModel}.");
viewInstance.ViewModel = viewModel; viewInstance.ViewModel = viewModel;
if (viewInstance is IStyledElement styled) if (viewInstance is IDataContextProvider provider)
styled.DataContext = viewModel; provider.DataContext = viewModel;
Content = viewInstance; Content = viewInstance;
} }
} }

81
tests/Avalonia.ReactiveUI.UnitTests/AutoDataTemplateBindingHookTest.cs

@ -28,10 +28,17 @@ namespace Avalonia.ReactiveUI.UnitTests
public class ExampleView : ReactiveUserControl<ExampleViewModel> public class ExampleView : ReactiveUserControl<ExampleViewModel>
{ {
public ItemsControl List { get; } = new ItemsControl(); public ItemsControl List { get; } = new ItemsControl
{
Template = GetTemplate()
};
public ExampleView() public ExampleView(Action<ItemsControl> adjustItemsControl = null)
{ {
adjustItemsControl?.Invoke(List);
List.ApplyTemplate();
List.Presenter.ApplyTemplate();
Content = List; Content = List;
ViewModel = new ExampleViewModel(); ViewModel = new ExampleViewModel();
this.OneWayBind(ViewModel, x => x.Items, x => x.List.Items); this.OneWayBind(ViewModel, x => x.Items, x => x.List.Items);
@ -50,18 +57,15 @@ namespace Avalonia.ReactiveUI.UnitTests
{ {
var view = new ExampleView(); var view = new ExampleView();
Assert.NotNull(view.List.ItemTemplate); Assert.NotNull(view.List.ItemTemplate);
Assert.IsType<FuncDataTemplate<object>>(view.List.ItemTemplate);
} }
[Fact] [Fact]
public void Should_Use_View_Model_View_Host_As_Data_Template() public void Should_Use_ViewModelViewHost_As_Data_Template_By_Default()
{ {
var view = new ExampleView(); var view = new ExampleView();
view.ViewModel.Items.Add(new NestedViewModel()); view.ViewModel.Items.Add(new NestedViewModel());
view.List.Template = GetTemplate();
view.List.ApplyTemplate();
view.List.Presenter.ApplyTemplate();
var child = view.List.Presenter.Panel.Children[0]; var child = view.List.Presenter.Panel.Children[0];
var container = (ContentPresenter) child; var container = (ContentPresenter) child;
container.UpdateChild(); container.UpdateChild();
@ -70,16 +74,11 @@ namespace Avalonia.ReactiveUI.UnitTests
} }
[Fact] [Fact]
public void Should_Resolve_And_Embedd_Appropriate_View_Model() public void ViewModelViewHost_Should_Resolve_And_Embedd_Appropriate_View_Model()
{ {
var view = new ExampleView(); var view = new ExampleView();
var root = new TestRoot { Child = view };
view.ViewModel.Items.Add(new NestedViewModel()); view.ViewModel.Items.Add(new NestedViewModel());
view.List.Template = GetTemplate();
view.List.ApplyTemplate();
view.List.Presenter.ApplyTemplate();
var child = view.List.Presenter.Panel.Children[0]; var child = view.List.Presenter.Panel.Children[0];
var container = (ContentPresenter) child; var container = (ContentPresenter) child;
container.UpdateChild(); container.UpdateChild();
@ -93,19 +92,55 @@ namespace Avalonia.ReactiveUI.UnitTests
Assert.IsType<string>(host.DataContext); Assert.IsType<string>(host.DataContext);
} }
private FuncControlTemplate GetTemplate() [Fact]
public void Should_Not_Override_Data_Template_Binding_When_Item_Template_Is_Set()
{
var view = new ExampleView(control => control.ItemTemplate = GetItemTemplate());
Assert.NotNull(view.List.ItemTemplate);
Assert.IsType<FuncDataTemplate<TextBlock>>(view.List.ItemTemplate);
}
[Fact]
public void Should_Not_Use_View_Model_View_Host_When_Item_Template_Is_Set()
{
var view = new ExampleView(control => control.ItemTemplate = GetItemTemplate());
view.ViewModel.Items.Add(new NestedViewModel());
var child = view.List.Presenter.Panel.Children[0];
var container = (ContentPresenter) child;
container.UpdateChild();
Assert.IsType<TextBlock>(container.Child);
}
[Fact]
public void Should_Not_Use_View_Model_View_Host_When_Data_Templates_Are_Not_Empty()
{
var view = new ExampleView(control => control.DataTemplates.Add(GetItemTemplate()));
view.ViewModel.Items.Add(new NestedViewModel());
var child = view.List.Presenter.Panel.Children[0];
var container = (ContentPresenter) child;
container.UpdateChild();
Assert.IsType<TextBlock>(container.Child);
}
private static FuncDataTemplate GetItemTemplate()
{
return new FuncDataTemplate<TextBlock>((parent, scope) => new TextBlock());
}
private static FuncControlTemplate GetTemplate()
{ {
return new FuncControlTemplate<ItemsControl>((parent, scope) => return new FuncControlTemplate<ItemsControl>((parent, scope) => new Border
{ {
return new Border Background = new Media.SolidColorBrush(0xffffffff),
Child = new ItemsPresenter
{ {
Background = new Media.SolidColorBrush(0xffffffff), Name = "PART_ItemsPresenter",
Child = new ItemsPresenter [~ItemsPresenter.ItemsProperty] = parent[~ItemsControl.ItemsProperty],
{ }.RegisterInNameScope(scope)
Name = "PART_ItemsPresenter",
[~ItemsPresenter.ItemsProperty] = parent[~ItemsControl.ItemsProperty],
}.RegisterInNameScope(scope)
};
}); });
} }
} }

76
tests/Avalonia.ReactiveUI.UnitTests/RoutedViewHostTest.cs

@ -62,51 +62,85 @@ namespace Avalonia.ReactiveUI.UnitTests
PageTransition = null PageTransition = null
}; };
var root = new TestRoot var root = new TestRoot
{ {
Child = host Child = host
}; };
Assert.NotNull(host.Content); Assert.NotNull(host.Content);
Assert.Equal(typeof(TextBlock), host.Content.GetType()); Assert.IsType<TextBlock>(host.Content);
Assert.Equal(defaultContent, host.Content); Assert.Equal(defaultContent, host.Content);
var first = new FirstRoutableViewModel(); var first = new FirstRoutableViewModel();
screen.Router.Navigate screen.Router.Navigate.Execute(first).Subscribe();
.Execute(first)
.Subscribe();
Assert.NotNull(host.Content); Assert.NotNull(host.Content);
Assert.Equal(typeof(FirstRoutableView), host.Content.GetType()); Assert.IsType<FirstRoutableView>(host.Content);
Assert.Equal(first, ((FirstRoutableView)host.Content).DataContext); Assert.Equal(first, ((FirstRoutableView)host.Content).DataContext);
Assert.Equal(first, ((FirstRoutableView)host.Content).ViewModel); Assert.Equal(first, ((FirstRoutableView)host.Content).ViewModel);
var second = new SecondRoutableViewModel(); var second = new SecondRoutableViewModel();
screen.Router.Navigate screen.Router.Navigate.Execute(second).Subscribe();
.Execute(second)
.Subscribe();
Assert.NotNull(host.Content); Assert.NotNull(host.Content);
Assert.Equal(typeof(SecondRoutableView), host.Content.GetType()); Assert.IsType<SecondRoutableView>(host.Content);
Assert.Equal(second, ((SecondRoutableView)host.Content).DataContext); Assert.Equal(second, ((SecondRoutableView)host.Content).DataContext);
Assert.Equal(second, ((SecondRoutableView)host.Content).ViewModel); Assert.Equal(second, ((SecondRoutableView)host.Content).ViewModel);
screen.Router.NavigateBack screen.Router.NavigateBack.Execute(Unit.Default).Subscribe();
.Execute(Unit.Default)
.Subscribe();
Assert.NotNull(host.Content); Assert.NotNull(host.Content);
Assert.Equal(typeof(FirstRoutableView), host.Content.GetType()); Assert.IsType<FirstRoutableView>(host.Content);
Assert.Equal(first, ((FirstRoutableView)host.Content).DataContext); Assert.Equal(first, ((FirstRoutableView)host.Content).DataContext);
Assert.Equal(first, ((FirstRoutableView)host.Content).ViewModel); Assert.Equal(first, ((FirstRoutableView)host.Content).ViewModel);
screen.Router.NavigateBack screen.Router.NavigateBack.Execute(Unit.Default).Subscribe();
.Execute(Unit.Default)
.Subscribe(); Assert.NotNull(host.Content);
Assert.IsType<TextBlock>(host.Content);
Assert.Equal(defaultContent, host.Content);
}
[Fact]
public void RoutedViewHost_Should_Show_Default_Content_When_Router_Is_Null()
{
var screen = new ScreenViewModel();
var defaultContent = new TextBlock();
var host = new RoutedViewHost
{
DefaultContent = defaultContent,
PageTransition = null,
Router = null
};
var root = new TestRoot
{
Child = host
};
Assert.NotNull(host.Content); Assert.NotNull(host.Content);
Assert.Equal(typeof(TextBlock), host.Content.GetType());
Assert.Equal(defaultContent, host.Content); Assert.Equal(defaultContent, host.Content);
host.Router = screen.Router;
Assert.NotNull(host.Content);
Assert.Equal(defaultContent, host.Content);
var first = new FirstRoutableViewModel();
screen.Router.Navigate.Execute(first).Subscribe();
Assert.NotNull(host.Content);
Assert.IsType<FirstRoutableView>(host.Content);
host.Router = null;
Assert.NotNull(host.Content);
Assert.Equal(defaultContent, host.Content);
host.Router = screen.Router;
Assert.NotNull(host.Content);
Assert.IsType<FirstRoutableView>(host.Content);
} }
} }
} }

Loading…
Cancel
Save