CmdPal: Pull out VM bits from ShellPage.xaml.cs (#40479)

ref #40113

Moves a lot of the "model" logic out of `ShellPage.xaml.cs` into
`ShellViewModel`.

The LARGE majority of this code is copy-paste moving code. We're now
using a couple more messages to pass navigation between the VM and the
page. And a couple new messages for passing ETW events.
This commit is contained in:
Mike Griese
2025-07-09 18:49:21 -05:00
committed by GitHub
parent f341aeb627
commit 608eb1e034
11 changed files with 453 additions and 286 deletions

View File

@@ -0,0 +1,7 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Microsoft.CmdPal.UI.ViewModels.Messages;
public record BeginInvokeMessage;

View File

@@ -0,0 +1,7 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Microsoft.CmdPal.UI.ViewModels.Messages;
public record CmdPalInvokeResultMessage(Microsoft.CommandPalette.Extensions.CommandResultKind Kind);

View File

@@ -0,0 +1,10 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Microsoft.CmdPal.UI.ViewModels.Messages;
public record GoBackMessage(bool WithAnimation = true, bool FocusSearch = true)
{
// TODO! sticking these properties here feels like leaking the UI into the models
}

View File

@@ -4,6 +4,7 @@
namespace Microsoft.CmdPal.UI.ViewModels.Messages;
public record GoHomeMessage()
// TODO! sticking these properties here feels like leaking the UI into the models
public record GoHomeMessage(bool WithAnimation = true, bool FocusSearch = true)
{
}

View File

@@ -0,0 +1,9 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Microsoft.CmdPal.UI.ViewModels.Messages;
public record NavigateToPageMessage(PageViewModel Page, bool WithAnimation)
{
}

View File

@@ -0,0 +1,9 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Microsoft.CmdPal.UI.ViewModels.Messages;
public record ShowConfirmationMessage(Microsoft.CommandPalette.Extensions.IConfirmationArgs? Args)
{
}

View File

@@ -0,0 +1,9 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Microsoft.CmdPal.UI.ViewModels.Messages;
public record ShowToastMessage(string Message)
{
}

View File

@@ -18,8 +18,14 @@ using WinRT;
namespace Microsoft.CmdPal.UI.ViewModels;
public partial class ShellViewModel(IServiceProvider _serviceProvider, TaskScheduler _scheduler) : ObservableObject
public partial class ShellViewModel : ObservableObject,
IRecipient<PerformCommandMessage>
{
private readonly IServiceProvider _serviceProvider;
private readonly TaskScheduler _scheduler;
private readonly Lock _invokeLock = new();
private Task? _handleInvokeTask;
[ObservableProperty]
public partial bool IsLoaded { get; set; } = false;
@@ -29,7 +35,7 @@ public partial class ShellViewModel(IServiceProvider _serviceProvider, TaskSched
[ObservableProperty]
public partial bool IsDetailsVisible { get; set; }
private PageViewModel _currentPage = new LoadingPageViewModel(null, _scheduler);
private PageViewModel _currentPage;
public PageViewModel CurrentPage
{
@@ -57,6 +63,19 @@ public partial class ShellViewModel(IServiceProvider _serviceProvider, TaskSched
private MainListPage? _mainListPage;
private IExtensionWrapper? _activeExtension;
private bool _isNested;
public bool IsNested { get => _isNested; }
public ShellViewModel(IServiceProvider serviceProvider, TaskScheduler scheduler)
{
_serviceProvider = serviceProvider;
_scheduler = scheduler;
_currentPage = new LoadingPageViewModel(null, _scheduler);
// Register to receive messages
WeakReferenceMessenger.Default.Register<PerformCommandMessage>(this);
}
[RelayCommand]
public async Task<bool> LoadAsync()
@@ -164,6 +183,241 @@ public partial class ShellViewModel(IServiceProvider _serviceProvider, TaskSched
}
}
public void Receive(PerformCommandMessage message)
{
PerformCommand(message);
}
private void PerformCommand(PerformCommandMessage message)
{
var command = message.Command.Unsafe;
if (command == null)
{
return;
}
if (!CurrentPage.IsNested)
{
// on the main page here
PerformTopLevelCommand(message);
}
IExtensionWrapper? extension = null;
try
{
// In the case that we're coming from a top-level command, the
// current page's host is the global instance. We only really want
// to use that as the host of last resort.
var pageHost = CurrentPage?.ExtensionHost;
if (pageHost == CommandPaletteHost.Instance)
{
pageHost = null;
}
var messageHost = message.ExtensionHost;
// Use the host from the current page if it has one, else use the
// one specified in the PerformMessage for a top-level command,
// else just use the global one.
CommandPaletteHost host;
// Top level items can come through without a Extension set on the
// message. In that case, the `Context` is actually the
// TopLevelViewModel itself, and we can use that to get at the
// extension object.
extension = pageHost?.Extension ?? messageHost?.Extension ?? null;
if (extension == null && message.Context is TopLevelViewModel topLevelViewModel)
{
extension = topLevelViewModel.ExtensionHost?.Extension;
host = pageHost ?? messageHost ?? topLevelViewModel?.ExtensionHost ?? CommandPaletteHost.Instance;
}
else
{
host = pageHost ?? messageHost ?? CommandPaletteHost.Instance;
}
if (extension != null)
{
if (messageHost != null)
{
Logger.LogDebug($"Activated top-level command from {extension.ExtensionDisplayName}");
}
else
{
Logger.LogDebug($"Activated command from {extension.ExtensionDisplayName}");
}
}
SetActiveExtension(extension);
if (command is IPage page)
{
Logger.LogDebug($"Navigating to page");
var isMainPage = command is MainListPage;
// Construct our ViewModel of the appropriate type and pass it the UI Thread context.
var pageViewModel = GetViewModelForPage(page, !isMainPage, host);
if (pageViewModel == null)
{
Logger.LogError($"Failed to create ViewModel for page {page.GetType().Name}");
throw new NotSupportedException();
}
// Kick off async loading of our ViewModel
LoadPageViewModel(pageViewModel);
_isNested = !isMainPage;
OnUIThread(() => { WeakReferenceMessenger.Default.Send<UpdateCommandBarMessage>(new(null)); });
WeakReferenceMessenger.Default.Send<NavigateToPageMessage>(new(pageViewModel, message.WithAnimation));
// Note: Originally we set our page back in the ViewModel here, but that now happens in response to the Frame navigating triggered from the above
// See RootFrame_Navigated event handler.
}
else if (command is IInvokableCommand invokable)
{
Logger.LogDebug($"Invoking command");
WeakReferenceMessenger.Default.Send<BeginInvokeMessage>();
StartInvoke(message, invokable);
}
}
catch (Exception ex)
{
// TODO: It would be better to do this as a page exception, rather
// than a silent log message.
CommandPaletteHost.Instance.Log(ex.Message);
}
}
private void StartInvoke(PerformCommandMessage message, IInvokableCommand invokable)
{
// TODO GH #525 This needs more better locking.
lock (_invokeLock)
{
if (_handleInvokeTask != null)
{
// do nothing - a command is already doing a thing
}
else
{
_handleInvokeTask = Task.Run(() =>
{
SafeHandleInvokeCommandSynchronous(message, invokable);
});
}
}
}
private void SafeHandleInvokeCommandSynchronous(PerformCommandMessage message, IInvokableCommand invokable)
{
try
{
// Call out to extension process.
// * May fail!
// * May never return!
var result = invokable.Invoke(message.Context);
// But if it did succeed, we need to handle the result.
UnsafeHandleCommandResult(result);
_handleInvokeTask = null;
}
catch (Exception ex)
{
_handleInvokeTask = null;
// TODO: It would be better to do this as a page exception, rather
// than a silent log message.
CommandPaletteHost.Instance.Log(ex.Message);
}
}
private void UnsafeHandleCommandResult(ICommandResult? result)
{
if (result == null)
{
// No result, nothing to do.
return;
}
var kind = result.Kind;
Logger.LogDebug($"handling {kind.ToString()}");
WeakReferenceMessenger.Default.Send<CmdPalInvokeResultMessage>(new(kind));
switch (kind)
{
case CommandResultKind.Dismiss:
{
// Reset the palette to the main page and dismiss
GoHome(withAnimation: false, focusSearch: false);
WeakReferenceMessenger.Default.Send<DismissMessage>();
break;
}
case CommandResultKind.GoHome:
{
// Go back to the main page, but keep it open
GoHome();
break;
}
case CommandResultKind.GoBack:
{
GoBack();
break;
}
case CommandResultKind.Hide:
{
// Keep this page open, but hide the palette.
WeakReferenceMessenger.Default.Send<DismissMessage>();
break;
}
case CommandResultKind.KeepOpen:
{
// Do nothing.
break;
}
case CommandResultKind.Confirm:
{
if (result.Args is IConfirmationArgs a)
{
WeakReferenceMessenger.Default.Send<ShowConfirmationMessage>(new(a));
}
break;
}
case CommandResultKind.ShowToast:
{
if (result.Args is IToastArgs a)
{
WeakReferenceMessenger.Default.Send<ShowToastMessage>(new(a.Message));
UnsafeHandleCommandResult(a.Result);
}
break;
}
}
}
private PageViewModel? GetViewModelForPage(IPage page, bool nested, CommandPaletteHost host)
{
return page switch
{
IListPage listPage => new ListViewModel(listPage, _scheduler, host)
{
IsNested = nested,
},
IContentPage contentPage => new ContentPageViewModel(contentPage, _scheduler, host),
_ => null,
};
}
public void SetActiveExtension(IExtensionWrapper? extension)
{
if (extension != _activeExtension)
@@ -196,9 +450,15 @@ public partial class ShellViewModel(IServiceProvider _serviceProvider, TaskSched
}
}
public void GoHome()
public void GoHome(bool withAnimation = true, bool focusSearch = true)
{
SetActiveExtension(null);
WeakReferenceMessenger.Default.Send<GoHomeMessage>(new(withAnimation, focusSearch));
}
public void GoBack(bool withAnimation = true, bool focusSearch = true)
{
WeakReferenceMessenger.Default.Send<GoBackMessage>(new(withAnimation, focusSearch));
}
// You may ask yourself, why aren't we using CsWin32 for this?
@@ -214,4 +474,13 @@ public partial class ShellViewModel(IServiceProvider _serviceProvider, TaskSched
[SupportedOSPlatform("windows5.0")]
internal static extern unsafe global::Windows.Win32.Foundation.HRESULT CoAllowSetForegroundWindow(nint pUnk, [Optional] void* lpvReserved);
}
private void OnUIThread(Action action)
{
_ = Task.Factory.StartNew(
action,
CancellationToken.None,
TaskCreationOptions.None,
_scheduler);
}
}

View File

@@ -144,6 +144,8 @@ public partial class App : Application
services.AddSingleton<IExtensionService, ExtensionService>();
services.AddSingleton<TrayIconService>();
services.AddSingleton(new TelemetryForwarder());
// ViewModels
services.AddSingleton<ShellViewModel>();

View File

@@ -0,0 +1,40 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.CmdPal.UI.Events;
using Microsoft.CmdPal.UI.ViewModels.Messages;
using Microsoft.PowerToys.Telemetry;
namespace Microsoft.CmdPal.UI;
/// <summary>
/// TelemetryForwarder is responsible for forwarding telemetry events from the
/// command palette core to PowerToys Telemetry.
/// This allows us to emit telemetry events as messages from the core,
/// and then handle them by logging to our PT telemetry provider.
///
/// We may in the future want to replace this with a more generic "ITelemetryService"
/// or something similar, but this works for now.
/// </summary>
internal sealed class TelemetryForwarder :
IRecipient<BeginInvokeMessage>,
IRecipient<CmdPalInvokeResultMessage>
{
public TelemetryForwarder()
{
WeakReferenceMessenger.Default.Register<BeginInvokeMessage>(this);
WeakReferenceMessenger.Default.Register<CmdPalInvokeResultMessage>(this);
}
public void Receive(CmdPalInvokeResultMessage message)
{
PowerToysTelemetry.Log.WriteEvent(new CmdPalInvokeResult(message.Kind));
}
public void Receive(BeginInvokeMessage message)
{
PowerToysTelemetry.Log.WriteEvent(new BeginInvoke());
}
}

View File

@@ -6,11 +6,9 @@ using System.ComponentModel;
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.WinUI;
using ManagedCommon;
using Microsoft.CmdPal.Common.Services;
using Microsoft.CmdPal.UI.Events;
using Microsoft.CmdPal.UI.Settings;
using Microsoft.CmdPal.UI.ViewModels;
using Microsoft.CmdPal.UI.ViewModels.MainPage;
using Microsoft.CmdPal.UI.ViewModels.Messages;
using Microsoft.CommandPalette.Extensions;
using Microsoft.Extensions.DependencyInjection;
@@ -28,15 +26,18 @@ namespace Microsoft.CmdPal.UI.Pages;
/// </summary>
public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
IRecipient<NavigateBackMessage>,
IRecipient<PerformCommandMessage>,
IRecipient<OpenSettingsMessage>,
IRecipient<HotkeySummonMessage>,
IRecipient<ShowDetailsMessage>,
IRecipient<HideDetailsMessage>,
IRecipient<ClearSearchMessage>,
IRecipient<HandleCommandResultMessage>,
IRecipient<LaunchUriMessage>,
IRecipient<SettingsWindowClosedMessage>,
IRecipient<GoHomeMessage>,
IRecipient<GoBackMessage>,
IRecipient<ShowConfirmationMessage>,
IRecipient<ShowToastMessage>,
IRecipient<NavigateToPageMessage>,
INotifyPropertyChanged
{
private readonly DispatcherQueue _queue = DispatcherQueue.GetForCurrentThread();
@@ -50,8 +51,6 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
private readonly ToastWindow _toast = new();
private readonly Lock _invokeLock = new();
private Task? _handleInvokeTask;
private SettingsWindow? _settingsWindow;
public ShellViewModel ViewModel { get; private set; } = App.Current.Services.GetService<ShellViewModel>()!;
@@ -64,8 +63,6 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
// how we are doing navigation around
WeakReferenceMessenger.Default.Register<NavigateBackMessage>(this);
WeakReferenceMessenger.Default.Register<PerformCommandMessage>(this);
WeakReferenceMessenger.Default.Register<HandleCommandResultMessage>(this);
WeakReferenceMessenger.Default.Register<OpenSettingsMessage>(this);
WeakReferenceMessenger.Default.Register<HotkeySummonMessage>(this);
WeakReferenceMessenger.Default.Register<SettingsWindowClosedMessage>(this);
@@ -76,6 +73,12 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
WeakReferenceMessenger.Default.Register<ClearSearchMessage>(this);
WeakReferenceMessenger.Default.Register<LaunchUriMessage>(this);
WeakReferenceMessenger.Default.Register<GoHomeMessage>(this);
WeakReferenceMessenger.Default.Register<GoBackMessage>(this);
WeakReferenceMessenger.Default.Register<ShowConfirmationMessage>(this);
WeakReferenceMessenger.Default.Register<ShowToastMessage>(this);
WeakReferenceMessenger.Default.Register<NavigateToPageMessage>(this);
RootFrame.Navigate(typeof(LoadingPage), ViewModel);
}
@@ -103,195 +106,72 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
}
}
public void Receive(PerformCommandMessage message)
public void Receive(NavigateToPageMessage message)
{
PerformCommand(message);
// TODO GH #526 This needs more better locking too
_ = _queue.TryEnqueue(() =>
{
// Also hide our details pane about here, if we had one
HideDetails();
// Navigate to the appropriate host page for that VM
RootFrame.Navigate(
message.Page switch
{
ListViewModel => typeof(ListPage),
ContentPageViewModel => typeof(ContentPage),
_ => throw new NotSupportedException(),
},
message.Page,
message.WithAnimation ? _slideRightTransition : _noAnimation);
PowerToysTelemetry.Log.WriteEvent(new OpenPage(RootFrame.BackStackDepth));
// Refocus on the Search for continual typing on the next search request
SearchBox.Focus(Microsoft.UI.Xaml.FocusState.Programmatic);
if (!ViewModel.IsNested)
{
// todo BODGY
RootFrame.BackStack.Clear();
}
});
}
private void PerformCommand(PerformCommandMessage message)
public void Receive(ShowConfirmationMessage message)
{
var command = message.Command.Unsafe;
if (command == null)
DispatcherQueue.TryEnqueue(async () =>
{
try
{
await HandleConfirmArgsOnUiThread(message.Args);
}
catch (Exception ex)
{
Logger.LogError(ex.ToString());
}
});
}
public void Receive(ShowToastMessage message)
{
DispatcherQueue.TryEnqueue(() =>
{
_toast.ShowToast(message.Message);
});
}
// This gets called from the UI thread
private async Task HandleConfirmArgsOnUiThread(IConfirmationArgs? args)
{
if (args == null)
{
return;
}
if (!ViewModel.CurrentPage.IsNested)
{
// on the main page here
ViewModel.PerformTopLevelCommand(message);
}
IExtensionWrapper? extension = null;
// TODO: Actually loading up the page, or invoking the command -
// that might belong in the model, not the view?
// Especially considering the try/catch concerns around the fact that the
// COM call might just fail.
// Or the command may be a stub. Future us problem.
try
{
// In the case that we're coming from a top-level command, the
// current page's host is the global instance. We only really want
// to use that as the host of last resort.
var pageHost = ViewModel.CurrentPage?.ExtensionHost;
if (pageHost == CommandPaletteHost.Instance)
{
pageHost = null;
}
var messageHost = message.ExtensionHost;
// Use the host from the current page if it has one, else use the
// one specified in the PerformMessage for a top-level command,
// else just use the global one.
CommandPaletteHost host;
// Top level items can come through without a Extension set on the
// message. In that case, the `Context` is actually the
// TopLevelViewModel itself, and we can use that to get at the
// extension object.
extension = pageHost?.Extension ?? messageHost?.Extension ?? null;
if (extension == null && message.Context is TopLevelViewModel topLevelViewModel)
{
extension = topLevelViewModel.ExtensionHost?.Extension;
host = pageHost ?? messageHost ?? topLevelViewModel?.ExtensionHost ?? CommandPaletteHost.Instance;
}
else
{
host = pageHost ?? messageHost ?? CommandPaletteHost.Instance;
}
if (extension != null)
{
if (messageHost != null)
{
Logger.LogDebug($"Activated top-level command from {extension.ExtensionDisplayName}");
}
else
{
Logger.LogDebug($"Activated command from {extension.ExtensionDisplayName}");
}
}
ViewModel.SetActiveExtension(extension);
if (command is IPage page)
{
Logger.LogDebug($"Navigating to page");
// TODO GH #526 This needs more better locking too
_ = _queue.TryEnqueue(() =>
{
// Also hide our details pane about here, if we had one
HideDetails();
WeakReferenceMessenger.Default.Send<UpdateCommandBarMessage>(new(null));
var isMainPage = command is MainListPage;
// Construct our ViewModel of the appropriate type and pass it the UI Thread context.
PageViewModel pageViewModel = page switch
{
IListPage listPage => new ListViewModel(listPage, _mainTaskScheduler, host)
{
IsNested = !isMainPage,
},
IContentPage contentPage => new ContentPageViewModel(contentPage, _mainTaskScheduler, host),
_ => throw new NotSupportedException(),
};
// Kick off async loading of our ViewModel
ViewModel.LoadPageViewModel(pageViewModel);
// Navigate to the appropriate host page for that VM
RootFrame.Navigate(
page switch
{
IListPage => typeof(ListPage),
IContentPage => typeof(ContentPage),
_ => throw new NotSupportedException(),
},
pageViewModel,
message.WithAnimation ? _slideRightTransition : _noAnimation);
PowerToysTelemetry.Log.WriteEvent(new OpenPage(RootFrame.BackStackDepth));
// Refocus on the Search for continual typing on the next search request
SearchBox.Focus(Microsoft.UI.Xaml.FocusState.Programmatic);
if (isMainPage)
{
// todo BODGY
RootFrame.BackStack.Clear();
}
// Note: Originally we set our page back in the ViewModel here, but that now happens in response to the Frame navigating triggered from the above
// See RootFrame_Navigated event handler.
});
}
else if (command is IInvokableCommand invokable)
{
Logger.LogDebug($"Invoking command");
PowerToysTelemetry.Log.WriteEvent(new BeginInvoke());
HandleInvokeCommand(message, invokable);
}
}
catch (Exception ex)
{
// TODO: It would be better to do this as a page exception, rather
// than a silent log message.
CommandPaletteHost.Instance.Log(ex.Message);
}
}
private void HandleInvokeCommand(PerformCommandMessage message, IInvokableCommand invokable)
{
// TODO GH #525 This needs more better locking.
lock (_invokeLock)
{
if (_handleInvokeTask != null)
{
// do nothing - a command is already doing a thing
}
else
{
_handleInvokeTask = Task.Run(() =>
{
try
{
var result = invokable.Invoke(message.Context);
DispatcherQueue.TryEnqueue(() =>
{
try
{
HandleCommandResultOnUiThread(result);
}
finally
{
_handleInvokeTask = null;
}
});
}
catch (Exception ex)
{
_handleInvokeTask = null;
// TODO: It would be better to do this as a page exception, rather
// than a silent log message.
CommandPaletteHost.Instance.Log(ex.Message);
}
});
}
}
}
// This gets called from the UI thread
private void HandleConfirmArgs(IConfirmationArgs args)
{
ConfirmResultViewModel vm = new(args, new(ViewModel.CurrentPage));
var initializeDialogTask = Task.Run(() => { InitializeConfirmationDialog(vm); });
initializeDialogTask.Wait();
await initializeDialogTask;
var resourceLoader = Microsoft.CmdPal.UI.Helpers.ResourceLoaderInstance.ResourceLoader;
var confirmText = resourceLoader.GetString("ConfirmationDialog_ConfirmButtonText");
@@ -322,19 +202,16 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
// };
}
DispatcherQueue.TryEnqueue(async () =>
var result = await dialog.ShowAsync();
if (result == ContentDialogResult.Primary)
{
var result = await dialog.ShowAsync();
if (result == ContentDialogResult.Primary)
{
var performMessage = new PerformCommandMessage(vm);
PerformCommand(performMessage);
}
else
{
// cancel
}
});
var performMessage = new PerformCommandMessage(vm);
WeakReferenceMessenger.Default.Send(performMessage);
}
else
{
// cancel
}
}
private void InitializeConfirmationDialog(ConfirmResultViewModel vm)
@@ -342,79 +219,6 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
vm.SafeInitializePropertiesSynchronous();
}
private void HandleCommandResultOnUiThread(ICommandResult? result)
{
try
{
if (result != null)
{
var kind = result.Kind;
Logger.LogDebug($"handling {kind.ToString()}");
PowerToysTelemetry.Log.WriteEvent(new CmdPalInvokeResult(kind));
switch (kind)
{
case CommandResultKind.Dismiss:
{
// Reset the palette to the main page and dismiss
GoHome(withAnimation: false, focusSearch: false);
WeakReferenceMessenger.Default.Send<DismissMessage>();
break;
}
case CommandResultKind.GoHome:
{
// Go back to the main page, but keep it open
GoHome();
break;
}
case CommandResultKind.GoBack:
{
GoBack();
break;
}
case CommandResultKind.Hide:
{
// Keep this page open, but hide the palette.
WeakReferenceMessenger.Default.Send<DismissMessage>();
break;
}
case CommandResultKind.KeepOpen:
{
// Do nothing.
break;
}
case CommandResultKind.Confirm:
{
if (result.Args is IConfirmationArgs a)
{
HandleConfirmArgs(a);
}
break;
}
case CommandResultKind.ShowToast:
{
if (result.Args is IToastArgs a)
{
_toast.ShowToast(a.Message);
HandleCommandResultOnUiThread(a.Result);
}
break;
}
}
}
}
catch
{
}
}
public void Receive(OpenSettingsMessage message)
{
_ = DispatcherQueue.TryEnqueue(() =>
@@ -467,14 +271,6 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
public void Receive(LaunchUriMessage message) => _ = global::Windows.System.Launcher.LaunchUriAsync(message.Uri);
public void Receive(HandleCommandResultMessage message)
{
DispatcherQueue.TryEnqueue(() =>
{
HandleCommandResultOnUiThread(message.Result.Unsafe);
});
}
private void HideDetails()
{
ViewModel.Details = null;
@@ -554,6 +350,11 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
WeakReferenceMessenger.Default.Send<FocusSearchBoxMessage>();
}
public void Receive(GoBackMessage message)
{
_ = DispatcherQueue.TryEnqueue(() => GoBack(message.WithAnimation, message.FocusSearch));
}
private void GoBack(bool withAnimation = true, bool focusSearch = true)
{
HideDetails();
@@ -591,14 +392,17 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
}
}
public void Receive(GoHomeMessage message)
{
_ = DispatcherQueue.TryEnqueue(() => GoHome(withAnimation: message.WithAnimation, focusSearch: message.FocusSearch));
}
private void GoHome(bool withAnimation = true, bool focusSearch = true)
{
while (RootFrame.CanGoBack)
{
GoBack(withAnimation, focusSearch);
}
WeakReferenceMessenger.Default.Send<GoHomeMessage>();
}
private void BackButton_Tapped(object sender, Microsoft.UI.Xaml.Input.TappedRoutedEventArgs e) => WeakReferenceMessenger.Default.Send<NavigateBackMessage>(new());