[c#] WPF에서 콤보 상자 컨트롤에 열거 형을 바인딩하는 방법?



Answers

내가 바인딩하는 모든 객체가 ViewModel 정의되어 있으므로 가능하면 xaml에 <ObjectDataProvider> 를 사용하지 않는 것이 좋습니다.

내 솔루션은 뷰에 정의 된 데이터를 사용하지 않으며 코드 숨김도 없습니다. DataBinding, 재사용 가능한 ValueConverter, 모든 Enum 유형에 대한 설명 모음을 가져 오는 메소드 및 바인딩 할 ViewModel의 단일 속성.

EnumComboBox 에 바인딩하려면 표시 할 텍스트가 Enum 값과 일치하지 않으므로 [Description()] 특성을 사용하여 실제로 ComboBox 에서 보려는 텍스트를 제공합니다. 게임에서 캐릭터 클래스 열거 형을 사용하면 다음과 같이 보일 것입니다.

public enum PlayerClass
{
  // add an optional blank value for default/no selection
  [Description("")]
  NOT_SET = 0,
  [Description("Shadow Knight")]
  SHADOW_KNIGHT,
  ...
}

먼저 열거 형을 처리하는 두 가지 방법으로 도우미 클래스를 만들었습니다. 하나의 메소드는 특정 값에 대한 설명을 가져오고, 다른 메소드는 모든 값과 해당 유형에 대한 설명을 가져옵니다.

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();
  }
}

다음으로 ValueConverter 만듭니다. MarkupExtension 에서 상속하면 XAML에서 쉽게 사용할 수 있으므로 리소스로 선언 할 필요가 없습니다.

[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;
  }
}

ViewModel 은 내 View 가 콤보 상자의 SelectedValueItemsSource 에 대해 바인딩 할 수있는 속성이 하나만 필요합니다.

private PlayerClass playerClass;

public PlayerClass SelectedClass
{
  get { return playerClass; }
  set
  {
    if (playerClass != value)
    {
      playerClass = value;
      OnPropertyChanged(nameof(SelectedClass));
    }
  }
}

마지막으로 ( ItemsSource 바인딩의 ValueConverter 를 사용하여) ComboBox 보기를 바인딩합니다.

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

이 솔루션을 구현하려면 EnumHelper 클래스와 EnumToCollectionConverter 클래스를 복사하면됩니다. 그들은 어떤 enum과도 작동 할 것입니다. 또한 여기에 포함시키지 않았지만 ValueDescription 클래스는 Value 라는 속성, Description 이라는 Description 이라는 2 개의 공용 객체 속성이있는 단순한 클래스입니다. 직접 만들거나 Tuple<object, object> 또는 KeyValuePair<object, object> 를 사용하도록 코드를 변경할 수 있습니다.

Question

enum이있는 그대로 간단한 예제를 찾으려고합니다. 필자가 보았던 모든 예제는 멋진 디스플레이 문자열을 추가하려고 시도하지만 그 복잡성을 원하지 않습니다.

기본적으로 DataContext를이 클래스로 설정하고 바인딩을 xaml 파일에 지정하여 바인딩하는 모든 속성을 보유하는 클래스가 있습니다.

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

하지만이 항목으로 ComboBox 열거 형 값을 표시하지 않습니다.




ReactiveUI 사용하여 다음 대체 솔루션을 만들었습니다. 그것은 우아한 올인원 솔루션이 아니지만, 적어도 그것은 읽을 수 있다고 생각합니다.

필자의 경우 enum 목록을 컨트롤에 바인딩하는 경우는 드뭅니다. 따라서 코드 기반에서 솔루션을 확장 할 필요가 없습니다. 그러나 EffectStyleLookup.ItemObject 로 변경하면 코드를보다 일반적으로 만들 수 있습니다. 내 코드로 테스트했지만 다른 수정은 필요하지 않습니다. 즉, 하나의 도우미 클래스가 모든 enum 목록에 적용될 수 있음을 의미합니다. ReactiveList<EnumLookupHelper> 는 가독성을 떨어 뜨릴지라도 큰 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" />

뷰에서 ViewModel의 SelectedEffectStyle 에 원래의 enum 을 바인딩 할 수 있지만 ComboBoxToString() 값을 표시합니다.

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) );
});



Nick의 대답이 정말로 도움이되었지만, ValueDescription이라는 추가적인 클래스를 피하기 위해 약간 조정할 수 있음을 깨달았습니다. 이미 프레임 워크에 KeyValuePair 클래스가 있으므로 키를 대신 사용할 수 있다는 것을 기억했습니다.

코드가 약간만 변경됩니다.

public static IEnumerable<KeyValuePair<string, string>> GetAllValuesAndDescriptions<TEnum>() where TEnum : struct, IConvertible, IComparable, IFormattable
    {
        if (!typeof(TEnum).IsEnum)
        {
            throw new ArgumentException("TEnum must be an Enumeration type");
        }

        return from e in Enum.GetValues(typeof(TEnum)).Cast<Enum>()
               select new KeyValuePair<string, string>(e.ToString(),  e.Description());
    }


public IEnumerable<KeyValuePair<string, string>> PlayerClassList
{
   get
   {
       return EnumHelper.GetAllValuesAndDescriptions<PlayerClass>();
   }
}

마지막으로 XAML :

<ComboBox ItemSource="{Binding Path=PlayerClassList}"
          DisplayMemberPath="Value"
          SelectedValuePath="Key"
          SelectedValue="{Binding Path=SelectedClass}" />

나는 이것이 다른 사람들에게 도움이되기를 바랍니다.




열거 형의 int 표현이 아닌 ViewModel의 실제 enum 속성에 바인딩하는 경우 작업이 까다로워집니다. 위의 모든 예에서 예상되는대로 int 값이 아닌 문자열 표현에 바인딩해야한다는 것을 알았습니다.

이 경우에 ViewModel에 바인딩하려는 속성에 간단한 텍스트 상자를 바인딩하여 알 수 있습니다. 텍스트가 표시되면 문자열에 바인딩하십시오. 숫자가 있으면 값에 바인딩하십시오. 참고 디스플레이를 두 번 사용했는데 일반적으로 오류가 발생하지만 작동하는 유일한 방법입니다.

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

그렉




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}}"



public class EnumItemsConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (!value.GetType().IsEnum)
            return false;

        var enumName = value.GetType();
        var obj = Enum.Parse(enumName, value.ToString());

        return System.Convert.ToInt32(obj);
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return Enum.ToObject(targetType, System.Convert.ToInt32(value));
    }
}

열거 형 개체 모델 속성에 직접 바인딩하는 경우 이러한 Enum 값 변환기를 사용하여 Rogers와 Greg의 대답을 확장해야합니다.




Related