programming4us
programming4us
DATABASE

Silverlight : Converting Values During Data Binding

- How To Install Windows Server 2012 On VirtualBox
- How To Bypass Torrent Connection Blocking By Your ISP
- How To Install Actual Facebook App On Kindle Fire
11/30/2012 1:53:37 AM

1. Problem

You are trying to bind to a data source and need to convert the source value to either a different type or a different value suitable for display in the UI.

2. Solution

Implement System.Windows.Data.IValueConverter to create a value converter type, and associate it to the binding to appropriately convert the value.

3. How It Works

Often, you will come across scenarios where the source value that you are trying to bind to is either a data type that needs to be converted before it can be bound or has the same data type as the target but needs some logical or contextual transformation before it can be meaningful to the UI.

As an example, imagine the Visibility property of a control. It is natural to think of Visibility as a Boolean, and thus express it in code as a bool. However, trying to bind a bool to the Visibility property of a Silverlight control will pose a challenge: in Silverlight, Visibility is expressed in terms of the Visibility enumeration, which has two values, Visible and Collapsed. In this case, you will need to convert from a source type (bool) to a target type (Visibility).

Imagine another scenario where you have the monthly spending of a family broken into categories as a data source, and you need to visually represent each expenditure as a percentage of the total. In this case, the data types of both the source and the target can be the same (say a double), but there is a logical transformation required between them—from an absolute value to a percentage.

3.1. Implementing Value Conversion

To use value conversion, you implement the System.Windows.Data.IValueConverter interface. The IValueConverter interface accommodates both source-to-target conversion through the Convert() method and target-to-source conversion through the ConvertBack() method.

Listing 1 shows a sample converter implementation that converts bool to Visibility and back.

Listing 1. Value Converter from bool to Visibility
public class BoolToVisibilityConverter : IValueConverter
{
  public object Convert(object value, Type targetType,
    object parameter, System.Globalization.CultureInfo culture)
  {
    //check to see that the parameter types are conformant
    if (value.GetType() != typeof(bool) || targetType != typeof(Visibility))
      return null;
    bool src = (bool)value;
    //translate
    return (src == true) ? Visibility.Visible : Visibility.Collapsed;
  }

  public object ConvertBack(object value, Type targetType,
    object parameter, System.Globalization.CultureInfo culture)
  {
    //check to see that the parameter types are conformant
    if (value.GetType() != typeof(Visibility) || targetType != typeof(bool))
      return null;
    Visibility src = (Visibility)value;
    //translate
    return (src == Visibility.Visible) ? true : false;
  }
}

					  

In both methods, the first parameter named value is the source value and the second parameter named targetType is the data type of the target to which the value needs to be converted. The ConvertBack() method will need to be fully implemented if you have a two-way binding, where an edit on the UI would require the change to be sent back to the source. If you do not update the data through your UI, you can simply either return null or throw a suitable exception from the ConvertBack() method.

Also note that each method accepts a parameter, aptly named parameter, where you can pass additional information as may be required by the conversion logic, as well as the target culture as the last parameter, in case you need to take into account a difference in the culture between source and target.

To use the value converter, you first declare it as a resource in your XAML, with an appropriate custom namespace mapping to bring in the assembly, in this case local:

<local:BoolToVisibilityConverter x:Name="REF_BoolToVisibilityConverter" />

After the converter resource has been declared as shown here, you can associate it to a Binding by using its Converter property. Once the converter is associated, every piece of data flowing through the Binding either way is passed through the converter methods—Convert() if the data is flowing from the source to the target property, and ConvertBack() if it is the other way. A sample usage is shown here

<ContentControl Visibility="{Binding IsControlVisible,
  Converter={StaticResource REF_BoolToVisibilityConverter}}"/>

where IsControlVisible is a Boolean property on a data source CLR type bound to the control.

4. The Code

The code sample builds a simple spending analysis application for a family, where the expenditure for different categories are maintained in a DataGrid and also graphed in a bar graph as a percentage of the total. The application allows you to change the spending in each category to different values and watch the graph change accordingly. It also allows you to drag any bar in the graph using your mouse and watch the corresponding value change in the DataGrid, maintaining the same total. Figure 1 shows the application output.

