From b8dfc1db75b14f7e912a6e14b58840f3c00ecdea Mon Sep 17 00:00:00 2001 From: Cezary Piatek Date: Tue, 18 Mar 2025 21:07:08 +0100 Subject: [PATCH 1/2] Improve UX for searchbox control --- .../Parameters/ParamsPanelFactory.cs | 20 +++++++++++- .../Views/SearchableComboBox.axaml.cs | 32 ++++++++++++++++--- 2 files changed, 47 insertions(+), 5 deletions(-) diff --git a/src/ScriptRunner/ScriptRunner.GUI/Parameters/ParamsPanelFactory.cs b/src/ScriptRunner/ScriptRunner.GUI/Parameters/ParamsPanelFactory.cs index 647cae0..389f14d 100644 --- a/src/ScriptRunner/ScriptRunner.GUI/Parameters/ParamsPanelFactory.cs +++ b/src/ScriptRunner/ScriptRunner.GUI/Parameters/ParamsPanelFactory.cs @@ -10,6 +10,7 @@ using Avalonia.Controls; using Avalonia.Controls.Shapes; using Avalonia.Input; +using Avalonia.Interactivity; using Avalonia.Layout; using Avalonia.Media; using Avalonia.Threading; @@ -283,7 +284,8 @@ private IControlRecord CreateControlRecord(ScriptParam p, string? value, int ind VerticalAlignment = VerticalAlignment.Stretch, HorizontalContentAlignment = HorizontalAlignment.Center }; - generateButton.Click += async(sender, args) => + bool wasGenerated = false; + EventHandler generate = async(sender, args) => { generateButton.IsEnabled = false; generateButton.Classes.Add("spinning"); @@ -297,8 +299,24 @@ private IControlRecord CreateControlRecord(ScriptParam p, string? value, int ind } generateButton.Classes.Remove("spinning"); generateButton.IsEnabled = true; + wasGenerated = true; + if (inputControl is SearchableComboBox scb) + { + scb.ShowAll(); + } }); }; + generateButton.Click += generate; + if(inputControl is SearchableComboBox scb) + { + scb.GotFocus += (sender, args) => + { + if (wasGenerated == false) + { + generate(sender, args); + } + }; + } Attached.SetIcon(generateButton, "fas fa-sync"); ToolTip.SetTip(generateButton, "Refresh available options"); actionPanel.Children.Add(generateButton); diff --git a/src/ScriptRunner/ScriptRunner.GUI/Views/SearchableComboBox.axaml.cs b/src/ScriptRunner/ScriptRunner.GUI/Views/SearchableComboBox.axaml.cs index 3d33ce2..5768811 100644 --- a/src/ScriptRunner/ScriptRunner.GUI/Views/SearchableComboBox.axaml.cs +++ b/src/ScriptRunner/ScriptRunner.GUI/Views/SearchableComboBox.axaml.cs @@ -1,9 +1,11 @@ using System; using System.Collections.ObjectModel; +using System.Threading.Tasks; using Avalonia; using Avalonia.Controls; using Avalonia.Interactivity; using Avalonia.Markup.Xaml; +using Avalonia.Threading; namespace ScriptRunner.GUI.Views; @@ -72,29 +74,51 @@ private void InitializeComponent() _autoCompleteBox.ItemsSource = Items; _autoCompleteBox.SelectedItem = SelectedItem; //_autoCompleteBox.TextChanged += AutoCompleteBox_TextChanged; + _autoCompleteBox.GotFocus += (sender, args) => + { + _autoCompleteBox.IsDropDownOpen = true; + }; _autoCompleteBox.LostFocus += (sender, args) => { - if(_autoCompleteBox.SelectedItem == null) + Task.Run(async () => { - _autoCompleteBox.Text = ""; - } + await Task.Delay(TimeSpan.FromSeconds(1)); + Dispatcher.UIThread.InvokeAsync(() => + { + if(_autoCompleteBox.SelectedItem == null) + { + _autoCompleteBox.Text = ""; + } + }); + }); }; + _autoCompleteBox.SelectionChanged += AutoCompleteBox_SelectionChanged; } } + + private string previousValue = ""; private void AutoCompleteBox_SelectionChanged(object? sender, SelectionChangedEventArgs e) { - if (_autoCompleteBox?.SelectedItem is string selected) + if (_autoCompleteBox?.SelectedItem is string selected && selected != previousValue) { SelectedItem = selected; + previousValue = selected; } } private void ShowAllOptions_Click(object? sender, RoutedEventArgs e) + { + ShowAll(); + } + + public void ShowAll() { if (_autoCompleteBox != null) { + _autoCompleteBox.Text = ""; + _autoCompleteBox.Focus(); _autoCompleteBox.ItemsSource = new ObservableCollection(Items); _autoCompleteBox.IsDropDownOpen = true; } From 61791094f23939a99ec146cd5d960390f1c67ebe Mon Sep 17 00:00:00 2001 From: Cezary Piatek Date: Tue, 18 Mar 2025 21:13:09 +0100 Subject: [PATCH 2/2] Add option to specify delimiter of dropdown options --- schema/v1/ScriptRunnerSchema.json | 4 ++++ .../ScriptRunner.GUI/Parameters/ParamsPanelFactory.cs | 5 +++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/schema/v1/ScriptRunnerSchema.json b/schema/v1/ScriptRunnerSchema.json index 2197d26..2db5e25 100644 --- a/schema/v1/ScriptRunnerSchema.json +++ b/schema/v1/ScriptRunnerSchema.json @@ -188,6 +188,10 @@ "type": "boolean" }, "optionsGeneratorCommand": + { + "type": "string" + }, + "delimiter": { "type": "string" } diff --git a/src/ScriptRunner/ScriptRunner.GUI/Parameters/ParamsPanelFactory.cs b/src/ScriptRunner/ScriptRunner.GUI/Parameters/ParamsPanelFactory.cs index 389f14d..c8834c3 100644 --- a/src/ScriptRunner/ScriptRunner.GUI/Parameters/ParamsPanelFactory.cs +++ b/src/ScriptRunner/ScriptRunner.GUI/Parameters/ParamsPanelFactory.cs @@ -240,7 +240,8 @@ private IControlRecord CreateControlRecord(ScriptParam p, string? value, int ind MaskingRequired = true, }; case PromptType.Dropdown: - var initialOptions = p.GetPromptSettings("options", out var options) ? options.Split(","):Array.Empty(); + var delimiterForOptions = p.GetPromptSettings("delimiter", x => x, ","); + var initialOptions = p.GetPromptSettings("options", out var options) ? options.Split(delimiterForOptions):Array.Empty(); var observableOptions = new ObservableCollection(initialOptions); var searchable = p.GetPromptSettings("searchable", bool.Parse, false); var optionsGeneratorCommand = p.GetPromptSettings("optionsGeneratorCommand", out var optionsGeneratorCommandText) ? optionsGeneratorCommandText : null; @@ -293,7 +294,7 @@ private IControlRecord CreateControlRecord(ScriptParam p, string? value, int ind Dispatcher.UIThread.Post(() => { observableOptions.Clear(); - foreach (var option in result.Split(new[]{'\r', '\n',','}, StringSplitOptions.RemoveEmptyEntries).Distinct().OrderBy(x=>x)) + foreach (var option in result.Split(new[]{"\r", "\n",delimiterForOptions}, StringSplitOptions.RemoveEmptyEntries).Distinct().OrderBy(x=>x)) { observableOptions.Add(option); }