Podoba mi się, że wszystkie obiekty, które wiążę, są zdefiniowane w moim ViewModel
, więc staram się unikać używania <ObjectDataProvider>
w xaml, jeśli to możliwe.
Moje rozwiązanie nie wykorzystuje danych zdefiniowanych w widoku i nie zawiera kodu. Tylko DataBinding, ValueConverter wielokrotnego użytku, metoda uzyskiwania kolekcji opisów dla dowolnego typu Enum oraz pojedyncza właściwość w ViewModel do powiązania.
Gdy chcę związać Enum
z ComboBox
tekstem chcę wyświetlić nigdy odpowiada wartości Enum
, więc używam [Description()]
atrybut dać to tekst, który faktycznie chcę widzieć w ComboBox
. Gdybym miał wyliczenie dni tygodnia, wyglądałoby to tak:
public enum DayOfWeek
{
// add an optional blank value for default/no selection
[Description("")]
NOT_SET = 0,
[Description("Sunday")]
SUNDAY,
[Description("Monday")]
MONDAY,
...
}
Najpierw stworzyłem klasę pomocnika z kilkoma metodami radzenia sobie z wyliczeniami. Jedna metoda otrzymuje opis konkretnej wartości, druga metoda pobiera wszystkie wartości i ich opisy dla typu.
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();
}
}
Następnie tworzymy ValueConverter
. Dziedziczenie po MarkupExtension
ułatwia korzystanie z XAML, więc nie musimy deklarować go jako zasobu.
[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;
}
}
My ViewModel
tylko potrzebuje 1 właściwość, że mój View
może wiązać się zarówno dla SelectedValue
i ItemsSource
od combobox:
private DayOfWeek dayOfWeek;
public DayOfWeek SelectedDay
{
get { return dayOfWeek; }
set
{
if (dayOfWeek != value)
{
dayOfWeek = value;
OnPropertyChanged(nameof(SelectedDay));
}
}
}
I wreszcie, aby związać ComboBox
widok (używając ValueConverter
w ItemsSource
wiążący) ...
<ComboBox ItemsSource="{Binding Path=SelectedDay, Converter={x:EnumToCollectionConverter}, Mode=OneTime}"
SelectedValuePath="Value"
DisplayMemberPath="Description"
SelectedValue="{Binding Path=SelectedDay}" />
Aby wdrożyć to rozwiązanie, wystarczy skopiować moją EnumHelper
klasę i EnumToCollectionConverter
klasę. Będą pracować z dowolnymi wyliczeniami. Ponadto nie uwzględniłem go tutaj, ale ValueDescription
klasa jest tylko prostą klasą z 2 właściwościami obiektów publicznych, jedną o nazwie Value
, jedną o nazwie Description
. Możesz to zrobić samodzielnie lub zmienić kod, aby użyć Tuple<object, object>
lubKeyValuePair<object, object>