我有一个这样的enum:

public enum MyLovelyEnum
{
    FirstSelection,
    TheOtherSelection,
    YetAnotherOne
};

我在DataContext中有一个属性:

public MyLovelyEnum VeryLovelyEnum { get; set; }

我有三个电台按钮在我的WPF客户端。

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

现在,我如何绑定RadioButtons属性为适当的双向绑定?


当前回答

对于EnumToBooleanConverter的答案: 而不是返回DependencyProperty。考虑返回Binding。对于单选按钮IsChecked值变为false的情况不做任何事情。 前者表示有问题(可能会向用户显示一个红色矩形或类似的验证指示器),而后者只是表示不应该做任何事情,这正是在这种情况下所需要的。

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

其他回答

您可以使用更通用的转换器

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部分中,您使用:

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

我将在ListBox中使用RadioButtons,然后绑定到SelectedValue。

这是一个关于这个主题的旧帖子,但基本思想应该是相同的:http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/323d067a-efef-4c9f-8d99-fecf45522395/

我已经创建了一个新类来处理绑定RadioButtons和复选框到枚举。它适用于标记的枚举(有多个复选框选择),也适用于单个选择复选框或单选按钮的非标记枚举。它也根本不需要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>

When the UI loads, the "Manual" radio button will be selected and you can alter your selection between "Manual" or "Automatic" but either one of them must always be selected. Every day of the week will be unchecked, but any number of them can be checked or unchecked. "Option A" and "Option B" will both initially be unchecked. You can check one or the other, checking one will uncheck the other (similar to RadioButtons), but now you can also uncheck both of them (which you cannot do with WPF's RadioButton, which is why CheckBox is being used here)

处理这种情况的一种方法是在ViewModel类中使用单独的bool属性。以下是我处理这种情况的方法:

ViewModel:

public enum MyLovelyEnum { FirstSelection, TheOtherSelection, YetAnotherOne };
private MyLovelyEnum CurrentSelection;

public bool FirstSelectionProperty
{
    get
    {
        return CurrentSelection == MyLovelyEnum.FirstSelection;
    }
    set
    {
        if (value)
            CurrentSelection = MyLovelyEnum.FirstSelection;
    }
}

public bool TheOtherSelectionProperty
{
    get
    {
        return CurrentSelection == MyLovelyEnum.TheOtherSelection;
    }
    set
    {
        if (value)
            CurrentSelection = MyLovelyEnum.TheOtherSelection;
    }
}

public bool YetAnotherOneSelectionProperty
{
    get
    {
        return CurrentSelection == MyLovelyEnum.YetAnotherOne;
    }
    set
    {
        if (value)
            CurrentSelection = MyLovelyEnum.YetAnotherOne;
    }
}

XAML:

<RadioButton IsChecked="{Binding SimilaritySort, Mode=TwoWay}">Similarity</RadioButton>
<RadioButton IsChecked="{Binding DateInsertedSort, Mode=TwoWay}">Date Inserted</RadioButton>
<RadioButton IsChecked="{Binding DateOfQuestionSort, Mode=TwoWay}">Date of Question</RadioButton>
<RadioButton IsChecked="{Binding DateModifiedSort, Mode=TwoWay}">Date Modified</RadioButton>

它不像其他解决方案那样健壮或动态,但优点是它是非常自包含的,不需要创建自定义转换器或类似的东西。

对于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}"/>

缺点是必须为希望支持的每种类型定义一个转换器。