C# TextBox display dynamically currency format - c#

I'm very new in C#
I tried almost evrything by searching all the web for answer, but still can't do it by myself.
My question is about formating textbox
all i want is that when I writing a number in the textbox, the textbox will show this number with decimal and currency symbol.
and because i have 10 textbox that presenting numbers as currency
i understand that i must do binding.
Sorry for my english.
here is my class converter
class Bindings : IvalueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value !=null)
{
double valueshop = (double)value;
return string.Format(culture, "{0:C}", valueshop;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
this is my xaml
<window.Resources>
<local:Bindings x:Key="ForText"/>
</window.Resources>
<StackPanel Margin="10">
<TextBox Name="MoneyOne" Text="{Binding Converter={StaticResources ForText}, Mode=OneWay, Path=valueshop, UpdateSourceTrigger=PropertyChanged, StringFormat='C'}"/>
</StackPanel>
I hope somebody can help, thanks.

You can try:
switch to MaskedTextBox (from Extended WPF Toolkit, available at NuGet) instead of just regular TextBox, component sources are available here: https://github.com/xceedsoftware/wpftoolkit/wiki/MaskedTextBox
implement it as an attached property, something like this: https://stackoverflow.com/a/1103822/1964969
shape this example to your needs (creating your own mask-equipped descendant from TextBox) https://www.codeproject.com/Tips/1080813/How-To-Implement-a-Mask-for-a-Text-Box-in-W-P-F

Related

c# wpf xaml using StringFormat without or with CUSTOM error message

In my TextBox I have;
Text="{Binding Amount, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, StringFormat={}{0:N2}}"
The binding data is decimal while the textbox formatting should be like 1,000.00.
My only concern here is that when the TextBox is empty, or if I deleted the value, then the border of the textbox gets red and I get the error message of Value * could not be converted just below the textbox, this causes by using StringFormat.
Now, I really don't care if the value is empty because in my database, the default value is zero and I do accept an empty value, and also the textbox accepts only digits.
What I want to know is how can I disable this validation but still be able to use the StringFormat? Second, just in case in the future, I wanted to use the same behavior, how can I change the default error message to something else?
EDIT:
As suggested, I tried using a binding converter and DataTrigger to apply the StringFormat, but I still got the error message.
Using Binding Converter
//AmountFormatter.class
public class AmountFormatter : IValueConverter{
public object? Convert(object value, Type targetType, object parameter, CultureInfo culture){
decimal amount = (decimal.TryParse(value.ToString(), out decimal n)) ? n : 0; // if it failed to convert into decimal, meaning wrong value, then set default value as 0.
return (amount>0)?string.Format(culture, "{0:N2}", amount):null; //return null, empty if value is less than 1. Maybe the user wants to type new value, so leave the textbox empty.
}
public object? ConvertBack(object value, Type targetType, object parameter, CultureInfo culture){
return null;
}
}
//xaml layout
<DataTemplate>
<DockPanel LastChildFill="True">
<TextBox x:Name="TextBoxAmount" Text="{Binding Amount, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource AmountFormatter}}" />
</DockPanel>
</DataTemplate>
//App.xaml
<converters:DecimalOnly x:Key="AmountFormatter" />
Using DataTrigger to apply StringFormat
//AmountFormatter.class
public class AmountFormatter : IValueConverter{
public object? Convert(object value, Type targetType, object parameter, CultureInfo culture){
decimal amount = (decimal.TryParse(value.ToString(), out decimal n)) ? n : 0; // if it failed to convert into decimal, meaning wrong value, then set default value as 0.
return (amount>0) //return true or false if amount is greather than zero. Maybe the user wants to type new value, so leave the textbox empty.
}
public object? ConvertBack(object value, Type targetType, object parameter, CultureInfo culture){
return null;
}
}
//xaml layout
<DataTemplate>
<DockPanel LastChildFill="True">
<TextBox x:Name="TextBoxAmount" />
</DockPanel>
<DataTemplate.Triggers>
<!-- StringFormat when value is greather than 0 -->
<DataTrigger Binding="{Binding Path=Amount, Converter={StaticResource AmountFormatter}}" Value="true">
<Setter TargetName="TextBoxAmount" Property="Text" Value="{Binding Amount, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, StringFormat={}{0:N2}}" />
</DataTrigger>
<!-- Otherwise, no StringFormat -->
<DataTrigger Binding="{Binding Path=Amount, Converter={StaticResource AmountFormatter}}" Value="false">
<Setter TargetName="TextBoxAmount" Property="Text" Value="{Binding Amount, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
//App.xaml
<converters:DecimalOnly x:Key="AmountFormatter" />
It turns out that the validation error message is not caused by using the StringFormat.
The data type of Amount in my ViewModel which is bound into the TextBox is Decimal.
What happens when there is no value in the TextBox is that, the UpdateSourceTrigger=PropertyChanged is being triggered. But since it's empty, it cannot convert the value into the format I wanted. As result, the border of my TextBox turns to red, and an error message below it saying value * cannot be converted. I don't know if this is a fact but I think it's a kind of feature when binding data.
My solution is from the snippet code of Mark Feldman.
First, in my ViewModel, change the data type of Amount from Decimal into String.
/** MyViewModel Class **/
public class MyViewModel{
...
public string? Amount { get; set; } //instead of decimal, I used string. This is the one that is causing the validation error/message when no value in the textbox.
}
Next is to create an IValueConverter class which I named AmountFormatter.class. This will be the class that will handle the formatting when typing.
/** AmountFormatter.class **/
// (1) convert the value of the textbox which is string into decimal.
// //a succesfull convert, meaning the value is valid and in correct format.
// (2) format the decimal into {0:N2}
public class AmountFormatter : IValueConverter{
public object? Convert(object value, Type targetType, object parameter, CultureInfo culture){
decimal amount = (decimal.TryParse(value.ToString(), out decimal n)) ? n : 0; // if it failed to convert into decimal, meaning wrong value, then set default value as 0.
return (amount>0)?string.Format(culture, "{0:N2}", amount):""; //return "" (empty string) if value is less than 1. Maybe the user wants to type new value, so leave the textbox empty.
}
public object? ConvertBack(object value, Type targetType, object parameter, CultureInfo culture){
if ((value==null) || (string.IsNullOrEmpty(value.ToString()))) return ""; // returning empty string will also trigger the `updatesourcetrigger`.
return (decimal.TryParse(value.ToString(), out decimal n)) ? n : "";
}
}
Then declared the AmountFormatter.class into my App.xaml.
<Application
x:Class="MyApp.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:converters="clr-namespace:MyApp.Converters"
xmlns:local="clr-namespace:MyApp"
StartupUri="MainWindow.xaml">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
....
</ResourceDictionary.MergedDictionaries>
...
<converters:AmountFormatter x:Key="AmountFormatter" />
</ResourceDictionary>
</Application.Resources>
</Application>
After that in my xaml layout which contains my TextBox, bind the Amount and the AmountFormatter converter;
/** xaml layout **/
<DataTemplate>
<DockPanel LastChildFill="True">
<TextBox
x:Name="TextBoxAmount"
DataObject.Pasting="TextBoxAmount_Paste"
PreviewKeyDown="TextBoxAmount_PreviewKeyDown"
PreviewTextInput="TextBoxAmount_PreviewTextInput"
Text="{Binding Amount, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource AmountFormatter}}" />
</DockPanel>
</DataTemplate>
And viola!
Miscellaneus;
The Textbox is for currency, I wanted to format it as the user types on the TextBox. In order to make sure that the users type the correct input, I limit the textbox to positive numbers only, no spacing is allowed, single decimal point, and two decimal places only. I also don't allow copy-paste unless the data is in correct format.
In my xaml layout class, I have the following;
/** xaml layout class **/
//handle the pasting event, if the copied data failed to convert into decimal, then the format is invalid and do not allow to paste it.
private void TextBoxAmount_Paste(object sender, DataObjectPastingEventArgs e){
if (e.DataObject.GetDataPresent(typeof(String))){
if (!decimal.TryParse((String)e.DataObject.GetData(typeof(String)), out _)) e.CancelCommand();
} else {
e.CancelCommand();
}
}
//prevent from using space
private void TextBoxAmount_PreviewKeyDown(object sender, System.Windows.Input.KeyEventArgs e){
e.Handled = (e.Key == Key.Space); //do not allow spacing
}
//accept only numbers, single decimal, and two decimal places.
private void TextBoxAmount_PreviewTextInput(object sender, System.Windows.Input.TextCompositionEventArgs e){
TextBox tb = (TextBox)sender;
char ch = e.Text[0];
if (!Char.IsDigit(ch) && (ch!='.')) e.Handled = true;
if ((ch == '.') && tb.Text.IndexOf('.') > -1) e.Handled = true;
}
I hope there is no bug in this method.
Does this solve your problem?
Converter:
using System;
using System.Globalization;
using System.Windows.Data;
internal class DecimalConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null)
return "";
return ((Decimal)value).ToString("0.00");
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
try
{
if ((value == null) || (value.ToString() == ""))
return Binding.DoNothing;
return Decimal.Parse(value.ToString());
}
catch
{
return null;
}
}
}
Usage:
<TextBox>
<TextBox.Text>
<Binding Path="Amount" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged" StringFormat="{}{0:N2}">
<Binding.Converter>
<local:DecimalConverter />
</Binding.Converter>
</Binding>
</TextBox.Text>
</TextBox>

