Bind textbox to float value. Unable to input dot / comma - c#

When I try to input a DOT or a COMMA in a textbox, for example 1.02 or 83,33 the textbox prevents me to input such a value (and the input turns red). The textbox is bound to a float property. Why?
I have bound a textbox to a float Property Power of a class implementing INotifyPropertyChanged.
private float _power;
public float Power
{
get { return _power; }
set
{
_power = value;
OnPropertyChanged("Power");
}
}
In Xaml
<TextBox Name="txtPower" Height="23" TextWrapping="Wrap" Text="{Binding Path=Power, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"></TextBox>
I have no custom validation at all right now.
Also tried decimal but it does not work either. For string everything works fine.

If you have .NET 4.5 or newer, you may enforce the pre 4.5 behaviour
System.Windows.FrameworkCompatibilityPreferences.KeepTextBoxDisplaySynchronizedWithTextProperty = false;
See Sebastian Lux's blog:
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 intended.

Try adding a StringFormat definition to the binding.
Like so:
<TextBox Name="txtPower" Height="23"
TextWrapping="Wrap" Text="{Binding Path=Power, Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged,StringFormat=N2}"></TextBox>

to fix dot and comma issue in textbox binding to decimal or float
1- UpdateSourceTrigger = LostFocus
2- add string format StringFormat={}{0:#.##} to escape unneeded zeros
<TextBox Name="txtPower" Height="23"
TextWrapping="Wrap" Text="{Binding Path=Power, Mode=TwoWay,
UpdateSourceTrigger=LostFocus ,StringFormat={}{0:#.##}}"></TextBox>

Related

Formatting decimal as currency without code sign and extra static text in XAML

I have try:
<TextBlock Text="{Binding Path=ItemPrice, StringFormat='#,##0.00'}" FontSize="18" Foreground="Black" FontFamily="Franklin Gothic Demi" TextWrapping="Wrap"/>
and yes it shows 4.2 as 4.20 but currently I want to surround the value with some static text for example:-
(+4.20) as adding "(+{price})".
I cannot find the way to format a text within a binding price format on my search engine. Mostly show one kind of stringformat
Answering solutions based from comment. Credit to both #Babbillumpa and #XAMIMAX.
Answer 1: Text="{Binding Path=ItemPrice, StringFormat='(+{0:N})'}"
Answer 2: Text="{Binding Path=ItemPrice, StringFormat='(+#,##0.00)'}"
Both works.

Binding multiple textbox values to single property

I was wondering if there is way to bind multiple textboxes to a single property in VM and a single validation logic. Perhaps a good example of that would be a typical Serial Number in any product activation process. Usually, when asked to enter serial number, the end user has, say, 5 text boxes with 5 max symbols.
Now lets imagine that in my VM I have only one property called SerialNumber. So my XAML would look like this:
<StackPanel Orientation="Horizontal">
<TextBox Text="{Binding SerialNumber}"/>
<TextBox Text="{Binding SerialNumber}"/>
<TextBox Text="{Binding SerialNumber}"/>
<TextBox Text="{Binding SerialNumber}"/>
<TextBox Text="{Binding SerialNumber}"/>
<StackPanel>
And my code will be like this:
class ViewModel
{
public string SerialNumber{get;set;}
}
Is there a way to bind these textboxes so that they each point ot the same property on VM and that property is updated only when the validation on all 5 textboxes passes?
EDIT: As some posters pointed out, yes, I could go with 5 separate properties for each textbox, the problem with that is that the actual situation is much more complex than in the example that I've provided. One of the reasons that the suggested approach is unfavorable is because this view will be reused in multiple places with different VM classes and going with the 5 properties approach I would have to copy them in each and every VM class that will use this View. If it were only as simple as taking five string properties and concatenating them, it would be tolerable. But in the real world scenario there is a very complicated verification, validation and combination logic behind these properties, that it makes it impractical to rewrite the same logic in each and every VM, which is why I'm looking for something reusable, something that could be done in XAML as much as possible. I was wondering if BindingGroup with some sort of ValidationRule and ValueConverter could be used in this case.
You should try to bind it into separate properties and then use the + operator to link them togeather, in a separate property and use that after in the model.
You cannot data bind different UI controls to one property and have them reflect different values. Instead, you will need to define additional properties to data bind to the other TextBoxes. It's stil a bit unclear as to exactly what you want, but if you want several TextBoxes that each show a few characters of a pass code, or something similar, then you'll need to do it more like this:
<StackPanel Orientation="Horizontal">
<TextBox Text="{Binding SerialNumber1}" />
<TextBox Text="{Binding SerialNumber2}" />
<TextBox Text="{Binding SerialNumber3}" />
<TextBox Text="{Binding SerialNumber4}" />
<TextBox Text="{Binding SerialNumber5}" />
<StackPanel>
...
string serialNumber = string.Concat(SerialNumber1, SerialNumber2, SerialNumber3,
SerialNumber4, SerialNumber5);
Alternatively, if you want to compare the supposedly identical values of two TextBoxes, as in a typical password entry field, you could do something like this:
<StackPanel Orientation="Horizontal">
<TextBox Text="{Binding SerialNumber1}" />
<TextBox Text="{Binding SerialNumber2}" />
<StackPanel>
...
bool isValid = SerialNumber1 == SerialNumber2;
In all cases, you will need to add further properties.
Based on the edit of the question, it is impractical for the O/P to change the viewmodel(s) to add additional string properties for the serial number parts.
A custom IValueConverter
In this situation, a custom IValueConverter can provide the required functionality. Let's call this custom converter SerialNumberConverter.
As already hinted at by IL_Agent's very brief answer, you would use the converter in XAML simliar to the following:
<StackPanel Orientation="Horizontal">
<StackPanel.Resources>
<My:SerialNumberConverter x:Key="SerialNumberConverter" />
</StackPanel.Resources>
<TextBox Text="{Binding SerialNumber, ConverterParameter=0, Converter={StaticResource SerialNumberConverter}}"/>
<TextBox Text="{Binding SerialNumber, ConverterParameter=1, Converter={StaticResource SerialNumberConverter}}"/>
<TextBox Text="{Binding SerialNumber, ConverterParameter=2, Converter={StaticResource SerialNumberConverter}}"/>
<TextBox Text="{Binding SerialNumber, ConverterParameter=3, Converter={StaticResource SerialNumberConverter}}"/>
<TextBox Text="{Binding SerialNumber, ConverterParameter=4, Converter={StaticResource SerialNumberConverter}}"/>
</StackPanel>
The implementation of the SerialNumberConverter looks somewhat unconventional:
public class SerialNumberConverter : IValueConverter
{
private readonly string[] _serialNumberParts = new string[5];
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
int serialPartIndex;
if (!int.TryParse(parameter.ToString(), out serialPartIndex)
|| serialPartIndex < 0
|| serialPartIndex >= _serialNumberParts.Length
)
return Binding.DoNothing;
string completeSerialNumber = (string) value;
if (string.IsNullOrEmpty(completeSerialNumber))
{
for (int i = 0; i < _serialNumberParts.Length; ++i)
_serialNumberParts[i] = null;
return "";
}
_serialNumberParts[serialPartIndex] = completeSerialNumber.Substring(serialPartIndex * 6, 5);
return _serialNumberParts[serialPartIndex];
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
int serialPartIndex;
if (!int.TryParse(parameter.ToString(), out serialPartIndex)
|| serialPartIndex < 0
|| serialPartIndex >= _serialNumberParts.Length
)
return Binding.DoNothing;
_serialNumberParts[serialPartIndex] = (string)value;
return (_serialNumberParts.Any(string.IsNullOrEmpty)) ?
Binding.DoNothing :
string.Join("-", _serialNumberParts);
}
}
How does it work? For the following explanation, the reader is required to have a basic understanding of how the binding mechanism of WPF utilizes IValueConverters.
Convert method
First, let's take a look at the Convert method. The value passed to that method is obviously coming from the view models SerialNumber property and thus is a complete serial number.
Based on the ConverterParameter - which specifies the serial number part to be used for a particular binding - the appropriate portion of the serial number string will be extracted. In the example converter given here, i assumed a serial number format of five parts with 5 characters each, and each part being separated from another by a hyphen - character (i.e., a serial number would look like "11111-22222-33333-44444-55555").
Obviously the Convert method will return this serial number part, but before doing so it will memorize it in a private string array _serialNumberParts. The reason for doing this becomes clear when looking at the ConvertBack method.
Another responsibility of the Convert method is erasing the _serialNumberParts array in case the bound SerialNumber property provides an empty string or null.
ConvertBack method
The ConvertBack method essentially converts the data from the text box before it is being assigned to the SerialNumber property of the view model. However, the text box will only provide one part of the serial number -- but the SerialNumber property needs to receive a complete serial number.
To create a complete serial number, ConvertBack relies on the serial number parts memorized in the _serialNumberParts array. However, before composing the complete serial number the _serialNumberParts array will be updated with the new data provided by the text box.
In case your UI starts with empty text boxes, the ConvertBack method will not return a serial number until all text boxes have provided their data (i.e., until the user has typed something into all text boxes). Instead, the method will return Binding.DoNothing in case a complete serial number cannot be composed yet. (Binding.DoNothing instructs the binding to do (erm...) nothing.)
Considerations regarding SerialNumberConverter
For this converter to work without troubles, the following considerations need to be taken into account:
The bindings of each text box belonging to the same serial number need to use the same converter instance (so that the _serialNumberParts array will be able to keep track of the complete serial number)
If the UI provides several text box groups for entering multiple serial numbers, then each of these text box groups need to use a separate converter instance (otherwise, serial number parts of different serial numbers could mix in the same _serialNumberParts array). In my XAML example above, i ensured this by placing the converter instance in the resource dictionary of the parent StackPanel of the text boxes (which makes the converter instance local to this StackPanel and its descendent elements).
It is required to use data bindings for all serial number parts. Otherwise, the _serialNumberParts array will never be populated fully, which in turn will prevent ConvertBack from returning any complete serial number.
A ValidationRule for the text boxes
If validation of input should be handled for each text box individually, a custom ValidationRule is required:
public class SerialNumberValidationRule : ValidationRule
{
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
string serialNumberPart = value.ToString();
return (serialNumberPart.All(c => '0' <= c && c <= '9')) ?
(serialNumberPart.Length == 5) ?
ValidationResult.ValidResult :
new ValidationResult(false, "Serial number part must be 5 numbers") :
new ValidationResult(false, "Invalid characters in serial number part");
}
}
In the example SerialNumberValidationRule given here i assume that only number characters are valid characters for a serial number (you would of course implement the ValidationRule differently depending on the specification of your serial number format...)
While implementing such a ValidationRule is rather easy and straightforward, attaching it to the data bindings in XAML is unfortunately not as elegant:
<StackPanel Orientation="Horizontal">
<StackPanel.Resources>
<My:SerialNumberConverter x:Key="SerialNumberConverter" />
</StackPanel.Resources>
<TextBox>
<TextBox.Text>
<Binding Path="SerialNumber" ConverterParameter="0" Converter="{StaticResource SerialNumberConverter}">
<Binding.ValidationRules>
<My:SerialNumberValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<TextBox>
<TextBox.Text>
<Binding Path="SerialNumber" ConverterParameter="1" Converter="{StaticResource SerialNumberConverter}">
<Binding.ValidationRules>
<My:SerialNumberValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
...here follow the remaining text boxes...
</StackPanel>
The reason for this convoluted XAML is Binding.ValidationRules being a read-only property. Unfortunately that means we cannot simply write something like
<Binding ValidationRules="{StaticResource MyValidator}" ... />
but instead need to resort to this kind of verbose XAML shown above to add our SerialNumberValidationRule to the Binding.ValidationRules collection.
Final notes
For the sake of readability, i omitted any sanity checks in my example converter code which are not required to get an understanding of how the code works. Depending on your requirements and application scenario you might need to add sanity checks to the converter code to prevent it from going haywire if the view model's SerialNumber property could possibly provide improper data.
The validation as depicted above will just show a slim red rectangle around a text box if the ValidationRule fails (this is default behavior for a text box). If your UI should present a more elaborate validation error response, most certainly you will need to do much more than just only adding the ValidationRule to the bindings...
I think the best way is using of 5 separate properties, but if you want only one you could use Converter and pass order number of each part as parameter.
<StackPanel Orientation="Horizontal">
<TextBox Text="{Binding SerialNumber, Converter={StaticResource SerialNumberConverter}, ConverterParameter=0}"/>
<TextBox Text="{Binding SerialNumber, Converter={StaticResource SerialNumberConverter}, ConverterParameter=1}"/>
<TextBox Text="{Binding SerialNumber, Converter={StaticResource SerialNumberConverter}, ConverterParameter=2}"/>
<TextBox Text="{Binding SerialNumber, Converter={StaticResource SerialNumberConverter}, ConverterParameter=3}"/>
<TextBox Text="{Binding SerialNumber, Converter={StaticResource SerialNumberConverter}, ConverterParameter=4}"/>
<StackPanel>

