如何对实体集合提供动态过滤?

发布于 2024-09-15 14:53:24 字数 656 浏览 8 评论 0原文

我有一个继承 ObservableCollection 的类 ShipmentsCollection,其中包含装运对象(实体)。这显示在我的 ShipmentsView UserControl 上的列表框中。我的目的是允许用户在列表上方的文本框中输入内容,并使用包含该字符串的项目过滤列表,以及基于多个复选框和单选按钮的选项(交付状态和排序方向)进行过滤。

我已经尝试了几种方法,但没有一种看起来非常优雅或真正实用。我尝试过的事情如下:

  • 将 ShipmentsCollection 放入 CollectionViewSource 中并通过谓词进行过滤。无法找出使过滤器根据用户输入或选项更改自动更新的好方法。
  • 重构为继承 collectionViewSource 的类,并尝试直接在 XAML 中声明,但出现以下错误:“在配置中找不到指定的命名连接,不打算与 EntityClient 提供程序一起使用,或者无效”。尝试修复但找不到有效的解决方案。
  • 重构为继承自 CollectionView,在代码隐藏的事件处理程序中实现过滤器逻辑。仍在尝试弄清楚如何在不命名 filtertext 文本框控件的情况下将过滤器字符串获取到事件处理程序。

任何人都对在 MVVM 设计模式中实现此功能有一些好主意。我预计列表中最多有 200 个对象,因此这不会是一个巨大的过滤操作。

科里

I have a class ShipmentsCollection that inherits ObservableCollection which contains shipment objects (entities). This is displayed in a listbox on my ShipmentsView UserControl. My intent is to allow a user to type into a textbox above the list and filter the list with items that contain that string, as well as filter based on several checkbox and radiobutton based options (Delivery status and orderby direction).

I have tried this several ways, but none seem very elegant or really functional. Things I have tried follows:

  • Put ShipmentsCollection into a CollectionViewSource and filtered via predicate. Could not figure out a good way to make the filter auto update based on user typing or option change.
  • Refactored as a Class that Inherits collectionViewSource and tried to declare directly in XAML but got the following error: "The specified named connection is either not found in the configuration, not intended to be used with the EntityClient provider, or not valid". Tried fixing but could not find solution that worked.
  • Refactored to inherit from CollectionView, implemented filter logic, in event handler in codebehind. Still trying to figure out how I can get the filter string to the event handler without naming the filtertext textbox control.

Anyone got some good ideas in regard to implementing this functionality in an MVVM design pattern. I expect to have at most 200 objects in the list, so it will not be an enormous filter operation.

Cory

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

音栖息无 2024-09-22 14:53:24

我建议您的第一个选择。为了让自动过滤器根据键入工作,我会在 ViewModel 中执行类似 SearchString 属性的操作,将文本框文本绑定到该属性,并将绑定中的 UpdateSourceTrigger 设置为 PropertyChanged,以便它每次都会调用 SearchString PropertyChanged 事件键入一个键的时间,而不是等到该框失去焦点。

XAML:

<TextBox Text="{Binding SearchString, UpdateSourceTrigger=PropertyChanged}" />

ViewModel:将上述属性设置为 PropertyChanged 后,只要键入键,就会调用“Set”方法,而不仅仅是在文本框失去焦点时调用。

private string _searchString;
public string SearchString
{
    get { return _searchString; }
    set
    {
        if (_searchString != value)
        {
            _searchString = value;
            OnPropertyChanged("SearchString");
        }
    }
}

Your first option would be the one I would suggest. To get the auto-filter to work based on typing, I'd do something like a SearchString property in my ViewModel, bind the textbox text to that, and set the UpdateSourceTrigger in the binding to PropertyChanged so it will call the SearchString PropertyChanged event every time a key is typed instead of waiting until the box loses focus.

XAML:

<TextBox Text="{Binding SearchString, UpdateSourceTrigger=PropertyChanged}" />

ViewModel: With the above property set to PropertyChanged, the "Set" method gets called anytime a key is typed instead of just when the textbox loses focus.

private string _searchString;
public string SearchString
{
    get { return _searchString; }
    set
    {
        if (_searchString != value)
        {
            _searchString = value;
            OnPropertyChanged("SearchString");
        }
    }
}
青春有你 2024-09-22 14:53:24

我知道这个问题已经结束并且很老了。但对于像我这样寻找动态过滤的人,可以参考以下链接

https://github.com/lokeshlal/WPFDynamicFilters

