Как привязать перечисление к элементу управления списком в WPF? - PullRequest
163 голосов
/ 27 мая 2011

Я пытаюсь найти простой пример, где перечисления отображаются как есть.Все примеры, которые я видел, пытаются добавить хорошо выглядящие строки отображения, но мне не нужна эта сложность.

В основном у меня есть класс, который содержит все свойства, которые я связываю, сначала устанавливая DataContext для этого класса,и затем указание привязки в файле xaml следующим образом:

<ComboBox ItemsSource="{Binding Path=EffectStyle}"/>

Но это не показывает значения перечисления в ComboBox как элементы.

Ответы [ 16 ]

1 голос
/ 18 июля 2018

Простое и понятное объяснение: http://brianlagunas.com/a-better-way-to-data-bind-enums-in-wpf/

xmlns:local="clr-namespace:BindingEnums"
xmlns:sys="clr-namespace:System;assembly=mscorlib"

...

<Window.Resources>
    <ObjectDataProvider x:Key="dataFromEnum" MethodName="GetValues"
                        ObjectType="{x:Type sys:Enum}">
        <ObjectDataProvider.MethodParameters>
            <x:Type TypeName="local:Status"/>
        </ObjectDataProvider.MethodParameters>
    </ObjectDataProvider>
</Window.Resources>

...

<Grid>
    <ComboBox HorizontalAlignment="Center" VerticalAlignment="Center" MinWidth="150"
              ItemsSource="{Binding Source={StaticResource dataFromEnum}}"/>
</Grid>
1 голос
/ 06 августа 2016

Мне понравился ответ tom.maruska , но мне нужно было поддерживать любой тип enum, с которым мой шаблон может столкнуться во время выполнения. Для этого мне пришлось использовать привязку, чтобы указать тип расширения разметки. Я смог работать в этом ответе от nicolay.anykienko, чтобы придумать очень гибкое расширение разметки, которое будет работать в любом случае, о котором я только могу подумать. Потребляется так:

<ComboBox SelectedValue="{Binding MyEnumProperty}" 
          SelectedValuePath="Value"
          ItemsSource="{local:EnumToObjectArray SourceEnum={Binding MyEnumProperty}}" 
          DisplayMemberPath="DisplayName" />

Источник для расширения разметки, на которое делаются помехи, упомянутый выше:

class EnumToObjectArray : MarkupExtension
{
    public BindingBase SourceEnum { get; set; }

    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        IProvideValueTarget target = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
        DependencyObject targetObject;
        DependencyProperty targetProperty;

        if (target != null && target.TargetObject is DependencyObject && target.TargetProperty is DependencyProperty)
        {
            targetObject = (DependencyObject)target.TargetObject;
            targetProperty = (DependencyProperty)target.TargetProperty;
        }
        else
        {
            return this;
        }

        BindingOperations.SetBinding(targetObject, EnumToObjectArray.SourceEnumBindingSinkProperty, SourceEnum);

        var type = targetObject.GetValue(SourceEnumBindingSinkProperty).GetType();

        if (type.BaseType != typeof(System.Enum)) return this;

        return Enum.GetValues(type)
            .Cast<Enum>()
            .Select(e => new { Value=e, Name = e.ToString(), DisplayName = Description(e) });
    }

    private static DependencyProperty SourceEnumBindingSinkProperty = DependencyProperty.RegisterAttached("SourceEnumBindingSink", typeof(Enum)
                       , typeof(EnumToObjectArray), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.Inherits));

    /// <summary>
    /// Extension method which returns the string specified in the Description attribute, if any.  Oherwise, name is returned.
    /// </summary>
    /// <param name="value">The enum value.</param>
    /// <returns></returns>
    public static string Description(Enum value)
    {
        var attrs = value.GetType().GetField(value.ToString()).GetCustomAttributes(typeof(DescriptionAttribute), false);
        if (attrs.Any())
            return (attrs.First() as DescriptionAttribute).Description;

        //Fallback
        return value.ToString().Replace("_", " ");
    }
}
1 голос
/ 18 августа 2013

Если вы привязываете к фактическому свойству enum в вашей ViewModel, а не к int представлению enum, все становится сложно. Я обнаружил, что необходимо привязать к строковому представлению, а НЕ к значению int, как ожидается во всех приведенных выше примерах.

Вы можете определить, так ли это, связав простое текстовое поле со свойством, с которым вы хотите связать вашу ViewModel. Если он показывает текст, привязать к строке. Если он показывает число, привязать к значению. Обратите внимание, что я дважды использовал Display, что обычно было бы ошибкой, но это единственный способ, которым он работает.

