Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix activation for nested conductor / screen scenarios #898

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 116 additions & 0 deletions src/Caliburn.Micro.Core.Tests/ConductorWithCollectionOneActiveTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,46 @@ protected override async Task OnDeactivateAsync(bool close, CancellationToken ca
}
}

private class ConductorExposeChangeActiveItem<T> : Conductor<T>.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()
{
Expand Down Expand Up @@ -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<IScreen>.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<IScreen>.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<Screen>() { DisplayName = "Outer Conductor" };
var somePage = new Screen();
outerConductor.Items.Add(somePage);
var innerConductor = new Conductor<IScreen>.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()
{
Expand Down
4 changes: 2 additions & 2 deletions src/Caliburn.Micro.Core/Conductor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,10 @@ public override async Task DeactivateItemAsync(T item, bool close, CancellationT
}

/// <summary>
/// Called when activating.
/// Called when view has been activated.
/// </summary>
/// <returns>A task that represents the asynchronous operation.</returns>
protected override Task OnActivateAsync(CancellationToken cancellationToken)
protected override Task OnActivatedAsync(CancellationToken cancellationToken)
{
return ScreenExtensions.TryActivateAsync(ActiveItem, cancellationToken);
}
Expand Down
8 changes: 4 additions & 4 deletions src/Caliburn.Micro.Core/ConductorWithCollectionAllActive.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,9 @@ public AllActive()
public IObservableCollection<T> Items => _items;

/// <summary>
/// Called when activating.
/// Called when view has been activated.
/// </summary>
protected override Task OnActivateAsync(CancellationToken cancellationToken)
protected override Task OnActivatedAsync(CancellationToken cancellationToken)
{
return Task.WhenAll(_items.OfType<IActivate>().Select(x => x.ActivateAsync(cancellationToken)));
}
Expand Down Expand Up @@ -112,9 +112,9 @@ public override async Task<bool> CanCloseAsync(CancellationToken cancellationTok
}

/// <summary>
/// Called when initializing.
/// Called when view has been initialized
/// </summary>
protected override async Task OnInitializeAsync(CancellationToken cancellationToken)
protected override async Task OnInitializedAsync(CancellationToken cancellationToken)
{
if (_openPublicItems)
await Task.WhenAll(GetType().GetRuntimeProperties()
Expand Down
4 changes: 2 additions & 2 deletions src/Caliburn.Micro.Core/ConductorWithCollectionOneActive.cs
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the OnActivated name better but could you leave OnActivate and mark it as obsolete? This will keep the amount of code we break when someone updates the caliburn micro version in their project to a minimum

Original file line number Diff line number Diff line change
Expand Up @@ -184,11 +184,11 @@ public override async Task<bool> CanCloseAsync(CancellationToken cancellationTok
}

/// <summary>
/// Called when activating.
/// Called when view has been activated.
/// </summary>
/// <param name="cancellationToken">The cancellation token to cancel operation.</param>
/// <returns>A task that represents the asynchronous operation.</returns>
protected override Task OnActivateAsync(CancellationToken cancellationToken)
protected override Task OnActivatedAsync(CancellationToken cancellationToken)
{
return ScreenExtensions.TryActivateAsync(ActiveItem, cancellationToken);
}
Expand Down
11 changes: 11 additions & 0 deletions src/Caliburn.Micro.Core/Screen.cs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ async Task IActivate.ActivateAsync(CancellationToken cancellationToken)
{
await OnInitializeAsync(cancellationToken);
IsInitialized = initialized = true;
await OnInitializedAsync(cancellationToken);
}

Log.Info("Activating {0}.", this);
Expand Down Expand Up @@ -173,14 +174,24 @@ public virtual async Task TryCloseAsync(bool? dialogResult = null)
/// <summary>
/// Called when initializing.
/// </summary>
[Obsolete("Override OnInitializedAsync")]
protected virtual Task OnInitializeAsync(CancellationToken cancellationToken)
{
return Task.FromResult(true);
}

/// <summary>
/// Called when view has been initialized
/// </summary>
protected virtual Task OnInitializedAsync(CancellationToken cancellationToken)
{
return Task.FromResult(true);
}

/// <summary>
/// Called when activating.
/// </summary>
[Obsolete("Override OnActivatedAsync")]
protected virtual Task OnActivateAsync(CancellationToken cancellationToken)
{
return Task.FromResult(true);
Expand Down