简体   繁体   中英

Apply filter based on several values from TextBoxes to DataGrid (ItemCollectionViewSource)

I have made a tool for database that is showing items with certain filtering options. However after reading some more about WPF and C#. I used this https://www.codeproject.com/Articles/683429/Guide-to-WPF-DataGrid-formatting-using-bindings tutorial to modify my application to deal with ItemCollectionViewSource

I have used this before for filtering:

    public void FilterSetup()
    {
        try
        {
            string qry = null;
            _conditions["name"] = null;

            if (!string.IsNullOrEmpty(BusinessIDSearch.Text))
            {
                qry = string.Format("LY Like '{0}%'", BusinessIDSearch.Text);
            }
            if (!string.IsNullOrEmpty(NameSearch.Text))
            {
                if (!string.IsNullOrEmpty(qry))
                    qry += " AND ";
                qry += string.Format("HAKUNIMI Like '%{0}%'", NameSearch.Text);
            }
            if (!string.IsNullOrEmpty(GroupSearch.Text))
            {
                if (!string.IsNullOrEmpty(qry))
                    qry += " AND ";
                qry += string.Format("KONSERNI Like '{0}%'", GroupSearch.Text);
            }
            if (!string.IsNullOrEmpty(LiinosIDSearch.Text))
            {
                if (!string.IsNullOrEmpty(qry))
                    qry += " AND ";
                qry += string.Format("YRNRO Like '{0}%'", LiinosIDSearch.Text);
            }

            _conditions["name"] = qry;

            UpdateFilter();

            //LiinosFICount.Content = DataGrid1.Items.Count;
        }
        catch (Exception)
        {

            throw;
        }
    }

Then this:

    private void UpdateFilter()
    {
        try
        {
            var activeConditions = _conditions.Where(c => c.Value != null).Select(c => "(" + c.Value + ")");
            DataView dv = DataGrid1.ItemsSource as DataView;
            dv.RowFilter = string.Join(" AND ", activeConditions);
        }
        catch (Exception)
        {
            //MessageBox.Show(ex.Message);
        }
    }

However it looks like I need another approach now with ItemCollectionViewSource .

Here is XAML part for textbox:

    <TextBox Style="{StaticResource TextBox_Style}" x:Name="GroupSearch" HorizontalAlignment="Left" Margin="414,121,0,0" TextWrapping="Wrap" Text="" VerticalAlignment="Top" Width="90" Height="20" TextChanged="GroupSearch_TextChanged"/>
    <TextBox Style="{StaticResource TextBox_Style}" x:Name="NameSearch" HorizontalAlignment="Left" Margin="130,121,0,0" TextWrapping="Wrap" Text="" VerticalAlignment="Top" Width="279" Height="20" TextChanged="NameSearch_TextChanged"/>
    <TextBox Style="{StaticResource TextBox_Style}" Text="{Binding FilterString, UpdateSourceTrigger=PropertyChanged}" x:Name="LiinosIDSearch" HorizontalAlignment="Left" Margin="20,121,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="105" Height="20"/>
    <TextBox Style="{StaticResource TextBox_Style}" x:Name="BusinessIDSearch" HorizontalAlignment="Left" Margin="509,121,0,0" TextWrapping="Wrap" Text="" VerticalAlignment="Top" Width="192" Height="20" TextChanged="BusinessIDSearch_TextChanged"/>

Here is XAML for DataGrid:

    <DataGrid Margin="0,146,0,0" Background="{x:Null}" BorderBrush="{x:Null}"
              CanUserAddRows="False" CanUserDeleteRows="False" CanUserResizeRows="False" IsReadOnly="True" 
              HorizontalGridLinesBrush="#FF377A6C" VerticalGridLinesBrush="#FF377A6C" 
          DataContext="{StaticResource ItemCollectionViewSource}"
          ItemsSource="{Binding}"
          AutoGenerateColumns="False" FontFamily="Arial Nova" Foreground="White" >

        <DataGrid.RowStyle>
            <Style TargetType="DataGridRow" >
                <Setter Property="Background" Value="{Binding YRNRO, Converter={StaticResource LiinosIDToBackgroundConverter}}" />
            </Style>
        </DataGrid.RowStyle>

        <DataGrid.Resources>
            <Style BasedOn="{StaticResource {x:Type DataGridColumnHeader}}" TargetType="{x:Type DataGridColumnHeader}">
                <Setter Property="Background" Value="#377A6C" />
                <Setter Property="Foreground" Value="White" />
                <Setter Property="MinHeight" Value="20" />
            </Style>
            <SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" 
               Color="#5AC37E"/>
        </DataGrid.Resources>

        <DataGrid.Columns>
            <DataGridTextColumn Binding="{Binding YRNRO}">
                <DataGridTextColumn.Header>
                    <TextBlock Text="LIINOS ID" FontWeight="Bold" TextAlignment="Left"/>
                </DataGridTextColumn.Header>
            </DataGridTextColumn>
            <DataGridTextColumn Binding="{Binding HAKUNIMI}">
                <DataGridTextColumn.Header>
                    <TextBlock Text="SEARCH NAME" FontWeight="Bold" TextAlignment="Left"/>
                </DataGridTextColumn.Header>
            </DataGridTextColumn>
            <DataGridTextColumn Binding="{Binding KONSERNI}">
                <DataGridTextColumn.Header>
                    <TextBlock Text="GROUP" FontWeight="Bold" TextAlignment="Left"/>
                </DataGridTextColumn.Header>
            </DataGridTextColumn>
            <DataGridTextColumn Binding="{Binding LY}">
                <DataGridTextColumn.Header>
                    <TextBlock Text="BUSINESS ID" FontWeight="Bold" TextAlignment="Left"/>
                </DataGridTextColumn.Header>
            </DataGridTextColumn>
        </DataGrid.Columns>
    </DataGrid>

