diff --git a/src/Avalonia.Controls/MenuItemAccessKeyHandler.cs b/src/Avalonia.Controls/MenuItemAccessKeyHandler.cs index 002946bb79d..78cbc6c2641 100644 --- a/src/Avalonia.Controls/MenuItemAccessKeyHandler.cs +++ b/src/Avalonia.Controls/MenuItemAccessKeyHandler.cs @@ -12,7 +12,7 @@ namespace Avalonia.Controls /// /// Handles access keys within a /// - public class MenuItemAccessKeyHandler : IAccessKeyHandler + public class MenuItemAccessKeyHandler : IAccessKeyHandler, ITextInputHandler { /// /// The registered access keys. @@ -50,7 +50,16 @@ public void SetOwner(IInputRoot owner) _owner = owner; - _owner.AddHandler(InputElement.TextInputEvent, OnTextInput); + _owner.AddHandler(InputElement.TextInputHandlerSelectionEvent, OnTextInputHandlerSelection, RoutingStrategies.Bubble); + } + + private void OnTextInputHandlerSelection(object sender, TextInputHandlerSelectionEventArgs e) + { + if (!e.Handled && e.Handler == null) + { + e.Handler = this; + e.Handled = true; + } } /// @@ -84,20 +93,18 @@ public void Unregister(IInputElement element) /// /// Handles a key being pressed in the menu. + /// We are currently using "text" input event, which we shouldn't /// - /// The event sender. - /// The event args. - protected virtual void OnTextInput(object sender, TextInputEventArgs e) + // TODO: Replace this with KeyDown event + void ITextInputHandler.OnTextEntered (uint timestamp, string text) { - if (!string.IsNullOrWhiteSpace(e.Text)) + if (!string.IsNullOrWhiteSpace(text)) { - var text = e.Text.ToUpper(); + text = text.ToUpperInvariant(); var focus = _registered .FirstOrDefault(x => x.Item1 == text && x.Item2.IsEffectivelyVisible)?.Item2; focus?.RaiseEvent(new RoutedEventArgs(AccessKeyHandler.AccessKeyPressedEvent)); - - e.Handled = true; } } } diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index dc2884b36b8..d2d07d35271 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -18,7 +18,8 @@ namespace Avalonia.Controls { - public class TextBox : TemplatedControl, UndoRedoHelper.IUndoRedoHost + public class TextBox : TemplatedControl, UndoRedoHelper.IUndoRedoHost, + ITextInputHandler { public static readonly StyledProperty AcceptsReturnProperty = AvaloniaProperty.Register(nameof(AcceptsReturn)); @@ -308,13 +309,20 @@ protected override void OnLostFocus(RoutedEventArgs e) _presenter?.HideCaret(); } - protected override void OnTextInput(TextInputEventArgs e) + protected override void OnTextInputHandlerSelection(TextInputHandlerSelectionEventArgs e) { if (!e.Handled) { - HandleTextInput(e.Text); + e.Handler = this; e.Handled = true; } + base.OnTextInputHandlerSelection(e); + } + + void ITextInputHandler.OnTextEntered (uint timestamp, string text) + { + if (!RaiseCompatibleTextInput(text)) + HandleTextInput(text); } private void HandleTextInput(string input) diff --git a/src/Avalonia.Diagnostics/ViewModels/EventOwnerTreeNode.cs b/src/Avalonia.Diagnostics/ViewModels/EventOwnerTreeNode.cs index 06749184002..b360b0ea18d 100644 --- a/src/Avalonia.Diagnostics/ViewModels/EventOwnerTreeNode.cs +++ b/src/Avalonia.Diagnostics/ViewModels/EventOwnerTreeNode.cs @@ -18,7 +18,7 @@ internal class EventOwnerTreeNode : EventTreeNodeBase Button.ClickEvent, InputElement.KeyDownEvent, InputElement.KeyUpEvent, - InputElement.TextInputEvent, + InputElement.TextInputHandlerSelectionEvent, InputElement.PointerReleasedEvent, InputElement.PointerPressedEvent, }; diff --git a/src/Avalonia.Input/IInputElement.cs b/src/Avalonia.Input/IInputElement.cs index c9924dbffbd..b151796355f 100644 --- a/src/Avalonia.Input/IInputElement.cs +++ b/src/Avalonia.Input/IInputElement.cs @@ -33,9 +33,9 @@ public interface IInputElement : IInteractive, IVisual event EventHandler KeyUp; /// - /// Occurs when a user typed some text while the control has focus. + /// Occurs when text input system asks for a handler to be selected /// - event EventHandler TextInput; + event EventHandler TextInputHandlerSelection; /// /// Occurs when the pointer enters the control. diff --git a/src/Avalonia.Input/ITextInputHandler.cs b/src/Avalonia.Input/ITextInputHandler.cs new file mode 100644 index 00000000000..559dffd1393 --- /dev/null +++ b/src/Avalonia.Input/ITextInputHandler.cs @@ -0,0 +1,7 @@ +namespace Avalonia.Input +{ + public interface ITextInputHandler + { + void OnTextEntered(uint timestamp, string text); + } +} diff --git a/src/Avalonia.Input/InputElement.cs b/src/Avalonia.Input/InputElement.cs index e7fea94d3d8..a8dac0d05d9 100644 --- a/src/Avalonia.Input/InputElement.cs +++ b/src/Avalonia.Input/InputElement.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; using Avalonia.Interactivity; using Avalonia.VisualTree; @@ -85,11 +86,11 @@ public class InputElement : Interactive, IInputElement RoutingStrategies.Tunnel | RoutingStrategies.Bubble); /// - /// Defines the event. + /// Defines the event. /// - public static readonly RoutedEvent TextInputEvent = - RoutedEvent.Register( - "TextInput", + public static readonly RoutedEvent TextInputHandlerSelectionEvent = + RoutedEvent.Register( + "TextInputHandlerSelection", RoutingStrategies.Tunnel | RoutingStrategies.Bubble); /// @@ -160,7 +161,7 @@ static InputElement() LostFocusEvent.AddClassHandler(x => x.OnLostFocus); KeyDownEvent.AddClassHandler(x => x.OnKeyDown); KeyUpEvent.AddClassHandler(x => x.OnKeyUp); - TextInputEvent.AddClassHandler(x => x.OnTextInput); + TextInputHandlerSelectionEvent.AddClassHandler(x => x.OnTextInputHandlerSelection); PointerEnterEvent.AddClassHandler(x => x.OnPointerEnterCore); PointerLeaveEvent.AddClassHandler(x => x.OnPointerLeaveCore); PointerMovedEvent.AddClassHandler(x => x.OnPointerMoved); @@ -210,12 +211,12 @@ public event EventHandler KeyUp } /// - /// Occurs when a user typed some text while the control has focus. + /// Occurs when text input system asks for a handler to be selected /// - public event EventHandler TextInput + public event EventHandler TextInputHandlerSelection { - add { AddHandler(TextInputEvent, value); } - remove { RemoveHandler(TextInputEvent, value); } + add { AddHandler(TextInputHandlerSelectionEvent, value); } + remove { RemoveHandler(TextInputHandlerSelectionEvent, value); } } /// @@ -430,14 +431,6 @@ protected virtual void OnKeyUp(KeyEventArgs e) { } - /// - /// Called before the event occurs. - /// - /// The event args. - protected virtual void OnTextInput(TextInputEventArgs e) - { - } - /// /// Called before the event occurs. /// @@ -540,5 +533,121 @@ private void UpdateIsEnabledCore(InputElement parent) child.UpdateIsEnabledCore(this); } } + + #region OnTextInput compatibility layer + + private static Dictionary s_compatibleTextInputRegistry = new Dictionary(); + private CompatibleTextInputHandler _compatibleTextInputHandler; + class CompatibleTextInputHandler : ITextInputHandler + { + private readonly InputElement _parent; + + public CompatibleTextInputHandler(InputElement parent) + { + _parent = parent; + } + + public EventHandler Event; + public void OnTextEntered(uint timestamp, string text) + { + _parent.RaiseCompatibleTextInput(text); + } + } + + /// + /// Called before the event occurs. + /// + /// The event args. + protected virtual void OnTextInputHandlerSelection(TextInputHandlerSelectionEventArgs e) + { + if (_compatibleTextInputHandler != null && !e.Handled) + { + e.Handler = _compatibleTextInputHandler; + e.Handled = true; + } + } + + [Obsolete("Use TextInputHandlerSelection")] + public event EventHandler TextInput + { + add + { + if (_compatibleTextInputHandler == null) + { + _compatibleTextInputHandler = new CompatibleTextInputHandler(this); + _compatibleTextInputHandler.Event += value; + } + } + remove + { + if (_compatibleTextInputHandler != null) + { + _compatibleTextInputHandler.Event -= value; + if (_compatibleTextInputHandler.Event == null) + _compatibleTextInputHandler = null; + } + } + } + + protected internal bool RaiseCompatibleTextInput(string text) + { + if (_compatibleTextInputHandler != null) + { + var ev = new TextInputEventArgs + { + Text = text, + Source = this + }; + _compatibleTextInputHandler.Event?.Invoke(this, ev); + if(!ev.Handled) + OnTextInput(ev); + return ev.Handled; + } + + return false; + } + + [Obsolete("Use TextInputHandlerSelection")] + protected virtual void OnTextInput(TextInputEventArgs e) + { + + } + + static bool CheckIfOverridden(MethodInfo method, Type target) + { + while (true) + { + var baseMethod = method.GetBaseDefinition(); + if(baseMethod == method || baseMethod == null) + break; + method = baseMethod; + } + + var fromType = method.DeclaringType; + while (fromType != target && target != null) + { + var found = target.GetMethod(method.Name, + BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, + null, method.GetParameters().Select(p => p.ParameterType).ToArray(), null); + if (found != null && found.DeclaringType != fromType) + return true; + + target = target.BaseType; + } + + return false; + } + + public InputElement() + { + var type = GetType(); + if (!s_compatibleTextInputRegistry.TryGetValue(type, out var needsCompat)) + s_compatibleTextInputRegistry[type] = needsCompat = + CheckIfOverridden(new Action(OnTextInput).Method, type); + if (needsCompat) + TextInput += (o, e) => OnTextInput(e); + } + + #endregion } } diff --git a/src/Avalonia.Input/KeyboardDevice.cs b/src/Avalonia.Input/KeyboardDevice.cs index 00606bd9b17..4936b46aa9c 100644 --- a/src/Avalonia.Input/KeyboardDevice.cs +++ b/src/Avalonia.Input/KeyboardDevice.cs @@ -115,20 +115,23 @@ public void ProcessRawEvent(RawInputEventArgs e) } } + + // Compatibility layer with backends that still use RawTextInputEventArgs var text = e as RawTextInputEventArgs; if (text != null) { - var ev = new TextInputEventArgs() + var ev = new TextInputHandlerSelectionEventArgs { - Device = this, - Text = text.Text, Source = element, - RoutedEvent = InputElement.TextInputEvent + RoutedEvent = InputElement.TextInputHandlerSelectionEvent }; - element.RaiseEvent(ev); - e.Handled = ev.Handled; + if (ev.Handled && ev.Handler != null) + { + ev.Handler.OnTextEntered(e.Timestamp, text.Text); + e.Handled = ev.Handled; + } } } } diff --git a/src/Avalonia.Input/Raw/RawTextInputEventArgs.cs b/src/Avalonia.Input/Raw/RawTextInputEventArgs.cs index 6e427c3751f..563e2cf5c27 100644 --- a/src/Avalonia.Input/Raw/RawTextInputEventArgs.cs +++ b/src/Avalonia.Input/Raw/RawTextInputEventArgs.cs @@ -1,8 +1,11 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. +using System; + namespace Avalonia.Input.Raw { + [Obsolete("Use RawTextInputHandlerSelectionEventArgs")] public class RawTextInputEventArgs : RawInputEventArgs { public string Text { get; set; } diff --git a/src/Avalonia.Input/TextInputEventArgs.cs b/src/Avalonia.Input/TextInputEventArgs.cs index b7203b54d58..5ef97ee69eb 100644 --- a/src/Avalonia.Input/TextInputEventArgs.cs +++ b/src/Avalonia.Input/TextInputEventArgs.cs @@ -1,10 +1,12 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. +using System; using Avalonia.Interactivity; namespace Avalonia.Input { + [Obsolete("Use TextInputHandlerSelection")] public class TextInputEventArgs : RoutedEventArgs { public IKeyboardDevice Device { get; set; } diff --git a/src/Avalonia.Input/TextInputHandlerSelectionEventArgs.cs b/src/Avalonia.Input/TextInputHandlerSelectionEventArgs.cs new file mode 100644 index 00000000000..203b5672f89 --- /dev/null +++ b/src/Avalonia.Input/TextInputHandlerSelectionEventArgs.cs @@ -0,0 +1,9 @@ +using Avalonia.Interactivity; + +namespace Avalonia.Input +{ + public class TextInputHandlerSelectionEventArgs : RoutedEventArgs + { + public ITextInputHandler Handler { get; set; } + } +} diff --git a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs b/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs index 0d87f6d0fe0..75bd985ab84 100644 --- a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs @@ -114,12 +114,7 @@ public void Typing_Beginning_With_0_Should_Not_Modify_Text_When_Bound_To_Int() Assert.Equal("0", target.Text); target.CaretIndex = 1; - target.RaiseEvent(new TextInputEventArgs - { - RoutedEvent = InputElement.TextInputEvent, - Text = "2", - }); - + RaiseTextEvent(target, "2"); Assert.Equal("02", target.Text); } } @@ -417,11 +412,12 @@ private void RaiseKeyEvent(TextBox textBox, Key key, InputModifiers inputModifie private void RaiseTextEvent(TextBox textBox, string text) { - textBox.RaiseEvent(new TextInputEventArgs + var ev = new TextInputHandlerSelectionEventArgs { - RoutedEvent = InputElement.TextInputEvent, - Text = text - }); + RoutedEvent = InputElement.TextInputHandlerSelectionEvent + }; + textBox.RaiseEvent(ev); + ev.Handler?.OnTextEntered(0, text); } private class Class1 : NotifyingBase