【发布时间】: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 绑定到一个组合框,用户可以选择他们想要过滤视图的列(即属性),我只需要设置我希望用户能够过滤的属性通过带有描述属性。如果属性类型是string、DateTime、int 或decimal,用户只需输入他们想要过滤的信息,它就会生成一个过滤器以应用于父ViewModel 的CollectionView .然后它将一个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