简体   繁体   中英

How predicate works with ListCollectionView

How predicate filters works with ListCollectionView? In my case i have ListCollectionView<Users> FilteredUserList . To filter single value i am using

private void AddFilterAndRefresh(string name, Predicate<User> predicate)
    {
        //Adds filter to filter list
        Filters.Add(name, predicate);

        //Filters doesn't fire event automatically
        OnPropertyChanged("Filters");

        //Refresh list to by correctly displayed
        FilteredUserList.Refresh();
    }

Usage example AddFilterAndRefresh(key, user => user.Name.Contains(u.Name))

Now things from here is not understandable for me. When i use function above, in datagridview is shown only one row. So it means that before filter all values are "true" to show, but when i pass filter all values becomes false and one value true?

For stacking filters i use

private bool FilterEntries(object obj)
    {
        User c = (User)obj;
        return Filters.Values.Aggregate(true, (prevValue, predicate) => prevValue && predicate(c));
    }

But I want to make excel like filter, when user checks values what to show. It means that i have to filter multiple values. When i do foreach(User u in SelectedOptions) {AddFilterAndRefresh()} Datagrid is empty - but it is obvious because after one filter datagrid shows one row. So how to filter multiple values?

Well. I did some modifications. It works but not correctly. Imagine I have user list:

  • Tabelis Name Departament
  • 5 Marius Some
  • 20 Darius unknown
  • 20 Koste unknown
  • 20 Gediminas unknown
  • 20 Nerijus tech

Now if i exclude by Departament "unknown" (uncheck chekbox) it filters ok:

  • Tabelis Name Departament
  • 5 Marius Some
  • 20 Nerijus tech

Now when i uncheck by Tabelis "20" it filter ok:

  • Tabelis Name Departament
  • 5 Marius Some

But when i out check on Tabelis "20" again it filters wrong:

  • Tabelis Name Departament
  • 5 Marius Some
  • 20 Darius unknown
  • 20 Koste unknown
  • 20 Gediminas unknown
  • 20 Nerijus tech

Turns Departament values on.

