|
| 1 | +--- |
| 2 | +title: Update a list of commands |
| 3 | +description: Learn how to add new commands to your Command Palette extension. |
| 4 | +ms.date: 3/23/2025 |
| 5 | +ms.topic: concept-article |
| 6 | +no-loc: [PowerToys, Windows, Insider] |
| 7 | +# Customer intent: As a Windows developer, I want to learn how to develop an extension for the Command Palette. |
| 8 | +--- |
| 9 | + |
| 10 | +# Update a list of commands |
| 11 | + |
| 12 | +**Previous**: [Adding commands](adding-commands.md). |
| 13 | + |
| 14 | +So far we've shown how to return a list of static items in your extension. However, your items can also change, to show real-time data, or to reflect the state of the system. In this article, we'll show you how to update the list of commands in your extension. |
| 15 | + |
| 16 | +## Updating a command |
| 17 | + |
| 18 | +Almost all extension objects in the Command Palette implement the `IPropChanged` interface. This allows them to notify the Command Palette when they've changed, and the Command Palette will update the UI to reflect those changes. If you're using the toolkit implementations, this interface has already been implemented for you for properties that support it. |
| 19 | + |
| 20 | +As a simple example, you can update the title of the page. To do this, you can add a command which will simply update the title of the page. |
| 21 | + |
| 22 | +```csharp |
| 23 | +public override IListItem[] GetItems() |
| 24 | +{ |
| 25 | + OpenUrlCommand command = new("https://learn.microsoft.com/windows/powertoys/command-palette/creating-an-extension"); |
| 26 | + |
| 27 | + AnonymousCommand updateCommand = new(action: () => { Title += " Hello"; }) { Result = CommandResult.KeepOpen() }; |
| 28 | + |
| 29 | + return [ |
| 30 | + new ListItem(command) |
| 31 | + { |
| 32 | + Title = "Open the Command Palette documentation", |
| 33 | + }, |
| 34 | + new ListItem(updateCommand), |
| 35 | + ]; |
| 36 | +} |
| 37 | +``` |
| 38 | + |
| 39 | +Here, we're using `AnonymousCommand` to create a command that will update the title of the page. `AnonymousCommand` is a helper that's useful for creating simple, lightweight commands that don't need to be reused. |
| 40 | + |
| 41 | +You can of course create custom `ListItem`s too: |
| 42 | + |
| 43 | +```csharp |
| 44 | +internal sealed partial class IncrementingListItem : ListItem |
| 45 | +{ |
| 46 | + public IncrementingListItem() : |
| 47 | + base(new NoOpCommand()) |
| 48 | + { |
| 49 | + Command = new AnonymousCommand(action: Increment) { Result = CommandResult.KeepOpen() }; |
| 50 | + Title = "Increment"; |
| 51 | + } |
| 52 | + |
| 53 | + private void Increment() |
| 54 | + { |
| 55 | + Subtitle = $"Count = {++_count}"; |
| 56 | + } |
| 57 | + |
| 58 | + private int _count; |
| 59 | +} |
| 60 | +``` |
| 61 | + |
| 62 | +```diff |
| 63 | + public override IListItem[] GetItems() |
| 64 | + { |
| 65 | + OpenUrlCommand command = new("https://learn.microsoft.com/windows/powertoys/command-palette/creating-an-extension"); |
| 66 | + return [ |
| 67 | + new ListItem(command) |
| 68 | + { |
| 69 | + Title = "Open the Command Palette documentation", |
| 70 | + }, |
| 71 | + new ListItem(new ShowMessageCommand()), |
| 72 | ++ new IncrementingListItem(), |
| 73 | + ]; |
| 74 | + } |
| 75 | +``` |
| 76 | + |
| 77 | +You're on your way to creating your own idle clicker game, as a Command Palette extension. |
| 78 | + |
| 79 | +## Updating the list of commands |
| 80 | + |
| 81 | +You can also change the list of items on the page. This can be useful for pages that load results asynchronously, or for pages that show different commands based on the state of the app. |
| 82 | + |
| 83 | +To do this, you can use the `RaiseItemsChanged` method on the `ListPage` object. This will notify the Command Palette that the list of items has changed, and it should re-fetch the list of items. As an example, let's make the `IncrementingListItem` from above update the list of items on the page. |
| 84 | + |
| 85 | +Update your list item to take a reference to the page, and add a method to increment the count: |
| 86 | +```csharp |
| 87 | +internal sealed partial class IncrementingListItem : ListItem |
| 88 | +{ |
| 89 | + public IncrementingListItem(ExtensionNamePage page) : |
| 90 | + base(new NoOpCommand()) |
| 91 | + { |
| 92 | + _page = page; |
| 93 | + Command = new AnonymousCommand(action: _page.Increment) { Result = CommandResult.KeepOpen() }; |
| 94 | + Title = "Increment"; |
| 95 | + } |
| 96 | + |
| 97 | + private ExtensionNamePage _page; |
| 98 | +} |
| 99 | +``` |
| 100 | + |
| 101 | +Then, change your page as follows: |
| 102 | + |
| 103 | +```cs |
| 104 | +public ExtensionNamePage() |
| 105 | +{ |
| 106 | + Icon = IconHelpers.FromRelativePath("Assets\\StoreLogo.png"); |
| 107 | + Title = "My sample extension"; |
| 108 | + Name = "Open"; |
| 109 | + |
| 110 | + _items = [new IncrementingListItem2this) { Subtitle = $"Item 0" }]; |
| 111 | +} |
| 112 | +public override IListItem[] GetItems() |
| 113 | +{ |
| 114 | + return _items.ToArray(); |
| 115 | +} |
| 116 | +internal void Increment() |
| 117 | +{ |
| 118 | + _items += new IncrementingListItem(this) { Subtitle = $"Item {_items.Count}" }; |
| 119 | + RaiseItemsChanged(); |
| 120 | +} |
| 121 | +private List<ListItem> _items; |
| 122 | +``` |
| 123 | + |
| 124 | +Now, every time you perform one of the `IncrementingListItem` commands, the list of items on the page will update to add another item. We're using a single `List` owned by the page to own all the items. Notably, we're creating the new items in the `Increment` method, before calling `RaiseItemsChanged`. The `Invoke` of a `IInvokableCommand` can take as long as you'd like. All your code is running in a separate process from the Command Palette, so you won't block the UI. But constructing the items before calling `RaiseItemsChanged` will help keep your extension _feeling_ more responsive. |
| 125 | + |
| 126 | +## Showing a loading spinner |
| 127 | + |
| 128 | +Everything so far has been pretty instantaneous. Many extensions however may need to do some work that takes a lot longer. In that case, you can set `Page.IsLoading` to `true` to show a loading spinner. This will help indicate that the extension is doing something in the background. |
| 129 | + |
| 130 | +```csharp |
| 131 | +internal void Increment() |
| 132 | +{ |
| 133 | + Page.IsLoading = true; |
| 134 | + Task.Run(() => |
| 135 | + { |
| 136 | + Thread.Sleep(5000); |
| 137 | + _items += new IncrementingListItem(this) { Subtitle = $"Item {_items.Count}" }; |
| 138 | + RaiseItemsChanged(); |
| 139 | + Page.IsLoading = false; |
| 140 | + }); |
| 141 | +} |
| 142 | +``` |
| 143 | + |
| 144 | +Best practice is to set `IsLoading` to `true` before starting the work. Then do all the work to build all the new `ListItems` you need to display to the user. Then, once the items are ready, call `RaiseItemsChanged` and set `IsLoading` back to `false`. This will ensure that the loading spinner is shown for the entire duration of the work, and that the UI is updated as soon as the work is done (without waiting for your extension to construct new `ListItem` objects). |
| 145 | + |
| 146 | +### Next up: [Command results](command-results.md) |
| 147 | + |
| 148 | +## Related content |
| 149 | + |
| 150 | +- [PowerToys Command Palette utility](overview.md) |
| 151 | +- [Extensibility overview](extensibility-overview.md) |
| 152 | +- [Extension samples](samples.md) |
0 commit comments