Thursday, July 16, 2009

Silverlight: Using Multiple Bindings and IValueConverter for Complex Templates

I'm going to give a simple example, but by "complex" I think you'll see how this can be extended.

I've addressed this issue before in a different way, and I think this is much better (see IMultiValueConverter with Silverlight). Basically, I have a compound object I need databound. In this case, I have a HyperlinkButton that needs both a URL and text, so it is getting values from multiple fields. Let's assume our class is Product with properties ID and Name. The URL is going to be "Products.aspx?id={id}" where {id} is, well, the product id.

First, we'll create a value converter to take the id and return the URL. Make a class called ProductUrlConverter and implement IValueConverter. It's one-way, so we'll leave ConvertBack unimplemented. For purity's sake, we should change it to throw a NotSupportedException if we don't intend to implement it. Now we can tackle the other method:

public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
   string val = value.ToString();
   return new Uri(string.Format("Products.aspx?id={0}", val), UriKind.Relative);
}

As you can see, we simply return the Uri that is relative to the product. Now for our grid. We want to fix the width of the column and ensure that longer product names wrap. We do that with a style:

<Style TargetType="TextBlock" x:Key="GridTextStyle">
   <Setter Property="TextWrapping" Value="Wrap"/>
</Style>

We also need to add a reference to the converter in the UserControl.Resources section, like this (Converters is my namespace for the converter)

...
<Converters:ProductUrlConverter x:Key="UrlConverter"/>
...

Now, we define the grid and our templates. The template for the navigation button looks like this:

<Controls:DataGridTemplateColumn SortMemberPath="Name"
    Header="Product" CanUserResize="True" CanUserReorder="True" CanUserSort="True" IsReadOnly="True" Width="100">
    <Controls:DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <HyperlinkButton NavigateUri="{Binding ID, Converter={StaticResource UrlConverter}}">
                <HyperlinkButton.Content>
                    <TextBlock Style="{StaticResource GridTextStyle}" Text="{Binding Name}"/>
                </HyperlinkButton.Content>
            </HyperlinkButton>
        </DataTemplate>
    </Controls:DataGridTemplateColumn.CellTemplate>
</Controls:DataGridTemplateColumn>

That's all there is to it - now I have a link that shows a friendly name, navigates to the product page when clicked, and wraps if the name is too long for the column.

Jeremy Likness