diff --git a/samples/ControlCatalog/Pages/TextBoxPage.xaml b/samples/ControlCatalog/Pages/TextBoxPage.xaml
index 74083998736..058bcff84de 100644
--- a/samples/ControlCatalog/Pages/TextBoxPage.xaml
+++ b/samples/ControlCatalog/Pages/TextBoxPage.xaml
@@ -43,7 +43,7 @@
+ SelectionBrush="Green" SelectionForegroundBrush="Yellow" ClearSelectionOnLostFocus="False"/>
diff --git a/src/Avalonia.Controls/Presenters/TextPresenter.cs b/src/Avalonia.Controls/Presenters/TextPresenter.cs
index e35d683a1f7..622727ccbe9 100644
--- a/src/Avalonia.Controls/Presenters/TextPresenter.cs
+++ b/src/Avalonia.Controls/Presenters/TextPresenter.cs
@@ -17,6 +17,9 @@ namespace Avalonia.Controls.Presenters
{
public class TextPresenter : Control
{
+ public static readonly StyledProperty ShowSelectionHighlightProperty =
+ AvaloniaProperty.Register(nameof(ShowSelectionHighlight), defaultValue: true);
+
public static readonly StyledProperty CaretIndexProperty =
TextBox.CaretIndexProperty.AddOwner(new(coerce: TextBox.CoerceCaretIndex));
@@ -105,7 +108,7 @@ public class TextPresenter : Control
static TextPresenter()
{
- AffectsRender(CaretBrushProperty, SelectionBrushProperty, SelectionForegroundBrushProperty, TextElement.ForegroundProperty);
+ AffectsRender(CaretBrushProperty, SelectionBrushProperty, SelectionForegroundBrushProperty, TextElement.ForegroundProperty, ShowSelectionHighlightProperty);
}
public TextPresenter() { }
@@ -121,6 +124,15 @@ public IBrush? Background
set => SetValue(BackgroundProperty, value);
}
+ ///
+ /// Gets or sets a value that determines whether the TextPresenter shows a selection highlight.
+ ///
+ public bool ShowSelectionHighlight
+ {
+ get => GetValue(ShowSelectionHighlightProperty);
+ set => SetValue(ShowSelectionHighlightProperty, value);
+ }
+
///
/// Gets or sets the text.
///
@@ -386,7 +398,7 @@ public sealed override void Render(DrawingContext context)
var selectionEnd = SelectionEnd;
var selectionBrush = SelectionBrush;
- if (selectionStart != selectionEnd && selectionBrush != null)
+ if (ShowSelectionHighlight && selectionStart != selectionEnd && selectionBrush != null)
{
var start = Math.Min(selectionStart, selectionEnd);
var length = Math.Max(selectionStart, selectionEnd) - start;
@@ -473,7 +485,7 @@ public void HideCaret()
_caretBlink = false;
RemoveTextSelectionCanvas();
_caretTimer?.Stop();
- InvalidateVisual();
+ InvalidateTextLayout();
}
internal void CaretChanged()
@@ -552,7 +564,7 @@ protected virtual TextLayout CreateTextLayout()
}
else
{
- if (length > 0 && SelectionForegroundBrush != null)
+ if (ShowSelectionHighlight && length > 0 && SelectionForegroundBrush != null)
{
textStyleOverrides = new[]
{
@@ -1031,6 +1043,7 @@ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs chang
case nameof(SelectionStart):
case nameof(SelectionEnd):
case nameof(SelectionForegroundBrush):
+ case nameof(ShowSelectionHighlightProperty):
case nameof(PasswordChar):
case nameof(RevealPassword):
diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs
index 458040ee7d4..62070f68474 100644
--- a/src/Avalonia.Controls/TextBox.cs
+++ b/src/Avalonia.Controls/TextBox.cs
@@ -44,6 +44,18 @@ public class TextBox : TemplatedControl, UndoRedoHelper.I
///
public static KeyGesture? PasteGesture => Application.Current?.PlatformSettings?.HotkeyConfiguration.Paste.FirstOrDefault();
+ ///
+ /// Defines the property
+ ///
+ public static readonly StyledProperty IsInactiveSelectionHighlightEnabledProperty =
+ AvaloniaProperty.Register(nameof(IsInactiveSelectionHighlightEnabled), defaultValue: true);
+
+ ///
+ /// Defines the property
+ ///
+ public static readonly StyledProperty ClearSelectionOnLostFocusProperty =
+ AvaloniaProperty.Register(nameof(ClearSelectionOnLostFocus), defaultValue: true);
+
///
/// Defines the property
///
@@ -373,6 +385,24 @@ public TextBox()
UpdatePseudoclasses();
}
+ ///
+ /// Gets or sets a value that determines whether the TextBox shows a selection highlight when it is not focused.
+ ///
+ public bool IsInactiveSelectionHighlightEnabled
+ {
+ get => GetValue(IsInactiveSelectionHighlightEnabledProperty);
+ set => SetValue(IsInactiveSelectionHighlightEnabledProperty, value);
+ }
+
+ ///
+ /// Gets or sets a value that determines whether the TextBox clears its selection after it loses focus.
+ ///
+ public bool ClearSelectionOnLostFocus
+ {
+ get=> GetValue(ClearSelectionOnLostFocusProperty);
+ set=> SetValue(ClearSelectionOnLostFocusProperty, value);
+ }
+
///
/// Gets or sets a value that determines whether the TextBox allows and displays newline or return characters
///
@@ -880,6 +910,13 @@ protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
_presenter.ShowCaret();
}
+ else
+ {
+ if (IsInactiveSelectionHighlightEnabled)
+ {
+ _presenter.ShowSelectionHighlight = true;
+ }
+ }
_presenter.PropertyChanged += PresenterPropertyChanged;
}
@@ -977,6 +1014,11 @@ protected override void OnGotFocus(GotFocusEventArgs e)
{
base.OnGotFocus(e);
+ if(_presenter != null)
+ {
+ _presenter.ShowSelectionHighlight = true;
+ }
+
// when navigating to a textbox via the tab key, select all text if
// 1) this textbox is *not* a multiline textbox
// 2) this textbox has any text to select
@@ -1001,7 +1043,11 @@ protected override void OnLostFocus(RoutedEventArgs e)
if ((ContextFlyout == null || !ContextFlyout.IsOpen) &&
(ContextMenu == null || !ContextMenu.IsOpen))
{
- ClearSelection();
+ if (ClearSelectionOnLostFocus)
+ {
+ ClearSelection();
+ }
+
SetCurrentValue(RevealPasswordProperty, false);
}
@@ -1010,6 +1056,11 @@ protected override void OnLostFocus(RoutedEventArgs e)
_presenter?.HideCaret();
_imClient.SetPresenter(null, null);
+
+ if (_presenter != null && !IsInactiveSelectionHighlightEnabled)
+ {
+ _presenter.ShowSelectionHighlight = false;
+ }
}
protected override void OnTextInput(TextInputEventArgs e)
diff --git a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs b/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs
index 30f82533cea..d6b24b627f9 100644
--- a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs
@@ -1552,6 +1552,48 @@ public void Backspace_Should_Delete_Last_Character_In_Line_And_Keep_Caret_On_Sam
Assert.Equal(oldCaretY, caretY);
}
+ [Fact]
+ public void Losing_Focus_Should_Not_Reset_Selection()
+ {
+ using (UnitTestApplication.Start(FocusServices))
+ {
+ var target1 = new TextBox
+ {
+ Template = CreateTemplate(),
+ Text = "1234",
+ ClearSelectionOnLostFocus = false
+ };
+
+ target1.ApplyTemplate();
+
+ var target2 = new TextBox
+ {
+ Template = CreateTemplate(),
+ };
+
+ target2.ApplyTemplate();
+
+ var sp = new StackPanel();
+ sp.Children.Add(target1);
+ sp.Children.Add(target2);
+
+ var root = new TestRoot() { Child = sp };
+
+ target1.SelectionStart = 0;
+ target1.SelectionEnd = 4;
+
+ target1.Focus();
+
+ Assert.True(target1.IsFocused);
+
+ Assert.Equal("1234", target1.SelectedText);
+
+ target2.Focus();
+
+ Assert.Equal("1234", target1.SelectedText);
+ }
+ }
+
private static TestServices FocusServices => TestServices.MockThreadingInterface.With(
focusManager: new FocusManager(),
keyboardDevice: () => new KeyboardDevice(),