1

Right now, to accomodate the use of commas as decimal placeholders in languages like Danish, I retrieve values stored with decimal points, e.g. ".123" from a .resx file like this:

// Getting a value from a .resx parameter
double minValue = Convert.ToDouble(AppParams.minVal, CultureInfo.InvariantCulture);

And when I need to work with a value received from a TextBox, e.g. ",321" I use this:

// Getting a value from a TextBox    
double newValue = Convert.ToDouble(value, CultureInfo.CurrentCulture);

In the .csproj file, I've added <SupportedCultures>da;</SupportedCultures> but otherwise haven't attempted some application-wide way of dealing with these two issues, other than what's shown.

Stonetip
  • 1,150
  • 10
  • 20

3 Answers3

4

You don't need to store the value as a string in resx file:

<data name="minVal" type="System.Double, mscorlib">
    <value>.123</value>
</data>

This way the generated minVal property will be of type double, and you won't have to convert it manually.

The only issue with this approach is that you have to edit the resx file manually in XML, since the resource designer can't handle resources of this type (actually you can rename or remove a resource or change its value, but you can't change its type and you can't create a new one). Anyway, I've switched to manual editing of resx file since I started using Resharper, because it provides some nice analysis and refactoring features for these files ;)

As a side note, I don't think this minValue constant is a good candidate for resources. If it's a setting that can be changed, put it in settings, not in resources. If it's really a constant, make it a const in C# code. The only good reason to put it in resources is if you want the value to be localizable, and it doesn't seem likely in this case.

Thomas Levesque
  • 286,951
  • 70
  • 623
  • 758
1

When you parse the string from the user input try to accept many possible inputs e.g.

public static class Helper
{
    public static bool TryParseDouble(this TextBox textbox, out double value)
    {
        if (double.TryParse(textbox.Text, NumberStyles.Any, CultureInfo.InvariantCulture, out value))
        {
            textbox.Foreground = Brushes.Black; //indicates that the user typed correct number
            return true;
        }
        else
        {
            textbox.Foreground = Brushes.Red; // not a number
            return false;
        }
    }
}

When you parse .resx, XML and other files use also InvariantCulture. Here is a problem that I've encountered with XML parser.

When showing data to the user use the current culture.

Community
  • 1
  • 1
Lukasz Madon
  • 14,664
  • 14
  • 64
  • 108
0

I much appreciate the answers from Thomas Levesque and lukas. They contained some useful insight and examples. I'm posting this as an answer because I want to provide more info and an example solution. As with many computing/UI problems, compromises often have to be made. I made the unfortunate discovery that none of the InputScopeNameValues (MSDN InputScopeNameValue Enumeration) switch between decimal (.) and comma (,) when changing region+language settings (and, yes, I double-checked that the keyboard was set on my phone to only use Deutsch).

enter image description here

However, because these TextBox inputs are numeric and need to entered quickly, the numeric InputScopes are still the best way to go. Interestingly, even if the user is forced to use the decimal point in place of a comma, as soon as it's entered in the TextBox the string format changes, e.g. from ".123" to ",123" even though as shown"{0:#.000}". Thus the compromise and in the code below, the workaround (tested so far in en-US and de-DE).

Note: As lukas mentioned, it is always wise to validate user input. I'm not using TryParse here (although I may) so I don't have to rewrite a lot of code. This is mitigated in the UI by choosing a numeric InputScope and in the handling code via try/catch blocks, which even handle correctly a user attempting to bypass the numeric input by pasting text from the clipboard:

<TextBox x:Name="myTBox" InputScope="Number" Text="{Binding SomeNumber, Mode=TwoWay}" />

And code:

public string SomeNumber

{ get { return String.Format("{0:#.000}", SomeProfileModel.Instance.SomeProfile.SomeNumber); }

set
{
    if (SomeProfileModel.Instance.SomeProfile.SomeNumber.ToString() == value) return;

    var oldValue = SomeProfileModel.Instance.SomeProfile.SomeNumber;

    try
    {
        double newValue;

        try
        {
            newValue = Convert.ToDouble(value, CultureInfo.CurrentCulture);
        }
        catch (Exception)
        {
            newValue = Convert.ToDouble(value, CultureInfo.InvariantCulture);
        }

        if (Convert.ToDouble(MyAppParams.SomeNumberMin, CultureInfo.InvariantCulture) > newValue || Convert.ToDouble(MyAppParams.SomeNumberMax, CultureInfo.InvariantCulture) < newValue)
        {
            // Revert back to previous value
            // NOTE: This has to be done here. If done in the catch statement, 
            // it will never run since the MessageBox interferes with it.
            throw new Exception();
        }

        SomeProfileModel.Instance.SomeProfile.SomeNumber = newValue;

        RaisePropertyChanged("SomeNumber", oldValue, newValue, true);
    }
    catch (Exception err)
    {
        System.Windows.MessageBox.Show("Value must be a number between " + MyAppParams.SomeNumberMin + " and " + MyAppParams.SomeNumberMax);
    }
}

}

Stonetip
  • 1,150
  • 10
  • 20