Setter not getting called for TextBox Text property in Data Model

In one of my Data Models I have a string property that is bound to the text of a textBox. When I change the text of this textBox I would like to update a property in another ViewModel. To achieve this, I believe that I would have to perform the operations under the property's setter, but whenever I change the textbox's text the only area of the string property that is called is the getter. There must be something that I don't know or am overlooking..
This is the string property that I am working with just in case it helps to see it:
public string DisplayName
{
get { return _displayName; }
set
{
_displayName = value;
NotifyPropertyChange(() => DisplayName);
//These values are from the other ViewModel
if (MainTreeCollection.SelectedItem != null
&& value != MainTreeCollection.SelectedItem.DisplayName)
MainTreeCollection.SelectedItem.DisplayName = value;
}
}
I noticed in this question he shows how to "get [his] ViewModel to know when the user has changed text in the TextBox and moved the focus away from the TextBox". It looks like he has achieved this by binding to a string property, which is what I did above.
How come the property's setter is not getting accessed? How would I change my method and code to perform these operations?
Update: This is the xaml of the textBox as requested:
<TextBox Text="{Binding Model.DisplayName}" Height="23"
HorizontalAlignment="Left" Name="title_TB"
VerticalAlignment="Top" Width="Auto" FontWeight="Bold"
FontSize="14" Margin="5,2,0,0" />
In case you want bounded property to be set while you are typing into it, set UpdateSourceTrigger to PropertyChanged.
Default value is LostFocus for TextBox i.e. bounded property gets updated only when LostFocus event gets raised for TextBox.
<TextBox Text="{Binding Model.DisplayName,
UpdateSourceTrigger=PropertyChanged}"
Height="23" HorizontalAlignment="Left" Name="title_TB"
VerticalAlignment="Top" Width="Auto" FontWeight="Bold"
FontSize="14" Margin="5,2,0,0"/>

