diff --git a/src/modules/cmdpal/Microsoft.CmdPal.Common/Messages/GetHwndMessage.cs b/src/modules/cmdpal/Microsoft.CmdPal.Common/Messages/GetHwndMessage.cs new file mode 100644 index 0000000000..f258f44470 --- /dev/null +++ b/src/modules/cmdpal/Microsoft.CmdPal.Common/Messages/GetHwndMessage.cs @@ -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.Common.Messages; + +public partial class GetHwndMessage +{ + public nint Hwnd { get; set; } = 0; +} diff --git a/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/ParametersViewModels.cs b/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/ParametersViewModels.cs index 438970ca30..f438eaf60d 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/ParametersViewModels.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.Core.ViewModels/ParametersViewModels.cs @@ -4,6 +4,7 @@ using System.Collections.ObjectModel; using CommunityToolkit.Mvvm.Messaging; +using Microsoft.CmdPal.Common.Messages; using Microsoft.CmdPal.Core.ViewModels.Messages; using Microsoft.CmdPal.Core.ViewModels.Models; using Microsoft.CommandPalette.Extensions; @@ -17,15 +18,51 @@ namespace Microsoft.CmdPal.Core.ViewModels; public abstract partial class ParameterRunViewModel : ExtensionObjectViewModel { + private ExtensionObject _model; + internal InitializedState Initialized { get; set; } = InitializedState.Uninitialized; protected bool IsInitialized => IsInErrorState || Initialized.HasFlag(InitializedState.Initialized); public bool IsInErrorState => Initialized.HasFlag(InitializedState.Error); - internal ParameterRunViewModel(WeakReference context) + internal ParameterRunViewModel(IParameterRun model, WeakReference context) : base(context) { + _model = new(model); + } + + public override void InitializeProperties() + { + if (IsInitialized) + { + return; + } + + var model = _model.Unsafe; + if (model == null) + { + return; + } + + model.PropChanged += Model_PropChanged; + } + + private void Model_PropChanged(object sender, IPropChangedEventArgs args) + { + try + { + FetchProperty(args.PropertyName); + } + catch (Exception ex) + { + ShowException(ex); + } + } + + protected virtual void FetchProperty(string propertyName) + { + // Override in derived classes } } @@ -36,7 +73,7 @@ public partial class LabelRunViewModel : ParameterRunViewModel public string Text { get; set; } = string.Empty; public LabelRunViewModel(ILabelRun labelRun, WeakReference context) - : base(context) + : base(labelRun, context) { _model = new(labelRun); } @@ -70,7 +107,7 @@ public partial class ParameterValueRunViewModel : ParameterRunViewModel public bool NeedsValue { get; set; } public ParameterValueRunViewModel(IParameterValueRun valueRun, WeakReference context) - : base(context) + : base(valueRun, context) { _model = new(valueRun); } @@ -147,18 +184,31 @@ public partial class StringParameterRunViewModel : ParameterValueRunViewModel } } -public partial class CommandParameterRunViewModel : ParameterValueRunViewModel +public partial class CommandParameterRunViewModel : ParameterValueRunViewModel, IDisposable { private ExtensionObject _model; + private ListViewModel? _listViewModel; + private CommandViewModel? _commandViewModel; + private AppExtensionHost _extensionHost; + public string DisplayText { get; set; } = string.Empty; public IconInfoViewModel Icon { get; set; } = new(null); - public CommandParameterRunViewModel(ICommandParameterRun commandRun, WeakReference context) + public string ButtonLabel => !string.IsNullOrEmpty(DisplayText) ? DisplayText : string.Empty; + + public string SearchBoxText + { + get => GetSearchText(); + set => SetSearchText(value); + } + + public CommandParameterRunViewModel(ICommandParameterRun commandRun, WeakReference context, AppExtensionHost extensionHost) : base(commandRun, context) { _model = new(commandRun); + _extensionHost = extensionHost; } public override void InitializeProperties() @@ -182,9 +232,71 @@ public partial class CommandParameterRunViewModel : ParameterValueRunViewModel Icon.InitializeProperties(); } + GetHwndMessage msg = new(); + WeakReferenceMessenger.Default.Send(msg); + var command = commandRun.GetSelectValueCommand((ulong)msg.Hwnd); + if (command == null) + { + } + else if (command is IListPage list) + { + if (PageContext.TryGetTarget(out var pageContext)) + { + _listViewModel = new ListViewModel(list, pageContext.Scheduler, _extensionHost); + _listViewModel.InitializeProperties(); + } + } + else if (command is IInvokableCommand invokable) + { + _commandViewModel = new CommandViewModel(invokable, this.PageContext); + _commandViewModel.InitializeProperties(); + } + UpdateProperty(nameof(DisplayText)); UpdateProperty(nameof(Icon)); } + + protected override void FetchProperty(string propertyName) + { + var model = this._model.Unsafe; + if (model is null) + { + return; // throw? + } + + switch (propertyName) + { + case nameof(ICommandParameterRun.DisplayText): + DisplayText = model.DisplayText; + break; + case nameof(ICommandParameterRun.Icon): + Icon = new(model.Icon); + if (Icon is not null) + { + Icon.InitializeProperties(); + } + + break; + } + + UpdateProperty(propertyName); + } + + private string GetSearchText() + { + return _listViewModel?.SearchText ?? string.Empty; + } + + private void SetSearchText(string value) + { + _listViewModel?.SearchTextBox = value; + } + + public void Dispose() + { + GC.SuppressFinalize(this); + _listViewModel?.Dispose(); + } } public partial class ParametersPageViewModel : PageViewModel, IDisposable @@ -257,7 +369,7 @@ public partial class ParametersPageViewModel : PageViewModel, IDisposable { ILabelRun labelRun => new LabelRunViewModel(labelRun, PageContext), IStringParameterRun stringRun => new StringParameterRunViewModel(stringRun, PageContext), - ICommandParameterRun commandRun => new CommandParameterRunViewModel(commandRun, PageContext), + ICommandParameterRun commandRun => new CommandParameterRunViewModel(commandRun, PageContext, this.ExtensionHost), _ => null, }; if (itemVm != null) diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/SearchBar.xaml b/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/SearchBar.xaml index ea52c72443..a18fa1eb91 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/SearchBar.xaml +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/SearchBar.xaml @@ -42,6 +42,19 @@ Style="{StaticResource SearchTextBoxStyle}" Text="{x:Bind Text, Mode=TwoWay}" /> + + + , IRecipient, IRecipient, + IRecipient, IDisposable { [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore", Justification = "Stylistically, window messages are WM_")] @@ -87,6 +88,7 @@ public sealed partial class MainWindow : WindowEx, WeakReferenceMessenger.Default.Register(this); WeakReferenceMessenger.Default.Register(this); WeakReferenceMessenger.Default.Register(this); + WeakReferenceMessenger.Default.Register(this); // Hide our titlebar. // We need to both ExtendsContentIntoTitleBar, then set the height to Collapsed @@ -747,4 +749,9 @@ public sealed partial class MainWindow : WindowEx, _localKeyboardListener.Dispose(); DisposeAcrylic(); } + + public void Receive(GetHwndMessage message) + { + message.Hwnd = this.GetWindowHandle(); + } } diff --git a/src/modules/cmdpal/ext/SamplePagesExtension/Pages/ParameterSamples.cs b/src/modules/cmdpal/ext/SamplePagesExtension/Pages/ParameterSamples.cs index 94f37e0d2e..5e1bf2c6f6 100644 --- a/src/modules/cmdpal/ext/SamplePagesExtension/Pages/ParameterSamples.cs +++ b/src/modules/cmdpal/ext/SamplePagesExtension/Pages/ParameterSamples.cs @@ -48,6 +48,38 @@ public sealed partial class SimpleParameterTest : ParametersPage public override IListItem Command => new ListItem(_command); } +public sealed partial class ButtonParameterTest : ParametersPage +{ + private readonly CommandParameterRun _stringParameter; + private readonly InvokableCommand _command; + + public ButtonParameterTest() + { + Name = "Open"; + _stringParameter = new FilePickerParameterRun(); + + _command = new AnonymousCommand(() => + { + var input = _stringParameter.Value?.ToString(); + var toast = new ToastStatusMessage(new StatusMessage() { Message = $"You entered: '{input}'" }); + toast.Show(); + }) + { + Name = "Submit", + Icon = new IconInfo("\uE724"), // Send + Result = CommandResult.KeepOpen(), + }; + } + + public override IParameterRun[] Parameters => new IParameterRun[] + { + new LabelRun("Enter a value:"), + _stringParameter, + }; + + public override IListItem Command => new ListItem(_command); +} + public sealed partial class CreateNoteParametersPage : ParametersPage { private readonly SelectFolderPage _selectFolderPage = new(); diff --git a/src/modules/cmdpal/ext/SamplePagesExtension/Parameters.cs b/src/modules/cmdpal/ext/SamplePagesExtension/Parameters.cs index 07a5f33606..99632e0272 100644 --- a/src/modules/cmdpal/ext/SamplePagesExtension/Parameters.cs +++ b/src/modules/cmdpal/ext/SamplePagesExtension/Parameters.cs @@ -95,7 +95,7 @@ public partial class StringParameterRun : ParameterValueRun, IStringParameterRun } } -public partial class CommandParameterRun : ParameterValueRun +public partial class CommandParameterRun : ParameterValueRun, ICommandParameterRun { private string? _displayText; @@ -164,35 +164,25 @@ internal sealed partial class FilePickerParameterRun : CommandParameterRun { public StorageFile? File { get; private set; } + public override string? DisplayText { get => File != null ? File.DisplayName : "Select a file"; } + public FilePickerParameterRun() { var command = new FilePickerCommand(); command.FileSelected += (s, file) => { File = file; - if (file != null) - { - Value = file; - DisplayText = file.Name; - - // Icon = new IconInfo("File"); - } - else - { - Value = null; - DisplayText = null; - - // Icon = new IconInfo("File"); - } + Value = file != null ? file : (object?)null; + OnPropertyChanged(nameof(DisplayText)); }; PlaceholderText = "Select a file"; - Icon = new IconInfo("File"); + Icon = new IconInfo("\uE710"); // Add Command = command; } private sealed partial class FilePickerCommand : InvokableCommand, IRequiresHostHwnd { - public override IconInfo Icon => new("File"); + public override IconInfo Icon => new("\uE710"); // Add public override string Name => "Pick a file"; diff --git a/src/modules/cmdpal/ext/SamplePagesExtension/SamplesListPage.cs b/src/modules/cmdpal/ext/SamplePagesExtension/SamplesListPage.cs index 2aa9053248..3adeca0d66 100644 --- a/src/modules/cmdpal/ext/SamplePagesExtension/SamplesListPage.cs +++ b/src/modules/cmdpal/ext/SamplePagesExtension/SamplesListPage.cs @@ -95,6 +95,11 @@ public partial class SamplesListPage : ListPage Title = "Sample parameters page", Subtitle = "A demo of a command that takes simple parameters", }, + new ListItem(new ButtonParameterTest()) + { + Title = "Button parameters page", + Subtitle = "A demo of a command that takes simple parameters", + }, // Evil edge cases // Anything weird that might break the palette - put that in here.