WPF의 ComboBox에 열거 형 속성 데이터 바인딩


256

예를 들어 다음 코드를 사용하십시오.

public enum ExampleEnum { FooBar, BarFoo }

public class ExampleClass : INotifyPropertyChanged
{
    private ExampleEnum example;

    public ExampleEnum ExampleProperty 
    { get { return example; } { /* set and notify */; } }
}

ExampleProperty 속성을 ComboBox에 데이터 바인딩하여 "FooBar"및 "BarFoo"옵션을 표시하고 TwoWay 모드에서 작동하도록하고 싶습니다. 최적의 ComboBox 정의는 다음과 같습니다.

<ComboBox ItemsSource="What goes here?" SelectedItem="{Binding Path=ExampleProperty}" />

현재 바인딩을 수동으로 수행하는 ComboBox.SelectionChanged 및 ExampleClass.PropertyChanged 이벤트에 대한 처리기가 내 창에 설치되어 있습니다.

더 나은 또는 어떤 종류의 정식 방법이 있습니까? 일반적으로 변환기를 사용하고 ComboBox를 올바른 값으로 채우는 방법은 무엇입니까? 나는 지금 i18n을 시작하고 싶지 않습니다.

편집하다

그래서 하나의 질문에 대답했습니다 .ComboBox를 올바른 값으로 채우는 방법.

정적 Enum.GetValues ​​메소드에서 ObjectDataProvider를 통해 문자열 값으로 Enum 값을 검색하십시오.

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

이것은 내 ComboBox의 ItemsSource로 사용할 수 있습니다.

<ComboBox ItemsSource="{Binding Source={StaticResource ExampleEnumValues}}"/>

4
나는 이것을 탐구하고 여기에 있는 WPF에서 사용할 수있는 솔루션을 가지고 있습니다 (현지화 완료) .
ageektrapped

답변:


208

맞춤 태그 확장을 만들 수 있습니다.

사용 예 :

enum Status
{
    [Description("Available.")]
    Available,
    [Description("Not here right now.")]
    Away,
    [Description("I don't have time right now.")]
    Busy
}

XAML 상단에서 :

    xmlns:my="clr-namespace:namespace_to_enumeration_extension_class

그리고...

<ComboBox 
    ItemsSource="{Binding Source={my:Enumeration {x:Type my:Status}}}" 
    DisplayMemberPath="Description" 
    SelectedValue="{Binding CurrentStatus}"  
    SelectedValuePath="Value"  /> 

그리고 구현은 ...

public class EnumerationExtension : MarkupExtension
  {
    private Type _enumType;


    public EnumerationExtension(Type enumType)
    {
      if (enumType == null)
        throw new ArgumentNullException("enumType");

      EnumType = enumType;
    }

    public Type EnumType
    {
      get { return _enumType; }
      private set
      {
        if (_enumType == value)
          return;

        var enumType = Nullable.GetUnderlyingType(value) ?? value;

        if (enumType.IsEnum == false)
          throw new ArgumentException("Type must be an Enum.");

        _enumType = value;
      }
    }

    public override object ProvideValue(IServiceProvider serviceProvider)
    {
      var enumValues = Enum.GetValues(EnumType);

      return (
        from object enumValue in enumValues
        select new EnumerationMember{
          Value = enumValue,
          Description = GetDescription(enumValue)
        }).ToArray();
    }

    private string GetDescription(object enumValue)
    {
      var descriptionAttribute = EnumType
        .GetField(enumValue.ToString())
        .GetCustomAttributes(typeof (DescriptionAttribute), false)
        .FirstOrDefault() as DescriptionAttribute;


      return descriptionAttribute != null
        ? descriptionAttribute.Description
        : enumValue.ToString();
    }

    public class EnumerationMember
    {
      public string Description { get; set; }
      public object Value { get; set; }
    }
  }

7
@Gregor S. 내 : 열거 란 무엇입니까?
Joshua

