2013-11-25 17 views
25

Mam dość nieprecyzyjne wymagania formatowania dla wartości decimal. W skrócie: wyświetlaj do dwóch miejsc po przecinku z końcowym odstępem, chyba że trzeci dziesiętny to 5, w którym to przypadku wyświetla się do trzech miejsc po przecinku.Poszerzanie niestandardowych możliwości formatowania wbudowanych typów

To formatowanie musi być dość elastyczne. W szczególności tylna przestrzeń nie zawsze będzie pożądana, a "½" może być preferowane, gdy trzeci dziesiętny jest "5".

Przykłady:

  • 1,13 będzie wyświetlany jako "01,13" ze spacją lub "01,13" bez niego
  • 1,315 będzie wyświetlany jako "01.315" lub "01.31½"

Muszę używać tej logiki w spójny sposób w innych niezwiązanych z nią elementach interfejsu użytkownika. Mam chwilowo napisane go jako konwerter wartości WPF, ale to tylko na pokaz:

public sealed class PriceConverter : IValueConverter 
{ 
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 
    { 
     if (!(value is decimal)) 
     { 
      return DependencyProperty.UnsetValue; 
     } 

     var decimalValue = (decimal)value; 
     var formattedDecimalValue = decimalValue.ToString("#0.000", CultureInfo.InvariantCulture); 
     var lastFormattedChar = formattedDecimalValue[formattedDecimalValue.Length - 1]; 

     switch (lastFormattedChar) 
     { 
      case '0': 
       return formattedDecimalValue.Substring(0, formattedDecimalValue.Length - 1) + " "; 
      case '5': 
       return formattedDecimalValue.Substring(0, formattedDecimalValue.Length - 1) + "½"; 
      default: 
       return formattedDecimalValue; 
     } 
    } 

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

Jestem teraz próbuje wydobyć to w bardziej fundamentalnym bloku budowlanego można używać przez całe warstwy UI. Moją pierwszą myślą było dostawcą formatu niestandardowego, które mogę potem używać z Binding:

<TextBlock Text="{Binding Value, FormatString=WHATEVER}"/> 

Chodzi o to, że ciąg formatu może być coś takiego jak „# 0,005”, która wskazuje tylko pokazać trzeciego miejsca po przecinku, jeśli jest to 5 lub "# 0.00F", który próbuje reprezentować trzeci dziesiętny jako ułamek. Jednak nie byłem w stanie znaleźć sposobu na użycie określonego dostawcy formatu z powiązania, co wydaje mi się poważnym ograniczeniem, ale może brakuje mi czegoś ...?

Po dalszych eksperymentów i dochodzenia, doszedłem do wniosku, że moją jedyną opcją jest, aby zdefiniować własny typ:

public struct Price : IFormattable 

Ten typ będzie hermetyzacji dodatkowe możliwości formatowania wymagam. Jednak teraz mam kolejną zagadkę: w mojej implementacji ToString, jak mogę wykorzystać istniejące możliwości formatowania decimal.ToString(string, IFormatProvider) bez ingerencji w moje własne? Wygląda na to, że byłoby to dość cholernie brudne i powoduje to, że skłaniam się ku bardziej ograniczonemu rozwiązaniu po prostu definiując "G" (dwa lub trzy miejsca po przecinku, bez spacji) i "S" (to samo co "G", ale z końcową przestrzenią jeśli to konieczne) dla mojej struktury Price.

Czy ktoś może mi powiedzieć, czy istnieje sposób dla mnie, aby zrobić tego rodzaju niestandardowe możliwości formatowania bez zbytniego wysiłku?

+0

Czy jakiś unikalny prefiks do formatowania pracy ciąg do odróżnić niestandardowe i domyślne formatowanie? Podobnie jak "& # 0,00" dla niestandardowego i "# 0,00" dla domyślnego (jeśli pierwszy znak specjalny - użyj niestandardowego ...) –

+0

@Alexei: działałoby to tylko wtedy, gdybym chciał przejąć pełną własność kodu formatującego, ale wolałbym przekazać to wbudowane formatowanie, a następnie uzupełnić moje własne bity. W przeciwnym razie musiałbym ponownie wdrożyć strasznie dużo wbudowanych funkcji. –

+0

spróbuj tego http://stackoverflow.com/questions/11438089/how-to-delegate-to-default-format-provider – amald

Odpowiedz

1

Aby uzyskać więcej informacji, patrz http://msdn.microsoft.com/en-us/library/system.iformatprovider.aspx.

// "01.13 " or "01.13". Standard formatting applied: $123.45 
// "01.315" or "01.31½". Standard formatting applied: $123.45 

public class Test 
{ 
    void Main() 
    { 
     decimal number1 = 1.13M; 
     decimal number2 = 1.315M; 

     string output1 = String.Format(new CustomNumberFormat(), 
           "\"{0:G}\" or \"{0:S}\". Standard formatting applied: {1:C2}", 
           number1, 123.45); 
     Console.WriteLine(output1); 

     string output2 = String.Format(new CustomNumberFormat(), 
           "\"{0:G}\" or \"{0:S}\". Standard formatting applied: {1:C2}", 
           number2, 123.45); 
     Console.WriteLine(output2); 
    } 
} 

public class CustomNumberFormat : System.IFormatProvider, System.ICustomFormatter 
{ 
    public object GetFormat(Type formatType) 
    { 
     if (formatType == typeof(ICustomFormatter)) 
      return this; 
     else 
      return null; 
    } 