上面的示例根据实体模型的属性上定义的属性为每个实体创建过滤器。

例如:

  1. 定义过滤器的属性

    公共类 FilterAttribute :属性
    {
        公共 FilterAttribute() { }
        公共字符串 FilterLabel { 获取;放; }
        公共对象 FilterValue { 获取;放; }
        公共字符串 FilterKey { 获取;放; }
        公共类型 FilterDataType { 获取;放; }
        公共布尔 IsDropDown { 获取;放; }
        公共字符串 DropDownList { 获取;放; }
        公共列表<对象> ObjectDropDownList { 获取;放; }
    }
    
  2. 在模型属性中应用上述属性

    公共类GridModel
    {
        [过滤器(FilterLabel = "Id",
            FilterKey = "Id",
            IsDropDown = false,
            FilterDataType = typeof(int))]
        公共 int Id { 得到;放; }
        [过滤器(FilterLabel = "名称",
            FilterKey = "名称",
            IsDropDown = false,
            FilterDataType = typeof(string))]
        公共字符串名称{获取;放; }
        [过滤器(FilterLabel = "国家/地区",
            FilterKey = "国家/地区",
            下拉=真,
            FilterDataType = typeof(int),
            DropDownList =“国家”)]
        公共字符串国家{获取;放; }
        [过滤器(FilterLabel = "地址",
            FilterKey = "地址",
            IsDropDown = false,
            FilterDataType = typeof(string))]
        公共字符串地址{获取;放; }
    }
    
  3. 定义将绑定到下拉类型的模型

    公共类国家
    {
        公共 int Id { 得到;放; } // id 将用于值
        公共字符串名称{获取;放; } // 名称将用于显示值
    }
    
  4. ViewModel

    的 模型

    公共类FilterViewModel
    {
        公共 ICommand CheckFiltersCommand { 获取;放; }
        公共 FilterViewModel()
        {
            CheckFiltersCommand = new DelegateCommand(GetFilters);
            GridSource = new List();
            GridSource.Add(new GridModel() { Id = 1, Name = "Name1", Country = "丹麦" });
            GridSource.Add(new GridModel() { Id = 2, Name = "Name2", Country = "印度" });
            GridSource.Add(new GridModel() { Id = 3, Name = "Name3", Country = "澳大利亚" });
            GridSource.Add(new GridModel() { Id = 4, Name = "Name4", Country = "印度" });
            GridSource.Add(new GridModel() { Id = 5, Name = "Name5", Country = "澳大利亚" });
            GridSource.Add(new GridModel() { Id = 6, Name = "Name6", Country = "香港" });
    
            FilterControlViewModel = new FilterControlViewModel();
            FilterControlViewModel.FilterDetails = new List();
    
            foreach(typeof(GridModel).GetProperties() 中的 var 属性)
            {
                if (property.GetCustomAttributes(true).Where(attr => attr.GetType() == typeof(FilterAttribute)).Any())
                {
                    var attribute = (FilterAttribute)property.GetCustomAttributes(true).Where(attr => attr.GetType() == typeof(FilterAttribute)).First();
                    FilterControlViewModel.FilterDetails.Add(属性);
                }
            }
        }
    
        私有无效 GetFilters()
        {
            FilterCollection = new Dictionary<字符串, 对象>();
            foreach(FilterControlViewModel.FilterDetails 中的 var 过滤器)
            {
                if (过滤器.IsDropDown)
                {
                    if (filter.FilterValue != null)
                        FilterCollection.Add(filter.FilterKey, filter.FilterValue.GetType().GetProperty("Id").GetValue(filter.FilterValue));
                }
                别的
                {
                    FilterCollection.Add(filter.FilterKey, filter.FilterValue);
                }
            }
    
            MessageBox.Show(string.Join(", ", FilterCollection.Select(m => m.Key + ":" + Convert.ToString(m.Value)).ToArray()));
        }
    
        公共列表网格源{获取;放; }
    
        公共字典<字符串,对象>过滤器集合 { 获取;放; }
    
        公共 FilterControlViewModel FilterControlViewModel { 获取;放; }
    }
    

