Tim Hoskins
Tim Hoskins

Reputation: 13

Localize currency values in WPF GridView with different cultures for each row based on XAML bindings to culture name property

First time poster here but have been reading SO for ages and finally have run into a question that I've not been able to answer.

I've got a ListView hosting a GridView with multiple columns. One displays a price and another displays a currency code (CAD, USD, GBP, etc). This is all pulled out of SQL server using Entity Framework so the GridView is databound to a IEnumerable which stores the result of my query. The currency code is stored in a separate table with a localization string (en-US, en-GB) which (in a WinForms version of this app) was previously used in String.Format() to localize the currency to display the appropriate currency format and symbol.

The problem I have is in XAML binding the ConverterCulture of the Price binding to the Currency.LocalizedCultureName to get it to format correctly. Here's my current XAML:

            <ListView Grid.Column="0" Name="pricingListingListView" ItemsSource="{Binding Source={StaticResource pricesByYear}}">
            <ListView.GroupStyle>
                <GroupStyle>
                    <GroupStyle.ContainerStyle>
                        <Style TargetType="{x:Type GroupItem}">
                            <Setter Property="Template">
                                <Setter.Value>
                                    <ControlTemplate TargetType="{x:Type GroupItem}">
                                        <GroupBox Header="{Binding Name}" Margin="0,0,0,10">
                                            <ItemsPresenter/>
                                        </GroupBox>
                                    </ControlTemplate>
                                </Setter.Value>
                            </Setter>
                        </Style>
                    </GroupStyle.ContainerStyle>
                </GroupStyle>
            </ListView.GroupStyle>
            <ListView.View>
                <GridView>
                    <GridViewColumn HeaderContainerStyle="{StaticResource leftAlignedColumnHeader}" Header="Date" DisplayMemberBinding="{Binding Source.Date}" Width="60" />
                    <GridViewColumn HeaderContainerStyle="{StaticResource leftAlignedColumnHeader}" Header="Price" DisplayMemberBinding="{Binding Price, StringFormat='{}{0:C}', ConverterCulture={Binding Currency.LocalizedCultureName}}" Width="60" />
                    <GridViewColumn HeaderContainerStyle="{StaticResource leftAlignedColumnHeader}" Header="Currency" DisplayMemberBinding="{Binding Currency.Code}" Width="60" />
                    <GridViewColumn HeaderContainerStyle="{StaticResource leftAlignedColumnHeader}" Header="Unit" DisplayMemberBinding="{Binding Unit.Name}" Width="60" />
                    <GridViewColumn HeaderContainerStyle="{StaticResource leftAlignedColumnHeader}" Header="Source" DisplayMemberBinding="{Binding Source.Name}" Width="125" />
                    <GridViewColumn HeaderContainerStyle="{StaticResource leftAlignedColumnHeader}" Header="Project" DisplayMemberBinding="{Binding Project.Description}" Width="125" />
                    <GridViewColumn HeaderContainerStyle="{StaticResource leftAlignedColumnHeader}" Header="Plant Type" DisplayMemberBinding="{Binding Project.Plant.Name}" Width="100" />
                </GridView>
            </ListView.View>
        </ListView>

PricesByYear is simply a CollectionViewSource which pulls the IEnumerable out of a DP in my code behind. The data is pulled out correctly, just not formatted.

This compiles fine, but generates a XamlParseException when I load the window containing it: A 'Binding' cannot be set on the 'ConverterCulture' property of type 'Binding'. A 'Binding' can only be set on a DependencyProperty of a DependencyObject.

The line generating the error is: <GridViewColumn HeaderContainerStyle="{StaticResource leftAlignedColumnHeader}" Header="Price" DisplayMemberBinding="{Binding Price, StringFormat='{}{0:C}', ConverterCulture={Binding Currency.LocalizedCultureName}}" Width="60" />

The short-form objective is to display the price, but format it according to the culture name stored as a string value. Each row in the gridview could potentially be different. As it seems I cannot bind within a binding, is there an alternative way I could go about this?

Answer

Multibinding did the trick, here's the working XAML:

<local:LocalizeCurrencyMultiConverter x:Key="localizeCurrencyMultiConverter"/>

...

<GridViewColumn HeaderContainerStyle="{StaticResource leftAlignedColumnHeader}" Header="Price"  Width="60">
                        <!--DisplayMemberBinding="{Binding Price, StringFormat='{}{0:C}', ConverterCulture={Binding Currency.LocalizedCultureName}}"-->
                        <GridViewColumn.DisplayMemberBinding>
                            <MultiBinding Converter="{StaticResource localizeCurrencyMultiConverter}">
                                <Binding Path="Price"/>
                                <Binding Path="Currency.LocalizedCultureName"/>
                            </MultiBinding>
                        </GridViewColumn.DisplayMemberBinding>
                    </GridViewColumn>

And the converter class:

    public class LocalizeCurrencyMultiConverter :System.Windows.Data.IMultiValueConverter {
    public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
        string localizedCurrency;

        if (!values.Any() || values[0] == null)
            throw new ArgumentException("Convert requires a minimum a price to display, and optionally a culture.");

        double originalCurrency;
        if (!double.TryParse(values[0].ToString(), out originalCurrency))
            return values[0];
        string localization = (values[1] ?? "en-CA").ToString();

        try {
            localizedCurrency = string.Format(System.Globalization.CultureInfo.CreateSpecificCulture(localization), "{0:c}", originalCurrency);
        } catch {
            localizedCurrency = string.Format(System.Globalization.CultureInfo.CreateSpecificCulture("en-CA"), "{0:c}", originalCurrency);
        }
        return localizedCurrency;
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture) {
        throw new NotImplementedException();
        return null;
    }
}

Works like a charm.

Upvotes: 1

Views: 2043

Answers (1)

Grafix
Grafix

Reputation: 826

  1. Use a MultiBinding with a binding to the Price property and a binding the Culture property,
  2. Write a MultiValueConverter and use the values to output the string you want.

I am going to make it easy for you:

MSDN MultiBinding

MSDN MultiConverter

Upvotes: 1

Related Questions