Figure 1. Family spending chart

Listing 2 shows the data classes used for this sample. The Spending class represents a specific expenditure, while the SpendingCollection extends ObservableCollection<Spending> to add some initialization code in a default constructor.

Listing 2. Application Data Classes
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;

namespace Recipe4_4
{
  public class SpendingCollection : ObservableCollection<Spending>,
    INotifyPropertyChanged
  {
    public SpendingCollection()
    {
      this.Add(new Spending
      {
        ParentCollection = this,
        Item = "Utilities",
        Amount = 300
      });
      this.Add(new Spending
      {
        ParentCollection = this,
        Item = "Food",
        Amount = 350
      });
      this.Add(new Spending
      {
        ParentCollection = this,
        Item = "Clothing",
        Amount = 200
      });
      this.Add(new Spending
      {
        ParentCollection = this,
        Item = "Transportation",
        Amount = 75
      });
      this.Add(new Spending
      {
        ParentCollection = this,
        Item = "Mortgage",
        Amount = 3000
      });
      this.Add(new Spending
      {

					  

ParentCollection = this,
        Item = "Education",
        Amount = 500
      });
      this.Add(new Spending
      {
        ParentCollection = this,
        Item = "Entertainment",
        Amount = 125
      });
      this.Add(new Spending
      {
        ParentCollection = this,
        Item = "Loans",
        Amount = 750
      });
      this.Add(new Spending
      {
        ParentCollection = this,
        Item = "Medical",
        Amount = 80
      });
      this.Add(new Spending
      {
        ParentCollection = this,
        Item = "Miscellaneous",
        Amount = 175
      });
    }

    public double Total
    {
      get
      {
        return this.Sum(spending => spending.Amount);
      }
    }
  }

  public class Spending : INotifyPropertyChanged
  {

    public event PropertyChangedEventHandler PropertyChanged;
    internal void RaisePropertyChanged(PropertyChangedEventArgs e)
    {

					  

if (PropertyChanged != null)
      {
        PropertyChanged(this, e);
      }
    }


    SpendingCollection _ParentCollection = null;

    public SpendingCollection ParentCollection
    {
      get { return _ParentCollection; }
      set { _ParentCollection = value; }
    }

    private string _Item;
    public string Item
    {
      get { return _Item; }
      set
      {
        string OldVal = _Item;
        if (OldVal != value)
        {
          _Item = value;
          RaisePropertyChanged(new PropertyChangedEventArgs("Item"));

        }
      }
    }

    private double _Amount;
    public double Amount
    {
      get { return _Amount; }
      set
      {
        double OldVal = _Amount;
        if (OldVal != value)
        {
          _Amount = value;


          foreach (Spending sp in ParentCollection)
            sp.RaisePropertyChanged(new PropertyChangedEventArgs("Amount"));

					  

}
      }
    }
  }
}

Listing 3 shows the XAML for the page. If you look at the resources section, you will notice two value converters. SpendingToBarWidthConverter converts a double value representing Spending to another double value representing a corresponding bar width, and vice versa. SpendingToPercentageStringConverter converts a Spending value to a percentage of the total spending, and vice versa. These converter implementations will be discussed in more detail momentarily.

