[wpf] RadioButton을 열거 형에 바인딩하는 방법은 무엇입니까?

나는 다음과 같은 열거 형을 가지고있다 :

public enum MyLovelyEnum
{
    FirstSelection,
    TheOtherSelection,
    YetAnotherOne
};

내 DataContext에 속성이 있습니다.

public MyLovelyEnum VeryLovelyEnum { get; set; }

WPF 클라이언트에 3 개의 RadioButton이 있습니다.

<RadioButton Margin="3">First Selection</RadioButton>
<RadioButton Margin="3">The Other Selection</RadioButton>
<RadioButton Margin="3">Yet Another one</RadioButton>

이제 적절한 양방향 바인딩을 위해 RadioButton을 속성에 어떻게 바인딩합니까?



답변

좀 더 일반적인 변환기를 사용할 수 있습니다

public class EnumBooleanConverter : IValueConverter
{
  #region IValueConverter Members
  public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
  {
    string parameterString = parameter as string;
    if (parameterString == null)
      return DependencyProperty.UnsetValue;

    if (Enum.IsDefined(value.GetType(), value) == false)
      return DependencyProperty.UnsetValue;

    object parameterValue = Enum.Parse(value.GetType(), parameterString);

    return parameterValue.Equals(value);
  }

  public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
  {
    string parameterString = parameter as string;
    if (parameterString == null)
        return DependencyProperty.UnsetValue;

    return Enum.Parse(targetType, parameterString);
  }
  #endregion
}

그리고 XAML-Part에서는 다음을 사용합니다.

<Grid>
    <Grid.Resources>
      <l:EnumBooleanConverter x:Key="enumBooleanConverter" />
    </Grid.Resources>
    <StackPanel >
      <RadioButton IsChecked="{Binding Path=VeryLovelyEnum, Converter={StaticResource enumBooleanConverter}, ConverterParameter=FirstSelection}">first selection</RadioButton>
      <RadioButton IsChecked="{Binding Path=VeryLovelyEnum, Converter={StaticResource enumBooleanConverter}, ConverterParameter=TheOtherSelection}">the other selection</RadioButton>
      <RadioButton IsChecked="{Binding Path=VeryLovelyEnum, Converter={StaticResource enumBooleanConverter}, ConverterParameter=YetAnotherOne}">yet another one</RadioButton>
    </StackPanel>
</Grid>


답변

허용 된 답변을 더 단순화 할 수 있습니다. 열거 형을 xaml에 문자열로 입력하고 필요한 것보다 변환기에서 더 많은 작업을 수행하는 대신 문자열 표현 대신 열거 형 값을 명시 적으로 전달할 수 있으며 CrimsonX가 언급 한대로 런타임이 아닌 컴파일 타임에 오류가 발생합니다.

ConverterParameter = {x : 정적 로컬 : YourEnumType.Enum1}

<StackPanel>
    <StackPanel.Resources>
        <local:ComparisonConverter x:Key="ComparisonConverter" />
    </StackPanel.Resources>
    <RadioButton IsChecked="{Binding Path=YourEnumProperty, Converter={StaticResource ComparisonConverter}, ConverterParameter={x:Static local:YourEnumType.Enum1}}" />
    <RadioButton IsChecked="{Binding Path=YourEnumProperty, Converter={StaticResource ComparisonConverter}, ConverterParameter={x:Static local:YourEnumType.Enum2}}" />
</StackPanel>

그런 다음 변환기를 단순화하십시오.

public class ComparisonConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return value?.Equals(parameter);
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return value?.Equals(true) == true ? parameter : Binding.DoNothing;
    }
}

편집 (12 월 16 일 10 일) :

DependencyProperty.UnsetValue 대신 Binding.DoNothing을 반환하도록 제안한 anon에게 감사드립니다.


참고-동일한 컨테이너에있는 여러 개의 RadioButton 그룹 (Feb 17 ’11) :

xaml에서 단일 선택 단추가 동일한 상위 컨테이너를 공유하는 경우 하나를 선택하면 다른 특성에 바인드되어 있어도 해당 컨테이너 내의 다른 모든 컨테이너가 선택 해제됩니다. 따라서 공통 속성에 바인딩 된 RadioButton을 스택 패널과 같은 자체 컨테이너에 그룹화하십시오. 관련 RadioButton이 단일 상위 컨테이너를 공유 할 수없는 경우 각 RadioButton의 GroupName 속성을 공통 값으로 설정하여 논리적으로 그룹화하십시오.


