diff --git a/build/ReactiveUI.props b/build/ReactiveUI.props index f827cb9a32..d8e86e917e 100644 --- a/build/ReactiveUI.props +++ b/build/ReactiveUI.props @@ -1,5 +1,5 @@ - + diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index d8e7f3a387..317b6d3f2e 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -652,8 +652,8 @@ namespace Avalonia.Controls PlatformImpl?.Show(); Renderer?.Start(); + SetWindowStartupLocation(Owner?.PlatformImpl); } - SetWindowStartupLocation(Owner?.PlatformImpl); OnOpened(EventArgs.Empty); } diff --git a/src/Avalonia.Controls/WindowBase.cs b/src/Avalonia.Controls/WindowBase.cs index eb6e7319f5..1efd6c8c1d 100644 --- a/src/Avalonia.Controls/WindowBase.cs +++ b/src/Avalonia.Controls/WindowBase.cs @@ -39,6 +39,7 @@ namespace Avalonia.Controls public static readonly StyledProperty TopmostProperty = AvaloniaProperty.Register(nameof(Topmost)); + private int _autoSizing; private bool _hasExecutedInitialLayoutPass; private bool _isActive; private bool _ignoreVisibilityChange; @@ -97,11 +98,7 @@ namespace Avalonia.Controls /// /// Whether an auto-size operation is in progress. /// - protected bool AutoSizing - { - get; - private set; - } + protected bool AutoSizing => _autoSizing > 0; /// /// Gets or sets the owner of the window. @@ -186,8 +183,8 @@ namespace Avalonia.Controls /// protected IDisposable BeginAutoSizing() { - AutoSizing = true; - return Disposable.Create(() => AutoSizing = false); + ++_autoSizing; + return Disposable.Create(() => --_autoSizing); } /// diff --git a/src/Avalonia.ReactiveUI/AutoDataTemplateBindingHook.cs b/src/Avalonia.ReactiveUI/AutoDataTemplateBindingHook.cs index 4881c77034..fe4e9dd94a 100644 --- a/src/Avalonia.ReactiveUI/AutoDataTemplateBindingHook.cs +++ b/src/Avalonia.ReactiveUI/AutoDataTemplateBindingHook.cs @@ -48,6 +48,10 @@ namespace Avalonia.ReactiveUI if (itemsControl.ItemTemplate != null) return true; + if (itemsControl.DataTemplates != null && + itemsControl.DataTemplates.Count > 0) + return true; + itemsControl.ItemTemplate = DefaultItemTemplate; return true; } diff --git a/src/Avalonia.ReactiveUI/RoutedViewHost.cs b/src/Avalonia.ReactiveUI/RoutedViewHost.cs index 1af8012a20..421633cd58 100644 --- a/src/Avalonia.ReactiveUI/RoutedViewHost.cs +++ b/src/Avalonia.ReactiveUI/RoutedViewHost.cs @@ -65,8 +65,15 @@ namespace Avalonia.ReactiveUI { this.WhenActivated(disposables => { - this.WhenAnyObservable(x => x.Router.CurrentViewModel) - .DistinctUntilChanged() + var routerRemoved = this + .WhenAnyValue(x => x.Router) + .Where(router => router == null) + .Cast(); + + this.WhenAnyValue(x => x.Router) + .Where(router => router != null) + .SelectMany(router => router.CurrentViewModel) + .Merge(routerRemoved) .Subscribe(NavigateToViewModel) .DisposeWith(disposables); }); @@ -92,6 +99,13 @@ namespace Avalonia.ReactiveUI /// ViewModel to which the user navigates. 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) { 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}."); viewInstance.ViewModel = viewModel; - if (viewInstance is IStyledElement styled) - styled.DataContext = viewModel; + if (viewInstance is IDataContextProvider provider) + provider.DataContext = viewModel; Content = viewInstance; } } diff --git a/tests/Avalonia.ReactiveUI.UnitTests/AutoDataTemplateBindingHookTest.cs b/tests/Avalonia.ReactiveUI.UnitTests/AutoDataTemplateBindingHookTest.cs index 8ef37dbd21..53bdff5dff 100644 --- a/tests/Avalonia.ReactiveUI.UnitTests/AutoDataTemplateBindingHookTest.cs +++ b/tests/Avalonia.ReactiveUI.UnitTests/AutoDataTemplateBindingHookTest.cs @@ -28,10 +28,17 @@ namespace Avalonia.ReactiveUI.UnitTests public class ExampleView : ReactiveUserControl { - public ItemsControl List { get; } = new ItemsControl(); + public ItemsControl List { get; } = new ItemsControl + { + Template = GetTemplate() + }; - public ExampleView() + public ExampleView(Action adjustItemsControl = null) { + adjustItemsControl?.Invoke(List); + List.ApplyTemplate(); + List.Presenter.ApplyTemplate(); + Content = List; ViewModel = new ExampleViewModel(); this.OneWayBind(ViewModel, x => x.Items, x => x.List.Items); @@ -50,18 +57,15 @@ namespace Avalonia.ReactiveUI.UnitTests { var view = new ExampleView(); Assert.NotNull(view.List.ItemTemplate); + Assert.IsType>(view.List.ItemTemplate); } [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(); 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 container = (ContentPresenter) child; container.UpdateChild(); @@ -70,16 +74,11 @@ namespace Avalonia.ReactiveUI.UnitTests } [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 root = new TestRoot { Child = view }; 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 container = (ContentPresenter) child; container.UpdateChild(); @@ -93,19 +92,55 @@ namespace Avalonia.ReactiveUI.UnitTests Assert.IsType(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>(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(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(container.Child); + } + + private static FuncDataTemplate GetItemTemplate() + { + return new FuncDataTemplate((parent, scope) => new TextBlock()); + } + + private static FuncControlTemplate GetTemplate() { - return new FuncControlTemplate((parent, scope) => + return new FuncControlTemplate((parent, scope) => new Border { - return new Border + Background = new Media.SolidColorBrush(0xffffffff), + Child = new ItemsPresenter { - Background = new Media.SolidColorBrush(0xffffffff), - Child = new ItemsPresenter - { - Name = "PART_ItemsPresenter", - [~ItemsPresenter.ItemsProperty] = parent[~ItemsControl.ItemsProperty], - }.RegisterInNameScope(scope) - }; + Name = "PART_ItemsPresenter", + [~ItemsPresenter.ItemsProperty] = parent[~ItemsControl.ItemsProperty], + }.RegisterInNameScope(scope) }); } } diff --git a/tests/Avalonia.ReactiveUI.UnitTests/RoutedViewHostTest.cs b/tests/Avalonia.ReactiveUI.UnitTests/RoutedViewHostTest.cs index f4d9222085..b82b1b1acc 100644 --- a/tests/Avalonia.ReactiveUI.UnitTests/RoutedViewHostTest.cs +++ b/tests/Avalonia.ReactiveUI.UnitTests/RoutedViewHostTest.cs @@ -62,51 +62,85 @@ namespace Avalonia.ReactiveUI.UnitTests PageTransition = null }; - var root = new TestRoot - { - Child = host + var root = new TestRoot + { + Child = host }; Assert.NotNull(host.Content); - Assert.Equal(typeof(TextBlock), host.Content.GetType()); + Assert.IsType(host.Content); Assert.Equal(defaultContent, host.Content); var first = new FirstRoutableViewModel(); - screen.Router.Navigate - .Execute(first) - .Subscribe(); + screen.Router.Navigate.Execute(first).Subscribe(); Assert.NotNull(host.Content); - Assert.Equal(typeof(FirstRoutableView), host.Content.GetType()); + Assert.IsType(host.Content); Assert.Equal(first, ((FirstRoutableView)host.Content).DataContext); Assert.Equal(first, ((FirstRoutableView)host.Content).ViewModel); var second = new SecondRoutableViewModel(); - screen.Router.Navigate - .Execute(second) - .Subscribe(); + screen.Router.Navigate.Execute(second).Subscribe(); Assert.NotNull(host.Content); - Assert.Equal(typeof(SecondRoutableView), host.Content.GetType()); + Assert.IsType(host.Content); Assert.Equal(second, ((SecondRoutableView)host.Content).DataContext); Assert.Equal(second, ((SecondRoutableView)host.Content).ViewModel); - screen.Router.NavigateBack - .Execute(Unit.Default) - .Subscribe(); + screen.Router.NavigateBack.Execute(Unit.Default).Subscribe(); Assert.NotNull(host.Content); - Assert.Equal(typeof(FirstRoutableView), host.Content.GetType()); + Assert.IsType(host.Content); Assert.Equal(first, ((FirstRoutableView)host.Content).DataContext); Assert.Equal(first, ((FirstRoutableView)host.Content).ViewModel); - screen.Router.NavigateBack - .Execute(Unit.Default) - .Subscribe(); + screen.Router.NavigateBack.Execute(Unit.Default).Subscribe(); + + Assert.NotNull(host.Content); + Assert.IsType(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.Equal(typeof(TextBlock), host.Content.GetType()); 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(host.Content); + + host.Router = null; + + Assert.NotNull(host.Content); + Assert.Equal(defaultContent, host.Content); + + host.Router = screen.Router; + + Assert.NotNull(host.Content); + Assert.IsType(host.Content); } } -} \ No newline at end of file +}