Listing 3. XAML for the Page
<UserControl x:Class="Recipe4_4.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:data=
    "clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
    xmlns:local="clr-namespace:Recipe4_4"
    Width="800" Height="510">

  <UserControl.Resources>
    <local:SpendingCollection x:Key="REF_SpendingList" />
    <local:SpendingToBarWidthConverter x:Key="REF_SpendingToBarWidthConverter" />
    <local:SpendingToPercentageStringConverter
      x:Key="REF_SpendingToPercentageStringConverter" />
    <DataTemplate x:Key="dtBarTemplate">
      <Grid HorizontalAlignment="Left" VerticalAlignment="Stretch"
            Height="30"  Margin="0,2,0,0" >
        <Grid.RowDefinitions>
          <RowDefinition Height="0.5*" />
          <RowDefinition Height="0.5*" />
        </Grid.RowDefinitions>
        <Rectangle Grid.Row="1" VerticalAlignment="Stretch"
                   Fill="Black" HorizontalAlignment="Left"
                   Width="{Binding Amount,Mode=TwoWay,
                    Converter={StaticResource REF_SpendingToBarWidthConverter},
                    ConverterParameter={StaticResource REF_SpendingList}}"
                   MouseMove="Rectangle_MouseMove"
                   MouseLeftButtonDown="Rectangle_MouseLeftButtonDown"
                   MouseLeftButtonUp="Rectangle_MouseLeftButtonUp"/>
        <StackPanel Orientation="Horizontal" Grid.Row="0">
          <TextBlock Text="{Binding Item}" FontSize="9" />
          <TextBlock Text="{Binding Amount,
            Converter={StaticResource REF_SpendingToPercentageStringConverter},

					  

ConverterParameter={StaticResource REF_SpendingList}}"
                     Margin="5,0,0,0"
                     FontSize="9"/>
        </StackPanel>
      </Grid>
    </DataTemplate>
  </UserControl.Resources>

  <Grid x:Name="LayoutRoot" Background="White" Width="694">
    <Grid.ColumnDefinitions>
      <ColumnDefinition Width="*"/>
      <ColumnDefinition Width="*"/>
    </Grid.ColumnDefinitions>
    <data:DataGrid HorizontalAlignment="Stretch" Margin="8,8,8,8"
                   VerticalAlignment="Stretch"
                   HeadersVisibility="Column" x:Name="dgSpending"
                   ItemsSource="{StaticResource REF_SpendingList}"
                   AutoGenerateColumns="False">
      <data:DataGrid.Columns>
        <data:DataGridTextColumn Header="Item"
                            Binding="{Binding Item,Mode=TwoWay}"/>
        <data:DataGridTextColumn Header="Value" Width="100"
                            Binding="{Binding Amount,Mode=TwoWay}"/>
      </data:DataGrid.Columns>
    </data:DataGrid>
    <Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
          Grid.Column="1" Margin="8,8,8,8" x:Name="GraphRoot"
          DataContext="{StaticResource REF_SpendingList}">
      <Grid.RowDefinitions>
        <RowDefinition Height="*"/>
        <RowDefinition Height="20"/>
      </Grid.RowDefinitions>
      <Rectangle Height="Auto" HorizontalAlignment="Left"
                 VerticalAlignment="Stretch" Width="2"
                 Stroke="#FF000000" StrokeThickness="0"
                 Fill="#FF000000" x:Name="rectYAxis" Margin="0,0,0,0"/>
      <Rectangle Height="2" HorizontalAlignment="Stretch"
                 VerticalAlignment="Bottom" Fill="#FF000000"
                 Stroke="#FF000000" StrokeThickness="0"
                 Stretch="Fill" x:Name="rectXAxis" Margin="0,0,0,0"
                 Width="350" />
      <Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
            Width="Auto" Grid.Row="1" Margin="2,0,0,0"
            x:Name="gridMarkers">
        <Grid.ColumnDefinitions>

					  

