I'm running into an issue with a formatting converter and data validation. I have the following textbox XAML declaration
<TextBox FontFamily="Segoe" FontSize="16" FontWeight="Medium"
TabIndex="{Binding TabBinding}" Foreground="Black"
Opacity="0.9" IsTabStop="True" Uid="{Binding PriceID}"
Text="{Binding NewPrice,Converter={StaticResource FormattingConverter},
ConverterParameter=' \{0:C\}', Mode=TwoWay, UpdateSourceTrigger=LostFocus}"
Background="#FFE6DED3" BorderBrush="#FFE6DED3"
DataContext="{Binding StringFormat=\{0:c\}, NotifyOnValidationError=True}"
Padding="0" KeyDown="TextBox_KeyDown" AcceptsReturn="False">
</TextBox>
The issue I'm up against is a data validation issue. When a user enters an invalid price (Ex: a value of "abc" or "0.0.4"), the textbox attempts to perform the conversion in the "FormattingConverter" method. (ConvertBack method pasted below) This causes an exception and the program errors out. Is there a way to delay the FormattingConverter call or bypass it if the data in the textbox is not valid?
public object ConvertBack(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var objTypeConverter = System.ComponentModel.TypeDescriptor.GetConverter(targetType);
object objReturnValue = null;
if (objTypeConverter.CanConvertFrom(value.GetType())) {
objReturnValue = objTypeConverter.ConvertFrom(value.ToString().Replace("$", ""));
}
return objReturnValue;
}
Thank you,
A converter's ConvertBack always runs to convert the data to a value suitable for the target object. It is the responsibility of the converter to handle exceptions (and in the case of exception, return the original value so the binding framework will also realize it's an invalid value).
public object ConvertBack(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var objTypeConverter = System.ComponentModel.TypeDescriptor.GetConverter(targetType);
// Default return value is the original value - if conversion fails, return
// this value so the binding framework will see an invalid value (and not
// just null).
object objReturnValue = value;
if (objTypeConverter.CanConvertFrom(value.GetType())) {
try {
objReturnValue = objTypeConverter.ConvertFrom(value.ToString().Replace("$", ""));
}
catch( FormatException ) { }
// Catch all of your possible exceptions and ignore them by returning the original value
}
return objReturnValue;
}
You can also return DependencyProperty.UnsetValue, however in practice I prefer to see the actual error message returned in validation from an invalid value than to simply return an unset value.
Related
i want in wpf application show only decimal number in a textblock.
I have for example "35.56", i want show only ".56"
Thank you
You can represent 0.56 as only .56 using <TextBlock Text="{Binding Path=yourprop, StringFormat='#.##'}" /> but 35.56 cannot be represented using .56 using a StringFormat because 35.56 and .56 are two different values.
If you want to represent 35.56 as .56 for some strange reason, you'd better format the string using some custom logic in your view model:
public string YourPropFormatted
{
get
{
string s = yourprop.ToString("#.00", CultureInfo.InvariantCulture);
int index = s.IndexOf('.');
return (index > 0) ? s.Substring(index) : s;
}
}
XAML is a markup language and can't handle this.
Complementing mm8 answer with another way to get the same result is implementing a IValueConverter.
public class NumberToDecimalPartConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if(!(value is double val))
return default(double).ToString(); // or ".00", you decide
string s = val.ToString("#.00", CultureInfo.InvariantCulture);
int index = s.IndexOf('.');
return index > 0 ? s.Substring(index) : s;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
// you don't need convert back
throw new NotImplementedException();
}
}
Assuming the converter is placed at same namespace
<Window.Resources>
<local:NumberToDecimalPartConverter x:Key="NumberToDecimalPart"/>
</Window.Resources>
<TextBlock Text="{Binding Path=yourprop, Converter={StaticResource NumberToDecimalPart}}" />
When your property changes, the UI will be updated, right?
With converter, you have one more step that is convert the value.
So what I'm doing here it's when the property (your probably double value) change, the WPF will call the Convert function from NumberToDecimalPartConverter. When the property have Mode setted as TwoWay will call ConvertBack.
So i have simple Gauge class with static int property that implement Propertychanged:
TotalPacketsSent
This property is raising all the time and i want to wrote simple converter and send this converter this property value and return some value base on this property:
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
int val = (int)value;
double percentage = ((double)MyClass.TotalPacketsSent / MyClass.TotalPacketsInList) * 100;
return percentage;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Under Window.Resources i have this:
<Convertors:GaugeValueConverter x:Key="GaugeValueConverter"/>
And this is my Gause:
<Controllers:Gauge x:Name="gauge"
Value="{Binding Path=(my:MyClass.TotalPacketsSent), Converter={StaticResource GaugeValueConverter}}"
Minimum="0"
Maximum="100"/>
So my issue is that my converter not working at all, i mean that i cannot see that this even executed.
Any ideas why ?
Edit:
This property is changing all the time and i have Label i am using this way to show its value:
Content="{Binding Path=(my:MyClass.TotalPacketsSent)}"
And this works fine.
You're casting the value argument as an int and then not using it.
I imagine what you're looking for is something like (deriving from IMultiValueConverter rather than IValueConverter):
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
double totalPacketsSent = (double)values[0];
double totalPacketsInList = (double)values[1];
// further validation for handling divide by zero, etc. may need to go here
return totalPacketsSent / totalPacketsInList * 100
}
And in the XAML:
<Controllers:Gauge x:Name="gauge" Minimum="0" Maximum="100">
<Controllers:Gauge.Value>
<MultiBinding Converter="{StaticResource GaugeValueConverter}">
<Binding Path="(my:MyClass.TotalPacketsSent)" />
<Binding Path="(my:MyClass.TotalPacketsInList)" />
</MultiBinding>
</Controllers:Gauge.Value>
</Controllers:Gauge>
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}">
I have run into the same problem, and have found a quite simple solution: use a custom validator, which does not return "valid" when the Text ends in "." or "0":
double val = 0;
string tmp = value.ToString();
if (tmp.EndsWith(",") || tmp.EndsWith("0") || tmp.EndsWith("."))
{
return new ValidationResult(false, "Enter another digit, or delete the last one.");
}
else
{
return ValidationResult.ValidResult;
}
Try using StringFormat on your binding:
<TextBox Width="100" Margin="10" Text="{Binding DoubleField, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True, StringFormat='0.0'}">
Not sure if that string format is even right since I've not done one for a while but it's just an example
I just noticed that WPF textboxes bound to numeric data do not fire Property Set when non-numeric events happen such as letters/spaces typed or text cleared. This becomes a problem when I'm trying to validate that a textbox has a valid number. If the user types in 5 and presses backspace, the databound property remains 5 while the textbox appears empty! I have no way of disabling a button to stop further progress. Is there anyway to enable non-numeric notifications when bound to numeric data? Or, am I forced to use a string property/data converter? Thanks.
If you do not like the default converter you need to create your own which returns a valid value if the input is empty or unparsable.
public class IntBindingConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value.ToString();
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
string input = value as string;
if (String.IsNullOrWhiteSpace(input))
{
return 0;
}
else
{
int outInt;
if (int.TryParse(input, out outInt))
{
return outInt;
}
else
{
return 0;
}
}
}
}
Example usage:
<TextBox>
<TextBox.Text>
<Binding Path="Max">
<Binding.Converter>
<vc:IntBindingConverter/>
</Binding.Converter>
</Binding>
</TextBox.Text>
</TextBox>
This may look a bit messy but normally you in fact just stop the user from proceeding.
am usina text block in usercontrol, but am sending value to textblock from other form, when i pass some value it viewed in textblock, but i need to convert the number to text. so i used converter in textblock. but its not working
<TextBlock Height="21" Name="txtStatus" Width="65" Background="Bisque" TextAlignment="Center" Text="{Binding Path=hM1,Converter={StaticResource TextConvert},Mode=OneWay}"/>
converter class
class TextConvert : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value != null)
{
if (value.ToString() == "1")
{
return value = "Good";
}
if (value.ToString() == "0")
{
return value = "NIL";
}
}
return value = "";
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return (string)value;
}
}
is it right? whats wrong in it??
ok I think I know what the problem is - let see if I can define it for you :)
in your xaml file where you want to use TextConvert, define Resource for it (unless you are doing it already, then I haven't a clue why its not working)
<Grid.Resources>
<Shared:TextConvert x:Key="TextConvertKey" />
</Grid.Resources>
shared being the xmlns ofcourse.
Then in the textbox use it like:
Text="{Binding Path=hM1,Converter={StaticResource TextConvertKey},Mode=OneWay}"/>
EDIT:
If you set a breakpoint in the converter class, does the debugger go in there?????
EDIT 2:
am using like this voodoo
local:HealthTextConvert x:Key="TextConvert"
This is absolutely wrong. How can you Call it HealthTextConvert when the converter name is TextConvert???
it should be
local:TextConvert x:Key="whateverKeyNameYouWant"
and
in the textbox is should be
Text="{Binding Path=hM1,Converter={StaticResource whateverKeyNameYouWant},Mode=OneWay}"
I can see immediately a problem with your converter definition.
class TextConvert : IValueConverter
{
...
Should be declared public to be able to use it as a resource.
public class TextConvert : IValueConverter
{
...
Also, its not a good thing to be doing this...
return value = "Good";
...
return value = "NIL";
It should just be (even though it will not matter if you leave it, just bad programming =P):
return "Good";
...
return "Nill";
Try by removing Path in the below line
Text="{Binding **Path**=hM1,Converter={StaticResource TextConvert},Mode=OneWay}".
Sometimes it works without Path :).
Also look into the output window(Alt+Cntl+O)...to see where the issue is.