【问题标题】:Data binding Int property to Enum in WPF在 WPF 中将 Int 属性数据绑定到 Enum
【发布时间】:2014-01-09 12:35:24
【问题描述】:

下面是我拥有的一个类的简化示例:

public class ExampleClassDto 
{
     public int DriverTypeId
}

我还有一个 Enum,它将 DriverType 的 Id 映射到有意义的名称:

public enum DriverType
{
    None,
    Driver1,
    Driver2,
    Driver3
}

但我想在 XAML 中将它绑定到组合框。不幸的是,由于类型不匹配,它不喜欢这样。所以在我的 ViewModel 中,我必须创建第二个属性来映射两者

public class ExampleViewModel
{
    private ExampleClassDto _selectedExampleClass;
    public ExampleClassDto SelectedExampleClass
    {
        get { return _selectedExampleClass; }
        set
        {
            _selectedExampleClass = value;
            SelectedDriverType = (DriverType)_selectedExampleClass.DriverTypeId;
            OnPropertyChanged("SelectedDeviceType");
        }
    }

public DriverType SelectedDriverType
{
    get
        {
            if (_selectedDeviceType != null) 
            { 
                return (DriverType)_selectedDeviceType.DriverTypeId;
            }
            return DriverType.None;
        }
        set
        {
            _selectedDeviceType.DriverTypeId = (int) value;
            OnPropertyChanged("SelectedDriverType");
        }
}
}

然后我绑定到新属性。

<ComboBox ItemsSource="{Binding Source={StaticResource DriverTypeEnum}}" SelectedValue="{Binding SelectedDriverType, Mode=TwoWay}"/>

现在,这可行,但感觉很恶心。它使用 SelectedDriverType 作为转换器。我想避免使 DTO 的属性成为不同的类型。还有其他更优雅的解决方案吗?

谢谢!

【问题讨论】:

  • DriverTypeId 不能是 DriverType 类型吗?
  • 可以,但是 Dto 来自一个单独的库,我现在无法更改 =\
  • 那么为什么不扩展现有的类 (public partial class) 并创建一个类型为 DriverType 的属性来获取/设置 DriverTypeId 呢?
  • @gleng:我认为,在这种情况下,我宁愿只保留转换器属性。我想知道是否还有其他方法可以在 XAML 中使用 SelectedValue 或 SelectedItem,或者 ValueConverter 是否可以发挥作用
  • 我记得我做了一个通用的IntToEnum 转换器,我会大量使用它。 ConverterParameter 通常设置为 {x:Type local:MyEnum} 以将枚举类型传递给它,它会将绑定的 int 转换为该枚举值/从该枚举值转换。可能值得花点时间为您的公共图书馆制作一个。

标签: c# wpf xaml data-binding


【解决方案1】:

接受的答案要求您将枚举的类型指定为每个绑定的转换器参数。

如果您绑定到枚举属性,转换器可以从targetType 属性中确定枚举类型,这样更符合人体工程学且不易出错。

public sealed class BidirectionalEnumAndNumberConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value == null)
            return null;

        if (targetType.IsEnum)
        {
            // convert int to enum
            return Enum.ToObject(targetType, value);
        }

        if (value.GetType().IsEnum)
        {
            // convert enum to int
            return System.Convert.ChangeType(
                value,
                Enum.GetUnderlyingType(value.GetType()));
        }

        return null;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        // perform the same conversion in both directions
        return Convert(value, targetType, parameter, culture);
    }
}

无论底层枚举类型如何(intshortbyte...),这也适用。

调用时,此转换器会根据 valuetargetType 值在 int/enum 值之间翻转值的类型。源代码中没有硬编码的枚举类型,因此可重用。

【讨论】:

  • 这似乎不适用于组合框,因为所选值是对象。接收 int 时的目标类型将是 object,因此您将不知道要转换为哪个枚举。
  • @aliceraunsbaek 你是对的,绑定目标必须提供枚举类型才能使这种方法工作。在像您这样的情况下,接受的答案可能会更好。
  • 我已经通过添加一个可选的EnumType 属性来解决这个问题,该属性在设置后将取代targetType。基本上,转换器可以在两种不同的模式下运行:静态,枚举类型被显式设置;或动态,由Convert 参数确定。
【解决方案2】:

你可以有一个通用转换器说EnumConverter,它将convert int to Enum在XAML上显示它,convert back from Enum to int在你的ViewModel类中设置。

它适用于任何枚举类型。您只需要在转换器参数中传递枚举类型即可。

public class EnumConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, 
                          System.Globalization.CultureInfo culture)
    {
        Enum enumValue = default(Enum);
        if (parameter is Type)
        {
            enumValue = (Enum)Enum.Parse((Type)parameter, value.ToString());
        }
        return enumValue;
     }

     public object ConvertBack(object value, Type targetType, object parameter, 
                               System.Globalization.CultureInfo culture)
     {
         int returnValue = 0;
         if (parameter is Type)
         {
             returnValue = (int)Enum.Parse((Type)parameter, value.ToString());
         }
         return returnValue;
     }
}

XAML 用法:

<ComboBox ItemsSource="{Binding Source={StaticResource DriverTypeEnum}}"
          SelectedValue="{Binding DriverTypeId,
                                Converter={StaticResource EnumConverter}, 
                                ConverterParameter={x:Type local:DriverType}}"/>

local 是声明 DriverType 的命名空间。

【讨论】:

  • 太棒了。那效果很好。只需将以下内容添加到我的主资源字典中,我就可以使用 EnumConverter 作为资源:&lt;converter:EnumConverter xmlns:converter="clr-namespace:Some.Name.Space.Helpers" x:Key="EnumConverter"/&gt;。非常高兴我可以在其他使用枚举的 ViewModel 中重用它。谢谢!
【解决方案3】:

首先将枚举存储为 int 是一个坏主意。无论如何,我会使用双向代理属性而不是单独的类:

public int DriverTypeId { get; set; }

public DriverType IntAsEnum // proxy property, doesn't store any value, only does the conversion
{
    get { return (DriverType)DriverTypeId; }
    set { DriverTypeId = (int)value; }
}

【讨论】:

  • 您能否提供更多信息,为什么这是一个坏主意?
  • 见:原始痴迷