2013-02-22 17 views
5

Mam następujący scenariusz: Mam wyliczenie i chcę go powiązać, pokazać go w DataGridView (databound) w DataGridViewTextBoxColumn.Wiązanie TypeConvertera bezpośrednio z wyliczeniem

Oto mój enum:

//[TypeConverter(typeof(EnumStringConverter))] 
    public enum YesNoNA 
    { 
     [EnumDescription("Yes")] 
     Yes, 
     [EnumDescription("No")] 
     No, 
     [EnumDescription("N/A")] 
     NA 
    } 

A oto prosty właściwość, która używa go:

[TypeConverter(typeof(EnumStringConverter))] 
    public YesNoNA HighLimitWithinBounds { get; protected set; } 

W sytuacji mam powyżej, TypeConverter działa dobrze. Dokonuje konwersji dla mnie.

Jest to jednak bardziej złożone niż moje idealne rozwiązanie. Jeśli umieściłbym typokonfigurator w samym Enum (odkomentowałem powyższy kod) i skomentuję typ-konwerter w obiekcie, to typononwerter nie jest już wywoływany!

Zrobiłem tę konwencję na innych lekcjach i działa dobrze.

Dlaczego umieszczanie typokontera bezpośrednio na wyliczeniu nie działa?

Dla porównania, oto moja TypeConverter:

public class EnumStringConverter : TypeConverter 
    { 
     public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, Object value, Type destinationType) 
     { 
     if (value != null && destinationType == typeof(string)) 
     { 
      return "Edited to protect the innocent!"; 
     } 
     return TypeDescriptor.GetConverter(typeof(Enum)).ConvertTo(context, culture, value, destinationType); 
     } 
     public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) 
     { 
     if (destinationType == typeof(string)) 
     { 
      return true; 
     } 
     return base.CanConvertTo(context, destinationType); 
     } 
    }; 
+1

Być może powinieneś spróbować tej metody: http://pl.wikipedia.org/wiki/Rubber_duck_debugging – espais

+0

możliwy duplikat [Jak zastąpić ToString w C# enums?] (Http://stackoverflow.com/questions/796607/how-do-i-override-tostring-in-c-sharp-enums) –

+0

Defaintely nie związane. Rozwiązanie odpowiedzi w tym wątku zostało już zaimplementowane powyżej. To pytanie zajmuje znacznie więcej. – greggorob64

Odpowiedz

2

Nie jestem pewien co masz na myśli mówiąc „to jest bardziej skomplikowane niż moim idealnym rozwiązaniem”. Mam sposób robienia tego, co jest inne niż twoje, ale może nie być mniej skomplikowane. Powiedziałbym, że mój sposób wiąże się z większym napowietrzeniem, ale opłaca się, im więcej go używasz w swojej aplikacji. Przygotowuje aplikację do lokalizacji i nie musisz dodawać atrybutów do każdej wartości wyliczeniowej.

1) Dokonaj Resource Manager Cache

Ta część jest opcjonalna; Jednak jeśli użyjesz wielu plików zasobów wielokrotnie, tak jak ja, może to zwiększyć wydajność, zmniejszając ilość refleksów.

using System; 
using System.Collections.Generic; 
using System.Reflection; 
using System.Resources; 

namespace AppResourceLib.Public.Reflection 
{ 
    internal static class ResourceManagerCache 
    { 
    private static Dictionary<Type, ResourceManager> _resourceManagerMap = 
     new Dictionary<Type, ResourceManager>(); 

    public static ResourceManager GetResourceManager(Type resourceType) 
    { 
     ResourceManager resourceManager = null; 

     // Make sure the type is valid. 
     if (null != resourceType) 
     { 
     // Try getting the cached resource manager. 
     if (!ResourceManagerCache._resourceManagerMap.TryGetValue(resourceType, out resourceManager)) 
     { 
      // If it is not in the cache create it. 
      resourceManager = resourceType.InvokeMember(
      "ResourceManager", 
      (BindingFlags.GetProperty | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic), 
      null,             
      null,             
      null) as ResourceManager; 

      // If it was created, add the resource manager to the cache. 
      if (null != resourceManager) 
      { 
      ResourceManagerCache._resourceManagerMap.Add(resourceType, resourceManager); 
      } 
     } 
     } 

     return resourceManager; 
    } 
    } 
} 

