Use "real" CultureInfo.CurrentCulture in WPF Binding, not CultureInfo from IetfLanguageTag
Asked Answered
J

8

46

In my case:

I have a TextBlock Binding to a property of type DateTime. I want it to be displayed as the Regional settings of the User says.

<TextBlock Text="{Binding Date, StringFormat={}{0:d}}" />

I am setting Language property as WPF XAML Bindings and CurrentCulture Display says:

this.Language = XmlLanguage.GetLanguage(CultureInfo.CurrentCulture.IetfLanguageTag);

But with this line of code it just displays the text as the default format of CultureInfo with IetfLanguageTag of CurrentCulture says, not as the effective value selected in systems region settings says:

(e.g. for "de-DE" dd.MM.yyyy is used instead of selected yyyy-MM-dd)

Region settings: not the default but yyy-MM-dd is used

Is there a way Binding uses the correct format without defining ConverterCulture on every single Binding?

In code

string.Format("{0:d}",Date);

uses the right Culture settings.

edit:

another way which doesn't work as desired (like this.Language = ... does):

xmlns:glob="clr-namespace:System.Globalization;assembly=mscorlib"

and

<Binding Source="{x:Static glob:CultureInfo.CurrentCulture}" 
 Path="IetfLanguageTag" 
 ConverterCulture="{x:Static glob:CultureInfo.InvariantCulture}" />
Jerkin answered 29/4, 2011 at 11:33 Comment(2)
Check out the following: #2765115Brickkiln
@Pavlo: the answer to this question also uses the IetfLanguageTag to get CultureInfo.Jerkin
H
30

You can create a subclass of binding (e.g. CultureAwareBinding) which sets the ConverterCulture automatically to the current culture when created.

It's not a perfect solution, but it's probably the only one, since retroactively forcing Binding to respect the culture could break other code in WPF which depends on this behavior.

Let me know if you need more help!

Hardaway answered 9/5, 2011 at 13:23 Comment(2)
thank you for your answer. this solution souns like the only one possible.Jerkin
"since retroactively forcing Binding to respect the culture could break other code in WPF which depends on this behavior." - I'd argue that in the long-term it's worse to keep on maintaining broken behaviour like this than it is to introduce a short-term break. Or they could fix it but use .NET's assembly binding features to detect when an application expects the older version and maintains the broken behaviour for them. It's insane that this bug has been in WPF for 12 years now and they don't have their own CultureAwareBinding built-in.Monteverdi
G
24

This is an extension of answer from aKzenT. They proposed that we should create a subclass of Binding class and set the ConverterCulture to CurrentCulture. Even though the answer is very straight forward, I feel some people may not be very comfortable implementing it, so I am sharing the code version of aKzenT's answer with an example of how to use it in XAML.

using System;
using System.Globalization;
using System.Windows.Data;

namespace MyWpfLibrary
{
    public class CultureAwareBinding : Binding
    {
        public CultureAwareBinding()
        {
            ConverterCulture = CultureInfo.CurrentCulture;
        }
    }
}

Example of how to use this in XAML

1) You need to import your namespace into your XAML file:

<Page
    ...
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:myWpfLib="clr-namespace:MyWpfLibrary;assembly=<assembly_name>"
    ...
>

2) Real world usage of the CultureAwareBinding

<Textblock Text="{myWpfLib:CultureAwareBinding Path=Salary, Source=Contact, StringFormat={}{0:C}}" />
Greig answered 5/11, 2013 at 18:38 Comment(4)
I like your detailed explanation on this. Any input on correcting errors for certain binds? That result in something like, "The type "CultureAwareBinding" does not include a constructor that has the specified number of arguments."Sundog
I haven't seen that error yet. I would need to see some code to determine why that would happen. Perhaps you could post a new question on Stack Overflow and add a link to it over here?Greig
Thanks, I fixed the error. It was bad coding, evidently, {Binding ITEM-NAME .... } is not good, I needed {Binding Path=ITEM-NAME, ... }. That resolved my error (which would still compile in both cases)Sundog
@Sundog You can also define an additional constructor for CultureAwareBinding that takes a string as sole parameter: public CultureAwareBinding(string path) : base(path) { ConverterCulture = CultureInfo.CurrentCulture; }. This way you don't need to specify Path=….Hsining
S
12

Put the following line of code, before any UI is initialized. This worked for me.

FrameworkElement.LanguageProperty.OverrideMetadata(typeof(FrameworkElement),
    new FrameworkPropertyMetadata(XmlLanguage.GetLanguage(CultureInfo.CurrentCulture.IetfLanguageTag)));

(And remove all explicit culture parameters)

Scoter answered 25/7, 2016 at 12:37 Comment(4)
Works like a charm and is just one line of code without the need to touch any other code. I don't understand, why this isn't the accepted answer and why it has so few upvotes. Are there any pitfalls?Reorganization
As was stated in the question, setting the language only causes WPF to use the language's default date/time formats, not the formats that are selected in the system's region settings.Psalmbook
I've put this code in my App.xaml.cs Application_Startup method and worked well. Now datetime is correctly formatted to my system locale.Wurst
@CollinK It works perfectly well not only for the date/time formats but also for the other regional settings, e.g. the decimal separator (that is not a dot but a comma in many system's regional settings).Ulrikaumeko
S
4

Your second attempt was close, and led me to a solution that does work for me.

The problem with setting the ConverterCulture is that it is only used when you have a Converter. So simply create a simple StringFormatConverter that takes the format as its parameter:

public sealed class StringFormatConverter : IValueConverter
{
    private static readonly StringFormatConverter instance = new StringFormatConverter();
    public static StringFormatConverter Instance
    {
        get
        {
            return instance;
        }
    }

    private StringFormatConverter()
    {
    }

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return string.Format(culture, (string)parameter, value);
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotSupportedException();
    }
}

