Mi piace che tutti gli oggetti che rilevo siano definiti nel mio ViewModel
, quindi cerco di evitare di usarlo <ObjectDataProvider>
in xaml quando possibile.
La mia soluzione non utilizza alcun dato definito nella vista e nessun code-behind. Solo un DataBinding, un ValueConverter riutilizzabile, un metodo per ottenere una raccolta di descrizioni per qualsiasi tipo di Enum e una singola proprietà nel ViewModel a cui associarsi.
Quando voglio associare un Enum
a un ComboBox
testo che voglio visualizzare non corrisponde mai ai valori di Enum
, quindi uso l' [Description()]
attributo per dargli il testo che in realtà voglio vedere in ComboBox
. Se avessi un enum di giorni della settimana, sarebbe simile a questo:
public enum DayOfWeek
{
// add an optional blank value for default/no selection
[Description("")]
NOT_SET = 0,
[Description("Sunday")]
SUNDAY,
[Description("Monday")]
MONDAY,
...
}
Per prima cosa ho creato una classe di supporto con un paio di metodi per gestire gli enum. Un metodo ottiene una descrizione per un valore specifico, l'altro metodo ottiene tutti i valori e le relative descrizioni per un tipo.
public static class EnumHelper
{
public static string Description(this Enum value)
{
var attributes = value.GetType().GetField(value.ToString()).GetCustomAttributes(typeof(DescriptionAttribute), false);
if (attributes.Any())
return (attributes.First() as DescriptionAttribute).Description;
// If no description is found, the least we can do is replace underscores with spaces
// You can add your own custom default formatting logic here
TextInfo ti = CultureInfo.CurrentCulture.TextInfo;
return ti.ToTitleCase(ti.ToLower(value.ToString().Replace("_", " ")));
}
public static IEnumerable<ValueDescription> GetAllValuesAndDescriptions(Type t)
{
if (!t.IsEnum)
throw new ArgumentException($"{nameof(t)} must be an enum type");
return Enum.GetValues(t).Cast<Enum>().Select((e) => new ValueDescription() { Value = e, Description = e.Description() }).ToList();
}
}
Successivamente, creiamo un ValueConverter
. Ereditando daMarkupExtension
semplifica l'utilizzo in XAML, quindi non è necessario dichiararlo come risorsa.
[ValueConversion(typeof(Enum), typeof(IEnumerable<ValueDescription>))]
public class EnumToCollectionConverter : MarkupExtension, IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return EnumHelper.GetAllValuesAndDescriptions(value.GetType());
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return null;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
}
La mia ViewModel
ha bisogno di solo 1 struttura che il mio View
può legarsi al sia per l' SelectedValue
e ItemsSource
del combobox:
private DayOfWeek dayOfWeek;
public DayOfWeek SelectedDay
{
get { return dayOfWeek; }
set
{
if (dayOfWeek != value)
{
dayOfWeek = value;
OnPropertyChanged(nameof(SelectedDay));
}
}
}
E infine per associare la ComboBox
vista (usando il ValueConverter
nell'associazione ItemsSource
) ...
<ComboBox ItemsSource="{Binding Path=SelectedDay, Converter={x:EnumToCollectionConverter}, Mode=OneTime}"
SelectedValuePath="Value"
DisplayMemberPath="Description"
SelectedValue="{Binding Path=SelectedDay}" />
Per implementare questa soluzione devi solo copiare la mia EnumHelper
classe e EnumToCollectionConverter
classe. Lavoreranno con qualsiasi enumerazione. Inoltre, non l'ho incluso qui, ma la ValueDescription
classe è solo una semplice classe con 2 proprietà di oggetto pubblico, una chiamata Value
, una chiamata Description
. Puoi crearlo tu stesso o puoi cambiare il codice per usare un Tuple<object, object>
oKeyValuePair<object, object>