<ColumnDefinition Width="0.1*" />
          <ColumnDefinition Width="0.1*" />
          <ColumnDefinition Width="0.1*" />
          <ColumnDefinition Width="0.1*" />
          <ColumnDefinition Width="0.1*" />
          <ColumnDefinition Width="0.1*" />
          <ColumnDefinition Width="0.1*" />
          <ColumnDefinition Width="0.1*" />
          <ColumnDefinition Width="0.1*" />
          <ColumnDefinition Width="0.1*" />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
          <RowDefinition Height="0.3*" />
          <RowDefinition Height="0.7*" />
        </Grid.RowDefinitions>
        <Rectangle Width="2" Fill="Black" VerticalAlignment="Stretch"
                   HorizontalAlignment="Right" Grid.Column="0" />
        <Rectangle Width="2" Fill="Black" VerticalAlignment="Stretch"
                   HorizontalAlignment="Right" Grid.Column="1" />
        <Rectangle Width="2" Fill="Black" VerticalAlignment="Stretch"
                   HorizontalAlignment="Right" Grid.Column="2" />
        <Rectangle Width="2" Fill="Black" VerticalAlignment="Stretch"
                   HorizontalAlignment="Right" Grid.Column="3" />
        <Rectangle Width="2" Fill="Black" VerticalAlignment="Stretch"
                   HorizontalAlignment="Right" Grid.Column="4" />
        <Rectangle Width="2" Fill="Black" VerticalAlignment="Stretch"
                   HorizontalAlignment="Right" Grid.Column="5" />
        <Rectangle Width="2" Fill="Black" VerticalAlignment="Stretch"
                   HorizontalAlignment="Right" Grid.Column="6" />
        <Rectangle Width="2" Fill="Black" VerticalAlignment="Stretch"
                   HorizontalAlignment="Right" Grid.Column="7" />
        <Rectangle Width="2" Fill="Black" VerticalAlignment="Stretch"
                   HorizontalAlignment="Right" Grid.Column="8" />
        <Rectangle Width="2" Fill="Black" VerticalAlignment="Stretch"
                   HorizontalAlignment="Right" Grid.Column="9" />
        <TextBlock HorizontalAlignment="Right" VerticalAlignment="Stretch"
                   Grid.Row="1" Grid.Column="0" Text="10%" FontSize="11"
                   FontWeight="Bold" />
        <TextBlock HorizontalAlignment="Right" VerticalAlignment="Stretch"
                   Grid.Row="1" Grid.Column="1" Text="20%" FontSize="11"
                   FontWeight="Bold" />
        <TextBlock HorizontalAlignment="Right" VerticalAlignment="Stretch"
                   Grid.Row="1" Grid.Column="2" Text="30%" FontSize="11"
                   FontWeight="Bold" />
        <TextBlock HorizontalAlignment="Right" VerticalAlignment="Stretch"

					  

Grid.Row="1" Grid.Column="3" Text="40%" FontSize="11"
                   FontWeight="Bold" />
        <TextBlock HorizontalAlignment="Right" VerticalAlignment="Stretch"
                   Grid.Row="1" Grid.Column="4" Text="50%" FontSize="11"
                   FontWeight="Bold" />
        <TextBlock HorizontalAlignment="Right" VerticalAlignment="Stretch"
                   Grid.Row="1" Grid.Column="5" Text="60%" FontSize="11"
                   FontWeight="Bold" />
        <TextBlock HorizontalAlignment="Right" VerticalAlignment="Stretch"
                   Grid.Row="1" Grid.Column="6" Text="70%" FontSize="11"
                   FontWeight="Bold" />
        <TextBlock HorizontalAlignment="Right" VerticalAlignment="Stretch"
                   Grid.Row="1" Grid.Column="7" Text="80%" FontSize="11"
                   FontWeight="Bold" />
        <TextBlock HorizontalAlignment="Right" VerticalAlignment="Stretch"
                   Grid.Row="1" Grid.Column="8" Text="90%" FontSize="11"
                   FontWeight="Bold" />
        <TextBlock HorizontalAlignment="Right" VerticalAlignment="Stretch"
                   Grid.Row="1" Grid.Column="9" Text="100%" FontSize="11"
                   FontWeight="Bold" />
      </Grid>
      <Grid Height="Auto" HorizontalAlignment="Stretch" Margin="2,0,0,2"
            VerticalAlignment="Stretch" Width="Auto" x:Name="gridBars"
            ShowGridLines="True">
        <ItemsControl ItemsSource="{StaticResource REF_SpendingList}"
                      ItemTemplate="{StaticResource dtBarTemplate}" />
      </Grid>
    </Grid>
  </Grid>
</UserControl>

					  