편집 (Apr 5 ’11) :

삼항 연산자를 사용하기 위해 단순화 된 ConvertBack의 if-else입니다.


참고-클래스에 중첩 된 열거 형 유형 (Apr 28 ’11) :

열거 형 유형이 네임 스페이스에 직접 포함되지 않고 클래스에 중첩 된 경우 ‘+’구문을 사용하여 XAML의 열거 형에 액세스하여 질문에 대한 (표시되지 않음) 답변에 나와있는 것처럼 답변 을 찾을 수 없습니다 WPF에서 정적 참조를위한 열거 형 :

ConverterParameter = {x : 정적 로컬 : YourClass + YourNestedEnumType.Enum1}

그러나이 Microsoft Connect 문제 로 인해 VS2010의 디자이너는 더 이상 상태를로드 "Type 'local:YourClass+YourNestedEnumType' was not found."하지 않지만 프로젝트는 성공적으로 컴파일 및 실행됩니다. 물론 열거 형을 네임 스페이스로 직접 이동할 수 있다면이 문제를 피할 수 있습니다.


편집 (Jan 27 ’12) :

Enum 플래그를 사용하는 경우 변환기는 다음과 같습니다.

public class EnumToBooleanConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return ((Enum)value).HasFlag((Enum)parameter);
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return value.Equals(true) ? parameter : Binding.DoNothing;
    }
}

편집 (5 월 7 일 ’15 일) :

(하는 null 허용 열거의 경우 하지 질문에 물었지만, 예를 들어, ORM DB에서 널 (null)을 반환하거나 때마다 프로그램 로직에 값이 제공되지 않는 것을 적합 할 수 있습니다, 경우에 따라 필요할 수 있습니다), 추가 기억 Convert 메서드에서 초기 null을 확인하고 적절한 부울 값을 반환합니다. 일반적으로 아래와 같이 false입니다 (라디오 버튼을 선택하지 않으려는 경우).

    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        if (value == null) {
            return false; // or return parameter.Equals(YourEnumType.SomeDefaultValue);
        }
        return value.Equals(parameter);
    }

참고-NullReferenceException (10 월 10 일 ’18) :

NullReferenceException 발생 가능성을 제거하도록 예제를 업데이트했습니다. IsCheckednullable 형식이므로 반환 Nullable<Boolean>하는 것이 합리적인 해결책 인 것 같습니다.


답변

EnumToBooleanConverter 답변의 경우 : DependencyProperty.UnsetValue를 반환하는 대신 라디오 버튼 IsChecked 값이 false가되는 경우 Binding.DoNothing을 반환하는 것을 고려하십시오. 전자는 문제를 나타내며 (사용자에게 빨간색 사각형 또는 유사한 유효성 표시기를 표시 할 수 있음) 후자는 아무 것도 수행하지 않아야한다는 것을 나타내며,이 경우 원하는 것입니다.

http://msdn.microsoft.com/en-us/library/system.windows.data.ivalueconverter.convertback.aspx
http://msdn.microsoft.com/en-us/library/system.windows.data.binding .donothing.aspx


답변

ListBox에서 RadioButton을 사용한 다음 SelectedValue에 바인딩합니다.

이것은이 주제에 관한 오래된 글이지만, 기본 아이디어는 동일해야합니다 .


답변

UWP의 경우 그렇게 간단하지 않습니다. 필드 값을 매개 변수로 전달하려면 추가 후프를 뛰어 넘어야합니다.

실시 예 1

WPF 및 UWP 모두에 유효합니다.

<MyControl>
    <MyControl.MyProperty>
        <Binding Converter="{StaticResource EnumToBooleanConverter}" Path="AnotherProperty">
            <Binding.ConverterParameter>
                <MyLibrary:MyEnum>Field</MyLibrary:MyEnum>
            </Binding.ConverterParameter>
        </MyControl>
    </MyControl.MyProperty>
</MyControl>

실시 예 2

WPF 및 UWP 모두에 유효합니다.

...
<MyLibrary:MyEnum x:Key="MyEnumField">Field</MyLibrary:MyEnum>
...

<MyControl MyProperty="{Binding AnotherProperty, Converter={StaticResource EnumToBooleanConverter}, ConverterParameter={StaticResource MyEnumField}}"/>

실시 예 3

WPF에만 유효합니다!

<MyControl MyProperty="{Binding AnotherProperty, Converter={StaticResource EnumToBooleanConverter}, ConverterParameter={x:Static MyLibrary:MyEnum.Field}}"/>

