Browse Source

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".
pull/691/head
Steven Kirk 10 years ago
parent
commit
a222fa9b0c
  1. 30
      src/Avalonia.Controls/TextBox.cs
  2. 65
      tests/Avalonia.Controls.UnitTests/TextBoxTests.cs

30
src/Avalonia.Controls/TextBox.cs

@ -90,6 +90,7 @@ namespace Avalonia.Controls
private bool _canScrollHorizontally; private bool _canScrollHorizontally;
private TextPresenter _presenter; private TextPresenter _presenter;
private UndoRedoHelper<UndoRedoState> _undoRedoHelper; private UndoRedoHelper<UndoRedoState> _undoRedoHelper;
private bool _ignoreTextChanges;
static TextBox() static TextBox()
{ {
@ -184,7 +185,13 @@ namespace Avalonia.Controls
public string Text public string Text
{ {
get { return _text; } get { return _text; }
set { SetAndRaise(TextProperty, ref _text, value); } set
{
if (!_ignoreTextChanges)
{
SetAndRaise(TextProperty, ref _text, value);
}
}
} }
public TextAlignment TextAlignment public TextAlignment TextAlignment
@ -247,7 +254,7 @@ namespace Avalonia.Controls
DeleteSelection(); DeleteSelection();
caretIndex = CaretIndex; caretIndex = CaretIndex;
text = Text ?? string.Empty; 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; CaretIndex += input.Length;
SelectionStart = SelectionEnd = CaretIndex; SelectionStart = SelectionEnd = CaretIndex;
_undoRedoHelper.DiscardRedo(); _undoRedoHelper.DiscardRedo();
@ -355,7 +362,7 @@ namespace Avalonia.Controls
case Key.Back: case Key.Back:
if (!DeleteSelection() && CaretIndex > 0) if (!DeleteSelection() && CaretIndex > 0)
{ {
Text = text.Substring(0, caretIndex - 1) + text.Substring(caretIndex); SetTextInternal(text.Substring(0, caretIndex - 1) + text.Substring(caretIndex));
--CaretIndex; --CaretIndex;
} }
@ -364,7 +371,7 @@ namespace Avalonia.Controls
case Key.Delete: case Key.Delete:
if (!DeleteSelection() && caretIndex < text.Length) if (!DeleteSelection() && caretIndex < text.Length)
{ {
Text = text.Substring(0, caretIndex) + text.Substring(caretIndex + 1); SetTextInternal(text.Substring(0, caretIndex) + text.Substring(caretIndex + 1));
} }
break; break;
@ -598,7 +605,7 @@ namespace Avalonia.Controls
var start = Math.Min(selectionStart, selectionEnd); var start = Math.Min(selectionStart, selectionEnd);
var end = Math.Max(selectionStart, selectionEnd); var end = Math.Max(selectionStart, selectionEnd);
var text = Text; var text = Text;
Text = text.Substring(0, start) + text.Substring(end); SetTextInternal(text.Substring(0, start) + text.Substring(end));
SelectionStart = SelectionEnd = CaretIndex = start; SelectionStart = SelectionEnd = CaretIndex = start;
return true; return true;
} }
@ -645,6 +652,19 @@ namespace Avalonia.Controls
return i; return i;
} }
private void SetTextInternal(string value)
{
try
{
_ignoreTextChanges = true;
SetAndRaise(TextProperty, ref _text, value);
}
finally
{
_ignoreTextChanges = false;
}
}
UndoRedoState UndoRedoHelper<UndoRedoState>.IUndoRedoHost.UndoRedoState UndoRedoState UndoRedoHelper<UndoRedoState>.IUndoRedoHost.UndoRedoState
{ {
get { return new UndoRedoState(Text, CaretIndex); } get { return new UndoRedoState(Text, CaretIndex); }

65
tests/Avalonia.Controls.UnitTests/TextBoxTests.cs

@ -1,7 +1,14 @@
// Copyright (c) The Avalonia Project. All rights reserved. // 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. // 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.Data;
using Avalonia.Input;
using Avalonia.Markup.Xaml.Data;
using Avalonia.Platform;
using Avalonia.UnitTests;
using Moq;
using Xunit; using Xunit;
namespace Avalonia.Controls.UnitTests namespace Avalonia.Controls.UnitTests
@ -15,5 +22,63 @@ namespace Avalonia.Controls.UnitTests
BindingMode.TwoWay, BindingMode.TwoWay,
TextBox.TextProperty.GetMetadata(typeof(TextBox)).DefaultBindingMode); 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<IStandardCursorFactory>());
private IControlTemplate CreateTemplate()
{
return new FuncControlTemplate<TextBox>(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(); }
}
}
} }
} }

Loading…
Cancel
Save