Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Search all SelectingItemsControl items with TextSearch on key input, not just realized ones #17506

Open
wants to merge 22 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
72e9357
Modify SelectingItemsControl to not just use unrealized items
jonko0493 Nov 13, 2024
7be67f4
Fixup null deref
jonko0493 Nov 13, 2024
e6249d6
Get unrealized items searched in comboboxes
jonko0493 Nov 14, 2024
0d241b2
Fixup one small comparison bug
jonko0493 Nov 14, 2024
2180732
Reset file that shouldn't have been changed
jonko0493 Nov 14, 2024
b8cfe0a
Try again
jonko0493 Nov 14, 2024
59ecd0b
Revert frfr
jonko0493 Nov 14, 2024
5770139
Revert frfrfr
jonko0493 Nov 14, 2024
c201b20
Merge branch 'master' into UnrealizedItemsTextSearch
jonko0493 Nov 14, 2024
e8734de
Merge branch 'master' into UnrealizedItemsTextSearch
jonko0493 Nov 17, 2024
2f726b4
Merge branch 'master' into UnrealizedItemsTextSearch
jonko0493 Nov 18, 2024
9caba66
Merge branch 'master' into UnrealizedItemsTextSearch
jonko0493 Nov 26, 2024
ad94b5f
Merge branch 'master' into UnrealizedItemsTextSearch
jonko0493 Dec 9, 2024
8f70456
Merge branch 'master' into UnrealizedItemsTextSearch
jonko0493 Dec 14, 2024
3ed74fb
Merge branch 'master' into UnrealizedItemsTextSearch
jonko0493 Dec 16, 2024
c0ce806
Merge branch 'master' into UnrealizedItemsTextSearch
jonko0493 Dec 28, 2024
a0a16e2
Merge branch 'master' into UnrealizedItemsTextSearch
jonko0493 Jan 12, 2025
d23e39c
Fixup per PR feedback
jonko0493 Feb 22, 2025
8341143
Remove documentation from internal method
jonko0493 Feb 22, 2025
2e9c975
Remove unused usings
jonko0493 Feb 22, 2025
88a7cdf
Merge branch 'master' into UnrealizedItemsTextSearch
jonko0493 Feb 22, 2025
092dba6
Merge branch 'master' into UnrealizedItemsTextSearch
jonko0493 Feb 24, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 38 additions & 2 deletions src/Avalonia.Controls/Presenters/ItemsPresenter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
using System.Diagnostics;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Layout;

namespace Avalonia.Controls.Presenters
{
Expand Down Expand Up @@ -198,6 +196,44 @@ private void CreateSimplePanelGenerator()
return v.GetRealizedContainers();
return Panel?.Children;
}

internal static bool ControlMatchesTextSearch(Control control, string textSearchTerm)
{
if (control is AvaloniaObject ao && ao.IsSet(TextSearch.TextProperty))
{
var searchText = ao.GetValue(TextSearch.TextProperty);

if (searchText?.StartsWith(textSearchTerm, StringComparison.OrdinalIgnoreCase) == true)
{
return true;
}
}
return control is IContentControl cc &&
cc.Content?.ToString()?.StartsWith(textSearchTerm, StringComparison.OrdinalIgnoreCase) == true;
}

internal int GetIndexFromTextSearch(string textSearch)
{
if (Panel is VirtualizingPanel v)
return v.GetIndexFromTextSearch(textSearch);
return GetIndexFromTextSearch(ItemsControl?.Items, textSearch);
}

internal static int GetIndexFromTextSearch(IReadOnlyList<object?>? items, string textSearchTerm)
{
if (items is null)
return -1;

for (var i = 0; i < items.Count; i++)
{
if (items[i] is Control c && ControlMatchesTextSearch(c, textSearchTerm)
|| items[i]?.ToString()?.StartsWith(textSearchTerm, StringComparison.OrdinalIgnoreCase) == true)
{
return i;
}
}
return -1;
}

