如何将 wpf 组合框绑定到枚举类型并将其显示在组合框中作为定义枚举的描述?

问题描述 投票:0回答:2
public enum IVAC_VT_SHIFT_SPD
{
    [Description("16.1299991607666")]
    DATA16,
    [Description("32.130001068115234")]
    DATA32,
}
<ComboBox Grid.Row="8"
          Grid.Column="2"
          Width="194"
          Height="40"
          Margin="0,0,3,0"
          HorizontalAlignment="Right"
          VerticalContentAlignment="Center"
          ItemsSource="{Binding Source={StaticResource IVAC_VT_SHIFT_SPD}}"
          SelectedValue="{Binding VTShiftSPD}" />

<ObjectDataProvider x:Key="IVAC_VT_SHIFT_SPD"
                    MethodName="GetValues"
                    ObjectType="{x:Type System:Enum}">
    <ObjectDataProvider.MethodParameters>
        <x:Type TypeName="enm:IVAC_VT_SHIFT_SPD" />
    </ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
private IVAC_VT_SHIFT_SPD _VTShiftSPD = IVAC_VT_SHIFT_SPD.DATA16;
public IVAC_VT_SHIFT_SPD VTShiftSPD
{
    get => _VTShiftSPD;
    set
    {
        _VTShiftSPD = value;
        OnPropertyChanged(nameof(VTShiftSPD));
    }
}

我想要的是将枚举类型值绑定到组合框。但是,我希望组合框的内容显示为说明的内容。该选择必须选择为枚举。

  1. 有没有不用转换器的方法?
  2. 有没有办法只用Xaml来完成它?
  3. 我想要一个简单的方法。
wpf data-binding enums combobox
2个回答
0
投票

我认为您可以非常接近地实现您正在寻找的目标。我确实使用了几个 C# 类来完成它,但这些类将适用于未来的任何枚举,而无需您不断创建其他类或代码来方便在下拉列表中获取描述。

在我的示例中,我能够产生以下结果

public enum IVAC_VT_SHIFT_SPD
{
   [Description("16.1299991607666")]
   DATA16,
   [Description("32.130001068115234")]
   DATA32,
}
<Window ...
   xmlns:local="clr-namespace:StackOverflowAnswers.Wpf"
   ...>

<ComboBox x:Name="Combo"
   ItemsSource="{Binding Source={enums:EnumBindingSource {x:Type enums:IVAC_VT_SHIFT_SPD}}}"
   DisplayMemberPath="Description"
   SelectedValuePath="Value" />

为了促进这一点,我使用了此链接中概述的方法的改编版:https://brianlagunas.com/a-better-way-to-data-bind-enums-in-wpf/。如果您直接遵循这一点,则无需枚举的 ViewModel 即可获得非常相似的结果。然而,我喜欢使用 ViewModel,因为它允许我根据需要调整一些东西,比如添加对图标属性的支持。

在我的示例中,我创建了一个

EnumViewModel
类来容纳组合框中将显示的内容:

    public class EnumViewModel
    {
        public EnumViewModel(Enum value)
        {
            Value = value;
            Description = getDescription(value);
        }

        public Enum Value { get; }
        public string Description { get; }

        private string getDescription(Enum value)
        {
            if (!(value?.GetType().GetField(value?.ToString()) is FieldInfo enumField))
                // value is null...
                return string.Empty;

            var descriptionAttribute = enumField.GetCustomAttributes(typeof(DescriptionAttribute), false)
                .OfType<DescriptionAttribute>()
                .FirstOrDefault();

            if (descriptionAttribute is null || string.IsNullOrEmpty(descriptionAttribute.Description))
                // description attribute is missing or blank...
                return value.ToString();

            return descriptionAttribute.Description;
        }
    }

还有一个

MarkupExtension
允许我直接绑定到它,而不必将其包含在视图模型或后面的代码中。这设置了使用
Source={enums:EnumBindingSource ...}
的能力。

    public class EnumBindingSourceExtension : MarkupExtension
    {
        private Type _enumType;
        public Type EnumType
        {
            get => this._enumType;
            set
            {
                if (value == this._enumType) return;

                if (!isEnumType(value)) throw new ArgumentException("Type must be for an Enum.");

                this._enumType = value;
            }
        }

        private bool isEnumType(Type type)
        {
            if (type is null) return false;
            var enumType = Nullable.GetUnderlyingType(type) ?? type;
            return enumType.IsEnum;
        }

        public EnumBindingSourceExtension() { }

        public EnumBindingSourceExtension(Type enumType)
        {
            this.EnumType = enumType;
        }

        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            if (this._enumType is null)
                throw new InvalidOperationException("The EnumType must be specified.");

            var actualEnumType = Nullable.GetUnderlyingType(this._enumType) ?? this._enumType;
            var enumValues = Enum.GetValues(actualEnumType)
                .OfType<Enum>()
                .Select(x => new EnumViewModel(x))
                .ToList();

            return enumValues;
        }
    }

