【问题标题】:Filter WPF DataGrid by multiple criterias按多个条件过滤 WPF DataGrid
【发布时间】:2020-02-15 11:46:25
【问题描述】:

我想实现 Excel 之类的三列过滤数据网格。

这是我的代码:

    using System.Data.Odbc;
    using System.Windows;
    using System.Data;

    namespace DB_inspector
    {
        /// <summary>
        /// Interaction logic for MainWindow.xaml
        /// </summary>
        public partial class MainWindow : Window
        {
            public MainWindow()
            {
                InitializeComponent();

            }

            private void Button_Click(object sender, RoutedEventArgs e)
            {
                OdbcConnection dbConnection = new OdbcConnection("Driver={Pervasive ODBC Client Interface};ServerName=875;dbq=@DBFS;Uid=Username;Pwd=Password;");
                string strSql = "select NRO,NAME,NAMEA,NAMEB,ADDRESS,POSTA,POSTN,POSTADR,COMPANYN,COUNTRY,ID,ACTIVE from COMPANY";
                dbConnection.Open();
                OdbcDataAdapter dadapter = new OdbcDataAdapter();
                dadapter.SelectCommand = new OdbcCommand(strSql, dbConnection);
                DataTable table = new DataTable("COMPANY");
                dadapter.Fill(table);
                DataGrid1.DataContext = table;
                DataGrid1.ItemsSource = table.DefaultView;
                dadapter.Update(table);
                dbConnection.Close();
            }

        private void TextBox_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
        {
            DataView dv = DataGrid1.ItemsSource as DataView;

            string filter = NameSearch.Text;
                if (string.IsNullOrEmpty(filter))
                    dv.RowFilter = null;
                else
                    dv.RowFilter = string.Format("NAME Like '%{0}%'", filter);

        }

        private void TextBox_TextChanged_1(object sender, System.Windows.Controls.TextChangedEventArgs e)
        {
            DataView dv = DataGrid1.ItemsSource as DataView;

            string filter = ActiveSearch.Text;
            if (string.IsNullOrEmpty(filter))
                dv.RowFilter = null;
            else
                dv.RowFilter = string.Format("ACTIVE Like '%{0}%'", filter);
        }

        private void CustomerNumberSearch_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
        {
            DataView dv = DataGrid1.ItemsSource as DataView;

            string filter = CustomerNumberSearch.Text;
            if (string.IsNullOrEmpty(filter))
                dv.RowFilter = null;
            else
                dv.RowFilter = string.Format("NRO Like '%{0}%'", filter); //this should be "Begins with" not "Like"
        }
    }
}

XAML

<Window x:Class="DB_inspector.MainWindow"
        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:DB_inspector"
        mc:Ignorable="d"
        Title="DB database inspector" Height="595.404" Width="1005.571">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="11*"/>
            <RowDefinition Height="553*"/>
        </Grid.RowDefinitions>
        <DataGrid x:Name="DataGrid1" Margin="0,51,0,0" Grid.Row="1"/>
        <Image Height="41" Margin="847,10,10,502" Width="141" Source="Logo_small.jpg" Grid.Row="1" HorizontalAlignment="Right" VerticalAlignment="Top"/>
        <TextBox x:Name="NameSearch" HorizontalAlignment="Left" Height="23" Margin="77,14,0,0" TextWrapping="Wrap" Text="" VerticalAlignment="Top" Width="409" Grid.Row="1" TextChanged="TextBox_TextChanged"/>
        <Button Content="Refrsh" HorizontalAlignment="Left" Height="23" Margin="736,14,0,0" VerticalAlignment="Top" Width="72" Click="Button_Click" Grid.Row="1" Background="#FF51C951" BorderBrush="{x:Null}" Foreground="White"/>
        <TextBox x:Name="ActiveSearch" HorizontalAlignment="Left" Height="23" Margin="502,14,0,0" Grid.Row="1" TextWrapping="Wrap" Text="" VerticalAlignment="Top" Width="66" TextChanged="TextBox_TextChanged_1"/>
        <TextBox x:Name="CustomerNumberSearch" HorizontalAlignment="Left" Height="23" Margin="580,14,0,0" Grid.Row="1" TextWrapping="Wrap" Text="" VerticalAlignment="Top" Width="82" TextChanged="CustomerNumberSearch_TextChanged"/>
        <ProgressBar x:Name="ProgressBar" HorizontalAlignment="Left" Height="10" Margin="0,1,0,0" VerticalAlignment="Top" Width="998" BorderBrush="{x:Null}" Background="{x:Null}"/>
        <Button Content="Button" HorizontalAlignment="Left" Height="23" Margin="680,14,0,0" Grid.Row="1" VerticalAlignment="Top" Width="51" Click="Button_Click_1"/>

    </Grid>