Where i am doing a mistake? view:

       <DataGrid x:Name="myGrd"   
              DataContext="{Binding ElementName=userPage, Path=DataContext}"
              SelectionMode="Single"    
              SelectionUnit="Cell"
              CurrentItem="{Binding SelectedUser, Mode=TwoWay}"
              CurrentColumn="{Binding CurrentColumn, Mode=TwoWay}"
              IsReadOnly="True"
              CanUserResizeColumns="False"
              Grid.Row="1" 
              ItemsSource="{Binding FilteredUserList}" 
              AutoGenerateColumns="True"             
              CanUserAddRows="False">
        <DataGrid.Resources>

            <!--Popup-->
            <ContextMenu x:Key="ContextMenu">
                <ContextMenu.Items>
                    <MenuItem Header="Filter by Selection" Command="{Binding IncludeCommand, Source={x:Reference vm}}"/>
                    <MenuItem Header="Filter exclude Selection" Command="{Binding ExcludeCommand, Source={x:Reference vm}}"/>
                    <MenuItem Header="Remove all Filters" Command="{Binding RemoveAllFiltersCommand, Source={x:Reference vm}}" Visibility="{Binding Filters.Count, Source={x:Reference vm}, Converter={Wpf:VisibilityConverter}}"/>
                </ContextMenu.Items>
            </ContextMenu>

            <!--Custom Datagrid header View-->               
            <Style TargetType="DataGridColumnHeader" x:Name="FilterHeader">
                <Setter Property="ContentTemplate">
                    <Setter.Value>
                        <DataTemplate>
                            <StackPanel>
                                <TextBox Margin="0,0,0,10" Width="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGridColumnHeader}}, Path=Width}" />
                                <StackPanel Orientation="Horizontal">
                                    <TextBlock Text="{Binding}" HorizontalAlignment="Center"/>
                                    <ToggleButton Name="FilterButton" 
                                                  Content="[-F-]" 
                                                  Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=DataGrid}, Path=DataContext.GenerateExcelFilterViewItemsCommand}" 
                                                  CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=DataGridColumnHeader}, Path=Content}"
                                                  >
                                    </ToggleButton>

                                    <Popup Name="MyPopup"                              
                                           StaysOpen="False" 
                                           Placement="Right"
                                           IsOpen="{Binding ElementName=FilterButton, Path=IsChecked}"
                                           PlacementTarget="{Binding ElementName=FilterButton}"
                                           >
                                        <Border CornerRadius="0" BorderThickness="2" BorderBrush="{StaticResource AQBlueBrush}">
                                        <StackPanel Width="Auto" Background="{StaticResource AQBackgroundLightBrush}" MinWidth="100">
                                            <TextBlock Margin="2" Text="Filter" Foreground="{StaticResource AQVeryDarkBlueBrush}"/>
                                                <Separator/>
                                                <ListView Padding="5" 
                                                          MaxHeight="150" 
                                                          MinHeight="80" 
                                                          Background="Transparent" 
                                                          BorderThickness="0" 
                                                          ScrollViewer.VerticalScrollBarVisibility="Auto" 
                                                          ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=DataGrid}, Path=DataContext.FilterList}">
                                                <ListView.ItemTemplate>
                                                    <DataTemplate>
                                                        <StackPanel Orientation="Horizontal">
                                                            <CheckBox IsChecked="{Binding IsSelected, Mode=TwoWay}"
                                                                  Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=DataGrid}, Path=DataContext.IsSelectedItemsCommand}"
                                                                  CommandParameter="{Binding}"/>
                                                            <ContentPresenter Content="{Binding Value}" />
                                                        </StackPanel>
                                                    </DataTemplate>
                                                </ListView.ItemTemplate>
                                            </ListView>
                                            <Button Margin="2,0,2,2" 
                                                    Content="Submit" 
                                                    Foreground="{StaticResource AQVeryDarkBlueBrush}" 
                                                    Background="AliceBlue"
                                                    Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=DataGrid}, Path=DataContext.AddMultipleFiltersAndRefreshCommand}"/>
                                        </StackPanel>
                                        </Border>
                                    </Popup> 
                                </StackPanel>
                            </StackPanel>
                        </DataTemplate>
                    </Setter.Value>
                </Setter>
            </Style>
        </DataGrid.Resources>

        <DataGrid.CellStyle>
            <Style TargetType="DataGridCell">
                <Setter Property="ContextMenu" Value="{StaticResource ContextMenu}"/>
            </Style>
        </DataGrid.CellStyle>
    </DataGrid>

