diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
index b4cfd9404c..c690726e71 100644
--- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
+++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
@@ -531,11 +531,23 @@ namespace Avalonia.Controls.Primitives
_textSearchTerm += e.Text;
- bool match(ItemContainerInfo info) =>
- info.ContainerControl is IContentControl control &&
- control.Content?.ToString()?.StartsWith(_textSearchTerm, StringComparison.OrdinalIgnoreCase) == true;
+ bool Match(ItemContainerInfo info)
+ {
+ if (info.ContainerControl.IsSet(TextSearch.TextProperty))
+ {
+ var searchText = info.ContainerControl.GetValue(TextSearch.TextProperty);
- var info = ItemContainerGenerator?.Containers.FirstOrDefault(match);
+ if (searchText?.StartsWith(_textSearchTerm, StringComparison.OrdinalIgnoreCase) == true)
+ {
+ return true;
+ }
+ }
+
+ return info.ContainerControl is IContentControl control &&
+ control.Content?.ToString()?.StartsWith(_textSearchTerm, StringComparison.OrdinalIgnoreCase) == true;
+ }
+
+ var info = ItemContainerGenerator?.Containers.FirstOrDefault(Match);
if (info != null)
{
diff --git a/src/Avalonia.Controls/Primitives/TextSearch.cs b/src/Avalonia.Controls/Primitives/TextSearch.cs
new file mode 100644
index 0000000000..949532cb16
--- /dev/null
+++ b/src/Avalonia.Controls/Primitives/TextSearch.cs
@@ -0,0 +1,37 @@
+using Avalonia.Interactivity;
+
+namespace Avalonia.Controls.Primitives
+{
+ ///
+ /// Allows to customize text searching in .
+ ///
+ public static class TextSearch
+ {
+ ///
+ /// Defines the Text attached property.
+ /// This text will be considered during text search in (such as )
+ ///
+ public static readonly AttachedProperty TextProperty
+ = AvaloniaProperty.RegisterAttached("Text", typeof(TextSearch));
+
+ ///
+ /// Sets the for a control.
+ ///
+ /// The control
+ /// The search text to set
+ public static void SetText(Control control, string text)
+ {
+ control.SetValue(TextProperty, text);
+ }
+
+ ///
+ /// Gets the of a control.
+ ///
+ /// The control
+ /// The property value
+ public static string GetText(Control control)
+ {
+ return control.GetValue(TextProperty);
+ }
+ }
+}
diff --git a/src/Avalonia.Input/MouseDevice.cs b/src/Avalonia.Input/MouseDevice.cs
index 3749947da9..eed51e6c4a 100644
--- a/src/Avalonia.Input/MouseDevice.cs
+++ b/src/Avalonia.Input/MouseDevice.cs
@@ -5,6 +5,7 @@ using System.Reactive.Linq;
using Avalonia.Input.Raw;
using Avalonia.Interactivity;
using Avalonia.Platform;
+using Avalonia.Utilities;
using Avalonia.VisualTree;
namespace Avalonia.Input
@@ -334,6 +335,13 @@ namespace Avalonia.Input
var hit = HitTest(root, p);
var source = GetSource(hit);
+ // KeyModifiers.Shift should scroll in horizontal direction. This does not work on every platform.
+ // If Shift-Key is pressed and X is close to 0 we swap the Vector.
+ if (inputModifiers == KeyModifiers.Shift && MathUtilities.IsZero(delta.X))
+ {
+ delta = new Vector(delta.Y, delta.X);
+ }
+
if (source is not null)
{
var e = new PointerWheelEventArgs(source, _pointer, root, p, timestamp, props, inputModifiers, delta);
diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs
index 514d3b5475..0e0ca7cd25 100644
--- a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs
@@ -1,9 +1,11 @@
+using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
+using System.Reactive.Disposables;
using Avalonia.Collections;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
@@ -13,7 +15,9 @@ using Avalonia.Data;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Markup.Data;
+using Avalonia.Platform;
using Avalonia.Styling;
+using Avalonia.Threading;
using Avalonia.UnitTests;
using Moq;
using Xunit;
@@ -1895,6 +1899,54 @@ namespace Avalonia.Controls.UnitTests.Primitives
Assert.Equal(1, carouselRaised);
}
+ [Fact]
+ public void Setting_IsTextSearchEnabled_Enables_Or_Disables_Text_Search()
+ {
+ var pti = Mock.Of(x => x.CurrentThreadIsLoopThread == true);
+
+ Mock.Get(pti)
+ .Setup(v => v.StartTimer(It.IsAny(), It.IsAny(), It.IsAny()))
+ .Returns(Disposable.Empty);
+
+ using (UnitTestApplication.Start(TestServices.StyledWindow.With(threadingInterface: pti)))
+ {
+ var items = new[]
+ {
+ new Item { [TextSearch.TextProperty] = "Foo" },
+ new Item { [TextSearch.TextProperty] = "Bar" }
+ };
+
+ var target = new SelectingItemsControl
+ {
+ Items = items,
+ Template = Template(),
+ IsTextSearchEnabled = false
+ };
+
+ Prepare(target);
+
+ target.RaiseEvent(new TextInputEventArgs
+ {
+ RoutedEvent = InputElement.TextInputEvent,
+ Device = KeyboardDevice.Instance,
+ Text = "Foo"
+ });
+
+ Assert.Null(target.SelectedItem);
+
+ target.IsTextSearchEnabled = true;
+
+ target.RaiseEvent(new TextInputEventArgs
+ {
+ RoutedEvent = InputElement.TextInputEvent,
+ Device = KeyboardDevice.Instance,
+ Text = "Foo"
+ });
+
+ Assert.Equal(items[0], target.SelectedItem);
+ }
+ }
+
private static void Prepare(SelectingItemsControl target)
{
var root = new TestRoot