Currency Converter using IValueConverter - c#

I am working on Windows 8.1 Store App using XAML and C#.
I have added 2 text boxes and implemented an IValueConverter Interface.
here is the Xaml Code
<Page.Resources>
<local:ConverterClass x:Key="C_Converter" />
<local:EuroConverterClass x:Key="Euro_Converter" />
<local:YenConverterClass x:Key="Yen_Converter" />
</Page.Resources>
<TextBox Name="PKR_TextBox"
Grid.Row="1"
Grid.Column="2"
Width="450"
Height="50"
FontSize="30"
FontWeight="Bold"
HorizontalAlignment="Left"
VerticalAlignment="Center" />
<TextBox Name="DOLLAR_TextBox"
Grid.Row="2"
Grid.Column="2"
Text="{Binding ElementName=PKR_TextBox, Path=Text, Converter={StaticResource C_Converter}}"
Width="450"
Height="50"
FontSize="30"
FontWeight="Bold"
HorizontalAlignment="Left"
VerticalAlignment="Center" />
Here my Converter Class Code:
class ConverterClass : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
int pkr;
int dollar = 0;
if (Int32.TryParse(value.ToString(), out pkr))
{
dollar = pkr * 0.0099;
}
return dollar;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
I an trying to convert currency at run time when user enters a value in PKR text box it should automatically update USD Textbox. But instead it is giving me an error "Cannot implicitly convert type 'double' to 'int'. An explicit conversion exists (are you missing a cast?)".
Please help and ignore my bad english.

The error message is pretty clear. You have to use double values for your calculation. double is the default floating point type when you use C#.
public object Convert(
object value, Type targetType, object parameter, string language)
{
double pkr;
double dollar = 0.0;
if (double.TryParse(value.ToString(), out pkr))
{
dollar = pkr * 0.0099;
}
return dollar;
}

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>

WPF/DataGrid: Binding to different properties for displaying and editing

I have an object that contains eg a string property like "10; 20; 30". I have also a get property that splits the string, converts each part to a double and sums them up. Thus I have "10; 20; 30" and 60.0 (as double).
Now the question is. Is there a way to display the 60.0 (as double) in a TextColumn, but when going to edit mode editing the string "10; 20; 30"?
So that I can bind to one property for displaying and to bind to another property for editing?
You can achieve this with your existing property itself by using different template displaying and editing.
Below CellTemplate and CellEditingTemplate can used for this.
<Grid>
<Grid.Resources>
<local:ValueConverter x:Key="ValueConverter"/>
<DataTemplate x:Key="DisplayTemplate" >
<TextBlock Text="{Binding StringProperty,
Converter={StaticResource ValueConverter}}"/>
</DataTemplate>
<DataTemplate x:Key="EditTemplate">
<TextBox Text="{Binding StringProperty}" />
</DataTemplate>
</Grid.Resources>
<DataGrid Name="DG1" ItemsSource="{Binding Items}" AutoGenerateColumns="False"
CanUserAddRows="False">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Total"
CellTemplate="{StaticResource DisplayTemplate}"
CellEditingTemplate="{StaticResource EditTemplate}" />
</DataGrid.Columns>
</DataGrid>
</Grid>
You can use IValueConverter to convert the updated string values to double as per your desired calculation.
public class ValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
try
{
double total = 0.0d;
foreach (var item in value.ToString().Split(';'))
total += System.Convert.ToDouble(item.Trim());
return total;
}
catch
{
return 0.0d;
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Note: You can add the necessary validation for the user values inside your ValueConverter class.

Binding Width to sum of actualWidths' of two elements in WPF

I want to make imitation of Datagrid with multilevel(2 level) column header. I am going to define first level with Grid on top of datagrid and the second level will be datagrid header itself as shown below:
The problem I have now (Which is probably in valueconventer) is that I want the width of GridColumn1 always to be sum of widths' of DataGridColumn1 and DataGridColumn2 columns. For this, I have created value converter:
public class DataGridExtraHeaderConventer : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
int actualWidth = 0;
foreach (var item in values)
{
actualWidth += System.Convert.ToInt32(item);
}
return actualWidth;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
var x = DependencyProperty.UnsetValue;
var y = DependencyProperty.UnsetValue;
return new object[] { x, y };
}
}
And Defined the GridColumn1 as shown below:
<Grid.ColumnDefinitions>
<ColumnDefinition>
<ColumnDefinition.Width>
<MultiBinding Converter="{StaticResource DataGridExtraHeaderConventer }">
<Binding ElementName="Column1" Path="ActualWidth"/>
<Binding ElementName="Column2" Path="ActualWidth"/>
</MultiBinding>
</ColumnDefinition.Width>
</ColumnDefinition>
<ColumnDefinition Width="*" />
Datagrid looks like this:
<DataGrid Name="datagrid1" AutoGenerateColumns="False" Grid.Row="3" RowHeaderWidth="10">
<DataGrid.Columns>
<DataGridTextColumn x:Name="Column1" Binding="{Binding something}" Header="column 1" Width="50*" />
<DataGridTextColumn x:Name="Column2" Binding="{Binding something}" Header="column 2" Width="60*" />
<DataGridTextColumn x:Name="Column3" Binding="{Binding something}" Header="column 3" Width="80" />
<DataGridTextColumn x:Name="Column4" Binding="{Binding something}" Header="column 4" Width="*" />
</DataGrid.Columns>
</DataGrid>
This code for some reason doesn't work. I run it successfully, buy the width of GridColumn is as it was without binding.
P.S If I want to bind DataGridColumn1 column's width to GridColumn1, this works:
<ColumnDefinition Width="{Binding ElementName=Column1, Path=ActualWidth}">
The converter in the question has two problems:
The values fed from the ActualWidth properties to the converter are of type double, but the converter converts them into integer values. This will cause rounding errors, causing the converter result to be somewhat inaccurate.
The ColumnDefinition.Width is of type GridLength. The targetType argument of the converter's Convert method will also tell this. Since a integer or floating point value is not implicitely/explictely convertible to GridLength, the converter itself has to produce and return such a GridLength object. Otherwise, the binding mechanism will reject the result produced by the converter.
Addressing the two issues, a working Convert method could look for example like this:
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
try
{
double sumActualWidths = values.Sum(v => System.Convert.ToDouble(v));
if (targetType == typeof(GridLength))
return new GridLength(sumActualWidths);
else
return System.Convert.ChangeType(sumActualWidths, targetType);
}
catch (Exception ex)
{
Oops! Something went wrong.
I better handle this exception in a meaningful way.
return Binding.DoNothing;
}
}

C# TextBox display dynamically currency format

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

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.

Categories

Resources