【问题标题】:Can I get an enum value having a given description attribute using a generic function?我可以使用通用函数获取具有给定描述属性的枚举值吗?
【发布时间】:2023-03-27 00:45:01
【问题描述】:

我正在创建一个通用过滤用户控件,以允许用户在 WPF 应用程序上的 CollectionView 上应用各种过滤器。

所以,我有一个充满实体和属性的CollectionView。因此,我没有为每个实体创建不同的用户控件,而是想出了这个:

foreach (PropertyInfo propertyInfo in _typeMessaging.Mensagem.GetProperties())
{
    var attrs = propertyInfo.GetCustomAttributes(true);
    foreach (object attr in attrs)
    {
        if (attr is DescriptionAttribute descr)
            fields.Add(new FilteringInfo() { Property = propertyInfo, Description = descr.Description }); ;
    }
}

foreach (FilteringInfo filteringInfo in fields.OrderBy(x => x.Property.Name))
{
    Columns.Add(filteringInfo);
}

所以我只需将Columns 绑定到一个组合框,用户可以选择他们想要过滤视图的列(即属性),我只需要设置我希望用户能够过滤的属性通过带有描述属性。如果属性类型是stringDateTimeintdecimal,用户只需输入他们想要过滤的信息,它就会生成一个过滤器以应用于父ViewModelCollectionView .然后它将一个FilteringInfo 对象返回给父ViewModel,它具有选择的PropertyInfo 和用户想要过滤的值,前面是一个过滤词作为参数。

这个FilteringInfo 被传递给一个FiltersCollection,它存储用户请求的所有过滤器并返回一个Filter 以添加到CollectionView

public class FiltersCollection
{
    private readonly GroupFilter _filtros = new();
    public Predicate<object> AddNewFilter(EntityBase entity)
    {
        FilteringInfo filteringInfo = entity as FilteringInfo;
        switch (filteringInfo.FilterInfo.Split(':')[0])
        {
            case "wholefield":
                _filtros.AddFilter(x => x is EntityBase entityBase && ((string)entityBase.GetPropValue(filteringInfo.Property.Name)).Equals(filteringInfo.FilterInfo.Split(':')[1], StringComparison.OrdinalIgnoreCase));
                break;
            case "contains":
                _filtros.AddFilter(x => x is EntityBase entityBase && ((string)entityBase.GetPropValue(filteringInfo.Property.Name)).Contains(filteringInfo.FilterInfo.Split(':')[1], StringComparison.OrdinalIgnoreCase));
                break;
            case "startswith":
                _filtros.AddFilter(x => x is EntityBase entityBase && ((string)entityBase.GetPropValue(filteringInfo.Property.Name)).StartsWith(filteringInfo.FilterInfo.Split(':')[1], StringComparison.OrdinalIgnoreCase));
                break;
            case "datebetween":
                string[] dates = filteringInfo.FilterInfo.Split(':')[1].Split(';');
                DateTime start = DateTime.Parse(dates[0]);
                DateTime end = DateTime.Parse(dates[1]).AddDays(1).AddSeconds(-1);
                _filtros.AddFilter(x => x is EntityBase entityBase && ((DateTime)entityBase.GetPropValue(filteringInfo.Property.Name)).IsBetween(start, end));
                break;
            case "valuebetween":
                string[] valuesBetween = filteringInfo.FilterInfo.Split(':')[1].Split(';');
                decimal startValue = decimal.Parse(valuesBetween[0]);
                decimal endValue = decimal.Parse(valuesBetween[1]);
                _filtros.AddFilter(x => x is EntityBase entityBase && ((decimal)entityBase.GetPropValue(filteringInfo.Property.Name)).IsBetween(startValue, endValue));
                break;
            case "enumvalue":
                _filtros.AddFilter(x => x is EntityBase entityBase && ((Enum)entityBase.GetPropValue(filteringInfo.Property.Name)).Equals(Enum.Parse(filteringInfo.Property.PropertyType, filteringInfo.FilterInfo.Split(':')[1])));
                break;
            case "abovevalue":
                string[] values = filteringInfo.FilterInfo.Split(':')[1].Split(';');
                if (filteringInfo.Property.PropertyType == typeof(int))
                {
                    int headValue = int.Parse(values[0]);
                    _filtros.AddFilter(x => x is EntityBase entityBase && (int)entityBase.GetPropValue(filteringInfo.Property.Name) >= headValue);
                }
                if (filteringInfo.Property.PropertyType == typeof(decimal))
                {
                    decimal headValue = decimal.Parse(values[0]);
                    _filtros.AddFilter(x => x is EntityBase entityBase && (decimal)entityBase.GetPropValue(filteringInfo.Property.Name) >= headValue);
                }
                break;
            case "clearfilters":
                _filtros.RemoveAllFilters();
                return null;
        }
        return _filtros.Filter;
    }
}