    public string Format(string fmt, object arg, System.IFormatProvider formatProvider) 
    { 
     // Provide default formatting if arg is not a decimal. 
     if (arg.GetType() != typeof(decimal)) 
      try 
      { 
       return HandleOtherFormats(fmt, arg); 
      } 
      catch (FormatException e) 
      { 
       throw new FormatException(String.Format("The format of '{0}' is invalid.", fmt), e); 
      } 

     // Provide default formatting for unsupported format strings. 
     string ufmt = fmt.ToUpper(System.Globalization.CultureInfo.InvariantCulture); 
     if (!(ufmt == "G" || ufmt == "S")) 
      try 
      { 
       return HandleOtherFormats(fmt, arg); 
      } 
      catch (FormatException e) 
      { 
       throw new FormatException(String.Format("The format of '{0}' is invalid.", fmt), e); 
      } 

     // Convert argument to a string. 
     string result = ((decimal)arg).ToString("0#.000"); 

     if (ufmt == "G") 
     { 
      var lastFormattedChar = result[result.Length - 1]; 
      switch (lastFormattedChar) 
      { 
       case '0': 
        result = result.Substring(0, result.Length - 1) + " "; 
        break; 
      } 

      return result; 
     } 
     else if (ufmt == "S") 
     { 
      var lastFormattedChar = result[result.Length - 1]; 
      switch (lastFormattedChar) 
      { 
       case '0': 
        result = result.Substring(0, result.Length - 1); 
        break; 
       case '5': 
        result = result.Substring(0, result.Length - 1) + "½"; 
        break; 
      } 

      return result; 
     } 
     else 
     { 
      return result; 
     } 
    } 

    private string HandleOtherFormats(string format, object arg) 
    { 
     if (arg is System.IFormattable) 
      return ((System.IFormattable)arg).ToString(format, System.Globalization.CultureInfo.CurrentCulture); 
     else if (arg != null) 
      return arg.ToString(); 
     else 
      return String.Empty; 
    } 
} 
+0

Dzięki, ale myślę, że przegapiłeś punkt mojego pytania :) Mój pomysł na temat "G"/'" S "' było jak plan awaryjny - taki, który wziąłem z dużo prostszą implementacją niż sugerowałeś ("IFormattable" było kluczowe i działało z wiązaniami, podczas gdy twoja sugestia nie będzie bez konwerterów). Tak naprawdę chciałem obsługiwać niestandardowe formatowanie, a nie standardowe formatowanie. Zobacz przykłady wcześniej w moim pytaniu, takie jak '# 0.005" '. Kiedy próbujesz uzupełnić swoje własne, niestandardowe formatowanie, musisz zreimplementować wiele rzeczy, które wolałbyś przekazać. –

0

Spróbuj przekazując dostawcy formacie jak parameter argumentu w implementacji IValueConverter.Convert:

<TextBlock Text="{Binding Value, Mode=OneWay, Converter={StaticResource PriceConverter}, ConverterParameter=#0.00F"/> 

Następnie wewnątrz konwertera:

public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 
{ 
    string formatString = parameter as string; 

    if(formatString != null) 
    { 
     // Your code here 
    } 
    else 
    { 
     // Whatever you want to do here 
    } 

} 
Powiązane problemy