-1
投票

枚举不应该显示在 UI 中。枚举是整数的简单枚举,其中每个值都有一个关联的名称。
您的场景根本超出了

enum
类型的意图。

如果您想定义一组常量,其中每个常量携带数据,那么您应该定义一个类型(

class
struct
)。这也将为您在视图中呈现数据提供更大的灵活性,而无需转换器和反射的额外开销。您最终会得到更干净、自然/直观的代码。在您的场景中使用
enum
并不优雅,也没有任何好处。
enum
必须具有代表性,属性或表中不存储任何元数据。

如果

enum
本身确实有意义,您甚至可以考虑使用此自定义类型来定义
enum
的属性 - 仅当该类型不是
enum
的扩展(用于携带元数据)时才会出现这种情况数据),因为
enum
值必须能够独立,即在没有元数据的情况下使用。

例如,如果您创建一个枚举

Color
,其中包含
Color.Red
等值,并且如果没有实际的红色值,则无法使用这些值,那么
Color.Red
不应该是枚举,而应该是
class
 struct
能够定义该元数据。

建议的解决方案如下所示:

VTShiftSPD.cs

// TODO::Implement IEquatable<VTShiftSPD> (don't forget to override GetHashCode)
public readonly struct VTShiftSPD : IEquatable<VTShiftSPD>
{
  public const string Data16 = "DATA16";
  public const string Data32 = "DATA32";

  public VTShiftSPD(string id, double value)
  {
    this.Id = id;
    this.Value = value;
  }

  public string Id { get; }
  public double Value { get; }
}

您可以使用和比较它,例如如下(没有任何反映!):

static Main()
{
  var vTShiftSPD = new VTShiftSPD(VTShiftSPD.Data16, 16.1299991607666);
  Evaluate(vTShiftSPD);
}

private static void Evaluate(VTShiftSPD vTShiftSPD)
{
  switch (vTShiftSPD.Id)
  {
    case VTShiftSPD.Data16: break;
    case VTShiftSPD.Data32: break;
    default: break;
  }
}

如果从

VTShiftSPD
创建的实例集不是开放集,您甚至可以将它们本身定义为常量:

VTShiftSPDs.cs

static class VTShiftSPDs
{
  public static readonly VTShiftSPD Spd16 = new VTShiftSPD(VTShiftSPD.Data16, 16.1299991607666);
  public static readonly VTShiftSPD Spd32 = new VTShiftSPD(VTShiftSPD.Data32, 32.130001068115234);
}

并按如下方式使用它:

static Main()
{
  Evaluate(VTShiftSPDs.Spd16);
}

private static void Evaluate(VTShiftSPD vTShiftSPD)
{
  switch (vTShiftSPD.Id)
  {
    case VTShiftSPD.Data16: break;
    case VTShiftSPD.Data32: break;
    default: break;
  }
}

并优雅(且高效)地使用它来填充数据视图:

MainWindow.xaml.cs

// The data source for the ComboBox.ItemsSource
public ObservableCollection<VTShiftSPD> VTShiftSPDs { get; } 
  = new ObservableCollection<VTShiftSPD> 
  { 
    VTShiftSPDs.Spd16,
    VTShiftSPDs.Spd32,
  };

MainWindow.xaml

<Window>
  <StackPanel>
    <ComboBox x:Name="VTShiftSPDSelector"
              ItemsSource="{Binding RelativeSource={RelativeSource AncestorType Window}, Path=VTShiftSPDs}"
              DisplayMemberPath="Value" />

    <Stackpanel DataContext="{Binding Elementname=VTShiftSPDSelector, Path=SelectedItem}>
      <TextBlock x:Name="Id"
                 Text{Binding Id}" /> <!-- Displays e.g. DATA16 -->
      <TextBlock x:Name="Value"
                 Text{Binding Value}" /> <!-- Displays e.g. 16.1299991607666 -->
    </StackPanel>
  </StackPanel>
</Window>
© www.soinside.com 2019 - 2024. All rights reserved.