I have tried several tutorials without success. Does anybody have any kind of tutorial to suggest or maybe provide an answer with solution I can implement to other TextBoxes as well? The tricky part that I have several TextBoxes and I need to combine filter query based on values in TextBoxes.

As you can see I have tried to apply Binding here:

<TextBox Style="{StaticResource TextBox_Style}" Text="{Binding FilterString, UpdateSourceTrigger=PropertyChanged}" x:Name="LiinosIDSearch" HorizontalAlignment="Left" Margin="20,121,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="105" Height="20"/>

I have got this from some tutorial and tried to modify it to my needs without success:

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Data;
using System.Linq;
using System.Windows.Data;

namespace Liinos_inspector_FilterTest
{
    public class ViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        private string _filter1 = "";
        private string _filter2 = "";
        private string _filter3 = "";

        public ViewModel()
        {

            var List = MainProcess.CustomersInLiinos.AsEnumerable()
           .GroupBy(x => x.Field<string>("HAKUNIMI"))
           .Where(x => x.Count() > 1)
           .SelectMany(x => x)
           .ToList();

            //ItemList = new ObservableCollection<Items>(List);
            ItemView = (CollectionView)CollectionViewSource.GetDefaultView(List);
            ItemView.Filter = TextFilter;

        }
        private bool TextFilter(object obj)
        {
            var data = obj as Items;
            if (data != null)
            {
                return data.Text1.StartsWith(_filter1) && data.Text2.StartsWith(_filter2) && data.Text3.StartsWith(_filter3);
            }
            return false;
        }

        private void NotifyPropertyChanged(string property)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
        }

        public ObservableCollection<Items> ItemList { get; set; }
        public CollectionView ItemView { get; set; }
        public string Filter1
        {
            get { return _filter1; }
            set
            {
                _filter1 = value;
                NotifyPropertyChanged("Filter1");
                ItemView.Refresh();
            }
        }
        public string Filter2
        {
            get { return _filter2; }
            set
            {
                _filter2 = value;
                NotifyPropertyChanged("Filter2");
                ItemView.Refresh();

            }
        }
        public string Filter3
        {
            get { return _filter3; }
            set
            {
                _filter3 = value;
                NotifyPropertyChanged("Filter3");
                ItemView.Refresh();
            }
        }

    }

    public class Items
    {
        public string Text1 { get; set; }
        public string Text2 { get; set; }
        public string Text3 { get; set; }

    }
}

EDIT:

I am loading data currently on a button click to DataGrid:

    private async void Button_Click_1(object sender, RoutedEventArgs e)
    {

        if (MainProcess.CheckForVPNInterface() == true)
        {
            if (MainProcess.Customers != null)
            {
                ProgressBar.IsIndeterminate = true;

                CollectionViewSource itemCollectionViewSource;
                itemCollectionViewSource = (CollectionViewSource)(FindResource("ItemCollectionViewSource"));
                itemCollectionViewSource.Source = await LoadMainTableDataAsync();

                ProgressBar.IsIndeterminate = false;

            }
        }
        else
        {
            string caption = "VPN connection missing";

            MessageBox.Show("Please, check your VPN connection!", caption,
                             MessageBoxButton.OK,
                             MessageBoxImage.Exclamation);
        }
    }

EDIT 2:

Here is LoadMainTableDataAsync method that I have been using:

    public Task<DataView> LoadMainTableDataAsync()
    {
        return Task.Run(() =>
        {
            MainProcess.MergedTable();

            return MainProcess.Customers.DefaultView;
        });
    }

Here is merging of DataTables:

    public static DataTable Customers = new DataTable();

    public static void MergedTable()
    {
        var t1 = ConnectAndRetriveDatatatableS(); // t1
        var t2 = ConnectAndRetriveDatatatableF(); // t2

        Customers = t1.Copy();
        Customers.Merge(t2);
    }

You don't need a CollectionViewSource to filter the collection. Simply bind to a collection of your view model. In WPF collections are consumed via a ICollectionsView under the hoods. Each collection returns a default view. Controls are operating on this view automatically. To change the order, grouping or filtering you modify the current view of the collection instead of the actual collection instance. Also don't set the DataContext explicitly to the CollectionViewSource . Bind directly to it.

