From e246f672cd93b0340f3792eefdfa58d955c6a18e Mon Sep 17 00:00:00 2001 From: Yinimi Date: Tue, 2 Apr 2024 12:13:49 +0200 Subject: [PATCH 1/4] Fix activation for nested conductor / screen scenarios --- .../ConductorWithCollectionOneActiveTests.cs | 116 ++++++++++++++++++ src/Caliburn.Micro.Core/Conductor.cs | 4 +- .../ConductorWithCollectionAllActive.cs | 4 +- .../ConductorWithCollectionOneActive.cs | 4 +- 4 files changed, 122 insertions(+), 6 deletions(-) diff --git a/src/Caliburn.Micro.Core.Tests/ConductorWithCollectionOneActiveTests.cs b/src/Caliburn.Micro.Core.Tests/ConductorWithCollectionOneActiveTests.cs index 6e3371cd0..4c7b01a31 100644 --- a/src/Caliburn.Micro.Core.Tests/ConductorWithCollectionOneActiveTests.cs +++ b/src/Caliburn.Micro.Core.Tests/ConductorWithCollectionOneActiveTests.cs @@ -24,6 +24,46 @@ protected override async Task OnDeactivateAsync(bool close, CancellationToken ca } } + private class ConductorExposeChangeActiveItem : Conductor.Collection.OneActive where T : Screen + { + public async Task ChangeActiveItemAsyncPublic(T newItem, bool closePrevious) + { + await ChangeActiveItemAsync(newItem, closePrevious); + } + } + + private class ActivateScreen : Screen + { + public event EventHandler RequestContinue; + + public int ActivateCalledCount { get; private set; } + public bool AutoContinue { get; set; } + + protected override async Task OnActivateAsync(CancellationToken cancellationToken) + { + ActivateCalledCount++; + await Task.Delay(100, cancellationToken); + if (AutoContinue) + RequestContinue?.Invoke(this, EventArgs.Empty); + } + } + + private class ActivatedScreen : Screen + { + public event EventHandler RequestContinue; + + public int ActivateCalledCount { get; private set; } + public bool AutoContinue { get; set; } + + protected override async Task OnActivatedAsync(CancellationToken cancellationToken) + { + ActivateCalledCount++; + await Task.Delay(100, cancellationToken); + if(AutoContinue) + RequestContinue?.Invoke(this, EventArgs.Empty); + } + } + [Fact] public void AddedItemAppearsInChildren() { @@ -81,6 +121,82 @@ public async Task ChildrenAreActivatedIfConductorIsActive() Assert.Equal(conducted, conductor.ActiveItem); } + [Fact(Skip = "Please use OnActiveAsync carefully https://github.com/Caliburn-Micro/Caliburn.Micro/issues/789")] + public async Task ActivateWhileActivateOneLevel() + { + var conductor = new Conductor.Collection.OneActive(); + var viewModel1 = new ActivateScreen() + { + DisplayName = "ViewModel 1", + AutoContinue = true + }; + conductor.Items.Add(viewModel1); + var viewModel2 = new ActivateScreen() { DisplayName = "ViewModel 2" }; + conductor.Items.Add(viewModel2); + viewModel1.RequestContinue += (sender, e) => { conductor.ActivateItemAsync(viewModel2).Wait(); }; + + await conductor.ActivateAsync(); + await conductor.ActivateItemAsync(viewModel1); + + Assert.False(viewModel1.IsActive); + Assert.Equal(1, viewModel1.ActivateCalledCount); + Assert.True(viewModel2.IsActive); + Assert.Equal(1, viewModel2.ActivateCalledCount); + } + + [Fact] + public async Task ActivatedWhileActivatedOneLevel() + { + var conductor = new Conductor.Collection.OneActive(); + var viewModel1 = new ActivatedScreen() + { + DisplayName = "ViewModel 1", + AutoContinue = true + }; + conductor.Items.Add(viewModel1); + var viewModel2 = new ActivatedScreen() { DisplayName = "ViewModel 2" }; + conductor.Items.Add(viewModel2); + viewModel1.RequestContinue += (sender, e) => { conductor.ActivateItemAsync(viewModel2).Wait(); }; + + await conductor.ActivateAsync(); + await conductor.ActivateItemAsync(viewModel1); + + Assert.False(viewModel1.IsActive); + Assert.Equal(1, viewModel1.ActivateCalledCount); + Assert.True(viewModel2.IsActive); + Assert.Equal(1, viewModel2.ActivateCalledCount); + } + + [Fact] + public async Task ActivateWhileActivateStackedLevels() + { + var outerConductor = new ConductorExposeChangeActiveItem() { DisplayName = "Outer Conductor" }; + var somePage = new Screen(); + outerConductor.Items.Add(somePage); + var innerConductor = new Conductor.Collection.OneActive() { DisplayName = "Inner Conductor" }; + outerConductor.Items.Add(innerConductor); + var viewModel1 = new ActivatedScreen() { DisplayName = "ViewModel 1" }; + innerConductor.Items.Add(viewModel1); + var viewModel2 = new ActivatedScreen() { DisplayName = "ViewModel 2" }; + innerConductor.Items.Add(viewModel2); + viewModel1.RequestContinue += (sender, e) => { innerConductor.ActivateItemAsync(viewModel2).Wait(); }; + + await outerConductor.ActivateAsync(); + await outerConductor.ActivateItemAsync(innerConductor); + await innerConductor.ActivateItemAsync(viewModel1); + + await outerConductor.ChangeActiveItemAsyncPublic(somePage, false); + Assert.True(somePage.IsActive); + viewModel1.AutoContinue = true; + await outerConductor.ChangeActiveItemAsyncPublic(innerConductor, true); + + Assert.True(innerConductor.IsActive); + Assert.False(viewModel1.IsActive); + Assert.Equal(2, viewModel1.ActivateCalledCount); + Assert.True(viewModel2.IsActive); + Assert.Equal(1, viewModel2.ActivateCalledCount); + } + [Fact(Skip = "ActiveItem currently set regardless of IsActive value. See http://caliburnmicro.codeplex.com/discussions/276375")] public async Task ChildrenAreNotActivatedIfConductorIsNotActive() { diff --git a/src/Caliburn.Micro.Core/Conductor.cs b/src/Caliburn.Micro.Core/Conductor.cs index c95033128..e76e1a320 100644 --- a/src/Caliburn.Micro.Core/Conductor.cs +++ b/src/Caliburn.Micro.Core/Conductor.cs @@ -69,10 +69,10 @@ public override async Task DeactivateItemAsync(T item, bool close, CancellationT } /// - /// Called when activating. + /// Called when view has been activated. /// /// A task that represents the asynchronous operation. - protected override Task OnActivateAsync(CancellationToken cancellationToken) + protected override Task OnActivatedAsync(CancellationToken cancellationToken) { return ScreenExtensions.TryActivateAsync(ActiveItem, cancellationToken); } diff --git a/src/Caliburn.Micro.Core/ConductorWithCollectionAllActive.cs b/src/Caliburn.Micro.Core/ConductorWithCollectionAllActive.cs index 610dcbea9..1aed1c493 100644 --- a/src/Caliburn.Micro.Core/ConductorWithCollectionAllActive.cs +++ b/src/Caliburn.Micro.Core/ConductorWithCollectionAllActive.cs @@ -65,9 +65,9 @@ public AllActive() public IObservableCollection Items => _items; /// - /// Called when activating. + /// Called when view has been activated. /// - protected override Task OnActivateAsync(CancellationToken cancellationToken) + protected override Task OnActivatedAsync(CancellationToken cancellationToken) { return Task.WhenAll(_items.OfType().Select(x => x.ActivateAsync(cancellationToken))); } diff --git a/src/Caliburn.Micro.Core/ConductorWithCollectionOneActive.cs b/src/Caliburn.Micro.Core/ConductorWithCollectionOneActive.cs index e871e216b..ba8c50657 100644 --- a/src/Caliburn.Micro.Core/ConductorWithCollectionOneActive.cs +++ b/src/Caliburn.Micro.Core/ConductorWithCollectionOneActive.cs @@ -184,11 +184,11 @@ public override async Task CanCloseAsync(CancellationToken cancellationTok } /// - /// Called when activating. + /// Called when view has been activated. /// /// The cancellation token to cancel operation. /// A task that represents the asynchronous operation. - protected override Task OnActivateAsync(CancellationToken cancellationToken) + protected override Task OnActivatedAsync(CancellationToken cancellationToken) { return ScreenExtensions.TryActivateAsync(ActiveItem, cancellationToken); } From b0cbfc64eca3da3e94d32db6149e816bf05089c1 Mon Sep 17 00:00:00 2001 From: yinimi Date: Tue, 2 Apr 2024 14:01:59 +0200 Subject: [PATCH 2/4] added OnInitializedAsync() method --- src/Caliburn.Micro.Core/Screen.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Caliburn.Micro.Core/Screen.cs b/src/Caliburn.Micro.Core/Screen.cs index 5175e64f3..c31d59de6 100644 --- a/src/Caliburn.Micro.Core/Screen.cs +++ b/src/Caliburn.Micro.Core/Screen.cs @@ -104,6 +104,7 @@ async Task IActivate.ActivateAsync(CancellationToken cancellationToken) { await OnInitializeAsync(cancellationToken); IsInitialized = initialized = true; + await OnInitializedAsync(cancellationToken); } Log.Info("Activating {0}.", this); @@ -178,6 +179,14 @@ protected virtual Task OnInitializeAsync(CancellationToken cancellationToken) return Task.FromResult(true); } + /// + /// Called when view has been initialized + /// + protected virtual Task OnInitializedAsync(CancellationToken cancellationToken) + { + return Task.FromResult(true); + } + /// /// Called when activating. /// From eccf4b067c0cecea2255ddc68e63c9ee8e8c60ac Mon Sep 17 00:00:00 2001 From: yinimi Date: Mon, 8 Apr 2024 13:12:01 +0200 Subject: [PATCH 3/4] use OnInitializedAsync in Conductor --- src/Caliburn.Micro.Core/ConductorWithCollectionAllActive.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Caliburn.Micro.Core/ConductorWithCollectionAllActive.cs b/src/Caliburn.Micro.Core/ConductorWithCollectionAllActive.cs index 1aed1c493..34b6dad61 100644 --- a/src/Caliburn.Micro.Core/ConductorWithCollectionAllActive.cs +++ b/src/Caliburn.Micro.Core/ConductorWithCollectionAllActive.cs @@ -112,9 +112,9 @@ public override async Task CanCloseAsync(CancellationToken cancellationTok } /// - /// Called when initializing. + /// Called when view has been initialized /// - protected override async Task OnInitializeAsync(CancellationToken cancellationToken) + protected override async Task OnInitializedAsync(CancellationToken cancellationToken) { if (_openPublicItems) await Task.WhenAll(GetType().GetRuntimeProperties() From 931a04e29387e4ba275de9e41826faf79ac5f68b Mon Sep 17 00:00:00 2001 From: yinimi Date: Mon, 8 Apr 2024 13:12:30 +0200 Subject: [PATCH 4/4] mark OnInitializeAsync and OnActivateAsync as deprecated --- src/Caliburn.Micro.Core/Screen.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Caliburn.Micro.Core/Screen.cs b/src/Caliburn.Micro.Core/Screen.cs index c31d59de6..eef08be4a 100644 --- a/src/Caliburn.Micro.Core/Screen.cs +++ b/src/Caliburn.Micro.Core/Screen.cs @@ -174,6 +174,7 @@ public virtual async Task TryCloseAsync(bool? dialogResult = null) /// /// Called when initializing. /// + [Obsolete("Override OnInitializedAsync")] protected virtual Task OnInitializeAsync(CancellationToken cancellationToken) { return Task.FromResult(true); @@ -190,6 +191,7 @@ protected virtual Task OnInitializedAsync(CancellationToken cancellationToken) /// /// Called when activating. /// + [Obsolete("Override OnActivatedAsync")] protected virtual Task OnActivateAsync(CancellationToken cancellationToken) { return Task.FromResult(true);