</Window>

我想为NRO 和ACTIVE 列实现过滤器,以便在加载NRO 列中的数据期间,数字仅以3 开头,而ACTIVE 列中的值仅以1 开头。在ACTIVE 中有值 1 和 0(1 有效,0 无效)。

我当前的设置仅按一个标准进行过滤。如何使所有三个标准功能正确?

【问题讨论】:

  • 您是否知道这将排除具有部分有效条件的行?将排除包含 NRO = 3 和 Active = 0 之类的值的行。还是应该将条件与 OR 结合起来?

标签: c# wpf datatable datagrid


【解决方案1】:

您需要在一个过滤器字符串中组合多个条件:

private Dictionary<string, string> _conditions = new Dictionary<string, string>();

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

private void TextBox_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
{
    string filter = NameSearch.Text;
    if (string.IsNullOrEmpty(filter))
        _conditions["name"] = null;
    else
        _conditions["name"] = string.Format("NAME Like '%{0}%'", filter);
    UpdateFilter();
}

private void TextBox_TextChanged_1(object sender, System.Windows.Controls.TextChangedEventArgs e)
{
    string filter = ActiveSearch.Text;
    if (string.IsNullOrEmpty(filter))
        _conditions["active"] = null;
    else
        _conditions["active"] = string.Format("ACTIVE Like '%{0}%'", filter);
    UpdateFilter();
}

private void CustomerNumberSearch_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
{
    string filter = CustomerNumberSearch.Text;
    if (string.IsNullOrEmpty(filter))
        _conditions["nro"] = null;
    else
        _conditions["nro"] = string.Format("NRO Like '%{0}%'", filter); //this should be "Begins with" not "Like"
    UpdateFilter();
}

【讨论】:

  • 这表示Error CS0019 Operator '!=' cannot be applied to operands of type 'KeyValuePair&lt;string, string&gt;' and '&lt;null&gt;' 指向c != null
  • 在我尝试过滤之前没有错误。据我了解,现在我将所有过滤器组合在 UpdateFilter 中,并且必须以某种方式在我的 XAML 中使用它?抱歉问题太多,我正在努力学习方法。
  • 您不必在 xaml 中执行任何操作。唯一对过滤很重要的行是dv.RowFilter = ...。动态构建过滤器需要其他一切。设置断点,检查dv.RowFilter值以确保它是正确的
  • 对不起,我的错!我必须根据我的设置配置所有内容。谢谢!似乎有效!
【解决方案2】:

您可以修改数据库查询,让数据库更高效地完成工作。
但是如果你仍然喜欢使用 C# 来做,你可以使用 LINQ 来过滤DataTable:

MainWindow.xaml

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

  <DataGrid ItemsSource={Binding Data} />
</Window>

ViewModel.cs

public class ViewModel
{
  public DataTable Data { get; set; }

  public ViewModel()
  {
    this.Data = new DataTable();
  }

  // Button ICommand handler
  private void ExecuteGetDataCommand(object param)
  {
    DataTable dataTable = QueryDatabase(); 

    // Filter DataTable using LINQ
    this.Data = dataTable
      .AsEnumerable()
      .Where(row => row["NRO"].ToString().StartsWith("3")
        && row["ACTIVE"].ToString().StartsWith("1"))
      .CopyToDataTable();
  }