14
@Crown 'my'는 xaml 파일 맨 위에 선언하는 네임 스페이스 접두사입니다 (예 : xmlns : my = "clr-namespace : namespace_to_enumeration_extension_class) xaml에서는 EnumerationExtension의 약자가 열거 형이므로 전체 확장 클래스 이름을 쓰지 않아도됩니다. .
그레고르 Slavec

33
+1, 그러나 가장
간단한

1
뷰에서 ItemsSource매개 변수로 모델의 일부 (열거 유형)에 대한 참조를 사용하는 방식이 마음에 들지 않습니다 . 뷰와 모델을 분리 상태로 유지하려면 ViewModel과 코드 ViewModel에서 열거 사본을 작성하여 둘 사이를 변환해야합니다. 솔루션을 더 이상 단순하게 만들 수는 없습니다. 아니면 ViewModel에서 유형 자체를 제공하는 방법이 있습니까?
lampak

6
또 다른 제한 사항은 여러 언어가있는 경우이 작업을 수행 할 수 없다는 것입니다.
River-Claire Williamson

176

뷰 모델에서 다음을 수행 할 수 있습니다.

public MyEnumType SelectedMyEnumType 
{
    get { return _selectedMyEnumType; }
    set { 
            _selectedMyEnumType = value;
            OnPropertyChanged("SelectedMyEnumType");
        }
}

public IEnumerable<MyEnumType> MyEnumTypeValues
{
    get
    {
        return Enum.GetValues(typeof(MyEnumType))
            .Cast<MyEnumType>();
    }
}

XAML에서 ItemSource바인딩 은에 바인딩 MyEnumTypeValues하고 SelectedItem바인딩합니다 SelectedMyEnumType.

<ComboBox SelectedItem="{Binding SelectedMyEnumType}" ItemsSource="{Binding MyEnumTypeValues}"></ComboBox>

이것은 내 유니버설 앱에서 훌륭하게 작동했으며 구현하기가 매우 쉽습니다. 감사합니다!
Nathan Strutz 2016

96

UI에서 열거 형 이름을 사용하지 않는 것이 좋습니다. 사용자 ( DisplayMemberPath)에 다른 값을 사용 하고 값 (이 경우 열거 형) ( )에 다른 값을 사용하는 것이 좋습니다 SelectedValuePath. 이 두 값은KeyValuePair 사전에 저장할 .

XAML

<ComboBox Name="fooBarComboBox" 
          ItemsSource="{Binding Path=ExampleEnumsWithCaptions}" 
          DisplayMemberPath="Value" 
          SelectedValuePath="Key"
          SelectedValue="{Binding Path=ExampleProperty, Mode=TwoWay}" > 

씨#

public Dictionary<ExampleEnum, string> ExampleEnumsWithCaptions { get; } =
    new Dictionary<ExampleEnum, string>()
    {
        {ExampleEnum.FooBar, "Foo Bar"},
        {ExampleEnum.BarFoo, "Reversed Foo Bar"},
        //{ExampleEnum.None, "Hidden in UI"},
    };


private ExampleEnum example;
public ExampleEnum ExampleProperty
{
    get { return example; }
    set { /* set and notify */; }
}

편집 : MVVM 패턴과 호환됩니다.


14
귀하의 답변이 과소 평가되었다고 생각합니다 .ComboBox 자체가 기대하는 것을 고려할 때 가장 좋은 옵션 인 것 같습니다. 아마도를 사용하여 getter에 사전 빌더를 넣을 수는 Enum.GetValues있지만 이름의 일부가 표시되지는 않습니다. 결국, 특히 I18n이 구현되면 열거 형이 변경되면 수동으로 항목을 변경해야합니다. 그러나 열거 형은 자주 변경되지 않아야합니까? +1
heltonbiker

2
이 답변은 훌륭하고 열거 형 설명을 현지화 할 수 있습니다 ... 감사합니다!
Shay

2
이 솔루션은 다른 솔루션보다 적은 코드로 열거 및 지역화를 모두 처리하므로 매우 좋습니다!
hfann

2
Dictionary의 문제점은 키가 해시 값으로 정렬되므로 그에 대한 제어가 거의 없다는 것입니다. 좀 더 장황하지만 List <KeyValuePair <enum, string >>을 대신 사용했습니다. 좋은 생각.
케빈 브록

3
@CoperNick @Pragmateek 새로운 수정 :public Dictionary<ExampleEnum, string> ExampleEnumsWithCaptions { get; } = new Dictionary<ExampleEnum, string>() { {ExampleEnum.FooBar, "Foo Bar"}, {ExampleEnum.BarFoo, "Reversed Foo Bar"}, //{ExampleEnum.None, "Hidden in UI"}, };
Jinjinov

40

XAML에서만 가능한지 모르겠지만 다음을 시도하십시오.

ComboBox의 이름을 코드 숨김에서 액세스 할 수 있도록 이름을 지정하십시오. "typesComboBox1"

이제 다음을 시도하십시오

typesComboBox1.ItemsSource = Enum.GetValues(typeof(ExampleEnum));

24

ageektrapped가 제공했지만 이제는 삭제 된 답변을 기반으로 고급 기능이없는 슬림 버전을 만들었습니다. 모든 코드가 여기에 포함되어 있으므로 복사하여 붙여 넣을 수 있으며 링크 로트에 의해 차단되지 않습니다.

나는 System.ComponentModel.DescriptionAttribute실제로 디자인 타임 설명을위한 것을 사용합니다 . 이 속성을 사용하는 것을 싫어하면 자신만의 것을 만들 수 있지만이 속성을 사용하면 실제로 작업이 완료됩니다. 이 속성을 사용하지 않으면 이름은 기본적으로 코드의 열거 형 값 이름이됩니다.

public enum ExampleEnum {

  [Description("Foo Bar")]
  FooBar,

  [Description("Bar Foo")]
  BarFoo

}

아이템 소스로 사용되는 클래스는 다음과 같습니다.

public class EnumItemsSource : Collection<String>, IValueConverter {

  Type type;

  IDictionary<Object, Object> valueToNameMap;

  IDictionary<Object, Object> nameToValueMap;

  public Type Type {
    get { return this.type; }
    set {
      if (!value.IsEnum)
        throw new ArgumentException("Type is not an enum.", "value");
      this.type = value;
      Initialize();
    }
  }

  public Object Convert(Object value, Type targetType, Object parameter, CultureInfo culture) {
    return this.valueToNameMap[value];
  }

  public Object ConvertBack(Object value, Type targetType, Object parameter, CultureInfo culture) {
    return this.nameToValueMap[value];
  }

  void Initialize() {
    this.valueToNameMap = this.type
      .GetFields(BindingFlags.Static | BindingFlags.Public)
      .ToDictionary(fi => fi.GetValue(null), GetDescription);
    this.nameToValueMap = this.valueToNameMap
      .ToDictionary(kvp => kvp.Value, kvp => kvp.Key);
    Clear();
    foreach (String name in this.nameToValueMap.Keys)
      Add(name);
  }

  static Object GetDescription(FieldInfo fieldInfo) {
    var descriptionAttribute =
      (DescriptionAttribute) Attribute.GetCustomAttribute(fieldInfo, typeof(DescriptionAttribute));
    return descriptionAttribute != null ? descriptionAttribute.Description : fieldInfo.Name;
  }

}

XAML에서 다음과 같이 사용할 수 있습니다.

<Windows.Resources>
  <local:EnumItemsSource
    x:Key="ExampleEnumItemsSource"
    Type="{x:Type local:ExampleEnum}"/>
</Windows.Resources>
<ComboBox
  ItemsSource="{StaticResource ExampleEnumItemsSource}"
  SelectedValue="{Binding ExampleProperty, Converter={StaticResource ExampleEnumItemsSource}}"/> 

23

ObjectDataProvider를 사용하십시오.

<ObjectDataProvider x:Key="enumValues"
   MethodName="GetValues" ObjectType="{x:Type System:Enum}">
      <ObjectDataProvider.MethodParameters>
           <x:Type TypeName="local:ExampleEnum"/>
      </ObjectDataProvider.MethodParameters>
 </ObjectDataProvider>

정적 리소스에 바인딩하십시오.

ItemsSource="{Binding Source={StaticResource enumValues}}"

이 블로그 에서이 솔루션을 찾으십시오


좋은 대답입니다. Converter또한 열거 형 문자열 문제에 대해 걱정할 필요가 없습니다 .
DonBoitnott

1
연결된 솔루션이 죽은 것 같습니다 (한국어 또는 일본어 텍스트?). XAML 리소스에 코드를 넣으면 WPF 프로젝트에서 Enum이 지원되지 않습니다.
Sebastian

6

내가 가장 좋아하는 방법 ValueConverter은 ItemsSource와 SelectedValue가 같은 속성에 바인딩되도록하는 것입니다. ViewModel을 깔끔하고 깨끗하게 유지하기 위해 추가 속성 이 필요 하지 않습니다 .

<ComboBox ItemsSource="{Binding Path=ExampleProperty, Converter={x:EnumToCollectionConverter}, Mode=OneTime}"
          SelectedValuePath="Value"
          DisplayMemberPath="Description"
          SelectedValue="{Binding Path=ExampleProperty}" />

그리고 변환기의 정의 :

public static class EnumHelper
{
  public static string Description(this Enum e)
  {
    return (e.GetType()
             .GetField(e.ToString())
             .GetCustomAttributes(typeof(DescriptionAttribute), false)
             .FirstOrDefault() as DescriptionAttribute)?.Description ?? e.ToString();
  }
}

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

이 변환기는 모든 열거 형에서 작동합니다. 속성과 속성을 ValueDescription가진 단순한 클래스입니다 . 열거 형 값과 해당 열거 형 값의 문자열 설명을 보유 할 수있는 한 값 및 설명 또는 선택한 다른 클래스 대신 with 및 , with 또는 with 및 with 를 쉽게 사용할 수 있습니다.ValueDescriptionTupleItem1Item2KeyValuePairKeyValue


좋은 대답입니다! 를 들어 ValueDescription클래스는 Description필요하지 않은 경우 속성은 생략 될 수있다. Value속성 만있는 간단한 클래스 도 작동합니다!
pogosama

또한 RadioButton에 바인딩하려는 경우 Convert 메서드는 클래스 .Select(e => e.ToString())를 사용하는 대신 문자열 목록을 반환해야합니다 ValueDescription.
pogosama

여기에 표시된 것처럼ValueDescription a 대신 KeyValuePair사용될 수도 있습니다
Apfelkuacha

5

다음은 도우미 메소드를 사용하는 일반적인 솔루션입니다. 또한 기본 유형 (바이트, sbyte, uint, long 등)의 열거 형을 처리 할 수 ​​있습니다.

도우미 방법 :

static IEnumerable<object> GetEnum<T>() {
    var type    = typeof(T);
    var names   = Enum.GetNames(type);
    var values  = Enum.GetValues(type);
    var pairs   =
        Enumerable.Range(0, names.Length)
        .Select(i => new {
                Name    = names.GetValue(i)
            ,   Value   = values.GetValue(i) })
        .OrderBy(pair => pair.Name);
    return pairs;
}//method

모델보기 :

public IEnumerable<object> EnumSearchTypes {
    get {
        return GetEnum<SearchTypes>();
    }
}//property

콤보 박스:

<ComboBox
    SelectedValue       ="{Binding SearchType}"
    ItemsSource         ="{Binding EnumSearchTypes}"
    DisplayMemberPath   ="Name"
    SelectedValuePath   ="Value"
/>

5

당신은 그런 것을 고려할 수 있습니다 :

  1. 텍스트 블록 또는 열거 형을 표시하는 데 사용하려는 다른 컨트롤의 스타일을 정의하십시오.

    <Style x:Key="enumStyle" TargetType="{x:Type TextBlock}">
        <Setter Property="Text" Value="&lt;NULL&gt;"/>
        <Style.Triggers>
            <Trigger Property="Tag">
                <Trigger.Value>
                    <proj:YourEnum>Value1<proj:YourEnum>
                </Trigger.Value>
                <Setter Property="Text" Value="{DynamicResource yourFriendlyValue1}"/>
            </Trigger>
            <!-- add more triggers here to reflect your enum -->
        </Style.Triggers>
    </Style>
  2. ComboBoxItem에 대한 스타일 정의

    <Style TargetType="{x:Type ComboBoxItem}">
        <Setter Property="ContentTemplate">
            <Setter.Value>
                <DataTemplate>
                    <TextBlock Tag="{Binding}" Style="{StaticResource enumStyle}"/>
                </DataTemplate>
            </Setter.Value>
        </Setter>
    </Style>
  3. 콤보 상자를 추가하고 열거 형 값으로로드하십시오.

    <ComboBox SelectedValue="{Binding Path=your property goes here}" SelectedValuePath="Content">
        <ComboBox.Items>
            <ComboBoxItem>
                <proj:YourEnum>Value1</proj:YourEnum>
            </ComboBoxItem>
        </ComboBox.Items>
    </ComboBox>

열거 형이 크면 많은 타이핑을 줄이면서 코드에서 동일하게 수행 할 수 있습니다. 현지화가 쉬워 지므로 접근 방식이 마음에 듭니다. 모든 템플릿을 한 번 정의한 다음 문자열 리소스 파일 만 업데이트합니다.


SelectedValuePath = "Content"가 여기에 도움이되었습니다. ComboBoxItems를 문자열 값으로 사용하여 ComboBoxItem을 내 Enum Type으로 변환 할 수 없습니다. 감사합니다
adriaanp

2

@rudigrobler 답변을 기반으로 MVVM을 사용하는 경우 다음을 수행 할 수 있습니다.

ViewModel 클래스에 다음 속성을 추가하십시오.

public Array ExampleEnumValues => Enum.GetValues(typeof(ExampleEnum));

그런 다음 XAML에서 다음을 수행하십시오.

<ComboBox ItemsSource="{Binding ExampleEnumValues}" ... />

1

DevExpress답변은Gregor S. 은 의결권 (현재 128 표).

이는 전체 애플리케이션에서 스타일을 일관되게 유지할 수 있음을 의미합니다.

여기에 이미지 설명을 입력하십시오

불행히도 원래 답변은 ComboBoxEdit 약간의 수정없이 DevExpress에서 .

먼저 다음에 대한 XAML ComboBoxEdit:

<dxe:ComboBoxEdit ItemsSource="{Binding Source={xamlExtensions:XamlExtensionEnumDropdown {x:myEnum:EnumFilter}}}"
    SelectedItem="{Binding BrokerOrderBookingFilterSelected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
    DisplayMember="Description"
    MinWidth="144" Margin="5" 
    HorizontalAlignment="Left"
    IsTextEditable="False"
    ValidateOnTextInput="False"
    AutoComplete="False"
    IncrementalFiltering="True"
    FilterCondition="Like"
    ImmediatePopup="True"/>

말할 것도없이, xamlExtensionsXAML 확장 클래스 (아래에 정의 됨)가 포함 된 네임 스페이스 를 가리켜 야 합니다.

xmlns:xamlExtensions="clr-namespace:XamlExtensions"

그리고 myEnum열거 형이 포함 된 네임 스페이스 를 가리켜 야 합니다.

xmlns:myEnum="clr-namespace:MyNamespace"

그런 다음 열거 형 :

namespace MyNamespace
{
    public enum EnumFilter
    {
        [Description("Free as a bird")]
        Free = 0,

        [Description("I'm Somewhat Busy")]
        SomewhatBusy = 1,

        [Description("I'm Really Busy")]
        ReallyBusy = 2
    }
}

XAML의 문제점은을 사용할 수 없다는 것입니다 SelectedItemValue. 세터에 액세스 할 수 없기 때문에 오류가 발생하기 때문입니다 (여러분의 감독 DevExpress). 따라서 ViewModel객체에서 직접 값을 얻 도록 수정 해야합니다.

private EnumFilter _filterSelected = EnumFilter.All;
public object FilterSelected
{
    get
    {
        return (EnumFilter)_filterSelected;
    }
    set
    {
        var x = (XamlExtensionEnumDropdown.EnumerationMember)value;
        if (x != null)
        {
            _filterSelected = (EnumFilter)x.Value;
        }
        OnPropertyChanged("FilterSelected");
    }
}

완전성을 위해 원래 답변의 XAML 확장은 다음과 같습니다 (약간의 이름 변경).

namespace XamlExtensions
{
    /// <summary>
    ///     Intent: XAML markup extension to add support for enums into any dropdown box, see http://bit.ly/1g70oJy. We can name the items in the
    ///     dropdown box by using the [Description] attribute on the enum values.
    /// </summary>
    public class XamlExtensionEnumDropdown : MarkupExtension
    {
        private Type _enumType;


        public XamlExtensionEnumDropdown(Type enumType)
        {
            if (enumType == null)
            {
                throw new ArgumentNullException("enumType");
            }

            EnumType = enumType;
        }

        public Type EnumType
        {
            get { return _enumType; }
            private set
            {
                if (_enumType == value)
                {
                    return;
                }

                var enumType = Nullable.GetUnderlyingType(value) ?? value;

                if (enumType.IsEnum == false)
                {
                    throw new ArgumentException("Type must be an Enum.");
                }

                _enumType = value;
            }
        }

        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            var enumValues = Enum.GetValues(EnumType);

            return (
                from object enumValue in enumValues
                select new EnumerationMember
                       {
                           Value = enumValue,
                           Description = GetDescription(enumValue)
                       }).ToArray();
        }

        private string GetDescription(object enumValue)
        {
            var descriptionAttribute = EnumType
                .GetField(enumValue.ToString())
                .GetCustomAttributes(typeof (DescriptionAttribute), false)
                .FirstOrDefault() as DescriptionAttribute;


            return descriptionAttribute != null
                ? descriptionAttribute.Description
                : enumValue.ToString();
        }

        #region Nested type: EnumerationMember
        public class EnumerationMember
        {
            public string Description { get; set; }
            public object Value { get; set; }
        }
        #endregion
    }
}

면책 조항 : DevExpress와는 아무런 관련이 없습니다. Telerik은 훌륭한 도서관이기도합니다.


기록을 위해, 나는 DevExpress와 제휴하지 않습니다. Telerik에는 또한 매우 훌륭한 라이브러리가 있으며이 기술은 라이브러리에 필요하지 않을 수도 있습니다.
Contango 2016 년

0

사용해보십시오

<ComboBox ItemsSource="{Binding Source={StaticResource ExampleEnumValues}}"
    SelectedValue="{Binding Path=ExampleProperty}" />

작동하지 않습니다. 콤보 박스는 단지 빈 텍스트를 보여줄 것이고 아무것도 바꾸지 않을 것입니다. 나는 여기에 변환기를 던지는 것이 가장 좋은 해결책이라고 생각합니다.
Maximilian

0

이 작업을 수행 하는 오픈 소스 CodePlex 프로젝트를 만들었습니다 . NuGet 패키지는 여기 에서 다운로드 할 수 있습니다 .

<enumComboBox:EnumComboBox EnumType="{x:Type demoApplication:Status}" SelectedValue="{Binding Status}" />
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.