WPF formatting labels with StringFormat

I have a WPF application. I have some labels and some datagrids which are bound to some public properties. Some of these properties are numerical values.
In the datagrids I have been using the line below to ensure the values only display two decimal places, which works. However when I use the same line below for my label it appears to have no effect on the display as the number shows to about 9 decimal places. I don't understand why it works for the datagrid but not the label?
StringFormat={}{0:0.##}
<Label Grid.Row="3" Grid.Column="1"
Content="{Binding Obs.Tstat, StringFormat={}{0:0.#}}"
HorizontalAlignment="Center" Foreground="{StaticResource brushLinFont}"
FontSize="13" FontWeight="Bold"/>
Updated code
<Label Grid.Row="3" Grid.Column="1"
Content="{Binding Obs.Tstat}" ContentStringFormat="{}{0:0.#}}"
HorizontalAlignment="Center" Foreground="{StaticResource brushLinFont}"
FontSize="13" FontWeight="Bold"/>
For label you need to use ContentStringFormat:
<Label Content="{Binding Obs.Tstat}" ContentStringFormat="{}{0:0.##}"/>
Reason:
Label's Content property is of type object and StringFormat is used only when binding property is of type String.
If you try your code with TextBlock's Text property it will work fine with StringFormat because Text property is of type string.
Just a quick addition I'd like to post along these lines in case anyone else runs in to it... My application uses localization since it has multi-country support, but we also support user's system settings. We noticed ContentStringFormat defaults to your UI culture.
That caused an issue in one of our forms where the user's machine-specific decimal places they had configured in windows settings were not respected when you specified the ContentStringFormat.
Since the ContentPresenter simply takes the string format without the converter culture you could normally specify in the binding , this means that a format of say: 0:N will only return two decimal places if English is my current UI culture, even though I have 5 decimals specified on my windows settings.
In our case we applied some binding overrides to work around this, but mainly just wanted to add this extra bit of info in case anyone else runs in to it ;)