The rest of the XAML is pretty simple. The SpendingCollection, through a resource reference named REF_SpendingList, is bound to a DataGrid named dgSpending. The bar graph is implemented as an ItemsControl, once again bound to the same SpendingCollection instance, using a DataTemplate named dtBarTemplate for each bar.

Note how you use the converters inside dtBarTemplate. You bind the Width of a Rectangle directly to the Amount property on the bound Spending instance and then use the Converter property of the Binding to associate the SpendingToBarWidthConverter. You also bind the Text property of a TextBlock similarly, using the SpendingToPercentageStringConverter instead. On both occasions, you also pass in the entire SpendingCollection instance through the ConverterParameter property of the Binding. The ConverterParameter property value maps to the method parameter named parameter in both the Convert() and ConvertBack() methods on the value converter. This makes the collection available inside the converter code.

SpendingToBarWidthConverter, shown in Listing 4, is used to convert a spending value to the length of the corresponding bar in the bar graph; both data types are double.

Listing 4. Value Converter Converting Spending to Bar Width
using System;
using System.Windows;
using System.Windows.Data;
using System.Windows.Shapes;

namespace Recipe4_4
{
  public class SpendingToBarWidthConverter : IValueConverter
  {
    public object Convert(object value, Type targetType,
      object parameter, System.Globalization.CultureInfo culture)
    {
      //verify validity of all the parameters
      if (value.GetType() != typeof(double) || targetType != typeof(double)
        || parameter == null
        || parameter.GetType() != typeof(SpendingCollection))
        return null;
      //cast appropriately
      double Spending = (double)value;
      double Total = ((SpendingCollection)parameter).Total;
      //find the xAxis
      Rectangle rectXAxis = (Rectangle)((MainPage)Application.Current.RootVisual)
        .FindName("rectXAxis");
      //calculate bar width in proportion to the xAxis width
      return (Spending / Total) * rectXAxis.Width;
    }

    public object ConvertBack(object value, Type targetType,
      object parameter, System.Globalization.CultureInfo culture)
    {
      //verify validity of all the parameters
      if (value.GetType() != typeof(double) || targetType != typeof(double)
        || parameter == null
        || parameter.GetType() != typeof(SpendingCollection))
        return null;
      //cast appropriately
      double BarWidth = (double)value;
      double Total = ((SpendingCollection)parameter).Total;
      //find the xAxis
      Rectangle rectXAxis = (Rectangle)((MainPage)Application.Current.RootVisual)
        .FindName("rectXAxis");
      //calculate new spending keeping total spending constant based on

					  

//new bar width to xAxis width ratio
      return (BarWidth / rectXAxis.Width) * Total;
    }
  }
}

To convert the spending value into bar width in SpendingToBarWidthConverter.Convert(), you calculate the ratio of the spending value in question to the total spending evaluated from the SpendingCollection passed in as parameter. You then calculate the bar width as the same ratio applied to the total width of the X axis of the graph, also defined as a Rectangle named rectXAxis in XAML. In SpendingToBarWidthConverter.ConvertBack(), you reverse that calculation.

Listing 5 shows the SpendingToPercentageStringConverter code. The calculation of the percentage value in Convert() is again based off the spending total derived from the SpendingCollection instance and then formatted appropriately to a string. Since you never do the reverse conversion, you do not implement ConvertBack() in this case.

Listing 5. Value Converter Converting Spending to a Percentage String
using System;
using System.Windows.Data;

namespace Recipe4_4
{
  public class SpendingToPercentageStringConverter : IValueConverter
  {
    public object Convert(object value, Type targetType,
      object parameter, System.Globalization.CultureInfo culture)
    {
      //verify validity of all the parameters
      if (value.GetType() != typeof(double) || targetType != typeof(string)
        || parameter == null
        || parameter.GetType() != typeof(SpendingCollection))
        return null;
      //cast appropriately
      double Spending = (double)value;
      double Total = ((SpendingCollection)parameter).Total;
      //calculate the spending percentage and format as string
      return ((Spending / Total) * 100).ToString("###.##") + " %";
    }

