Skip to content

Commit

Permalink
Invoke INavigationAware callbacks on UI thread (#1307)
Browse files Browse the repository at this point in the history
  • Loading branch information
elliot-gawthrop authored Feb 1, 2025
1 parent 58f3e36 commit 1e4fe2a
Show file tree
Hide file tree
Showing 3 changed files with 21 additions and 48 deletions.
28 changes: 6 additions & 22 deletions samples/Wpf.Ui.Demo.Mvvm/ViewModels/ViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ namespace Wpf.Ui.Demo.Mvvm.ViewModels;
public abstract class ViewModel : ObservableObject, INavigationAware
{
/// <inheritdoc />
public virtual async Task OnNavigatedToAsync()
public virtual Task OnNavigatedToAsync()
{
using CancellationTokenSource cts = new();
OnNavigatedTo();

await DispatchAsync(OnNavigatedTo, cts.Token);
return Task.CompletedTask;
}

/// <summary>
Expand All @@ -24,32 +24,16 @@ public virtual async Task OnNavigatedToAsync()
public virtual void OnNavigatedTo() { }

/// <inheritdoc />
public virtual async Task OnNavigatedFromAsync()
public virtual Task OnNavigatedFromAsync()
{
using CancellationTokenSource cts = new();
OnNavigatedFrom();

await DispatchAsync(OnNavigatedFrom, cts.Token);
return Task.CompletedTask;
}

/// <summary>
/// Handles the event that is fired before the component is navigated from.
/// </summary>
// ReSharper disable once MemberCanBeProtected.Global
public virtual void OnNavigatedFrom() { }

/// <summary>
/// Dispatches the specified action on the UI thread.
/// </summary>
/// <param name="action">The action to be dispatched.</param>
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
/// <returns>A task that represents the asynchronous operation.</returns>
protected static async Task DispatchAsync(Action action, CancellationToken cancellationToken)
{
if (cancellationToken.IsCancellationRequested)
{
return;
}

await Application.Current.Dispatcher.InvokeAsync(action);
}
}
28 changes: 6 additions & 22 deletions src/Wpf.Ui.Gallery/ViewModels/ViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ namespace Wpf.Ui.Gallery.ViewModels;
public abstract partial class ViewModel : ObservableObject, INavigationAware
{
/// <inheritdoc />
public virtual async Task OnNavigatedToAsync()
public virtual Task OnNavigatedToAsync()
{
using CancellationTokenSource cts = new();
OnNavigatedTo();

await DispatchAsync(OnNavigatedTo, cts.Token);
return Task.CompletedTask;
}

/// <summary>
Expand All @@ -22,32 +22,16 @@ public virtual async Task OnNavigatedToAsync()
public virtual void OnNavigatedTo() { }

/// <inheritdoc />
public virtual async Task OnNavigatedFromAsync()
public virtual Task OnNavigatedFromAsync()
{
using CancellationTokenSource cts = new();
OnNavigatedFrom();

await DispatchAsync(OnNavigatedFrom, cts.Token);
return Task.CompletedTask;
}

/// <summary>
/// Handles the event that is fired before the component is navigated from.
/// </summary>
// ReSharper disable once MemberCanBeProtected.Global
public virtual void OnNavigatedFrom() { }

/// <summary>
/// Dispatches the specified action on the UI thread.
/// </summary>
/// <param name="action">The action to be dispatched.</param>
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
/// <returns>A task that represents the asynchronous operation.</returns>
protected static async Task DispatchAsync(Action action, CancellationToken cancellationToken)
{
if (cancellationToken.IsCancellationRequested)
{
return;
}

await Application.Current.Dispatcher.InvokeAsync(action);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -211,27 +211,32 @@ private static void NotifyContentAboutNavigatingFrom(object content)
)]
private static void NotifyContentAboutNavigating(object content, Func<INavigationAware, Task> function)
{
async void PerformNotify(INavigationAware navigationAware)
{
await function(navigationAware).ConfigureAwait(false);
}

switch (content)
{
// The order in which the OnNavigatedToAsync/OnNavigatedFromAsync methods of View and ViewModel are called
// is not guaranteed
case INavigationAware navigationAwareNavigationContent:
_ = Task.Run(() => function(navigationAwareNavigationContent)).ConfigureAwait(false);
PerformNotify(navigationAwareNavigationContent);
if (
navigationAwareNavigationContent
is FrameworkElement { DataContext: INavigationAware viewModel }
&& !ReferenceEquals(viewModel, navigationAwareNavigationContent)
)
{
_ = Task.Run(() => function(viewModel)).ConfigureAwait(false);
PerformNotify(viewModel);
}

break;
case INavigableView<object> { ViewModel: INavigationAware navigationAwareNavigableViewViewModel }:
_ = Task.Run(() => function(navigationAwareNavigableViewViewModel)).ConfigureAwait(false);
PerformNotify(navigationAwareNavigableViewViewModel);
break;
case FrameworkElement { DataContext: INavigationAware navigationAwareCurrentContent }:
_ = Task.Run(() => function(navigationAwareCurrentContent)).ConfigureAwait(false);
PerformNotify(navigationAwareCurrentContent);
break;
}
}
Expand Down

0 comments on commit 1e4fe2a

Please sign in to comment.