Weird TextBox issues with .NET 4.5 - no '.' allowed

I've got a really weird problem related to .NET 4.5.
Today a User told me that he isn't able to enter floating numbers into a Textbox (like "2.75").
The textbox just doesn't accept ".", which is the correct 'seperator' for floating numbers in my Culture ("de-CH").
This issue occurred after I compiled the software with .NET 4.5 (formerly it was 4.0).
I can reproduce this error. All other textboxes in the application are working fine.
The textbox is a regular WPF Control. No fancy user defined control or anything like that.
Again: the textbox just doesn't accept '.' as a character. It seems that it completely ignores it. Every other character (even special ones like "#") are fine.
Recompiling the application on .NET 4.0 solves the problem.
The xaml for the textbox is:
<TextBox x:Name="_Hours" Grid.Row="9" Grid.Column="1" VerticalAlignment="Center"
TextAlignment="Center" FontWeight="Bold" FontSize="16" Text="{Binding ProcessHours,
Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" TextChanged="Hours_TextChanged" />
Definition of ProcessHours:
partial class ProjectTask
{
...
public double TotalProcessHours { get { return ProjectBookings.Sum(b =>
b.ProcessHours); }}
...
}
Hours_TextChanged is:
private void Hours_TextChanged(object sender, TextChangedEventArgs e)
{
UpdateHoursValidity();
}
UpdateHoursValidity() just fades a Text Message below the actual textbox. It is not connected with the "broken" textbox in any way:
private void UpdateHoursValidity()
{
string key = IsInvalidHoursWarning ? "ShowWarningStoryboard" :
"HideWarningStoryboard";
var storyboard = FindResource(key) as Storyboard;
if(storyboard != null) storyboard.Begin();
}
So nothing fancy here either.
What I tried so far:
- removing the textbox, recompiling, adding the textbox again, recompiling -> same situation
Setting the Language property of the textbox specifically in xaml (Language=de-CH)
Setting the culture according to these tips:
how to set default culture info for entire c# application
Setting the culture according to this blogpost:
http://www.west-wind.com/weblog/posts/2009/Jun/14/WPF-Bindings-and-CurrentCulture-Formatting
There is NO Message on the debugconsole when I try to enter a ".".
Any ideas on this one?
Thanks in advance!
This is a fairly well known (and documented) issue relating to TextBox controls and data bound float values. You can fix this issue, by adding a StringFormat to your Binding:
<TextBox x:Name="_Hours" Grid.Row="9" Grid.Column="1" VerticalAlignment="Center"
TextAlignment="Center" FontWeight="Bold" FontSize="16" Text="{Binding ProcessHours,
Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, StringFormat={}{##.##}}"
TextChanged="Hours_TextChanged" />
Please adjust the format to suit your situation. You can find more formats in the Custom Numeric Format Strings post at MSDN.
There is a detailed discussion on the .NET 4.5 change that causes this issue on the Microsoft Connect site: https://connect.microsoft.com/VisualStudio/feedback/details/737301/binding-to-wpf-textbox-seems-to-be-broken-in-net-4-5-beta
In addition to the StringFormat workaround, another option is to set a Delay for the binding.

Categories

Resources