internal int IndexFromContainer(Control container)
{
Expand Down
28 changes: 5 additions & 23 deletions src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
using System.Linq;
using Avalonia.Controls.Selection;
using Avalonia.Data;
using Avalonia.Data.Core;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Metadata;
Expand Down Expand Up @@ -115,7 +114,7 @@ public class SelectingItemsControl : ItemsControl
/// </summary>
public static readonly StyledProperty<bool> IsTextSearchEnabledProperty =
AvaloniaProperty.Register<SelectingItemsControl, bool>(nameof(IsTextSearchEnabled), false);

/// <summary>
/// Event that should be raised by containers when their selection state changes to notify
/// the parent <see cref="SelectingItemsControl"/> that their selection state has changed.
Expand Down Expand Up @@ -610,29 +609,12 @@ protected override void OnTextInput(TextInputEventArgs e)

_textSearchTerm += e.Text;

bool Match(Control container)
var newIndex = Presenter?.GetIndexFromTextSearch(_textSearchTerm);
if (newIndex >= 0)
{
if (container is AvaloniaObject ao && ao.IsSet(TextSearch.TextProperty))
{
var searchText = ao.GetValue(TextSearch.TextProperty);

if (searchText?.StartsWith(_textSearchTerm, StringComparison.OrdinalIgnoreCase) == true)
{
return true;
}
}

return container is IContentControl control &&
control.Content?.ToString()?.StartsWith(_textSearchTerm, StringComparison.OrdinalIgnoreCase) == true;
SelectedIndex = (int)newIndex;
}

var container = GetRealizedContainers().FirstOrDefault(Match);

if (container != null)
{
SelectedIndex = IndexFromContainer(container);
}


StartTextSearchTimer();

e.Handled = true;
Expand Down
6 changes: 6 additions & 0 deletions src/Avalonia.Controls/VirtualizingPanel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Collections.Specialized;
using System.Diagnostics.CodeAnalysis;
using Avalonia.Controls.Generators;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Utils;
using Avalonia.Input;
Expand Down Expand Up @@ -192,6 +193,11 @@ protected void RemoveInternalChildRange(int index, int count)
Children.RemoveRange(index, count);
}

internal int GetIndexFromTextSearch(string textSearchTerm)
{
return ItemsPresenter.GetIndexFromTextSearch(Items, textSearchTerm);
}

private protected override void InvalidateMeasureOnChildrenChanged()
{
// Don't invalidate measure when children are added or removed: the panel is responsible
Expand Down
21 changes: 16 additions & 5 deletions tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -198,9 +198,14 @@ private static FuncControlTemplate GetTemplate()
new Popup
{
Name = "PART_Popup",
Child = new ItemsPresenter
Child = new ScrollViewer
{
Name = "PART_ItemsPresenter",
Name = "PART_ScrollViewer",
Content = new ItemsPresenter
{
Name = "PART_ItemsPresenter",
ItemsPanel = new FuncTemplate<Panel>(() => new VirtualizingStackPanel()),
}.RegisterInNameScope(scope)
}.RegisterInNameScope(scope)
}.RegisterInNameScope(scope)
}
Expand Down Expand Up @@ -243,23 +248,30 @@ public void Detaching_Closed_ComboBox_Keeps_Current_Focus()
[InlineData(-1, 2, "c", "A item", "B item", "C item")]
[InlineData(0, 1, "b", "A item", "B item", "C item")]
[InlineData(2, 2, "x", "A item", "B item", "C item")]
[InlineData(0, 34, "y", "0 item", "1 item", "2 item", "3 item", "4 item", "5 item", "6 item", "7 item", "8 item", "9 item", "A item", "B item", "C item", "D item", "E item", "F item", "G item", "H item", "I item", "J item", "K item", "L item", "M item", "N item", "O item", "P item", "Q item", "R item", "S item", "T item", "U item", "V item", "W item", "X item", "Y item", "Z item")]
public void TextSearch_Should_Have_Expected_SelectedIndex(
int initialSelectedIndex,
int expectedSelectedIndex,
string searchTerm,
params string[] items)
{
using (UnitTestApplication.Start(TestServices.MockThreadingInterface))
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var target = new ComboBox
{
Template = GetTemplate(),
ItemsSource = items.Select(x => new ComboBoxItem { Content = x })
ItemsSource = items.Select(x => new ComboBoxItem { Content = x }),
};

TestRoot root = new(target)
{
ClientSize = new(500,500),
};

target.ApplyTemplate();
target.Presenter.ApplyTemplate();
target.SelectedIndex = initialSelectedIndex;
root.LayoutManager.ExecuteInitialLayoutPass();

var args = new TextInputEventArgs
{
Expand Down Expand Up @@ -293,7 +305,6 @@ public void SelectedItem_Validation()

Assert.True(DataValidationErrors.GetHasErrors(target));
Assert.True(DataValidationErrors.GetErrors(target).SequenceEqual(new[] { exception }));

}

}
Expand Down