Ich mag es, wenn alle Objekte, die ich binde, in meinem definiert werden ViewModel
, deshalb versuche ich, die Verwendung zu vermeiden<ObjectDataProvider>
binde, in der xaml nach Möglichkeit .
Meine Lösung verwendet keine in der Ansicht definierten Daten und keinen Code-Behind. Nur eine DataBinding, ein wiederverwendbarer ValueConverter, eine Methode zum Abrufen einer Sammlung von Beschreibungen für einen beliebigen Enum-Typ und eine einzelne Eigenschaft im ViewModel, an die gebunden werden soll.
Wenn ich einen Enum
an ComboBox
den Text binden möchte, den ich anzeigen möchte, stimmt er nie mit den Werten von überein. Daher verwende Enum
ich das [Description()]
Attribut, um ihm den Text zu geben, den ich tatsächlich im Text sehen möchte ComboBox
. Wenn ich eine Anzahl von Wochentagen hätte, würde es ungefähr so aussehen:
public enum DayOfWeek
{
// add an optional blank value for default/no selection
[Description("")]
NOT_SET = 0,
[Description("Sunday")]
SUNDAY,
[Description("Monday")]
MONDAY,
...
}
Zuerst habe ich eine Hilfsklasse mit ein paar Methoden erstellt, um mit Aufzählungen umzugehen. Eine Methode erhält eine Beschreibung für einen bestimmten Wert, die andere Methode erhält alle Werte und ihre Beschreibungen für einen Typ.
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();
}
}
Als nächstes erstellen wir eine ValueConverter
. Das Erben von MarkupExtension
erleichtert die Verwendung in XAML, sodass wir es nicht als Ressource deklarieren müssen.
[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;
}
}
Ich ViewModel
benötige nur 1 Eigenschaft, an die ich View
sowohl für die SelectedValue
als auch ItemsSource
für die Combobox binden kann :
private DayOfWeek dayOfWeek;
public DayOfWeek SelectedDay
{
get { return dayOfWeek; }
set
{
if (dayOfWeek != value)
{
dayOfWeek = value;
OnPropertyChanged(nameof(SelectedDay));
}
}
}
Und zum Schluss die ComboBox
Ansicht binden (mit der ValueConverter
in der ItemsSource
Bindung) ...
<ComboBox ItemsSource="{Binding Path=SelectedDay, Converter={x:EnumToCollectionConverter}, Mode=OneTime}"
SelectedValuePath="Value"
DisplayMemberPath="Description"
SelectedValue="{Binding Path=SelectedDay}" />
Um diese Lösung zu implementieren, müssen Sie nur meine EnumHelper
Klasse und EnumToCollectionConverter
Klasse kopieren . Sie werden mit allen Aufzählungen arbeiten. Ich habe es hier auch nicht aufgenommen, aber die ValueDescription
Klasse ist nur eine einfache Klasse mit 2 öffentlichen Objekteigenschaften, eine aufgerufene Value
, eine aufgerufene Description
. Sie können das selbst erstellen oder den Code ändern, um ein Tuple<object, object>
oder zu verwendenKeyValuePair<object, object>