So, today at work, I was working with some data binding, and I wanted the user to be able to input an angle in degrees or radians, whichever they are comfortable with. I also wanted to display the value in both degrees or radians, regardless of which they choose to enter it in. I looked around on the internet, and I found lots of examples of how to use Value Converters, and lots of examples on how to do Data binding. But none that really showed what I wanted to do. So here's how I did it.
Theres four essential steps to making this work. They are:
1.) Write a converter class that implements IValueConverter
2.) Include the namespace into your XAML document.
3.) Data Bind the controls.
4.) Convert the value
Step 1: Write the Converter Class.
This part is pretty simple. The class itself should be self explanatory. Remember to use System.Windows.Data (IValueConverter)
The Convert Function is to convert from Degrees to Radians. As you can see, I take the value parameter, cast it to double (for maths!), perform my calculation, and return the new value. The if statement at the beginning is to fix a 'bug' where the program throws an exception if nothing is in the text box. This just returns 0.
The ConvertBack function is to convert from Radians, back to Degrees. The exact same thing, just doing the calculation in reverse.
1public class DegRads : IValueConverter
2{
3 //Converts from Degrees (double) to Radians (double)
4 public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
5 {
6 if (((string)value) == "") return 0.0;
7 double degrees = System.Convert.ToDouble(value);
8 double radians = radians = degrees * (Math.PI / 180);
9 return radians;
10 }
11
12 //Convert back to Degrees (double) from Radians (double)
13 public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
14 {
15 if (((string)value) == "") return 0.0;
16 double radians = System.Convert.ToDouble(value);
17 double degrees = degrees = radians * (180 / Math.PI);
18 return degrees;
19 }
20}
Step 2: Including the Namespace in the XAML
This part is really easy. In your Window tag, just declare the namespace as shown. Substitute your own namespace name in for ValueConverterDemo. Note, that the namespace name is the namespace that the converter class is in. Also note, the myns is an alias for that namespace, to refer to it in the rest of the XAML document.
I originally had some problems with this step, and it seems they all stemmed from me changing the name of the namespace. I guess VS2005's refactoring doesn't do XAML, and if you manually change it, it can cause some issues... I remade the project. Might be easier ways to fix this.
<Window x:Class="ValueConverterDemo.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ValueConverterDemo" Height="78" Width="193"
xmlns:myns="clr-namespace:ValueConverterDemo"
>
Step 3: Data Bind
Again, this is really easy. What we want to do here, is set ElementName to the name of the control we're pulling data from, and set Path to the property that we're changing. Note, that we only need to data bind ONE of the text boxes. A side effect from that, is whenever I change the degrees, the radians change instantly. However, when I am changing the radians, I move focus away from the radians text box in order for the degrees text box to update. Now, I haven't don enough testing to see if we'd get some sort of cyclic error (stack overflow?) if we data bound both... Maybe WPF is smart enough to fix it?
<Grid>
<Label Grid.Row="0" Grid.Column="0">Degrees:</Label>
<TextBox Grid.Row="0" Grid.Column="1" x:Name="txtDegrees">0</TextBox>
<Label Grid.Row="1" Grid.Column="0">Radians:</Label>
<TextBox Grid.Row="1" Grid.Column="1" x:Name="txtRadians" Margin="0,0,0,1" Text="{Binding ElementName=txtDegrees, Path=Text, Converter={StaticResource DegRadsConverter}}" />
</Grid>
Step 4: Convert
Now, we have a type that will convert a value. We have a value. Sounds good, right? Wrong. The type itself cannot convert, we need an instance of that type. The XAML document knows about the type (it knows about the namespace from step 2, therefore it knows about its children). To create an instance, we need to go to a Resources section. I used Window.Resources in this case. Looking at the code below, you can see I used the namespace alias I created in step 2, and the name of the class. I also assigned a name to the object (key property). This is equivalent to the following: myns.DegRads DegRadsConverter;
<Grid.Resources>
<myns:DegRads x:Key="DegRadsConverter" />
</Grid.Resources>
Now that we have an instance in our Resources section, we need to actually convert. This is real easy. Just go to our Data bound control, and add the Converter property. Notice the StaticResource part. This tells the document to use the instance found in the resources section.
<TextBox Grid.Row="1" Grid.Column="1" x:Name="txtRadians" Margin="0,0,0,1"
Text="{Binding ElementName=txtDegrees, Path=Text,
Converter={StaticResource DegRadsConverter}}" />
That's it!
Data binding with calculations. There are tons of applications for this. Currency exchange, converting numbers to words, localization, etc. One thing I would like to learn more about in regards to IValueConverter, is there a way to use two inputs to make one output? Perhaps a universal currency converter, where I use information from two text boxes, Input Value, Exchange Rate, to generate an Output Value? Any ideas?
4 comments:
Hey! Just stumbled across this treasure trove! Thanks a lot and keep up the good work! I will be visiting you every day :D
M
It's nice to see we have reader(s) finally :)
For multiple input to only one output, you must create a class that implement IMultiValueConverter and use MultiBinding in your xaml file :-)
When back conversion is not needed, you can also write your simple converters inside xaml. Take a look at http://www.fikrimvar.net/lestirelim/?p=15
Post a Comment