|
|
|
@ -10,6 +10,7 @@ using Avalonia.Data; |
|
|
|
using Avalonia.Input; |
|
|
|
using Avalonia.Input.Platform; |
|
|
|
using Avalonia.Interactivity; |
|
|
|
using Avalonia.Threading; |
|
|
|
using Avalonia.VisualTree; |
|
|
|
|
|
|
|
#nullable enable |
|
|
|
@ -91,6 +92,12 @@ namespace Avalonia.Controls.Primitives |
|
|
|
AvaloniaProperty.Register<SelectingItemsControl, SelectionMode>( |
|
|
|
nameof(SelectionMode)); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Defines the <see cref="IsTextSearchEnabled"/> property.
|
|
|
|
/// </summary>
|
|
|
|
public static readonly StyledProperty<bool> IsTextSearchEnabledProperty = |
|
|
|
AvaloniaProperty.Register<ItemsControl, bool>(nameof(IsTextSearchEnabled), true); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Event that should be raised by items that implement <see cref="ISelectable"/> to
|
|
|
|
/// notify the parent <see cref="SelectingItemsControl"/> that their selection state
|
|
|
|
@ -110,6 +117,8 @@ namespace Avalonia.Controls.Primitives |
|
|
|
RoutingStrategies.Bubble); |
|
|
|
|
|
|
|
private static readonly IList Empty = Array.Empty<object>(); |
|
|
|
private string _textSearchTerm = string.Empty; |
|
|
|
private DispatcherTimer? _textSearchTimer; |
|
|
|
private ISelectionModel? _selection; |
|
|
|
private int _oldSelectedIndex; |
|
|
|
private object? _oldSelectedItem; |
|
|
|
@ -305,6 +314,15 @@ namespace Avalonia.Controls.Primitives |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets or sets a value that specifies whether a user can jump to a value by typing.
|
|
|
|
/// </summary>
|
|
|
|
public bool IsTextSearchEnabled |
|
|
|
{ |
|
|
|
get { return GetValue(IsTextSearchEnabledProperty); } |
|
|
|
set { SetValue(IsTextSearchEnabledProperty, value); } |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets or sets the selection mode.
|
|
|
|
/// </summary>
|
|
|
|
@ -490,6 +508,36 @@ namespace Avalonia.Controls.Primitives |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
protected override void OnTextInput(TextInputEventArgs e) |
|
|
|
{ |
|
|
|
if (!e.Handled) |
|
|
|
{ |
|
|
|
if (!IsTextSearchEnabled) |
|
|
|
return; |
|
|
|
|
|
|
|
StopTextSearchTimer(); |
|
|
|
|
|
|
|
_textSearchTerm += e.Text; |
|
|
|
|
|
|
|
bool match(ItemContainerInfo info) => |
|
|
|
info.ContainerControl is IContentControl control && |
|
|
|
control.Content?.ToString()?.StartsWith(_textSearchTerm, StringComparison.OrdinalIgnoreCase) == true; |
|
|
|
|
|
|
|
var info = ItemContainerGenerator.Containers.FirstOrDefault(match); |
|
|
|
|
|
|
|
if (info != null) |
|
|
|
{ |
|
|
|
SelectedIndex = info.Index; |
|
|
|
} |
|
|
|
|
|
|
|
StartTextSearchTimer(); |
|
|
|
|
|
|
|
e.Handled = true; |
|
|
|
} |
|
|
|
|
|
|
|
base.OnTextInput(e); |
|
|
|
} |
|
|
|
|
|
|
|
protected override void OnKeyDown(KeyEventArgs e) |
|
|
|
{ |
|
|
|
base.OnKeyDown(e); |
|
|
|
@ -962,6 +1010,32 @@ namespace Avalonia.Controls.Primitives |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
private void StartTextSearchTimer() |
|
|
|
{ |
|
|
|
_textSearchTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) }; |
|
|
|
_textSearchTimer.Tick += TextSearchTimer_Tick; |
|
|
|
_textSearchTimer.Start(); |
|
|
|
} |
|
|
|
|
|
|
|
private void StopTextSearchTimer() |
|
|
|
{ |
|
|
|
if (_textSearchTimer == null) |
|
|
|
{ |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
_textSearchTimer.Tick -= TextSearchTimer_Tick; |
|
|
|
_textSearchTimer.Stop(); |
|
|
|
|
|
|
|
_textSearchTimer = null; |
|
|
|
} |
|
|
|
|
|
|
|
private void TextSearchTimer_Tick(object sender, EventArgs e) |
|
|
|
{ |
|
|
|
_textSearchTerm = string.Empty; |
|
|
|
StopTextSearchTimer(); |
|
|
|
} |
|
|
|
|
|
|
|
// When in a BeginInit..EndInit block, or when the DataContext is updating, we need to
|
|
|
|
// defer changes to the selection model because we have no idea in which order properties
|
|
|
|
// will be set. Consider:
|
|
|
|
|