Convert Selected Combobox item to a decimal and store it in the database

My problem:
I have a Combobox, with the values of : 11,251,351,451 and 551.
I am using the MVVM pattern in WPF, and I want to use a converter, to convert the selected value to a decimal and save it to the database using the binding.
As soon as I try to convert the value, I get the following exception on my ConvertBack Method in my Converter.
Here is the code for my Converter:
{
try
{
string str_value = value.ToString();
decimal decimal_myvalue = decimal.Parse(str_value);
return decimal_myvalue;
}
catch (Exception e)
{
var a = e.Message;
}
return 0;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
here is my xaml code for my combobox:
<ComboBox SelectedValue="{Binding Transaction.PoisonSeeds, Converter={StaticResource PoisonSeedValueConverter}}" ...>
<ComboBoxItem Content="11"/>
<ComboBoxItem Content="251"/>
<ComboBoxItem Content="351"/>
<ComboBoxItem Content="451"/>
<ComboBoxItem Content="551"/>
</ComboBox>
is there anything noticable that I am doing wrong?
what can another solution be?
You should use the ConvertBack method instead of the Convert, since you are writing to the Source
In Convert you should go from decimal to string.
In 'ConvertBack' you should convert from string to decimal
And as #Clements has pointed out: add SelectedValuePath="Content"
Obviously, you throw exception in convert back... but going beyond that, there are a lot problems ahead. Let me explain...
It would be enough to place breakpoint in ConvertBack method of the converter to see that value to be converted is ComboBoxItem - not something you'd expect.
Alternatively, look in the output window (if you use Visual Studio), where you also get some information what happens "inside" the app:
System.Windows.Data Error: 23 : Cannot convert 'System.Windows.Controls.ComboBoxItem: 11' from type 'ComboBoxItem' to type 'System.Decimal' for 'en-US' culture with default conversions; consider using Converter property of Binding. NotSupportedException:'System.NotSupportedException: DecimalConverter cannot convert from System.Windows.Controls.ComboBoxItem.
at System.ComponentModel.TypeConverter.GetConvertFromException(Object value)
at System.ComponentModel.TypeConverter.ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, Object value)
at System.ComponentModel.BaseNumberConverter.ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, Object value)
at MS.Internal.Data.DefaultValueConverter.ConvertHelper(Object o, Type destinationType, DependencyObject targetElement, CultureInfo culture, Boolean isForward)'
So, instead of defining combobx items, you should bind to collection in your view model, which will holsd decimals - then you just bind to selected item, no need to convert anything.
So you have to options:
Dirty - in your converter, modify code so it handles ComboboxItem instead of a plain string (as I can assume from your code).
Clean MVVM way (suggested by #Clemens):
define on your view model items source
public ObservableCollection<decimal> CbxItems { get; } = new ObservableCollection<decimal>();
and in XAML you bind to it:
<Combobox ItemsSource="{Binding CbxItems}" SelectedItem="{Binding MyDecimal}" />

WPF C# Binding Converter Issue when swapping out content

My integer to month converter seems to work fine for displaying the month name in a combobox when my source only has the integer month value. However, my program uses messaging from MVVMLight to swap out content so i don't have so many windows. Why does the Convert method get called when i switch content to a different content? Normally, the converter gets an integer and converts it to a string. But upon swapping the content, it receives empty string as the value, therefore producing the InvalidCastException. How can i fix? I included a seriously dumbed down code that simulates this just be setting Content=null after pressing a button. Thanks! The items source is bound to a singelton list of ints to represent the months.
Basically:
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
string answer = "";
switch ((int)value)
{
case 1:
{
answer = DatesInfo.Instance.MonthsNames[0];
break;
}
default:
{
break;
}
}
return answer;
}
XAML:
<ComboBox Width="200" Height="30" SelectedItem="{Binding SelectedItem.Month}"
ItemsSource="{Binding MonthsValues, Source={x:Static local:DatesInfo.Instance}}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={StaticResource MonthNameConverter}}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
How about this?:
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value.GetType() != typeof(int)) return false; // <--------
string answer = "";
switch ((int)value)
{
case 1:
{
answer = DatesInfo.Instance.MonthsNames[0];
break;
}
default:
{
break;
}
}
return answer;
}
UPDATE >>>
Ok, so you want to know why you receive a null value in your Converter class... well in short, I can't answer that. However, I have a WPF application with an interchangeable view model bound to a ContentControl in the same way that you described, so I did a little experiment.
I have a DebugConverter which is basically an empty Converter class that simply returns the input value unchanged. I can hook this into any Binding and put a break point in it to check the values that are being passed. So I hooked it into my interchangeable view model Binding and even after several view changes, at no point was the input value null.
So to find out why you are getting null values could be tricky without you providing more code, possibly even that within the MVVM Light Toolkit source code. Perhaps it would be simpler to accept that it happens and program to negate that fact, eg. with a check for null.