组过滤器:

public class GroupFilter
{
    private List<Predicate<object>> _filters;

    public Predicate<object> Filter { get; private set; }

    public GroupFilter()
    {
        _filters = new List<Predicate<object>>();
        Filter = InternalFilter;
    }

    private bool InternalFilter(object o)
    {
        foreach (var filter in _filters)
        {
            if (!filter(o))
            {
                return false;
            }
        }

        return true;
    }

    public void AddFilter(Predicate<object> filter)
    {
        _filters.Add(filter);
    }

    public void RemoveFilter(Predicate<object> filter)
    {
        if (_filters.Contains(filter))
        {
            _filters.Remove(filter);
        }
    }

    public void RemoveAllFilters()
    {
        _filters.Clear();
    }
}

问题是用户想要过滤的属性是enum。我可以轻松地使用转换器使用enum 的描述属性填充组合框:

public class EnumDescriptionConverter : IValueConverter
{
    private string GetEnumDescription(Enum enumObj)
    {
        if (enumObj is null) return String.Empty;
        if (Enum.IsDefined(enumObj.GetType(), enumObj) is false) return String.Empty;

        FieldInfo fieldInfo = enumObj.GetType().GetField(enumObj.ToString());

        object[] attribArray = fieldInfo.GetCustomAttributes(false);

        if (attribArray.Length == 0)
        {
            return enumObj.ToString();
        }
        else
        {
            DescriptionAttribute attrib = attribArray[0] as DescriptionAttribute;
            return attrib.Description;
        }
    }

    object IValueConverter.Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        Enum myEnum = (Enum)value;
        string description = GetEnumDescription(myEnum);
        return description;
    }
}

但是,我很难从给定的描述中获得Enum。我发现https://stackoverflow.com/a/3422440/ 表明我可以使用 LINQ 遍历Enum.GetValues(myEnum),但它需要传递我想要评估的枚举,而绑定不会;据转换器所知,它试图转换回的目标类型只是Enum

我尝试传递用于填充可用值的enums 列表,以便ConvertBack 可以使用它,但我被告知绑定数据不能用作转换器参数。有没有办法我可以做到这一点?如果没有,我还有其他方法可以做到吗?

【问题讨论】:

  • 从您想要实现的目标来看,您可以更轻松地实现 OData,而不是使用 Reflection 绕道而行。
  • 您想要一个特定于每个属性的过滤器功能/字段,还是一个适合您的所有功能/字段?
  • 它是在 ASP.NET Web 应用程序上过滤、排序和分页的工具,它通过在控制器上设置开箱即用的默认注释来工作,前端告诉后端要做什么通过 QueryString 过滤、排序或分页
  • 您需要两个转换器。第一个将获取枚举类型并列出其值。您将此列表传递给视图以供用户选择。假设 ComboBox.ItemsSource。第二个转换器接收枚举值之一,如果它有要表示的属性,则返回其字符串表示。如果没有属性,则返回接收到的枚举值的字符串表示。此转换器已用于表示值集合中的一项。
  • 因此,用户将看到枚举属性列表,但实际上会选择枚举值。而且你已经可以轻松使用他选择的值了。

标签: c# wpf enums ivalueconverter


【解决方案1】:

如果IValueConverter.Convert中的targetType参数真的只给你Enum,而不是你需要转换成的具体枚举类型,那么我认为单独从Description值转换不会成为可能。实际上,无论如何它可能并不可靠,因为没有什么能阻止任何人在同一个枚举中创建两个不同的值并给它们相同的描述(从而导致歧义)。

这是我的建议:不要返回 string,而是返回自定义 struct。像这样的:

public struct EnumValue
{
    public EnumValue(Enum value, string description)
    {
        Value = value;
        Description = description;
    }

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

    public override string ToString()
    {
        return Description;
    }
}

返回类似上面的内容,而不仅仅是描述 string 值,将允许您通过读取 Value 属性转换回枚举值。

(您还可以更进一步,将检索描述的实际逻辑放入EnumValue 结构中,从构造函数中删除description 参数。)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-02-19
    • 1970-01-01
    • 2011-05-21
    • 1970-01-01
    • 2018-01-07
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多