<ComboBox SelectedValue="{Binding ElementMap.EdiDataType, Mode=TwoWay}"
                      DisplayMemberPath="Display"
                      SelectedValuePath="Display"
                      ItemsSource="{Binding Source={core:EnumToItemsSource {x:Type edi:EdiDataType}}}" />

Грег

0 голосов
/ 22 ноября 2018

Решение Ника можно упростить еще больше, без излишеств, вам понадобится только один конвертер:

[ValueConversion(typeof(Enum), typeof(IEnumerable<Enum>))]
public class EnumToCollectionConverter : MarkupExtension, IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        var r = Enum.GetValues(value.GetType());
        return r;
    }
    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return null;
    }
    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        return this;
    }
}

Затем вы используете это там, где хотите, чтобы ваше поле со списком появилось:

<ComboBox ItemsSource="{Binding PagePosition, Converter={converter:EnumToCollectionConverter}, Mode=OneTime}"  SelectedItem="{Binding PagePosition}" />
0 голосов
/ 12 октября 2017

Используя ReactiveUI, я создал следующее альтернативное решение. Это не элегантное решение «все в одном», но, по крайней мере, оно читаемо.

В моем случае привязка списка enum к элементу управления является редким случаем, поэтому мне не нужно масштабировать решение по базе кода. Однако код можно сделать более общим, изменив EffectStyleLookup.Item на Object. Я проверил это с моим кодом, никаких других изменений не требуется. Это означает, что один вспомогательный класс может быть применен к любому списку enum. Хотя это уменьшило бы его читабельность - у ReactiveList<EnumLookupHelper> нет большого звонка.

Использование следующего вспомогательного класса:

public class EffectStyleLookup
{
    public EffectStyle Item { get; set; }
    public string Display { get; set; }
}

В ViewModel преобразуйте список перечислений и выставьте его как свойство:

public ViewModel : ReactiveObject
{
  private ReactiveList<EffectStyleLookup> _effectStyles;
  public ReactiveList<EffectStyleLookup> EffectStyles
  {
    get { return _effectStyles; }
    set { this.RaiseAndSetIfChanged(ref _effectStyles, value); }
  }

  // See below for more on this
  private EffectStyle _selectedEffectStyle;
  public EffectStyle SelectedEffectStyle
  {
    get { return _selectedEffectStyle; }
    set { this.RaiseAndSetIfChanged(ref _selectedEffectStyle, value); }
  }

  public ViewModel() 
  {
    // Convert a list of enums into a ReactiveList
    var list = (IList<EffectStyle>)Enum.GetValues(typeof(EffectStyle))
      .Select( x => new EffectStyleLookup() { 
        Item = x, 
        Display = x.ToString()
      });

    EffectStyles = new ReactiveList<EffectStyle>( list );
  }
}

В ComboBox используйте свойство SelectedValuePath, чтобы привязать к исходному значению enum:

<ComboBox Name="EffectStyle" DisplayMemberPath="Display" SelectedValuePath="Item" />

В представлении это позволяет нам привязать исходный enum к SelectedEffectStyle в ViewModel, но отобразить значение ToString() в ComboBox:

this.WhenActivated( d =>
{
  d( this.OneWayBind(ViewModel, vm => vm.EffectStyles, v => v.EffectStyle.ItemsSource) );
  d( this.Bind(ViewModel, vm => vm.SelectedEffectStyle, v => v.EffectStyle.SelectedValue) );
});
0 голосов
/ 29 октября 2016

Я добавляю свой комментарий (к сожалению, в VB, но концепция может быть легко воспроизведена в C # в одно мгновение), потому что мне просто нужно было сослаться на это, и мне не понравились какие-либо ответы, поскольку они были слишком сложный. Это не должно быть так сложно.

Так что я придумал более простой способ. Привязать перечислители к словарю. Свяжите этот словарь с Combobox.

Мой комбинированный список:

<ComboBox x:Name="cmbRole" VerticalAlignment="Stretch" IsEditable="False" Padding="2" 
    Margin="0" FontSize="11" HorizontalAlignment="Stretch" TabIndex="104" 
    SelectedValuePath="Key" DisplayMemberPath="Value" />

Мой код позади. Надеюсь, это поможет кому-то еще.

Dim tDict As New Dictionary(Of Integer, String)
Dim types = [Enum].GetValues(GetType(Helper.Enumerators.AllowedType))
For Each x As Helper.Enumerators.AllowedType In types
    Dim z = x.ToString()
    Dim y = CInt(x)
    tDict.Add(y, z)
Next

cmbRole.ClearValue(ItemsControl.ItemsSourceProperty)
cmbRole.ItemsSource = tDict
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...