  private DataTable QueryDatabase()
  {
    OdbcConnection dbConnection = new OdbcConnection("Driver={Pervasive ODBC Client Interface};ServerName=875;dbq=@DBFS;Uid=Username;Pwd=Password;");
    string strSql = "select NRO,NAME,NAMEA,NAMEB,ADDRESS,POSTA,POSTN,POSTADR,COMPANYN,COUNTRY,ID,ACTIVE from COMPANY";
    dbConnection.Open();
    OdbcDataAdapter dadapter = new OdbcDataAdapter();
    dadapter.SelectCommand = new OdbcCommand(strSql, dbConnection);
    DataTable table = new DataTable("COMPANY");
    dadapter.Fill(table);
    return table;
  }
}

更新

这是基于您发布的代码的解决方案。我用CheckBox 替换了TextBox 以过滤ACTIVE 列,以添加一些类型安全性(以防止无效输入,例如字母顺序)。我还使用数据绑定将DataGrid 连接到DataTable。您应该更喜欢使用数据绑定而不是代码隐藏,并尽可能在 XAML 中实现:

MainWindow.xaml

<Window x:Class="DB_inspector.MainWindow"
        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:Liinos_inspector"
        mc:Ignorable="d"
        Title="DB database inspector" Height="595.404" Width="1005.571">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="11*"/>
            <RowDefinition Height="553*"/>
        </Grid.RowDefinitions>

        <DataGrid x:Name="DataGrid1" Margin="0,51,0,0" Grid.Row="1"
                  ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type MainWindow}}, Path=FilteredData}" />

        <Image Height="41" Margin="847,10,10,502" Width="141" Source="Logo_small.jpg" Grid.Row="1" HorizontalAlignment="Right" VerticalAlignment="Top"/>

        <TextBox x:Name="NameSearch" HorizontalAlignment="Left" Height="23" Margin="77,14,0,0" TextWrapping="Wrap" Text="" VerticalAlignment="Top" Width="409" Grid.Row="1" 
                 TextChanged="OnNameSearchTextBox_TextChanged"/>
        <Button Content="Refrsh" HorizontalAlignment="Left" Height="23" Margin="736,14,0,0" VerticalAlignment="Top" Width="72" Click="Button_Click" Grid.Row="1" Background="#FF51C951" BorderBrush="{x:Null}" Foreground="White"/>
        <CheckBox x:Name="ActiveSearch" HorizontalAlignment="Left" Height="23" Margin="502,14,0,0" Grid.Row="1" VerticalAlignment="Top" Width="66" 
                  IsChecked="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type MainWindow}}, Path=IsActiveFilterValue}" />
        <TextBox x:Name="CustomerNumberSearch" HorizontalAlignment="Left" Height="23" Margin="580,14,0,0" Grid.Row="1" TextWrapping="Wrap" Text="" VerticalAlignment="Top" Width="82" 
                 TextChanged="OnCustomerNumberSearch_TextChanged"/>

        <ProgressBar x:Name="ProgressBar" HorizontalAlignment="Left" Height="10" Margin="0,1,0,0" VerticalAlignment="Top" Width="998" BorderBrush="{x:Null}" Background="{x:Null}"/>
        <Button Content="Button" HorizontalAlignment="Left" Height="23" Margin="680,14,0,0" Grid.Row="1" VerticalAlignment="Top" Width="51" Click="Button_Click_1"/>

    </Grid>
</Window>

MainWindow.xaml.cs

public partial class MainWindow : Window
{
  public static readonly DependencyProperty FilteredDataProperty = DependencyProperty.Register(
    "FilteredData", 
    typeof(DataTable),
    typeof(MainWindow));

  public DataTable FilteredData 
  {
    get { return (DataTable)GetValue(FilteredDataProperty); }
    set { SetValue(FilteredDataProperty, value); }
  }

  public static readonly DependencyProperty IsActiveFilterValueProperty = DependencyProperty.Register(
    "IsActiveFilterValue", 
    typeof(bool),
    typeof(MainWindow), 
    new PropertyMetadata(false, OnIsActiveFilterValueChanged));

  public bool IsActiveFilterValue
  {
    get { return (bool)GetValue(IsActiveFilterValueProperty); }
    set { SetValue(IsActiveFilterValueProperty, value); }
  }

