I'm trying to bind TextBox
to double
property of some object with UpdateSourceTrigger=PropertyChanged
. The goal is to immediately during editing validate entered value to be in allowed range (and display an error if not). I want to implement validation on Model level, i.e. via IDataErrorInfo
.
All works great when I bind to int property, but if property is double then a frustrating editing behavior appears: after erasing last significant digit in fractional part of number - the decimal separator is automatically erased (with all possible fractional zeroes). For example, after erasing digit '3' from number '12.03' the text is changed to '12' instead of '12.0'.
Please, help.
Here is the sample code:
MainWindow.xaml:
<Window x:Class="BindWithValidation.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="80" Width="200" WindowStartupLocation="CenterOwner">
<StackPanel>
<TextBox Width="100" Margin="10" Text="{Binding DoubleField, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}">
<TextBox.Style>
<Style TargetType="TextBox">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
</StackPanel>
</Window>
MainWindow.xaml.cs:
namespace BindWithValidation
{
public partial class MainWindow : Window
{
private UISimpleData _uiData = new UISimpleData();
public MainWindow()
{
InitializeComponent();
DataContext = _uiData;
}
}
}
UISimpleData.cs:
namespace BindWithValidation
{
public class UISimpleData : INotifyPropertyChanged, IDataErrorInfo
{
private double _doubleField = 12.03;
public double DoubleField
{
get
{
return _doubleField;
}
set
{
if (_doubleField == value)
return;
_doubleField = value;
RaisePropertyChanged("DoubleField");
}
}
public string this[string propertyName]
{
get
{
string validationResult = null;
switch (propertyName)
{
case "DoubleField":
{
if (DoubleField < 2 || DoubleField > 5)
validationResult = "DoubleField is out of range";
break;
}
default:
throw new ApplicationException("Unknown Property being validated on UIData");
}
return validationResult;
}
}
public string Error { get { return "not implemented"; } }
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string property)
{
if ( PropertyChanged != null )
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
The behavior of binding float values to a textbox has been changed from .NET 4 to 4.5. With .NET 4.5 it is no longer possible to enter a separator character (comma or dot) with ‘UpdateSourceTrigger = PropertyChanged’ by default.
Microsoft says, this (is) intended
If you still want to use ‘UpdateSourceTrigger = PropertyChanged’, you can force the .NET 4 behavior in your .NET 4.5 application by adding the following line of code to the constructor of your
App.xaml.cs
:public App() { System.Windows.FrameworkCompatibilityPreferences .KeepTextBoxDisplaySynchronizedWithTextProperty = false; }
(Sebastian Lux - Copied verbatim from here)
I realize I'm a little late to the party but I found a (I think) rather clean solution to this problem.
A clever converter that remembers the last string converted to double and returns that if it exists should do everything you want.
Note that when the user changes the contents of the textbox, ConvertBack will store the string the user input, parse the string for a double, and pass that value to the view model. Immediately after, Convert is called to display the newly changed value. At this point, the stored string is not null and will be returned.
If the application instead of the user causes the double to change only Convert is called. This means that the cached string will be null and a standard ToString() will be called on the double.
In this way, the user avoids strange surprises when modifying the contents of the textbox but the application can still trigger a change.
public class DoubleToPersistantStringConverter : IValueConverter
{
private string lastConvertBackString;
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (!(value is double)) return null;
var stringValue = lastConvertBackString ?? value.ToString();
lastConvertBackString = null;
return stringValue;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (!(value is string)) return null;
double result;
if (double.TryParse((string)value, out result))
{
lastConvertBackString = (string)value;
return result;
}
return null;
}
}
Tried formatting the value with decimal places?
It may be weird though since you will then always have N decimal places.
<TextBox.Text>
<Binding Path="DoubleField" StringFormat="{}{0:0.00}" UpdateSourceTrigger="PropertyChanged" ValidatesOnDataErrors="True"/>
</TextBox.Text>
If having fixed decimal places is not good enough, you may have to write a converter that treats the value as a string and converts it back to a double.
The problem is that you are updating your property every time the value changes. When you change 12.03 to 12.0 it is rounded to 12.
You can see changes by providing delay
by changing the TextBox
in xaml
like this
<TextBox Width="100" Margin="10" Text="{Binding DoubleField, UpdateSourceTrigger=PropertyChanged,Delay=500, ValidatesOnDataErrors=True}">
but delay
will notify and set the property after the delay time in mili sec.
Better use StringFormat
like this
<TextBox Width="100" Margin="10" Text="{Binding DoubleField, UpdateSourceTrigger=PropertyChanged,StringFormat=N2, ValidatesOnDataErrors=True}">
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With