CmdPal: Add context commands for pinning nested commands (#45673)

_targets #45572_

This change allows our contact menu factory to actually create and add
additional context menu commands for pinning commands to the top level.
Now for any command provider built with the latest SDK that return
subcommands with an ID, we will add additional context menu commands
that allows you to pin that command to the top level.

<img width="540" height="181" alt="image"
src="https://github.com/user-attachments/assets/6c2cfe3c-4143-44d1-9308-bfc71db4c842"
/>
<img width="729" height="317" alt="image"
src="https://github.com/user-attachments/assets/4ff75c9f-1f35-4c1e-a03e-6fab5cbab423"
/>

related to https://github.com/microsoft/PowerToys/issues/45191
related to https://github.com/microsoft/PowerToys/issues/45201


This PR notably does not remove pinning from the apps extension. I
thought that made sense to do as a follow-up PR for the sake of
reviewability.

--- 

description from #45676 which was merged into this

Removes the code that the apps provider was using to support pinning
apps to the top level list of commands. Now the all apps provider just
uses the global support for pinning commands to the top level.

This does have the side effect of removing the separation of pinned apps
from unpinned apps on the All Apps page. However, we all pretty much
agree that wasn't a particularly widely used feature, and it's safe to
remove.

With this, we can finally call this issue done 🎉
closes https://github.com/microsoft/PowerToys/issues/45191
This commit is contained in:
Mike Griese
2026-02-26 10:09:17 -06:00
committed by GitHub
parent cdeae7c854
commit 7a0e4ac891
30 changed files with 389 additions and 434 deletions

View File

@@ -36,7 +36,7 @@ public partial class ListViewModel : PageViewModel, IDisposable
private readonly ExtensionObject<IListPage> _model;
private readonly Lock _listLock = new();
private readonly IContextMenuFactory? _contextMenuFactory;
private readonly IContextMenuFactory _contextMenuFactory;
private InterlockedBoolean _isLoading;
private bool _isFetching;
@@ -96,12 +96,12 @@ public partial class ListViewModel : PageViewModel, IDisposable
}
}
public ListViewModel(IListPage model, TaskScheduler scheduler, AppExtensionHost host, CommandProviderContext providerContext, IContextMenuFactory? contextMenuFactory)
public ListViewModel(IListPage model, TaskScheduler scheduler, AppExtensionHost host, ICommandProviderContext providerContext, IContextMenuFactory contextMenuFactory)
: base(model, scheduler, host, providerContext)
{
_model = new(model);
_contextMenuFactory = contextMenuFactory;
EmptyContent = new(new(null), PageContext, _contextMenuFactory);
EmptyContent = new(new(null), PageContext, contextMenuFactory: null);
}
private void FiltersPropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
@@ -243,7 +243,7 @@ public partial class ListViewModel : PageViewModel, IDisposable
continue;
}
var viewModel = new ListItemViewModel(item, new(this));
var viewModel = new ListItemViewModel(item, new(this), _contextMenuFactory);
// If an item fails to load, silently ignore it.
if (viewModel.SafeFastInit())
@@ -636,7 +636,7 @@ public partial class ListViewModel : PageViewModel, IDisposable
UpdateProperty(nameof(SearchText));
UpdateProperty(nameof(InitialSearchText));
EmptyContent = new(new(model.EmptyContent), PageContext);
EmptyContent = new(new(model.EmptyContent), PageContext, _contextMenuFactory);
EmptyContent.SlowInitializeProperties();
Filters?.PropertyChanged -= FiltersPropertyChanged;
@@ -732,7 +732,7 @@ public partial class ListViewModel : PageViewModel, IDisposable
SearchText = model.SearchText;
break;
case nameof(EmptyContent):
EmptyContent = new(new(model.EmptyContent), PageContext);
EmptyContent = new(new(model.EmptyContent), PageContext, contextMenuFactory: null);
EmptyContent.SlowInitializeProperties();
break;
case nameof(Filters):
@@ -806,7 +806,7 @@ public partial class ListViewModel : PageViewModel, IDisposable
base.UnsafeCleanup();
EmptyContent?.SafeCleanup();
EmptyContent = new(new(null), PageContext); // necessary?
EmptyContent = new(new(null), PageContext, contextMenuFactory: null); // necessary?
_cancellationTokenSource?.Cancel();
filterCancellationTokenSource?.Cancel();