2) Utwórz zlokalizowane Opis Atrybut

Jest to atrybut, który można zastosować do typu Enum. Najfajniejsze jest to, że nie musisz dodawać atrybutu do każdego wyliczenia, tylko raz powyżej deklaracji typu wyliczeniowego.

using System; 
using System.ComponentModel; 

namespace AppResourceLib.Public.Reflection 
{ 
    /// <summary> 
    /// A resource type attribute that can be applied to enumerations. 
    /// </summary> 
    [AttributeUsage(AttributeTargets.Enum)] 
    public sealed class LocalizedDescriptionAttribute : Attribute 
    { 
    /// <summary> 
    /// The type of resource associated with the enum type. 
    /// </summary> 
    private Type _resourceType; 

    public LocalizedDescriptionAttribute(Type resourceType) 
    { 
     this._resourceType = resourceType; 
    } 

    /// <summary> 
    /// The type of resource associated with the enum type. 
    /// </summary> 
    public Type ResourceType 
    { 
     get 
     { 
     return this._resourceType; 
     } 
    } 
    } 
} 

3) Lokalizuj Opis Konwerter

ten konwertuje wartość enum do łańcucha dasz go w ciągi zasobu (.resx) plików.

using System; 
using System.Globalization; 
using System.Linq; 
using System.Reflection; 
using System.Resources; 
using System.Windows.Data; 

namespace AppResourceLib.Public.Reflection 
{ 
    [ValueConversion(typeof(Object), typeof(String))] 
    public class LocalizedDescriptionConverter : IValueConverter 
    { 
    public Object Convert(Object value, Type targetType, Object param, CultureInfo cultureInfo) 
    { 
     String description = null; 

     if (null != value) 
     { 
     // If everything fails then at least return the value.ToString(). 
     description = value.ToString(); 

     // Get the LocalizedDescriptionAttribute of the object. 
     LocalizedDescriptionAttribute attribute = 
      value.GetType().GetCustomAttribute(typeof(LocalizedDescriptionAttribute)) 
      as LocalizedDescriptionAttribute; 

     // Make sure we found a LocalizedDescriptionAttribute. 
     if (null != attribute) 
     {   
      ResourceManager resourceManager = 
      ResourceManagerCache.GetResourceManager(attribute.ResourceType); 

      if (null != resourceManager) 
      { 
      // Use the ResourceManager to get the description you gave the object value. 
      // Here we just use the object value.ToString() (the name of the object) to get 
      // the string in the .resx file. The only constraint here is that you have to 
      // name your object description strings in the .resx file the same as your objects. 
      // The benefit is that you only have to declare the LocalizedDescriptionAttribute 
      // above the object type, not an attribute over every object. 
      // And this way is localizable. 
      description = resourceManager.GetString(value.ToString(), cultureInfo); 

      String formatString = (param as String); 

      // If a format string was passed in as a parameter, 
      // make a string out of that. 
      if (!String.IsNullOrEmpty(formatString)) 
      { 
       formatString = formatString.Replace("\\t", "\t"); 
       formatString = formatString.Replace("\\n", "\n"); 
       formatString = formatString.Replace("\\r", "\r"); 

       description = String.Format(formatString, value.ToString(), description);    
      }   
      } 
     } 
     } 

     return description;  
    } 

    public Object ConvertBack(Object value, Type targetType, Object param, CultureInfo cultureInfo) 
    { 
     throw new NotImplementedException(); 

     return null; 
     } 
    } 
} 

4) Tworzenie zasobu (.resx) String File

Teraz chcesz utworzyć plik zasobów, który będzie zawierał opisy chcesz dla swojego stylu wartość klucza wyliczenia. Rozumiem przez to, że w kolumnie "Nazwa" zasobów ciągów wpisujesz dokładną nazwę poszczególnych wyliczeń, aw kolumnie "Wartość" wstawiasz ciąg znaków, który chcesz uzyskać, konwertując to wyliczenie.
Załóżmy na przykład, że masz następujące wyliczenia.