在上面的视图模型中,'FilterControlViewModel'属性将迭代模型的所有属性并收集属性的过滤器信息。
这个相同的属性将被分配给用户控件,如下面的 xaml 文件中所述

    <Window x:Class="WPFDynamicFilters.GridWithFilters"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:WPFDynamicFilters"
            mc:Ignorable="d"
            Title="gridwithfilters" Height="481.239" Width="858.171">
        <Grid>
            <Grid HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" x:Name="FilterGrid" Height="209" Width="830">
                <Border BorderThickness="1" BorderBrush="Gold"/>
                <local:Filter DataContext="{Binding FilterControlViewModel, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Margin="0,0,0,10" />
            </Grid>
            <DataGrid x:Name="DataGrid" ItemsSource="{Binding GridSource}" HorizontalAlignment="Left" Margin="10,294,0,0" VerticalAlignment="Top" Height="146" Width="830"/>
            <Button x:Name="button" Content="Check Filters" HorizontalAlignment="Left" Margin="10,245,0,0" VerticalAlignment="Top" Width="110" Command="{Binding CheckFiltersCommand}"/>
        </Grid>
    </Window>

过滤器控件将获取所有属性并使用 itemscontrol 呈现控件

    <UserControl x:Class="WPFDynamicFilters.Filter"
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
                 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
                 xmlns:local="clr-namespace:WPFDynamicFilters"
                 mc:Ignorable="d" 
                 d:DesignHeight="40" 
                 >
        <UserControl.Resources>
            <DataTemplate x:Key="TStringTemplate">
                <StackPanel FlowDirection="LeftToRight">
                    <TextBlock Text="{Binding FilterKey}" />
                    <TextBox x:Name="TxtFieldValue" 
                         Text="{Binding FilterValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"   
                         RenderTransformOrigin="-9.3,0.5" Width="200" FontSize="16" TextAlignment="Left" VerticalAlignment="Center"/>
                </StackPanel>
            </DataTemplate>
            <DataTemplate x:Key="TIntegerTemplate">
                <StackPanel FlowDirection="LeftToRight">
                    <TextBlock Text="{Binding FilterKey}" />
                    <TextBox x:Name="IntFieldValue" 
                         Text="{Binding FilterValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"   
                         RenderTransformOrigin="-9.3,0.5" Width="200" FontSize="16" TextAlignment="Left" VerticalAlignment="Center"/>
                </StackPanel>
            </DataTemplate>
            <DataTemplate x:Key="TDropDownTemplate">
                <StackPanel FlowDirection="LeftToRight">
                    <TextBlock Text="{Binding FilterKey}" />
                    <ComboBox 
                    SelectedItem="{Binding FilterValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
                          SelectedIndex="{Binding FilterValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
                          ItemsSource="{Binding ObjectDropDownList, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
                          DisplayMemberPath="Name"
                                 RenderTransformOrigin="-9.3,0.5" Width="200" FontSize="16" />
                </StackPanel>
            </DataTemplate>
            <local:FilterTemplateSelector x:Key="FilterTemplateSelector" 
                    StringTemplate="{StaticResource TStringTemplate}"
                    IntegerTemplate="{StaticResource TIntegerTemplate}"
                    DropDownTemplate="{StaticResource TDropDownTemplate}"
                    />
        </UserControl.Resources>
        <Grid>
            <ItemsControl ItemsSource="{Binding FilterDetails}" >
                <ItemsControl.ItemsPanel>
                    <ItemsPanelTemplate>
                        <UniformGrid Columns="3" />
                    </ItemsPanelTemplate>
                </ItemsControl.ItemsPanel>
                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                            <ContentControl 
                                Content="{Binding}" 
                                HorizontalAlignment="Left" 
                                ContentTemplateSelector="{StaticResource FilterTemplateSelector}" />
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
            </ItemsControl>
        </Grid>
    </UserControl>

最后定义模板选择器

public class FilterTemplateSelector : DataTemplateSelector
    {
        public DataTemplate StringTemplate { get; set; }
        public DataTemplate IntegerTemplate { get; set; }
        public DataTemplate DropDownTemplate { get; set; }

        public override DataTemplate SelectTemplate(object item,
                     DependencyObject container)
        {
            var filter = (item as FilterAttribute);
            if (filter == null) return StringTemplate;

            if (!filter.IsDropDown)
            {
                switch (filter.FilterDataType.Name.ToLower())
                {
                    case "int32":
                    case "int64":
                        return IntegerTemplate;
                    case "string":
                        return StringTemplate;
                }
            }
            else
            {
                // display drop down
                switch (filter.DropDownList)
                {
                    case "Country":
                        filter.ObjectDropDownList = GetDropDown.GetCountries().ToList<object>();
                        break;
                }
                return DropDownTemplate;
            }

            return StringTemplate;
        }
    }