UWP는 지원하지 않습니다 x:Static그래서 예 3은 밖으로 질문입니다; 예제 1을 사용 한다고 가정하면 결과는 더 자세한 코드입니다. 예제 2 가 약간 더 좋지만 여전히 이상적이지는 않습니다.

해결책

public abstract class EnumToBooleanConverter<TEnum> : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, string language)
    {
        var Parameter = parameter as string;

        if (Parameter == null)
            return DependencyProperty.UnsetValue;

        if (Enum.IsDefined(typeof(TEnum), value) == false)
            return DependencyProperty.UnsetValue;

        return Enum.Parse(typeof(TEnum), Parameter).Equals(value);
    }

    public object ConvertBack(object value, Type targetType, object parameter, string language)
    {
        var Parameter = parameter as string;
        return Parameter == null ? DependencyProperty.UnsetValue : Enum.Parse(typeof(TEnum), Parameter);
    }
}

그런 다음 지원하려는 각 유형에 대해 열거 형 유형을 상자 화하는 변환기를 정의하십시오.

public class MyEnumToBooleanConverter : EnumToBooleanConverter<MyEnum>
{
    //Nothing to do!
}

박스에 넣어야하는 이유는 ConvertBack메서드 에서 형식을 참조 할 방법이 없기 때문입니다 . 권투는 그것을 처리합니다. 처음 두 예제 중 하나를 사용하는 경우 매개 변수 유형을 참조하면 상자 클래스에서 상속 할 필요가 없습니다. 가능한 한 한 줄로 모든 것을하고 싶다면, 후자의 솔루션이 이상적입니다.

사용법은 예 2 와 비슷 하지만 실제로는 덜 장황합니다.

<MyControl MyProperty="{Binding AnotherProperty, Converter={StaticResource MyEnumToBooleanConverter}, ConverterParameter=Field}"/>

단점은 지원하려는 각 유형에 대한 변환기를 정의해야한다는 것입니다.


답변

RadioButtons 및 CheckBoxes를 열거 형에 바인딩하는 것을 처리하는 새 클래스를 만들었습니다. 단일 선택 확인란 또는 단일 선택 단추에 대해 플래그가 지정된 열거 형 (복수의 확인란 선택)과 비 플래그 형 열거 형에 작동합니다. 또한 ValueConverters가 전혀 필요하지 않습니다.

처음에는 더 복잡해 보일 수 있지만 일단이 클래스를 프로젝트에 복사하면 완료됩니다. 일반적이므로 모든 열거 형에 쉽게 재사용 할 수 있습니다.

public class EnumSelection<T> : INotifyPropertyChanged where T : struct, IComparable, IFormattable, IConvertible
{
  private T value; // stored value of the Enum
  private bool isFlagged; // Enum uses flags?
  private bool canDeselect; // Can be deselected? (Radio buttons cannot deselect, checkboxes can)
  private T blankValue; // what is considered the "blank" value if it can be deselected?

  public EnumSelection(T value) : this(value, false, default(T)) { }
  public EnumSelection(T value, bool canDeselect) : this(value, canDeselect, default(T)) { }
  public EnumSelection(T value, T blankValue) : this(value, true, blankValue) { }
  public EnumSelection(T value, bool canDeselect, T blankValue)
  {
    if (!typeof(T).IsEnum) throw new ArgumentException($"{nameof(T)} must be an enum type"); // I really wish there was a way to constrain generic types to enums...
    isFlagged = typeof(T).IsDefined(typeof(FlagsAttribute), false);

    this.value = value;
    this.canDeselect = canDeselect;
    this.blankValue = blankValue;
  }

  public T Value
  {
    get { return value; }
    set
    {
      if (this.value.Equals(value)) return;
      this.value = value;
      OnPropertyChanged();
      OnPropertyChanged("Item[]"); // Notify that the indexer property has changed
    }
  }

  [IndexerName("Item")]
  public bool this[T key]
  {
    get
    {
      int iKey = (int)(object)key;
      return isFlagged ? ((int)(object)value & iKey) == iKey : value.Equals(key);
    }
    set
    {
      if (isFlagged)
      {
        int iValue = (int)(object)this.value;
        int iKey = (int)(object)key;

        if (((iValue & iKey) == iKey) == value) return;

        if (value)
          Value = (T)(object)(iValue | iKey);
        else
          Value = (T)(object)(iValue & ~iKey);
      }
      else
      {
        if (this.value.Equals(key) == value) return;
        if (!value && !canDeselect) return;

        Value = value ? key : blankValue;
      }
    }
  }