Then you can adjust your binding (assuming you've imported the converter's namespace as "my")

<TextBlock Text="{Binding Date, Converter={x:Static my:StringFormatConverter.Instance}, ConverterCulture={x:Static glob:CultureInfo.CurrentCulture}, ConverterParameter={}{0:d}}" />
Shilling answered 25/8, 2011 at 21:8 Comment(0)
K
3

I use that code with proper results to my needs. Hope it could fills your :-) ! Perhaps you are better throwing an exception if cannot "TryParse". Up to you.

public sealed class CurrentCultureDoubleConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return ((double)value).ToString((string)parameter ?? "0.######", CultureInfo.CurrentCulture);
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        double result;
        if (Double.TryParse(value as string, NumberStyles.Number, CultureInfo.CurrentCulture, out result))
        {
            return result;
        }

        throw new FormatException("Unable to convert value:" + value);
        // return value;
    }
}

Usage:

<Window
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:simulatorUi="clr-namespace:SimulatorUi"
        xmlns:Converter="clr-namespace:HQ.Wpf.Util.Converter;assembly=WpfUtil" x:Class="SimulatorUi.DlgTest"
        Title="DlgTest" Height="300" Width="300">
    <Window.DataContext>
        <simulatorUi:DlgTestModel/>
    </Window.DataContext>

    <Window.Resources>
        <Converter:CurrentCultureDoubleConverter x:Key="CurrentCultureDoubleConverter"/>
    </Window.Resources>

    <Grid>
        <TextBox Text="{Binding DoubleVal, Converter={StaticResource CurrentCultureDoubleConverter}}"/>
    </Grid>
</Window>
Kaliski answered 21/4, 2015 at 20:48 Comment(0)
H
3

I came up with a hack/workaround that avoids updating all your bindings. Add this code to the constructor of your main window.

XmlLanguage language = XmlLanguage.GetLanguage("My-Language");
typeof(XmlLanguage).GetField("_compatibleCulture", BindingFlags.Instance | BindingFlags.NonPublic).SetValue(language, CultureInfo.CurrentCulture);
this.Language = language;

Since it's using reflection there is no guarantee that it will work in the future, but for now it does (.NET 4.6).

Hiddenite answered 26/11, 2015 at 15:23 Comment(0)
A
1

We can create a DateTime Converter using the IValueConverter

[ValueConversion(typeof(DateTime), typeof(String))]
    class DateTimeToLocalConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (!(value is DateTime)) return "Invalid DateTime";
            DateTime DateTime = (DateTime)value;
            return DateTime.ToLocalTime().ToShortDateString();

        }


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


    }

Apply this in the XAML as shown below

Binding="{Binding Path=createdDateTime,Converter={StaticResource DateTimeConverter}}"

Also change the current culture to get the desired format and the same needs to be applied on the application startup

/// <summary>
        /// Set Culture
        /// </summary>
        private void SetCulture() {
            var newCulture = new CultureInfo("en-IN");
            newCulture.DateTimeFormat.ShortDatePattern = "dd-MMM-yyyy";
            newCulture.DateTimeFormat.LongDatePattern = "dd-MMM-yyyy";
            newCulture.DateTimeFormat.FullDateTimePattern = "dd-MMM-yyyy";
            CultureInfo.DefaultThreadCurrentCulture = newCulture;
            CultureInfo.DefaultThreadCurrentUICulture = newCulture;
            Thread.CurrentThread.CurrentCulture = newCulture;
            Thread.CurrentThread.CurrentUICulture = newCulture;
            FrameworkElement.LanguageProperty.OverrideMetadata(typeof(FrameworkElement), new FrameworkPropertyMetadata(
                System.Windows.Markup.XmlLanguage.GetLanguage(CultureInfo.CurrentCulture.IetfLanguageTag)));
        }
Adiathermancy answered 10/1, 2019 at 5:55 Comment(0)
D
-1

How about changing the lanaguge in the code behind?

this.Language = XmlLanguage.GetLanguage(Thread.CurrentThread.CurrentCulture.Name);
Decontrol answered 19/7, 2012 at 9:28 Comment(3)
This method ignores the custom settings in "Region and Language" dialog and just uses the name to obtain language.Jerkin
Its the simplest solution and works fine for me too (Windows Phone 7). @Markus k: Sorry, didn't catch it what you're trying to say/explain?Deliciadelicious
@hfmobile What he meant was that if you select a language in regional settings (ex German Germany), and then manually modify any of the settings (ex Short Date change to d.M.yyyy), setting the language suggested above will not pick up the new format.Padnag

© 2022 - 2024 — McMap. All rights reserved.