I know this question is closed and old. but for someone like me searching for dynamic filtering, can refer to the following link

https://github.com/lokeshlal/WPFDynamicFilters

the above example creates filters for each entity based on the attribute defined on property of entity model.

As an example:

  1. Define an attribute for filters

    public class FilterAttribute : Attribute
    {
        public FilterAttribute() { }
        public string FilterLabel { get; set; }
        public object FilterValue { get; set; }
        public string FilterKey { get; set; }
        public Type FilterDataType { get; set; }
        public bool IsDropDown { get; set; }
        public string DropDownList { get; set; }
        public List<object> ObjectDropDownList { get; set; }
    }
    
  2. Apply the above attribute in model properties

    public class GridModel
    {
        [Filter(FilterLabel = "Id",
            FilterKey = "Id",
            IsDropDown = false,
            FilterDataType = typeof(int))]
        public int Id { get; set; }
        [Filter(FilterLabel = "Name",
            FilterKey = "Name",
            IsDropDown = false,
            FilterDataType = typeof(string))]
        public string Name { get; set; }
        [Filter(FilterLabel = "Country",
            FilterKey = "Country",
            IsDropDown = true,
            FilterDataType = typeof(int),
            DropDownList = "Country")]
        public string Country { get; set; }
        [Filter(FilterLabel = "Address",
            FilterKey = "Address",
            IsDropDown = false,
            FilterDataType = typeof(string))]
        public string Address { get; set; }
    }
    
  3. Define the model that will bind to the drop down type

    public class Country
    {
        public int Id { get; set; } // id will be used for value
        public string Name { get; set; } // Name will be used for display value
    }
    
  4. ViewModel of actual View

    public class FilterViewModel
    {
        public ICommand CheckFiltersCommand { get; set; }
        public FilterViewModel()
        {
            CheckFiltersCommand = new DelegateCommand(GetFilters);
            GridSource = new List<GridModel>();
            GridSource.Add(new GridModel() { Id = 1, Name = "Name1", Country = "Denmark" });
            GridSource.Add(new GridModel() { Id = 2, Name = "Name2", Country = "India" });
            GridSource.Add(new GridModel() { Id = 3, Name = "Name3", Country = "Australia" });
            GridSource.Add(new GridModel() { Id = 4, Name = "Name4", Country = "India" });
            GridSource.Add(new GridModel() { Id = 5, Name = "Name5", Country = "Australia" });
            GridSource.Add(new GridModel() { Id = 6, Name = "Name6", Country = "Hongkong" });
    
            FilterControlViewModel = new FilterControlViewModel();
            FilterControlViewModel.FilterDetails = new List<FilterAttribute>();
    
            foreach (var property in typeof(GridModel).GetProperties())
            {
                if (property.GetCustomAttributes(true).Where(attr => attr.GetType() == typeof(FilterAttribute)).Any())
                {
                    var attribute = (FilterAttribute)property.GetCustomAttributes(true).Where(attr => attr.GetType() == typeof(FilterAttribute)).First();
                    FilterControlViewModel.FilterDetails.Add(attribute);
                }
            }
        }
    
        private void GetFilters()
        {
            FilterCollection = new Dictionary<string, object>();
            foreach (var filter in FilterControlViewModel.FilterDetails)
            {
                if (filter.IsDropDown)
                {
                    if (filter.FilterValue != null)
                        FilterCollection.Add(filter.FilterKey, filter.FilterValue.GetType().GetProperty("Id").GetValue(filter.FilterValue));
                }
                else
                {
                    FilterCollection.Add(filter.FilterKey, filter.FilterValue);
                }
            }
    
            MessageBox.Show(string.Join(", ", FilterCollection.Select(m => m.Key + ":" + Convert.ToString(m.Value)).ToArray()));
        }
    
        public List<GridModel> GridSource { get; set; }
    
        public Dictionary<string, object> FilterCollection { get; set; }
    
        public FilterControlViewModel FilterControlViewModel { get; set; }
    }
    

