From a222fa9b0c2c14380db40d98dd553f2d744517f2 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 18 Aug 2016 01:24:08 +0200 Subject: [PATCH] Don't modify text due to binding when typing. If a TextBox is e.g. bound to an int and the user types "02" then the TextBox text should be "02" - not the value that comes back from the binding which will be "2". --- src/Avalonia.Controls/TextBox.cs | 30 +++++++-- .../TextBoxTests.cs | 65 +++++++++++++++++++ 2 files changed, 90 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index 2d89019f93..67cf04a4a1 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -90,6 +90,7 @@ namespace Avalonia.Controls private bool _canScrollHorizontally; private TextPresenter _presenter; private UndoRedoHelper _undoRedoHelper; + private bool _ignoreTextChanges; static TextBox() { @@ -184,7 +185,13 @@ namespace Avalonia.Controls public string Text { get { return _text; } - set { SetAndRaise(TextProperty, ref _text, value); } + set + { + if (!_ignoreTextChanges) + { + SetAndRaise(TextProperty, ref _text, value); + } + } } public TextAlignment TextAlignment @@ -247,7 +254,7 @@ namespace Avalonia.Controls DeleteSelection(); caretIndex = CaretIndex; text = Text ?? string.Empty; - Text = text.Substring(0, caretIndex) + input + text.Substring(caretIndex); + SetTextInternal(text.Substring(0, caretIndex) + input + text.Substring(caretIndex)); CaretIndex += input.Length; SelectionStart = SelectionEnd = CaretIndex; _undoRedoHelper.DiscardRedo(); @@ -355,7 +362,7 @@ namespace Avalonia.Controls case Key.Back: if (!DeleteSelection() && CaretIndex > 0) { - Text = text.Substring(0, caretIndex - 1) + text.Substring(caretIndex); + SetTextInternal(text.Substring(0, caretIndex - 1) + text.Substring(caretIndex)); --CaretIndex; } @@ -364,7 +371,7 @@ namespace Avalonia.Controls case Key.Delete: if (!DeleteSelection() && caretIndex < text.Length) { - Text = text.Substring(0, caretIndex) + text.Substring(caretIndex + 1); + SetTextInternal(text.Substring(0, caretIndex) + text.Substring(caretIndex + 1)); } break; @@ -598,7 +605,7 @@ namespace Avalonia.Controls var start = Math.Min(selectionStart, selectionEnd); var end = Math.Max(selectionStart, selectionEnd); var text = Text; - Text = text.Substring(0, start) + text.Substring(end); + SetTextInternal(text.Substring(0, start) + text.Substring(end)); SelectionStart = SelectionEnd = CaretIndex = start; return true; } @@ -645,6 +652,19 @@ namespace Avalonia.Controls return i; } + private void SetTextInternal(string value) + { + try + { + _ignoreTextChanges = true; + SetAndRaise(TextProperty, ref _text, value); + } + finally + { + _ignoreTextChanges = false; + } + } + UndoRedoState UndoRedoHelper.IUndoRedoHost.UndoRedoState { get { return new UndoRedoState(Text, CaretIndex); } diff --git a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs b/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs index d19bfebb00..12121fcf6c 100644 --- a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs @@ -1,7 +1,14 @@ // 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 Avalonia.Controls.Presenters; +using Avalonia.Controls.Templates; using Avalonia.Data; +using Avalonia.Input; +using Avalonia.Markup.Xaml.Data; +using Avalonia.Platform; +using Avalonia.UnitTests; +using Moq; using Xunit; namespace Avalonia.Controls.UnitTests @@ -15,5 +22,63 @@ namespace Avalonia.Controls.UnitTests BindingMode.TwoWay, TextBox.TextProperty.GetMetadata(typeof(TextBox)).DefaultBindingMode); } + + [Fact] + public void Typing_Beginning_With_0_Should_Not_Modify_Text_When_Bound_To_Int() + { + using (UnitTestApplication.Start(Services)) + { + var source = new Class1(); + var target = new TextBox + { + DataContext = source, + Template = CreateTemplate(), + }; + + target.ApplyTemplate(); + target.Bind(TextBox.TextProperty, new Binding(nameof(Class1.Foo), BindingMode.TwoWay)); + + Assert.Equal("0", target.Text); + + target.CaretIndex = 1; + target.RaiseEvent(new TextInputEventArgs + { + RoutedEvent = InputElement.TextInputEvent, + Text = "2", + }); + + Assert.Equal("02", target.Text); + } + } + + private static TestServices Services => TestServices.MockThreadingInterface.With( + standardCursorFactory: Mock.Of()); + + private IControlTemplate CreateTemplate() + { + return new FuncControlTemplate(control => + new TextPresenter + { + Name = "PART_TextPresenter", + [!!TextPresenter.TextProperty] = new Binding + { + Path = "Text", + Mode = BindingMode.TwoWay, + Priority = BindingPriority.TemplatedParent, + RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent), + }, + }); + } + + private class Class1 : NotifyingBase + { + private int _foo; + + public int Foo + { + get { return _foo; } + set { _foo = value; RaisePropertyChanged(); } + } + } } }