As a side note, since you are binding to the model items, Items should implement INotifyPropertyChanged or you provoke memory leaks.

Don't handle the ProgressBar in your view model. Set a bool property and bind it to the ProgressBar.Visibility .

Don't show a MessageBox or any user dialog from the view model in order to handle an exception. Rather throw a meaning full custom exception or wrapper exception (with the original exceptions as inner exception) and handle it in your view eg by showing an interaction dialog.

Don't handle database or any other data (model) access in your view. Rather implement an ICommand and assign it to the button to replace the event handler, and execute the operations in your view model. See Microsoft Docs: Relaying Command Logic for a simple implementation of RelayCommand .

This is a basic example how to filter a collection using the collection's default ICollectionView , incorporating the suggested improvements. You need to adjust the actual filter logic to your requirements:

VpnInterfaceException.cs

class VpnInterfaceException : Exception
{
  public VpnInterfaceException(string message) : base(message)
  {
  }

  public VpnInterfaceException(string message, Exception innerException) : base(message, innerException)
  {
  }
}

Item.cs

class Item : INotifyPropertyChanged
{
  private string text;   
  public string Text 
  {
    get => this.text;
    set 
    { 
      this.text = value; 
      OnPropertyChanged();
    }
  }

  public event PropertyChangedEventHandler PropertyChanged;
  protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
  {
    this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
  }
}

ViewModel.cs

class ViewModel : INotifyPropertyChanged
{
  public ObservableCollection<Item> Items { get; set; }
  public ICommand LoadMainTableDataCommand => new RelayCommand(ExecuteLoadMainTableDataAsync);

  // Binding source for the TextBox
  private string searchKey;   
  public string SearchKey
  {
    get => this.searchKey;
    set 
    { 
      this.searchKey = value; 
      OnPropertyChanged();  
      
     // Refresh the ICollectionView to update the filter expression
      CollectionViewSource.GetDefaultView(this.Items).Refresh();
    }
  }

  private bool hasProgress;   
  public bool HasProgress
  {
    get => this.hasProgress;
    set 
    { 
      this.hasProgress = value; 
      OnPropertyChanged();
    }
  }

  public ViewModel()
  {
    this.Items = new ObservableCollection<Item>();
    EnableItemsFiltering();
  }

  public void EnableItemsFiltering()
  {
    // Assign the filter expression which is executed when items are added 
    // or the 'ICollectionView.Refresh()'  was called
    CollectionViewSource.GetDefaultView(this.Items).Filter = FilterPredicate;
  }

  // The filter expression.
  // Returns 'true' to include the current item.
  private bool FilterPredicate(object item) 
    => string.IsNullOrWhiteSpace(this.SearchKey) 
       || ((item as Item)?.Text.StartsWith(this.SearchKey, StringComparison.OrdinalIgnoreCase) ?? false);

  private async Task ExecuteLoadMainTableDataAsync(object commandParameter)
  {
    if (MainProcess.CheckForVPNInterface())
    {
      if (MainProcess.Customers != null)
      {
        this.HasProgress = true;

        IEnumerable<Item> resultItems = await LoadMainTableDataAsync();
        this.Items = new ObservableCollection<Item>(resultItems);
        EnableItemsFiltering();

        this.HasProgress = false;
      }
    }
    else
    {
      // Throw an exception since the operation cannot be completed unexpectedly. 
      // The view can catch this exception to execute the error handling.
      throw new VpnInterfaceException("Please, check your VPN connection!");
    }
  }

  public event PropertyChangedEventHandler PropertyChanged;
  protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    => this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

MainWindow.xaml.cs

partial class MainWindow : Window
{
  public MainWindow()
  {
    InitializeComponent();
      
    // Handle specific view model exceptions for user interaction
    this.Dispatcher.UnhandledException += (sender, args) =>
    {
      // Only handle selected exceptions, that can be handled through user interaction
      if (args.Exception is VpnInterfaceException exception)
      {
        args.Handled = true;
        string caption = "VPN connection missing";
 
        // Convert exception message to user friendly message
        MessageBox.Show(exception.Message, 
          caption,
          MessageBoxButton.OK, 
          MessageBoxImage.Exclamation);
      }
    };
  }
}

MainWindow.xaml

<Window>
  <Window.DataContext>
    <ViewModel />
  </Window.DataContext>

  <Window.Resources>
    <BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
  </Window.Resources>
 
  <StackPanel>
    <ProgressBar IsIndeterminate="True"
                 Visibility="{Binding HasProgress, Converter={StaticResource BooleanToVisibilityConverter}}" />

    <Button Command="{Binding LoadMainTableDataCommand}" 
            Content="Load Data" />

    <TextBox Text="{Binding SearchKey}" />
    <DataGrid ItemsSource="{Binding Items}" />
  <StackPanel>
</Window>

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