XAML Binding to a converter

what I am trying to do is relatively simple. I am just trying to bind the Y element of a TranslateTransform on an ellipse to 1/2 the height of the ellipse:
<Ellipse Name="EllipseOnlyLFA" Height="200" Fill="Yellow" HorizontalAlignment="Left" VerticalAlignment="Bottom" ClipToBounds="True">
<Ellipse.Width>
<Binding ElementName="EllipseOnlyLFA" Path="Height"/>
</Ellipse.Width>
<Ellipse.RenderTransform>
<TranslateTransform>
<TranslateTransform.Y>
<Binding Converter="MultiplyByFactor" ElementName="EllipseOnlyLFA" Path="Height" ConverterParameter="0.5"/>
</TranslateTransform.Y>
</TranslateTransform>
</Ellipse.RenderTransform>
</Ellipse>
I also have the following converter:
public class MultiplyByFactor : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return ((double)value * (double)parameter);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return true;
}
}
I am getting an error on the XAML line where I actually use the converter. The error is
'Set property
'System.Windows.Data.Binding.Converter'
threw an exception.' Line number '22'
and line position '8'.
Can anyone shed some light on how to do this? EDIT: Yes, I have the converter added as a resource.
There are 2 thing wrong with your code
1) your converter needs to be accessed using the StaticResource declaration
<Binding Converter="{StaticResource myMultiplyByFactor}"
ElementName="EllipseOnlyLFA" Path="Height" ConverterParameter="0.5"/
2) Your converter parameter is a string by default, so you need to convert it to a double
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
double.TryParse((parameter as string).Replace(',', '.'), NumberStyles.Any, CultureInfo.InvariantCulture, out double param);
return param * (double)value;
}
You need to add the converter to the resources
Edit
You need to add the namespace too
xmlns:c="clr-namespace:WpfApplication1"
end edit
<Window.Resources>
<c:MultiplyByFactor x:Key="myMultiplyByFactor"/>
</Window.Resources>
Then you can use the instance from the resources
<TranslateTransform.Y>
<Binding Converter={StaticResource myMultiplyByFactor}"
ElementName="EllipseOnlyLFA"
Path="Height" ConverterParameter="0.5"/>
</TranslateTransform.Y>
The Parameter probably gets passed as a String. Set a breakpoint in your Converter and look at the values of value and parameter. You might need to use double.Parse instead of the cast.