ViewModel:

    public class UsersViewModel: BaseViewModel
    {

        #region Private Properties

        /// <summary>
        /// User's information holding list that could be used
        /// </summary>
        private ObservableCollection<User> UserList = new ObservableCollection<User>();

        /// <summary>
        /// Selected Header in datagrid
        /// </summary>
        private string SelectedColumnHeader { get; set; }


        /// <summary>
        /// List of isSelected Values
        /// </summary>
        private ObservableCollection<FilterPredicate> SelectedFilters { get; set; } = new ObservableCollection<FilterPredicate>();
        #endregion

        #region public Properties


        /// <summary>
        /// Filter list used to excel like filter functionality
        /// </summary>
        public ObservableCollection<FilterPredicate> FilterList { get; set; }


        /// <summary>
        /// Filters every filter used is saved in this dictionary
        /// </summary>
        public List<Predicate<User>> Filters { get; set; } = new List<Predicate<User>>();

        /// <summary>
        /// Filtered user list
        /// </summary>
        public ListCollectionView FilteredUserList { get; set; }
        /// <summary>
        /// Current Selected Object
        /// </summary>
        public User SelectedUser { get; set; }

        /// <summary>
        /// Current Selected Column
        /// </summary>
        public DataGridColumn CurrentColumn { get; set; }

        public bool PopupVisible { get; set; } 

        public UIElement FilterButton { get; set; }
        #endregion

        #region Commands

        public ICommand ExcludeCommand { get; set; }
        public ICommand IncludeCommand { get; set; }
        public ICommand RemoveAllFiltersCommand { get; set; }
        public ICommand RemoveFilterCommand { get; set; }
        public ICommand GenerateExcelFilterViewItemsCommand { get; set; }
        public ICommand IsSelectedItemsCommand { get; set; }
        public ICommand AddMultipleFiltersAndRefreshCommand { get; set; }


        #endregion

        #region Constructor
        /// <summary>
        /// Default constructor
        /// </summary>
        public UsersViewModel()
        {
            //Populate UserList
            GetUsers();

            //Set filtered user list with values
            FilterList = new ObservableCollection<FilterPredicate>();
            FilteredUserList = new ListCollectionView(UserList);

            ExcludeCommand = new RelayParamCommand((e) => ExcludeFilter(SelectedUser));
            IncludeCommand = new RelayParamCommand((e) => IncludeFilter(SelectedUser));
            RemoveAllFiltersCommand = new RelayCommand(() => RemoveAllFiltersAndRefresh());
            GenerateExcelFilterViewItemsCommand = new RelayParamCommand((e) => GenerateExcelFilterItems(e));
            IsSelectedItemsCommand = new RelayParamCommand((e) => IsSelectedItems(e));
            //AddMultipleFiltersAndRefreshCommand = new RelayCommand(AddMultipleFilterss);

            FilteredUserList.Filter = (e) => { return FilterEntries(e); };
        }

        #endregion

        #region Filter Methods

        /// <summary>
        /// Filter Collection view values
        /// </summary>
        private bool FilterEntries(object obj)
        {
            User c = (User)obj;
            bool isIn = true;
            if (Filters.Count == 0)
            {
                //return Filters.TrueForAll(x => x(c));
                return isIn;
            }
            else
            {
                 return Filters.TrueForAll(x => x(c));
               // return Filters.Aggregate(true, (prevValue, predicate) => prevValue && predicate(c));
            }
        }

        /// <summary>
        /// Exclude selected value
        /// </summary>
        /// <param name="obj"></param>
        public void ExcludeFilter(object obj)
        {
            User u = (User)obj;

            switch (CurrentColumn.DisplayIndex)
            {
                case 0:
                    AddFilterAndRefresh(user => !user.Tabelis.ToString().Contains(u.Tabelis.ToString()));
                    return;
                case 1:
                    AddFilterAndRefresh(user => !user.Name.Contains(u.Name));
                    return;
                case 2:
                    AddFilterAndRefresh(user => !user.Departament.Contains(u.Departament));
                    return;
            }
        }


        /// <summary>
        /// Include selected filter value
        /// </summary>
        /// <param name="obj">Object</param>
        private void IncludeFilter(object obj)
        {
            User u = (User)obj;

            switch (CurrentColumn.DisplayIndex)
            {
                case 0:
                    AddFilterAndRefresh(user => user.Tabelis.ToString().Contains(u.Tabelis.ToString()));
                    return;
                case 1:
                    AddFilterAndRefresh(user => user.Name.Contains(u.Name));
                    return;
                case 2:
                    AddFilterAndRefresh(user => user.Departament.Contains(u.Departament));
                    return;

            }
        }

        /// <summary>
        /// Add filter to Filter list
        /// </summary>
        /// <param name="name">Key</param>
        /// <param name="predicate">Filter (predicate) object</param>
        private void AddFilterAndRefresh(Predicate<User> predicate)
        {
            //Adds filter to filter list
            Filters.Add(predicate);

            //Filters doesn't fire event automatically
            OnPropertyChanged("Filters");

            //Refresh list to by correctly displayed
            FilteredUserList.Refresh();
        }


        /// <summary>
        /// Remove all filters from filter list
        /// </summary>
        private void RemoveAllFiltersAndRefresh()
        {
            Filters.Clear();
            FilterList.Clear();
            SelectedFilters.Clear();
            FilteredUserList.Refresh();
            OnPropertyChanged("Filters");
        }


        /// <summary>
        /// Gets property to get filter items by this property value
        /// </summary>
        /// <param name="obj">Object property</param>
        private void GenerateExcelFilterItems(object obj)
        {
            //set header name as string
            SelectedColumnHeader = (string)obj;

            //Clear Filter list
            FilterList.Clear();

            foreach(FilterPredicate i in SelectedFilters)
            {
                //insert right not selected value
                //because after filter excecution that value dissapears
                if (!FilterList.Contains(i) && i.ColumnName == SelectedColumnHeader) { FilterList.Add(i); }
            }

            //Fill filter list with new values depend on selectedColumnHeader property
            FillFilterValues(SelectedColumnHeader);
        }

        /// <summary>
        /// Remove or add "IsSelected" values to SelectedFilters List
        /// </summary>
        /// <param name="obj"></param>
        private void IsSelectedItems(object obj)
        {
            var SelectedItem = (FilterPredicate)obj;

            if (SelectedItem.IsSelected)
            {
                Filters.Add(AddMultipleFilters);

                //Refresh list to by correctly displayed
                FilteredUserList.Refresh();

            }
            else
            {
                FilterList[FilterList.IndexOf(SelectedItem)].IsSelected = false;


                Filters.Add(AddMultipleFilters);
                //RemoveMultipleFilters(SelectedItem);

                if(!SelectedFilters.Contains(SelectedItem))
                     SelectedFilters.Add(SelectedItem);

                //Refresh list to by correctly displayed
                FilteredUserList.Refresh();

            }

        }


        private bool AddMultipleFilters(object obj)
        {
            User u = (User)obj;                
            return FilterList.Any(x => x.IsSelected && x.Value.Contains(u.GetType().GetProperty(SelectedColumnHeader).GetValue(u).ToString()));      
        }

        #endregion

        #region helpers

        /// <summary>
        /// Fill filter items list with values from FilteredUsersList
        /// </summary>
        /// <param name="Property"></param>
        private void FillFilterValues(string Property)
        {
            foreach (User u in FilteredUserList)
            {
                User i = u;
                var item = new FilterPredicate(Property, u.GetType().GetProperty(Property).GetValue(i).ToString(), true);
                if (!FilterList.Any(x => x.ColumnName == item.ColumnName && x.Value == item.Value))
                    FilterList.Add(item);
            }
        }

        /// <summary>
        /// Fill userList with data from database
        /// </summary>
        private void GetUsers()
        {

            UserList.Add(new User
            {
                Name = "Marius",
                Departament = "some",
                Tabelis = 5
            });

            UserList.Add(
            new User
            {
                Name = "Darius",
                Departament = "unknown",
                Tabelis = 20
            });

            UserList.Add(
            new User
            {
                Name = "Koste",
                Departament = "unknown",
                Tabelis = 20
            });

            UserList.Add(
            new User
            {
                Name = "Gediminas",
                Departament = "unknown",
                Tabelis = 20
            });

            UserList.Add(
            new User
            {
                Name = "Nerijus",
                Departament = "Tech",
                Tabelis = 20
            });
        }
        #endregion
    }
}

Filter predicate should return true for items which needs to be displayed, and false for hidden items.

CollectionView needs only one predicate. To filter by selected names, keep them in a list and compare CollectionView item for possible match with any name , not with each name.

private bool FilterEntries(object obj)
{
    User u = (User)obj;

    // converting original condition to ANY instead of EACH
    return Filters.Values.Aggregate(false, (prevValue, predicate) => prevValue || predicate(u));

    // looks like Filters is a Dictionary, maybe simplify condition?
    return Filters.ContainsKey(u.Name);

    // or maybe search in a List without multiple predicates
    return Filters.Keys.Any(name => name.Contains(u.Name));
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM