diff --git a/NOTICE.md b/NOTICE.md
index 92fd725957..e97fc654c9 100644
--- a/NOTICE.md
+++ b/NOTICE.md
@@ -111,7 +111,7 @@ DEALINGS IN THE SOFTWARE.
# Metsys.Bson
-Copyright (c) 2010, Karl Seguin - http://www.openmymind.net/
+Copyright (c) 2010, Karl Seguin - https://www.openmymind.net/
All rights reserved.
Redistribution and use in source and binary forms, with or without
@@ -302,4 +302,4 @@ https://github.com/chromium/chromium
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/src/Android/Avalonia.Android/AvaloniaView.cs b/src/Android/Avalonia.Android/AvaloniaView.cs
index 34fa121cd7..94e863210b 100644
--- a/src/Android/Avalonia.Android/AvaloniaView.cs
+++ b/src/Android/Avalonia.Android/AvaloniaView.cs
@@ -74,7 +74,7 @@ namespace Avalonia.Android
class ViewImpl : TopLevelImpl
{
- public ViewImpl(AvaloniaView avaloniaView) : base(avaloniaView, true)
+ public ViewImpl(AvaloniaView avaloniaView) : base(avaloniaView)
{
View.Focusable = true;
View.FocusChange += ViewImpl_FocusChange;
diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs
index 964a153c8b..da4e90fb66 100644
--- a/src/Avalonia.Controls/TextBox.cs
+++ b/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);
+ ///
+ /// Defines the event.
+ ///
public static readonly RoutedEvent CopyingToClipboardEvent =
RoutedEvent.Register(
nameof(CopyingToClipboard), RoutingStrategies.Bubble);
+ ///
+ /// Defines the event.
+ ///
public static readonly RoutedEvent CuttingToClipboardEvent =
RoutedEvent.Register(
nameof(CuttingToClipboard), RoutingStrategies.Bubble);
+ ///
+ /// Defines the event.
+ ///
public static readonly RoutedEvent PastingFromClipboardEvent =
RoutedEvent.Register(
nameof(PastingFromClipboard), RoutingStrategies.Bubble);
+ ///
+ /// Defines the event.
+ ///
+ public static readonly RoutedEvent TextChangedEvent =
+ RoutedEvent.Register(
+ nameof(TextChanged), RoutingStrategies.Bubble);
+
+ ///
+ /// Defines the event.
+ ///
+ public static readonly RoutedEvent TextChangingEvent =
+ RoutedEvent.Register(
+ nameof(TextChanging), RoutingStrategies.Bubble);
+
readonly struct UndoRedoState : IEquatable
{
public string? Text { get; }
@@ -359,8 +383,8 @@ namespace Avalonia.Controls
///
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);
}
+ ///
+ /// Occurs asynchronously after text changes and the new text is rendered.
+ ///
+ public event EventHandler? TextChanged
+ {
+ add => AddHandler(TextChangedEvent, value);
+ remove => RemoveHandler(TextChangedEvent, value);
+ }
+
+ ///
+ /// Occurs synchronously when text starts to change but before it is rendered.
+ ///
+ ///
+ /// This event occurs just after the property value has been updated.
+ ///
+ public event EventHandler? TextChanging
+ {
+ add => AddHandler(TextChangingEvent, value);
+ remove => RemoveHandler(TextChangingEvent, value);
+ }
+
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
_presenter = e.NameScope.Get("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);
}
+ ///
+ /// Raises both the and events.
+ ///
+ ///
+ /// This must be called after the property is set.
+ ///
+ 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
{
diff --git a/src/Avalonia.Controls/TextBoxTextInputMethodClient.cs b/src/Avalonia.Controls/TextBoxTextInputMethodClient.cs
index d39d964277..5d5ffcc381 100644
--- a/src/Avalonia.Controls/TextBoxTextInputMethodClient.cs
+++ b/src/Avalonia.Controls/TextBoxTextInputMethodClient.cs
@@ -64,7 +64,7 @@ namespace Avalonia.Controls
return new TextInputMethodSurroundingText
{
- Text = lineText ?? "",
+ Text = lineText ?? "",
AnchorOffset = anchorOffset,
CursorOffset = cursorOffset
};
diff --git a/src/Avalonia.Controls/TextChangedEventArgs.cs b/src/Avalonia.Controls/TextChangedEventArgs.cs
new file mode 100644
index 0000000000..77c609f19b
--- /dev/null
+++ b/src/Avalonia.Controls/TextChangedEventArgs.cs
@@ -0,0 +1,20 @@
+using Avalonia.Interactivity;
+
+namespace Avalonia.Controls
+{
+ ///
+ /// Provides data specific to a TextChanged event.
+ ///
+ public class TextChangedEventArgs : RoutedEventArgs
+ {
+ public TextChangedEventArgs(RoutedEvent? routedEvent)
+ : base (routedEvent)
+ {
+ }
+
+ public TextChangedEventArgs(RoutedEvent? routedEvent, IInteractive? source)
+ : base(routedEvent, source)
+ {
+ }
+ }
+}
diff --git a/src/Avalonia.Controls/TextChangingEventArgs.cs b/src/Avalonia.Controls/TextChangingEventArgs.cs
new file mode 100644
index 0000000000..4dedbc927b
--- /dev/null
+++ b/src/Avalonia.Controls/TextChangingEventArgs.cs
@@ -0,0 +1,20 @@
+using Avalonia.Interactivity;
+
+namespace Avalonia.Controls
+{
+ ///
+ /// Provides data specific to a TextChanging event.
+ ///
+ public class TextChangingEventArgs : RoutedEventArgs
+ {
+ public TextChangingEventArgs(RoutedEvent? routedEvent)
+ : base (routedEvent)
+ {
+ }
+
+ public TextChangingEventArgs(RoutedEvent? routedEvent, IInteractive? source)
+ : base(routedEvent, source)
+ {
+ }
+ }
+}
diff --git a/tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs b/tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs
index 1f0b82b465..26a1ab88c7 100644
--- a/tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs
+++ b/tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs
@@ -235,7 +235,7 @@ namespace Avalonia.Base.UnitTests.VisualTree
public bool FillContains(Point point)
{
- // Use the algorithm from http://www.blackpawn.com/texts/pointinpoly/default.html
+ // Use the algorithm from https://www.blackpawn.com/texts/pointinpoly/default.html
// to determine if the point is in the geometry (since it will always be convex in this situation)
for (int i = 0; i < points.Count; i++)
{
diff --git a/tests/Avalonia.UnitTests/MockStreamGeometryImpl.cs b/tests/Avalonia.UnitTests/MockStreamGeometryImpl.cs
index 864e2efbaf..9d039a386e 100644
--- a/tests/Avalonia.UnitTests/MockStreamGeometryImpl.cs
+++ b/tests/Avalonia.UnitTests/MockStreamGeometryImpl.cs
@@ -148,7 +148,7 @@ namespace Avalonia.UnitTests
public bool FillContains(Point point)
{
- // Use the algorithm from http://www.blackpawn.com/texts/pointinpoly/default.html
+ // Use the algorithm from https://www.blackpawn.com/texts/pointinpoly/default.html
// to determine if the point is in the geometry (since it will always be convex in this situation)
for (int i = 0; i < points.Count; i++)
{