Why do I get a DependencyProperty.UnsetValue when converting a value in a MultiBinding?

I have an extremely simple IMultiValueConverter that simply OR's two values. In the example below, I want to invert the first value using an equally simple boolean inverter.
<MultiBinding Converter="{StaticResource multiBoolToVis}">
<Binding Path="ConditionA" Converter="{StaticResource boolInverter}"/>
<Binding Path="ConditionB"/>
</MultiBinding>
and the inverter:
public class BoolInverterConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is bool)
{
return !((bool)value);
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
When I include the boolInverter, the first value in the MultiValueConverter becomes a "DependencyProperty.UnsetValue". There are no problems when I do not use the converter (other than not the logic I am aiming for, of course).
Am I missing something? Stepping through the debugger shows that the InverseBoolConverter is properly inverting the value I pass it, but that value is then not being 'sent' to the MultiValueConverter.
From MSDN:
UnsetValue is a sentinel value that is used for scenarios where the WPF property system is unable to determine a requested DependencyProperty value. UnsetValue is used rather than null reference (Nothing in Visual Basic), because null reference could be a valid property value, as well as a valid (and frequently used) DefaultValue.
Which means one of the following things:
You use a template (ControlTemplate or DataTemplate), and the value does not have a DataSource set at the time of being Loaded. So it will hit your converter twice, first with the UnsetValue, second with the boolean value; so nothing to worry about;
Your Binding is incorrect, meaning the Binding cannot determine a value, thus resulting in the UnsetValue.. You should propbably see a warning..
Also, you cannot combine Converters like you do.. So its probably that.
Remove the Converter in the inner Binding, and it should be fixed! :)
Hope this helps!
Just in addition to all other answers, I usually add these lines to the beginning of Convert method:
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values.Any(x => x == DependencyProperty.UnsetValue))
return DependencyProperty.UnsetValue;
...
}
to make sure that none of the values is unset (that usually happens with DataGrid with CanUserAddRows="True").
If occuring in a datagrid try setting CanUserAddRows="False"

Categories

Resources