public enum MyColors 
{ 
    Black, 
    Blue, 
    White 
} 

Następnie plik zasobów ciąg wyglądałby następująco ...

Nazwa | Wartość

Czarna | Ciemny kolor
Niebieski | Chłodny kolor
Biały | Jasny kolor

5) Tworzenie wyliczenia z atrybutów

Teraz wreszcie złożyć oświadczenie Enum z LocalizedDescription. Parametrem przekazywanym do atrybutu LocalizedDescription jest typ pliku zasobów ciągów. Teraz, gdy używany jest konwerter, zobaczy on atrybut typu wyliczeniowego, pobierze plik zasobu, poszukaj ciągu znaków kluczowego, który pasuje do wartości ciągu określonej wartości wyliczenia, i zwróci wartość z pliku zasobów jako skonwertowany ciąg znaków.

using AppResourceLib.Public; 
using AppResourceLib.Public.Reflection; 

namespace MyEnums 
{ 
    [LocalizedDescription(typeof(MyColorStrings))] 
    public enum MyColors 
    { 
    Black, 
    Blue, 
    White 
    } 
} 

Główną wadą tego podejścia jest to, że to będzie działać tylko wtedy, gdy „nazwa” klucze w pliku zasobów dopasować nazwy swoich wartości enum. Jest to jedyny sposób na odniesienie wartości ciągu do pliku zasobu bez podawania enum atrybutów opisu. Więc w jaki sposób używasz tego do wyświetlania wartości? Oto przykład ...

W swoim kodzie XAML będziesz chciał zrobić dostawcę danych, aby uzyskać wartości wyliczeń do elementu interfejsu użytkownika (używam tutaj ComboBox ...). Następnie musisz udostępnić konwerter i zaprojektować swój element interfejsu do używania konwertera wyliczeniowego. Więc to idzie ...

 <!-- Enum Colors --> 
    <ObjectDataProvider x:Key="MyColorEnums" 
         MethodName="GetValues" 
         ObjectType="{x:Type sys:Enum}"> 
    <ObjectDataProvider.MethodParameters> 
     <x:Type TypeName="MyColors"/> 
    </ObjectDataProvider.MethodParameters> 
    </ObjectDataProvider> 


    <!-- Enum Type Converter --> 
    <LocalizedDescriptionConverter x:Key="EnumConverter"/> 


    <!-- Dropdown Expand ComboBox Template --> 
    <DataTemplate x:Key="MyColorsComboBoxTemplate"> 
    <Label Content="{Binding Path=., Mode=OneWay, 
     Converter={StaticResource EnumConverter}}" 
      Height="Auto" Margin="0" VerticalAlignment="Center"/> 
    </DataTemplate> 

    <!-- And finally the ComboBox that will display all of your enum values 
    but will use the strings from the resource file instead of enum.ToString() --> 
    <ComboBox Width="80" HorizontalAlignment="Left" 
    ItemTemplate="{StaticResource MyColorsComboBoxTemplate}" 
    ItemsSource="{Binding Source={StaticResource MyColorEnums}}"> 

Wow, przepraszam, to jest tak długo. Nie jestem pewien, czy jest to dla ciebie zbyt skomplikowane, ale jest to inna opcja. Mam nadzieję, że to pomoże!

+1

Mój przykład jest bardzo podobny do twojego (idziesz o krok dalej, używając pliku zasobów, którego używam również w innych miejscach, aby uzyskać wsparcie w wielu językach). Różnica w złożoności. Podkreślam, że mój przykład wymaga typ-konwertera. * W obiekcie * zamiast w samym enum. Niestety, nie sądzę, że nasze przykłady będą pasować. Używasz xaml, a ja używam czystych formalizacji .net. Bierzesz swój combobox. Twój xaml jest specyficznie wiążący combobox do konwertera, którego potrzebujesz, czego właśnie staram się uniknąć (nie sądzę, że się uda). Dzięki za odpowiedź! – greggorob64

+0

Och, rozumiem, co masz na myśli. Przepraszam, nie mogłem pomóc. – akagixxer