  private DataTable UnfilteredData { get; set; }
  private String NameFilterValue { get; set; }
  private int CustomerNumberFilterValue { get; set; }


  public MainWindow()
  {
    InitializeComponent();
  }

  private void Button_Click(object sender, RoutedEventArgs e)
  {
    string connectionString = "Driver={Pervasive ODBC Client Interface};ServerName=875;dbq=@DBFS;Uid=Username;Pwd=Password;";

    string queryString = "select NRO,NAME,NAMEA,NAMEB,ADDRESS,POSTA,POSTN,POSTADR,COMPANYN,COUNTRY,ID,ACTIVE from COMPANY";

    // using-statement will cleanly close and dispose unmanaged resources i.e. IDisposable instances
    using (OdbcConnection dbConnection = new OdbcConnection(connectionString))
    {    
      dbConnection.Open();
      OdbcDataAdapter dadapter = new OdbcDataAdapter();
      dadapter.SelectCommand = new OdbcCommand(queryString, dbConnection);

      this.UnfilteredData = new DataTable("COMPANY");
      dadapter.Fill(this.UnfilteredData);
      this.FilteredData = this.UnfilteredData;
    }
  }

  private void ApplyFilterOnDataTable()
  {
    // Filter DataTable using LINQ
    this.FilteredData = this.UnfilteredData
      .AsEnumerable()
      .Where(row => 
        string.IsNullOrWhiteSpace(this.NameFilterValue) 
          ? true // Filter criteria is ignored, when input is empty or null
          : row["NAME"].StartsWith(this.NameFilterValue)   
        && this.CustomerNumberFilterValue < 0 
          ? true // Filter criteria is ignored, when value is < 0
          : row["NRO"].ToString().StartsWith(this.CustomerNumberFilterValue.ToString())
        && this.IsActiveFilterValue 
          ? row["ACTIVE"].ToString().Equals("1")
          : true) // Filter criteris is ignored, when CheckBox is unchecked
      .CopyToDataTable();        
  }  

  private void OnNameSearchTextBox_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
  {       
    this.NameFilterValue = NameSearch.Text;
    ApplyFilterOnDataTable();  
  }

  private static void OnIsActiveFilterValueChanged(DependencyObject d, 
    DependencyPropertyChangedEventArgs e)
  {
    var _this = d as MainWindow;   
    _this.ApplyFilterOnDataTable();            
  }

  private void OnCustomerNumberSearch_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
  {  
    // Check if input is numeric 
    this.CustomerNumberFilterValue = Int32.TryParse(CustomerNumberSearch.Text, out int number) 
      ? number
      : -1; // Parsing failed -> not a numeric value. -1 will disable this filter criteria
    ApplyFilterOnDataTable();   
  }
}

【讨论】:

  • 谢谢你!昨天我一直在尝试使用 Linq 来理解这种方法,但没有得到它。我已将我当前的 XAML 添加到问题中。我不明白如何将您的 XAML 示例实施到我当前的设置中。是否可以使用我当前的设置来完成您的答案,以便我了解前进的逻辑。抱歉,我刚刚开始使用 WPF。
  • 我更新了我的答案。 dadapter.Update 之后的 dadapter.Fill 是多余的,所以我删除了它。 OdbcDataAdapter.Update 将写入数据库。
  • 这看起来像是一个完整的应用程序。我仍然面临一些问题。 Error CS1061 'MainWindow' does not contain a definition for 'Data' and no accessible extension method 'Data' accepting a first argument of type 'MainWindow' could be found (are you missing a using directive or an assembly reference?) 指向this.FilteredData = this.Data
  • 第二个是Error XDG0008 MainWindow is not supported in a Windows Presentation Foundation (WPF) project. Liinos inspector指向XAML
  • 全部替换。 Data 例如,来自带有 UnfilteredData 的 LINQ 表达式。我忘记重命名了。
猜你喜欢
  • 2018-12-05
  • 1970-01-01
  • 2020-06-06
  • 2020-05-18
  • 2017-07-05
  • 2021-10-20
  • 2018-03-25
  • 2015-06-12
相关资源
最近更新 更多