Browse Source

Merge pull request #9157 from robloo/more-textbox-events

Add TextBox.TextChanged and TextBox.TextChanging Events
pull/9184/head
Max Katz 3 years ago
committed by GitHub
parent
commit
556215966e
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 91
      src/Avalonia.Controls/TextBox.cs
  2. 2
      src/Avalonia.Controls/TextBoxTextInputMethodClient.cs
  3. 20
      src/Avalonia.Controls/TextChangedEventArgs.cs
  4. 20
      src/Avalonia.Controls/TextChangingEventArgs.cs

91
src/Avalonia.Controls/TextBox.cs

@ -18,6 +18,7 @@ using Avalonia.Media.TextFormatting;
using Avalonia.Media.TextFormatting.Unicode;
using Avalonia.Automation.Peers;
using System.Diagnostics;
using Avalonia.Threading;
namespace Avalonia.Controls
{
@ -159,18 +160,41 @@ namespace Avalonia.Controls
(o, v) => o.UndoLimit = v,
unsetValue: -1);
/// <summary>
/// Defines the <see cref="CopyingToClipboard"/> event.
/// </summary>
public static readonly RoutedEvent<RoutedEventArgs> CopyingToClipboardEvent =
RoutedEvent.Register<TextBox, RoutedEventArgs>(
nameof(CopyingToClipboard), RoutingStrategies.Bubble);
/// <summary>
/// Defines the <see cref="CuttingToClipboard"/> event.
/// </summary>
public static readonly RoutedEvent<RoutedEventArgs> CuttingToClipboardEvent =
RoutedEvent.Register<TextBox, RoutedEventArgs>(
nameof(CuttingToClipboard), RoutingStrategies.Bubble);
/// <summary>
/// Defines the <see cref="PastingFromClipboard"/> event.
/// </summary>
public static readonly RoutedEvent<RoutedEventArgs> PastingFromClipboardEvent =
RoutedEvent.Register<TextBox, RoutedEventArgs>(
nameof(PastingFromClipboard), RoutingStrategies.Bubble);
/// <summary>
/// Defines the <see cref="TextChanged"/> event.
/// </summary>
public static readonly RoutedEvent<TextChangedEventArgs> TextChangedEvent =
RoutedEvent.Register<TextBox, TextChangedEventArgs>(
nameof(TextChanged), RoutingStrategies.Bubble);
/// <summary>
/// Defines the <see cref="TextChanging"/> event.
/// </summary>
public static readonly RoutedEvent<TextChangingEventArgs> TextChangingEvent =
RoutedEvent.Register<TextBox, TextChangingEventArgs>(
nameof(TextChanging), RoutingStrategies.Bubble);
readonly struct UndoRedoState : IEquatable<UndoRedoState>
{
public string? Text { get; }
@ -359,8 +383,8 @@ namespace Avalonia.Controls
/// </summary>
public double LineHeight
{
get { return GetValue(LineHeightProperty); }
set { SetValue(LineHeightProperty, value); }
get => GetValue(LineHeightProperty);
set => SetValue(LineHeightProperty, value);
}
[Content]
@ -376,11 +400,19 @@ namespace Avalonia.Controls
CaretIndex = CoerceCaretIndex(caretIndex, value);
SelectionStart = CoerceCaretIndex(selectionStart, value);
SelectionEnd = CoerceCaretIndex(selectionEnd, value);
if (SetAndRaise(TextProperty, ref _text, value) && IsUndoEnabled && !_isUndoingRedoing)
var textChanged = SetAndRaise(TextProperty, ref _text, value);
if (textChanged && IsUndoEnabled && !_isUndoingRedoing)
{
_undoRedoHelper.Clear();
SnapshotUndoRedo(); // so we always have an initial state
}
if (textChanged)
{
RaiseTextChangeEvents();
}
}
}
@ -564,6 +596,27 @@ namespace Avalonia.Controls
remove => RemoveHandler(PastingFromClipboardEvent, value);
}
/// <summary>
/// Occurs asynchronously after text changes and the new text is rendered.
/// </summary>
public event EventHandler<TextChangedEventArgs>? TextChanged
{
add => AddHandler(TextChangedEvent, value);
remove => RemoveHandler(TextChangedEvent, value);
}
/// <summary>
/// Occurs synchronously when text starts to change but before it is rendered.
/// </summary>
/// <remarks>
/// This event occurs just after the <see cref="Text"/> property value has been updated.
/// </remarks>
public event EventHandler<TextChangingEventArgs>? TextChanging
{
add => AddHandler(TextChangingEvent, value);
remove => RemoveHandler(TextChangingEvent, value);
}
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
_presenter = e.NameScope.Get<TextPresenter>("PART_TextPresenter");
@ -1252,7 +1305,7 @@ namespace Avalonia.Controls
if (text != null && _wordSelectionStart >= 0)
{
var distance = caretIndex - _wordSelectionStart;
var distance = caretIndex - _wordSelectionStart;
if (distance <= 0)
{
@ -1539,11 +1592,39 @@ namespace Avalonia.Controls
return text.Substring(start, end - start);
}
/// <summary>
/// Raises both the <see cref="TextChanging"/> and <see cref="TextChanged"/> events.
/// </summary>
/// <remarks>
/// This must be called after the <see cref="Text"/> property is set.
/// </remarks>
private void RaiseTextChangeEvents()
{
// Note the following sequence of these events (following WinUI)
// 1. TextChanging occurs synchronously when text starts to change but before it is rendered.
// This occurs after the Text property is set.
// 2. TextChanged occurs asynchronously after text changes and the new text is rendered.
var textChangingEventArgs = new TextChangingEventArgs(TextChangingEvent);
RaiseEvent(textChangingEventArgs);
Dispatcher.UIThread.Post(() =>
{
var textChangedEventArgs = new TextChangedEventArgs(TextChangedEvent);
RaiseEvent(textChangedEventArgs);
}, DispatcherPriority.Normal);
}
private void SetTextInternal(string value, bool raiseTextChanged = true)
{
if (raiseTextChanged)
{
SetAndRaise(TextProperty, ref _text, value);
bool textChanged = SetAndRaise(TextProperty, ref _text, value);
if (textChanged)
{
RaiseTextChangeEvents();
}
}
else
{

2
src/Avalonia.Controls/TextBoxTextInputMethodClient.cs

@ -64,7 +64,7 @@ namespace Avalonia.Controls
return new TextInputMethodSurroundingText
{
Text = lineText ?? "",
Text = lineText ?? "",
AnchorOffset = anchorOffset,
CursorOffset = cursorOffset
};

20
src/Avalonia.Controls/TextChangedEventArgs.cs

@ -0,0 +1,20 @@
using Avalonia.Interactivity;
namespace Avalonia.Controls
{
/// <summary>
/// Provides data specific to a TextChanged event.
/// </summary>
public class TextChangedEventArgs : RoutedEventArgs
{
public TextChangedEventArgs(RoutedEvent? routedEvent)
: base (routedEvent)
{
}
public TextChangedEventArgs(RoutedEvent? routedEvent, IInteractive? source)
: base(routedEvent, source)
{
}
}
}

20
src/Avalonia.Controls/TextChangingEventArgs.cs

@ -0,0 +1,20 @@
using Avalonia.Interactivity;
namespace Avalonia.Controls
{
/// <summary>
/// Provides data specific to a TextChanging event.
/// </summary>
public class TextChangingEventArgs : RoutedEventArgs
{
public TextChangingEventArgs(RoutedEvent? routedEvent)
: base (routedEvent)
{
}
public TextChangingEventArgs(RoutedEvent? routedEvent, IInteractive? source)
: base(routedEvent, source)
{
}
}
}
Loading…
Cancel
Save