  public event PropertyChangedEventHandler PropertyChanged;

  private void OnPropertyChanged([CallerMemberName] string propertyName = "")
  {
    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
  }
}

그리고 그것을 사용하는 방법에 대해 수동 또는 자동으로 작업을 실행하기위한 열거 형이 있고 요일 동안 예약 할 수 있고 몇 가지 선택적 옵션이 있다고 가정 해 봅시다.

public enum StartTask
{
  Manual,
  Automatic
}

[Flags()]
public enum DayOfWeek
{
  Sunday = 1 << 0,
  Monday = 1 << 1,
  Tuesday = 1 << 2,
  Wednesday = 1 << 3,
  Thursday = 1 << 4,
  Friday = 1 << 5,
  Saturday = 1 << 6
}

public enum AdditionalOptions
{
  None = 0,
  OptionA,
  OptionB
}

이제이 클래스를 사용하는 것이 얼마나 쉬운 지 다음과 같습니다.

public class MyViewModel : ViewModelBase
{
  public MyViewModel()
  {
    StartUp = new EnumSelection<StartTask>(StartTask.Manual);
    Days = new EnumSelection<DayOfWeek>(default(DayOfWeek));
    Options = new EnumSelection<AdditionalOptions>(AdditionalOptions.None, true, AdditionalOptions.None);
  }

  public EnumSelection<StartTask> StartUp { get; private set; }
  public EnumSelection<DayOfWeek> Days { get; private set; }
  public EnumSelection<AdditionalOptions> Options { get; private set; }
}

이 클래스를 사용하여 확인란과 라디오 버튼을 쉽게 바인딩 할 수 있습니다.

<StackPanel Orientation="Vertical">
  <StackPanel Orientation="Horizontal">
    <!-- Using RadioButtons for exactly 1 selection behavior -->
    <RadioButton IsChecked="{Binding StartUp[Manual]}">Manual</RadioButton>
    <RadioButton IsChecked="{Binding StartUp[Automatic]}">Automatic</RadioButton>
  </StackPanel>
  <StackPanel Orientation="Horizontal">
    <!-- Using CheckBoxes for 0 or Many selection behavior -->
    <CheckBox IsChecked="{Binding Days[Sunday]}">Sunday</CheckBox>
    <CheckBox IsChecked="{Binding Days[Monday]}">Monday</CheckBox>
    <CheckBox IsChecked="{Binding Days[Tuesday]}">Tuesday</CheckBox>
    <CheckBox IsChecked="{Binding Days[Wednesday]}">Wednesday</CheckBox>
    <CheckBox IsChecked="{Binding Days[Thursday]}">Thursday</CheckBox>
    <CheckBox IsChecked="{Binding Days[Friday]}">Friday</CheckBox>
    <CheckBox IsChecked="{Binding Days[Saturday]}">Saturday</CheckBox>
  </StackPanel>
  <StackPanel Orientation="Horizontal">
    <!-- Using CheckBoxes for 0 or 1 selection behavior -->
    <CheckBox IsChecked="{Binding Options[OptionA]}">Option A</CheckBox>
    <CheckBox IsChecked="{Binding Options[OptionB]}">Option B</CheckBox>
  </StackPanel>
</StackPanel>
  1. UI가로드되면 “Manual”라디오 버튼이 선택되고 “Manual”또는 “Automatic”중에서 선택을 변경할 수 있지만 둘 중 하나를 항상 선택해야합니다.
  2. 요일마다 선택이 해제되지만 그 수는 선택하거나 해제 할 수 있습니다.
  3. “옵션 A”와 “옵션 B”는 모두 처음에 체크 해제되어 있습니다. 둘 중 하나를 검사하고, 하나를 검사하면 다른 하나를 선택 취소 할 수 있지만 (RadioButtons와 유사) 이제 둘 다 선택 해제 할 수 있습니다 (WPF의 RadioButton으로 수행 할 수 없으므로 CheckBox가 사용되는 이유).

답변

이것은 Checkbox 에서도 작동합니다 .

public class EnumToBoolConverter:IValueConverter
{
    private int val;
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        int intParam = (int)parameter;
        val = (int)value;

        return ((intParam & val) != 0);
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        val ^= (int)parameter;
        return Enum.Parse(targetType, val.ToString());
    }
}

단일 열거 형을 여러 확인란에 바인딩