    public object ConvertBack(object value, Type targetType,
      object parameter, System.Globalization.CultureInfo culture)
    {
      throw new NotImplementedException();
    }
  }
}

					  

NOTE

There is no requirement that a value converter also perform a type conversion. In the code sample for SpendingToBarWidthConverter, for example, you convert values of the same data type double, where the conversion is one of context—that is, from one kind of measure (Spending) to another (Width). Therefore, it is called a value conversion.

Listing 6 shows the codebehind for the MainPage. Of note is the MouseMove handler Rectangle_MouseMove() for each Rectangle representing a bar in the ItemsControl. In the handler, you calculate the distance moved as the difference of the current mouse position and its previous position and change the Width of the bar accordingly. You then store the current position as the previous position for the next move.

Listing 6. Codebehind for the Page
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Shapes;
namespace Recipe4_4
{
  public partial class MainPage : UserControl
  {
    private bool MouseLeftBtnDown = false;
    Point PreviousPos;
    public MainPage()
    {
      InitializeComponent();
    }

    private void Rectangle_MouseMove(object sender, MouseEventArgs e)
    {
      if (MouseLeftBtnDown)
      {
        Rectangle rect = (Rectangle)sender;
        Point CurrentPos = e.GetPosition(sender as Rectangle);
        double Moved = CurrentPos.X – PreviousPos.X;
        if (rect.Width + Moved >= 0)
        {
          rect.Width += Moved;
        }
        PreviousPos = CurrentPos;
      }

					  

}

    private void Rectangle_MouseLeftButtonDown(object sender,
      MouseButtonEventArgs e)
    {
      MouseLeftBtnDown = true;
      PreviousPos = e.GetPosition(sender as Rectangle);
    }

    private void Rectangle_MouseLeftButtonUp(object sender,
      MouseButtonEventArgs e)
    {
      MouseLeftBtnDown = false;
    }
  }
}
Other  
  •  Oracle Database 11g : Installing Oracle - Set Up the Operating System
  •  Oracle Database 11g : Installing Oracle - Research and Plan the Installation
  •  Microsoft Systems Management Server 2003 : Modifying SQL Server Parameters
  •  Microsoft Systems Management Server 2003 : Backing Up and Restoring the Database
  •   ASP.NET 4 : Data Source Controls (part 3) - Handling Errors, Updating Records
  •   ASP.NET 4 : Data Source Controls (part 2) - Parameterized Commands
  •   ASP.NET 4 : Data Source Controls (part 1) - The Page Life Cycle with Data Binding, The SqlDataSource, Selecting Records
  •  Silverlight : Data Binding - Receiving Change Notifications for Bound Data
  •  Silverlight : Binding Using a DataTemplate
  •  SQL Server 2005 : Advanced OLAP - Partitions, Aggregation Design, Storage Settings, and Proactive Caching
  •  
    Top 10
    - Microsoft Visio 2013 : Adding Structure to Your Diagrams - Finding containers and lists in Visio (part 2) - Wireframes,Legends
    - Microsoft Visio 2013 : Adding Structure to Your Diagrams - Finding containers and lists in Visio (part 1) - Swimlanes
    - Microsoft Visio 2013 : Adding Structure to Your Diagrams - Formatting and sizing lists
    - Microsoft Visio 2013 : Adding Structure to Your Diagrams - Adding shapes to lists
    - Microsoft Visio 2013 : Adding Structure to Your Diagrams - Sizing containers
    - Microsoft Access 2010 : Control Properties and Why to Use Them (part 3) - The Other Properties of a Control
    - Microsoft Access 2010 : Control Properties and Why to Use Them (part 2) - The Data Properties of a Control
    - Microsoft Access 2010 : Control Properties and Why to Use Them (part 1) - The Format Properties of a Control
    - Microsoft Access 2010 : Form Properties and Why Should You Use Them - Working with the Properties Window
    - Microsoft Visio 2013 : Using the Organization Chart Wizard with new data
    Video Sports
    programming4us programming4us
    programming4us
     
     
    programming4us