diff --git a/UI/Controls/MultiValueAutoCompleteBox.axaml b/UI/Controls/MultiValueAutoCompleteBox.axaml
index 6616642..874125f 100644
--- a/UI/Controls/MultiValueAutoCompleteBox.axaml
+++ b/UI/Controls/MultiValueAutoCompleteBox.axaml
@@ -12,9 +12,9 @@
RelativeSource={RelativeSource AncestorType=controls:MultiValueAutoCompleteBox}}"
Watermark="{Binding Watermark,
RelativeSource={RelativeSource AncestorType=controls:MultiValueAutoCompleteBox}}"
- TextChanged="TextBox_OnTextChanged"
- GotFocus="TextBox_OnGotFocus" LostFocus="TextBox_OnLostFocus"/>
-
+ TextChanged="TextBox_OnTextChanged" KeyDown="TextBox_OnKeyDown"
+ GotFocus="TextBox_OnGotFocus" LostFocus="TextBox_OnLostFocus" />
+
+ PointerReleased="ListBox_OnPointerReleased"
+ KeyDown="ListBox_OnKeyDown">
-
+
-
-
-
+
+
-
+
\ No newline at end of file
diff --git a/UI/Controls/MultiValueAutoCompleteBox.axaml.cs b/UI/Controls/MultiValueAutoCompleteBox.axaml.cs
index 7caaf35..88404fa 100644
--- a/UI/Controls/MultiValueAutoCompleteBox.axaml.cs
+++ b/UI/Controls/MultiValueAutoCompleteBox.axaml.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
+using System.Diagnostics;
using System.Linq;
using Avalonia;
using Avalonia.Controls;
@@ -14,55 +15,68 @@ namespace TagHierarchyManager.UI.Controls;
public partial class MultiValueAutoCompleteBox : UserControl
{
+ public static readonly StyledProperty?> ItemsSourceProperty =
+ AvaloniaProperty.Register?>(
+ nameof(ItemsSource), []);
+
+ public static readonly StyledProperty TextProperty =
+ AvaloniaProperty.Register(
+ nameof(Text), string.Empty, defaultBindingMode: BindingMode.TwoWay);
+
+ public static readonly StyledProperty WatermarkProperty =
+ AvaloniaProperty.Register(
+ nameof(Watermark), string.Empty);
+
+ private bool _isTextBoxActive;
+
+ private string _lastTypedRawText = string.Empty;
+
+ private bool _suppressPopup;
+
public MultiValueAutoCompleteBox()
{
- InitializeComponent();
+ this.InitializeComponent();
this.MultiValueAutoCompletePopup.PlacementTarget = this.MultiValueAutoCompleteBoxTextBox;
this.MultiValueAutoCompleteListBox.ItemsSource = this.FilteredItems;
this.MultiValueAutoCompleteBoxTextBox
.GetPropertyChangedObservable(BoundsProperty)
- .Subscribe(new AnonymousObserver(
- e => this.MultiValueAutoCompleteListBox.Width = ((Rect)e.NewValue!).Width));
+ .Subscribe(new AnonymousObserver(e =>
+ this.MultiValueAutoCompleteListBox.Width = ((Rect)e.NewValue!).Width));
+ this.KeyDown += (_, e) =>
+ {
+ if (e.Key != Key.Escape) return;
+ if (this.MultiValueAutoCompleteBoxTextBox.Text is null) return;
+ this._lastTypedRawText = this.MultiValueAutoCompleteBoxTextBox.Text;
+ this.FilteredItems.Clear();
+ this.MultiValueAutoCompleteBoxTextBox.Focus();
+ e.Handled = true;
+ };
}
-
- public static readonly StyledProperty TextProperty =
- AvaloniaProperty.Register(
- nameof(Text), defaultValue: string.Empty, defaultBindingMode: BindingMode.TwoWay);
-
- public static readonly StyledProperty WatermarkProperty =
- AvaloniaProperty.Register(
- nameof(Watermark), defaultValue: string.Empty);
-
- public static readonly StyledProperty?> ItemsSourceProperty =
- AvaloniaProperty.Register?>(
- nameof(ItemsSource), defaultValue: []);
- private bool _isTextBoxFocused;
-
- private bool CanShowPopup => this._isTextBoxFocused && this.FilteredItems.Any();
-
- public string Text
+ public IEnumerable? ItemsSource
{
- get => GetValue(TextProperty);
- set => SetValue(TextProperty, value);
+ get => this.GetValue(ItemsSourceProperty);
+ set => this.SetValue(ItemsSourceProperty, value);
}
-
- public string Watermark
+
+ public string Text
{
- get => GetValue(WatermarkProperty);
- set => SetValue(WatermarkProperty, value);
+ get => this.GetValue(TextProperty);
+ set => this.SetValue(TextProperty, value);
}
-
- public IEnumerable? ItemsSource
+
+ public string Watermark
{
- get => GetValue(ItemsSourceProperty);
- set => SetValue(ItemsSourceProperty, value);
+ get => this.GetValue(WatermarkProperty);
+ set => this.SetValue(WatermarkProperty, value);
}
-
- public ObservableCollection FilteredItems { get; set; } = [];
- private string _lastTypedRawText = string.Empty;
-
+ private bool CanShowPopup => (this._isTextBoxActive ||
+ (this.MultiValueAutoCompleteListBox?.IsKeyboardFocusWithin ?? false))
+ && this.FilteredItems.Any();
+
+ private ObservableCollection FilteredItems { get; } = [];
+
public void TextBox_OnTextChanged(object? sender, TextChangedEventArgs e)
{
if (sender is not TextBox box) return;
@@ -71,22 +85,13 @@ public partial class MultiValueAutoCompleteBox : UserControl
this._lastTypedRawText = rawText;
this.Text = rawText;
}
-
- private void TextBox_OnGotFocus(object? sender, GotFocusEventArgs e)
- {
- this._isTextBoxFocused = true;
- }
-
- private void TextBox_OnLostFocus(object? sender, RoutedEventArgs e)
- {
- this._isTextBoxFocused = false;
- this.UpdatePopupIsVisible();
- }
-
+
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
- System.Diagnostics.Debug.WriteLine($"CanShowPopup: {this.CanShowPopup}, focused: {this._isTextBoxFocused}, items: {this.FilteredItems.Count}");
+ Debug.WriteLine(
+ $"CanShowPopup: {this.CanShowPopup}, focused: {this._isTextBoxActive}, items: {this.FilteredItems.Count}");
+ // ReSharper disable once InvertIf
if (change.Property == TextProperty)
{
var last = this.Text.Split(';').Last();
@@ -94,7 +99,38 @@ public partial class MultiValueAutoCompleteBox : UserControl
this.RepopulateFilteredItems(last);
}
}
-
+
+ private void ApplyListBoxSelection(ListBox box, TagItemViewModel tag)
+ {
+ this._suppressPopup = true;
+ box.SelectedItem = null;
+
+ var lastSemicolon = this._lastTypedRawText.LastIndexOf(';');
+ var prefix = lastSemicolon >= 0 ? this._lastTypedRawText[..lastSemicolon].TrimEnd() + "; " : string.Empty;
+ var result = $"{prefix}{tag.CurrentName}";
+ this.Text = result;
+ this.MultiValueAutoCompletePopup.IsOpen = false;
+ this.MultiValueAutoCompleteBoxTextBox.Text = result;
+ this.MultiValueAutoCompleteBoxTextBox.CaretIndex = result.Length;
+ this.MultiValueAutoCompleteBoxTextBox.Focus();
+ this._lastTypedRawText = result;
+ this._suppressPopup = false;
+ }
+
+ private void ListBox_OnKeyDown(object? sender, KeyEventArgs e)
+ {
+ if (sender is not ListBox { SelectedItem: TagItemViewModel tag } listBox) return;
+
+ if (e.Key == Key.Enter)
+ this.ApplyListBoxSelection(listBox, tag);
+ }
+
+ private void ListBox_OnPointerReleased(object? sender, PointerReleasedEventArgs e)
+ {
+ if (sender is not ListBox { SelectedItem: TagItemViewModel tag } listBox) return;
+ this.ApplyListBoxSelection(listBox, tag);
+ }
+
private void RepopulateFilteredItems(string itemName)
{
this.FilteredItems.Clear();
@@ -103,37 +139,38 @@ public partial class MultiValueAutoCompleteBox : UserControl
{
var filtered = this.ItemsSource
.Where(t => t.CurrentName.Contains(itemName, StringComparison.OrdinalIgnoreCase))
- .OrderBy(t=> t.CurrentName, StringComparer.OrdinalIgnoreCase).ToList();
+ .OrderBy(t => t.CurrentName, StringComparer.OrdinalIgnoreCase).ToList();
foreach (var item in filtered)
this.FilteredItems.Add(item);
-
}
-
+
this.UpdatePopupIsVisible();
}
- private bool _isApplyingSelection;
- private void UpdatePopupIsVisible()
+ private void TextBox_OnGotFocus(object? sender, GotFocusEventArgs e)
{
- if (this._isApplyingSelection) return;
- this.MultiValueAutoCompletePopup.IsOpen = this.CanShowPopup;
+ this._isTextBoxActive = true;
}
-
- private void ListBox_OnSelectionChanged(object? sender, SelectionChangedEventArgs e)
+
+ private void TextBox_OnKeyDown(object? sender, KeyEventArgs e)
{
- if (sender is not ListBox listBox || listBox.SelectedItem is not TagItemViewModel tag) return;
- this._isApplyingSelection = true;
- listBox.SelectedItem = null;
-
- var lastSemicolon = this._lastTypedRawText.LastIndexOf(';');
- var prefix = lastSemicolon >= 0 ? this._lastTypedRawText[..(lastSemicolon)].TrimEnd() + "; " : string.Empty;
- var result = $"{prefix}{tag.CurrentName}";
- this.Text = result;
- this.MultiValueAutoCompletePopup.IsOpen = false;
- this.MultiValueAutoCompleteBoxTextBox.Text = result;
- this.MultiValueAutoCompleteBoxTextBox.CaretIndex = result.Length;
- this.MultiValueAutoCompleteBoxTextBox.Focus();
- this._lastTypedRawText = result;
- this._isApplyingSelection = false;
+ if (e.Key != Key.Down) return;
+ if (!this.MultiValueAutoCompletePopup.IsOpen) return;
+ this.MultiValueAutoCompleteListBox.SelectedIndex = 0;
+ var first = this.MultiValueAutoCompleteListBox.ContainerFromIndex(0);
+ first?.Focus();
+ e.Handled = true;
+ }
+
+ private void TextBox_OnLostFocus(object? sender, RoutedEventArgs e)
+ {
+ this._isTextBoxActive = false;
+ this.UpdatePopupIsVisible();
+ }
+
+ private void UpdatePopupIsVisible()
+ {
+ if (this._suppressPopup) return;
+ this.MultiValueAutoCompletePopup.IsOpen = this.CanShowPopup;
}
}
\ No newline at end of file