In the above view model 'FilterControlViewModel' property will iterate all property of model and collect the filter information of the properties.
This same property will be assigned to the user control as explained in xaml file below

    <Window x:Class="WPFDynamicFilters.GridWithFilters"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:WPFDynamicFilters"
            mc:Ignorable="d"
            Title="gridwithfilters" Height="481.239" Width="858.171">
        <Grid>
            <Grid HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" x:Name="FilterGrid" Height="209" Width="830">
                <Border BorderThickness="1" BorderBrush="Gold"/>
                <local:Filter DataContext="{Binding FilterControlViewModel, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Margin="0,0,0,10" />
            </Grid>
            <DataGrid x:Name="DataGrid" ItemsSource="{Binding GridSource}" HorizontalAlignment="Left" Margin="10,294,0,0" VerticalAlignment="Top" Height="146" Width="830"/>
            <Button x:Name="button" Content="Check Filters" HorizontalAlignment="Left" Margin="10,245,0,0" VerticalAlignment="Top" Width="110" Command="{Binding CheckFiltersCommand}"/>
        </Grid>
    </Window>

Filter control will take all the attributes and render the control using itemscontrol

    <UserControl x:Class="WPFDynamicFilters.Filter"
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
                 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
                 xmlns:local="clr-namespace:WPFDynamicFilters"
                 mc:Ignorable="d" 
                 d:DesignHeight="40" 
                 >
        <UserControl.Resources>
            <DataTemplate x:Key="TStringTemplate">
                <StackPanel FlowDirection="LeftToRight">
                    <TextBlock Text="{Binding FilterKey}" />
                    <TextBox x:Name="TxtFieldValue" 
                         Text="{Binding FilterValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"   
                         RenderTransformOrigin="-9.3,0.5" Width="200" FontSize="16" TextAlignment="Left" VerticalAlignment="Center"/>
                </StackPanel>
            </DataTemplate>
            <DataTemplate x:Key="TIntegerTemplate">
                <StackPanel FlowDirection="LeftToRight">
                    <TextBlock Text="{Binding FilterKey}" />
                    <TextBox x:Name="IntFieldValue" 
                         Text="{Binding FilterValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"   
                         RenderTransformOrigin="-9.3,0.5" Width="200" FontSize="16" TextAlignment="Left" VerticalAlignment="Center"/>
                </StackPanel>
            </DataTemplate>
            <DataTemplate x:Key="TDropDownTemplate">
                <StackPanel FlowDirection="LeftToRight">
                    <TextBlock Text="{Binding FilterKey}" />
                    <ComboBox 
                    SelectedItem="{Binding FilterValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
                          SelectedIndex="{Binding FilterValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
                          ItemsSource="{Binding ObjectDropDownList, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
                          DisplayMemberPath="Name"
                                 RenderTransformOrigin="-9.3,0.5" Width="200" FontSize="16" />
                </StackPanel>
            </DataTemplate>
            <local:FilterTemplateSelector x:Key="FilterTemplateSelector" 
                    StringTemplate="{StaticResource TStringTemplate}"
                    IntegerTemplate="{StaticResource TIntegerTemplate}"
                    DropDownTemplate="{StaticResource TDropDownTemplate}"
                    />
        </UserControl.Resources>
        <Grid>
            <ItemsControl ItemsSource="{Binding FilterDetails}" >
                <ItemsControl.ItemsPanel>
                    <ItemsPanelTemplate>
                        <UniformGrid Columns="3" />
                    </ItemsPanelTemplate>
                </ItemsControl.ItemsPanel>
                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                            <ContentControl 
                                Content="{Binding}" 
                                HorizontalAlignment="Left" 
                                ContentTemplateSelector="{StaticResource FilterTemplateSelector}" />
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
            </ItemsControl>
        </Grid>
    </UserControl>

Finally define the template selector

public class FilterTemplateSelector : DataTemplateSelector
    {
        public DataTemplate StringTemplate { get; set; }
        public DataTemplate IntegerTemplate { get; set; }
        public DataTemplate DropDownTemplate { get; set; }

        public override DataTemplate SelectTemplate(object item,
                     DependencyObject container)
        {
            var filter = (item as FilterAttribute);
            if (filter == null) return StringTemplate;

            if (!filter.IsDropDown)
            {
                switch (filter.FilterDataType.Name.ToLower())
                {
                    case "int32":
                    case "int64":
                        return IntegerTemplate;
                    case "string":
                        return StringTemplate;
                }
            }
            else
            {
                // display drop down
                switch (filter.DropDownList)
                {
                    case "Country":
                        filter.ObjectDropDownList = GetDropDown.GetCountries().ToList<object>();
                        break;
                }
                return DropDownTemplate;
            }

            return StringTemplate;
        }
    }
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文