mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-03 09:46:54 +02:00
CmdPal: Handle CommandItem Title changes properly and raise notification every time it changes (#40513)
<!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? --> ## Summary of the Pull Request <!-- Please review the items on the PR checklist before submitting--> ## PR Checklist - [x] **Closes:** #39167 - [ ] **Communication:** I've discussed this with core contributors already. If the work hasn't been agreed, this work might be rejected - [ ] **Tests:** Added/updated and all pass - [ ] **Localization:** All end-user-facing strings can be localized - [ ] **Dev docs:** Added/updated - [ ] **New binaries:** Added on the required places - [ ] [JSON for signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json) for new binaries - [ ] [WXS for installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs) for new binaries and localization folder - [ ] [YML for CI pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml) for new test projects - [ ] [YML for signed pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml) - [ ] **Documentation updated:** If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys) and link it here: #xxx <!-- Provide a more detailed description of the PR, other things fixed, or any additional comments/features here --> ## Detailed Description of the Pull Request / Additional comments <!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well --> ## Validation Steps Performed
This commit is contained in:
@@ -313,6 +313,10 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
|
||||
|
||||
Command = new(model.Command, PageContext);
|
||||
Command.InitializeProperties();
|
||||
|
||||
// Extensions based on Command Palette SDK < 0.3 CommandItem class won't notify when Title changes because Command
|
||||
// or Command.Name change. This is a workaround to ensure that the Title is always up-to-date for extensions with old SDK.
|
||||
_itemTitle = model.Title;
|
||||
UpdateProperty(nameof(Name));
|
||||
UpdateProperty(nameof(Title));
|
||||
UpdateProperty(nameof(Icon));
|
||||
@@ -385,6 +389,14 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
|
||||
switch (propertyName)
|
||||
{
|
||||
case nameof(Command.Name):
|
||||
// Extensions based on Command Palette SDK < 0.3 CommandItem class won't notify when Title changes because Command
|
||||
// or Command.Name change. This is a workaround to ensure that the Title is always up-to-date for extensions with old SDK.
|
||||
var model = _commandItemModel.Unsafe;
|
||||
if (model != null)
|
||||
{
|
||||
_itemTitle = model.Title;
|
||||
}
|
||||
|
||||
UpdateProperty(nameof(Title));
|
||||
UpdateProperty(nameof(Name));
|
||||
break;
|
||||
|
||||
@@ -7,6 +7,8 @@ namespace Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
public partial class CommandItem : BaseObservable, ICommandItem
|
||||
{
|
||||
private ICommand? _command;
|
||||
private WeakEventListener<CommandItem, object, IPropChangedEventArgs>? _commandListener;
|
||||
private string _title = string.Empty;
|
||||
|
||||
public virtual IIconInfo? Icon
|
||||
{
|
||||
@@ -20,17 +22,15 @@ public partial class CommandItem : BaseObservable, ICommandItem
|
||||
|
||||
public virtual string Title
|
||||
{
|
||||
get => !string.IsNullOrEmpty(field) ? field : _command?.Name ?? string.Empty;
|
||||
get => !string.IsNullOrEmpty(_title) ? _title : _command?.Name ?? string.Empty;
|
||||
|
||||
set
|
||||
{
|
||||
field = value;
|
||||
_title = value;
|
||||
OnPropertyChanged(nameof(Title));
|
||||
}
|
||||
}
|
||||
|
||||
= string.Empty;
|
||||
|
||||
public virtual string Subtitle
|
||||
{
|
||||
get;
|
||||
@@ -48,8 +48,33 @@ public partial class CommandItem : BaseObservable, ICommandItem
|
||||
get => _command;
|
||||
set
|
||||
{
|
||||
if (_commandListener != null)
|
||||
{
|
||||
_commandListener.Detach();
|
||||
_commandListener = null;
|
||||
}
|
||||
|
||||
_command = value;
|
||||
|
||||
if (value != null)
|
||||
{
|
||||
_commandListener = new(this, OnCommandPropertyChanged, listener => value.PropChanged -= listener.OnEvent);
|
||||
value.PropChanged += _commandListener.OnEvent;
|
||||
}
|
||||
|
||||
OnPropertyChanged(nameof(Command));
|
||||
if (string.IsNullOrWhiteSpace(_title))
|
||||
{
|
||||
OnPropertyChanged(nameof(Title));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnCommandPropertyChanged(CommandItem instance, object source, IPropChangedEventArgs args)
|
||||
{
|
||||
if (args.PropertyName == nameof(ICommand.Name))
|
||||
{
|
||||
instance.OnPropertyChanged(nameof(Title));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
// 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 System.ComponentModel;
|
||||
|
||||
namespace Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
/// <summary>
|
||||
/// Implements a weak event listener that allows the owner to be garbage
|
||||
/// collected if its only remaining link is an event handler.
|
||||
/// </summary>
|
||||
/// <typeparam name="TInstance">Type of instance listening for the event.</typeparam>
|
||||
/// <typeparam name="TSource">Type of source for the event.</typeparam>
|
||||
/// <typeparam name="TEventArgs">Type of event arguments for the event.</typeparam>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
internal sealed class WeakEventListener<TInstance, TSource, TEventArgs>
|
||||
where TInstance : class
|
||||
{
|
||||
/// <summary>
|
||||
/// WeakReference to the instance listening for the event.
|
||||
/// </summary>
|
||||
private readonly WeakReference<TInstance> _weakInstance;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="WeakEventListener{TInstance, TSource, TEventArgs}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="instance">Instance subscribing to the event.</param>
|
||||
/// <param name="onEventAction">Event handler executed when event is raised.</param>
|
||||
/// <param name="onDetachAction">Action to execute when instance was collected.</param>
|
||||
public WeakEventListener(
|
||||
TInstance instance,
|
||||
Action<TInstance, TSource, TEventArgs>? onEventAction = null,
|
||||
Action<WeakEventListener<TInstance, TSource, TEventArgs>>? onDetachAction = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(instance);
|
||||
|
||||
_weakInstance = new(instance);
|
||||
OnEventAction = onEventAction;
|
||||
OnDetachAction = onDetachAction;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the method to call when the event fires.
|
||||
/// </summary>
|
||||
public Action<TInstance, TSource, TEventArgs>? OnEventAction { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the method to call when detaching from the event.
|
||||
/// </summary>
|
||||
public Action<WeakEventListener<TInstance, TSource, TEventArgs>>? OnDetachAction { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Handler for the subscribed event calls OnEventAction to handle it.
|
||||
/// </summary>
|
||||
/// <param name="source">Event source.</param>
|
||||
/// <param name="eventArgs">Event arguments.</param>
|
||||
public void OnEvent(TSource source, TEventArgs eventArgs)
|
||||
{
|
||||
if (_weakInstance.TryGetTarget(out var target))
|
||||
{
|
||||
// Call registered action
|
||||
OnEventAction?.Invoke(target, source, eventArgs);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Detach from event
|
||||
Detach();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Detaches from the subscribed event.
|
||||
/// </summary>
|
||||
public void Detach()
|
||||
{
|
||||
OnDetachAction?.Invoke(this);
|
||||
OnDetachAction = null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user