Filtering DataGridView without changing datasource

C#WinformsVisual Studio-2010DatagridviewFilter

C# Problem Overview


I'm developing user control in C# Visual Studio 2010 - a kind of "quick find" textbox for filtering datagridview. It should work for 3 types of datagridview datasources: DataTable, DataBinding and DataSet. My problem is with filtering DataTable from DataSet object, which is displayed on DataGridView.

There could be 3 cases (examples for standard WinForm application with DataGridView and TextBox on it) - first 2 are working OK, I've problem with 3rd one:

1. datagridview.DataSource = dataTable : it works
so I can filter by setting: dataTable.DefaultView.RowFilter = "country LIKE '%s%'";

DataTable dt = new DataTable();

private void Form1_Load(object sender, EventArgs e)
{
    dt.Columns.Add("id", typeof(int));
    dt.Columns.Add("country", typeof(string));

    dt.Rows.Add(new object[] { 1, "Belgium" });
    dt.Rows.Add(new object[] { 2, "France" });
    dt.Rows.Add(new object[] { 3, "Germany" });
    dt.Rows.Add(new object[] { 4, "Spain" });
    dt.Rows.Add(new object[] { 5, "Switzerland" });
    dt.Rows.Add(new object[] { 6, "United Kingdom" });

    dataGridView1.DataSource = dt;
}

private void textBox1_TextChanged(object sender, EventArgs e)
{
    MessageBox.Show("DataSource type BEFORE = " + dataGridView1.DataSource.GetType().ToString());
        
    dt.DefaultView.RowFilter = string.Format("country LIKE '%{0}%'", textBox1.Text);
        
    MessageBox.Show("DataSource type AFTER = " + dataGridView1.DataSource.GetType().ToString());
} 

2. datagridview.DataSource = bindingSource: it works
so I can filter by setting: bindingSource.Filter = "country LIKE '%s%'";

DataTable dt = new DataTable();
BindingSource bs = new BindingSource();

private void Form1_Load(object sender, EventArgs e)
{
    dt.Columns.Add("id", typeof(int));
    dt.Columns.Add("country", typeof(string));

    dt.Rows.Add(new object[] { 1, "Belgium" });
    dt.Rows.Add(new object[] { 2, "France" });
    dt.Rows.Add(new object[] { 3, "Germany" });
    dt.Rows.Add(new object[] { 4, "Spain" });
    dt.Rows.Add(new object[] { 5, "Switzerland" });
    dt.Rows.Add(new object[] { 6, "United Kingdom" });

    bs.DataSource = dt;
    dataGridView1.DataSource = bs;
}

private void textBox1_TextChanged(object sender, EventArgs e)
{
    MessageBox.Show("DataSource type BEFORE = " + dataGridView1.DataSource.GetType().ToString());
        
    bs.Filter = string.Format("country LIKE '%{0}%'", textBox1.Text);
        
    MessageBox.Show("DataSource type AFTER = " + dataGridView1.DataSource.GetType().ToString());
}

3. datagridview.DataSource = dataSource; datagridview.DataMember = "TableName": it doesn't work
It happens when you design a table using designer: put the DataSet from toolbox on form, add dataTable to it and then set datagridview.DataSource = dataSource; and datagridview.DataMember = "TableName".
Code below pretends these operations:

DataSet ds = new DataSet();
DataTable dt = new DataTable();

private void Form1_Load(object sender, EventArgs e)
{
    dt.Columns.Add("id", typeof(int));
    dt.Columns.Add("country", typeof(string));

    dt.Rows.Add(new object[] { 1, "Belgium" });
    dt.Rows.Add(new object[] { 2, "France" });
    dt.Rows.Add(new object[] { 3, "Germany" });
    dt.Rows.Add(new object[] { 4, "Spain" });
    dt.Rows.Add(new object[] { 5, "Switzerland" });
    dt.Rows.Add(new object[] { 6, "United Kingdom" });

    ds.Tables.Add(dt);
    dataGridView1.DataSource = ds;
    dataGridView1.DataMember = dt.TableName;
}

private void textBox1_TextChanged(object sender, EventArgs e)
{
    MessageBox.Show("DataSource type BEFORE = " + dataGridView1.DataSource.GetType().ToString());  
    //it is not working
    ds.Tables[0].DefaultView.RowFilter = string.Format("country LIKE '%{0}%'", textBox1.Text);
        
    MessageBox.Show("DataSource type AFTER = " + dataGridView1.DataSource.GetType().ToString());
}

If you test it - although datatable is filtered (ds.Tables[0].DefaultView.Count changes), datagridview is not updated... I've been looking for a long time for any solution, but the problem is that DataSource cannot change - as it's additional control, I don't want it to mess up with programmer's code.

I know possible solutions are:

  • to bind DataTable from DataSet using DataBinding and use it as example 2: but it's up to the programmer during code writing,

  • to change dataSource to BindingSource, dataGridView.DataSource = dataSet.Tables[0], or to DefaultView programatically: however, it changes the DataSource. So the solution:

    private void textBox1_TextChanged(object sender, EventArgs e) { MessageBox.Show("DataSource type BEFORE = " + dataGridView1.DataSource.GetType().ToString(), ds.Tables[0].DefaultView.Count.ToString());

      DataView dv = ds.Tables[0].DefaultView;
      dv.RowFilter = string.Format("country LIKE '%{0}%'", textBox1.Text);
      dataGridView1.DataSource = dv;
    
      MessageBox.Show("DataSource type AFTER = " + dataGridView1.DataSource.GetType().ToString(), ds.Tables[0].DefaultView.Count.ToString());
    

    } is not acceptable, as you see on MessageBox's dataSource is changing...

I don't want to do that, because it's possible a programmer writes code similar to this:

private void textBox1_TextChanged(object sender, EventArgs e)
{
    MessageBox.Show("DataSource type BEFORE = " + dataGridView1.DataSource.GetType().ToString(), ds.Tables[0].DefaultView.Count.ToString());

    DataSet dsTmp = (DataSet)(dataGridView1.DataSource);   //<--- it is OK 

    DataView dv = ds.Tables[0].DefaultView;
    dv.RowFilter = string.Format("country LIKE '%{0}%'", textBox1.Text);
    dataGridView1.DataSource = dv;   //<--- here the source is changeing from DataSet to DataView

    MessageBox.Show("DataSource type AFTER = " + dataGridView1.DataSource.GetType().ToString(), ds.Tables[0].DefaultView.Count.ToString());
    
    dsTmp = (DataSet)(dataGridView1.DataSource);    //<-- throws an exception: Unable to cast object DataView to DataSet
}

He can do that, as he designed DataGridView with DataSet and DataMember in designer. Code will be compiled, however, after using a filter, it will throw an exception...

So the question is: how can I filter DataTable in DataSet and show the results on DataGridView without changing DataSource to another? Why I can filter DataTable from example 1 directly, while filtering DataTable from DataSet is not working? Maybe it's not DataTable bound to DataGridView in that case?

Please note, that my problem takes from designing issues, so the solution MUST WORK on example 3.

C# Solutions


Solution 1 - C#

I just spent an hour on a similar problem. For me the answer turned out to be embarrassingly simple.

(dataGridViewFields.DataSource as DataTable).DefaultView.RowFilter = string.Format("Field = '{0}'", textBoxFilter.Text);

Solution 2 - C#

I developed a generic statement to apply the filter:

string rowFilter = string.Format("[{0}] = '{1}'", columnName, filterValue);
(myDataGridView.DataSource as DataTable).DefaultView.RowFilter = rowFilter;

The square brackets allow for spaces in the column name.

Additionally, if you want to include multiple values in your filter, you can add the following line for each additional value:

rowFilter += string.Format(" OR [{0}] = '{1}'", columnName, additionalFilterValue);

Solution 3 - C#

A simpler way is to transverse the data, and hide the lines with the Visible property.

// Prevent exception when hiding rows out of view
CurrencyManager currencyManager = (CurrencyManager)BindingContext[dataGridView3.DataSource];
currencyManager.SuspendBinding();

// Show all lines
for (int u = 0; u < dataGridView3.RowCount; u++)
{
    dataGridView3.Rows[u].Visible = true;
    x++;
}

// Hide the ones that you want with the filter you want.
for (int u = 0; u < dataGridView3.RowCount; u++)
{
    if (dataGridView3.Rows[u].Cells[4].Value == "The filter string")
    {
        dataGridView3.Rows[u].Visible = true;
    }
    else
    {
        dataGridView3.Rows[u].Visible = false;
    }
}

// Resume data grid view binding
currencyManager.ResumeBinding();

Just an idea... it works for me.

Solution 4 - C#

For those of you who have implemented the checked answer yet still getting the error >(Object reference not set to an instance of an object)

As was mentioned in the comments, maybe the DataGridView's data source is not of the type DataTable, but if it is, try to assign the data table to the DataGridView's data source again. In my case, I assigned the data table to the DataGridView in FormLoad() and when I write this code

(dataGridViewFields.DataSource as DataTable).DefaultView.RowFilter = string.Format("Field = '{0}'", textBoxFilter.Text);

it was giving me the error I mentioned above. So, I reassigned the data table to the dgv again. So the code was something like

dataGridViewFields.DataSource = Dt;
(dataGridViewFields.DataSource as DataTable).DefaultView.RowFilter = string.Format("Field = '{0}'", textBoxFilter.Text);

And it worked.

Solution 5 - C#

You could create a DataView object from your datasource. This would allow you to filter and sort your data without directly modifying the datasource.

Also, remember to call dataGridView1.DataBind(); after you set the data source.

Solution 6 - C#

//"Comment" Filter datagrid without changing dataset,Perfectly works.

            (dg.ItemsSource as ListCollectionView).Filter = (d) =>
            {
                DataRow myRow = ((System.Data.DataRowView)(d)).Row;
                if (myRow["FName"].ToString().ToUpper().Contains(searchText.ToString().ToUpper()) || myRow["LName"].ToString().ToUpper().Contains(searchText.ToString().ToUpper()))
                    return true; //if want to show in grid
                return false;    //if don't want to show in grid
            };         

Solution 7 - C#

I have a clearer proposal on automatic search in a DataGridView

this is an example

private void searchTb_TextChanged(object sender, EventArgs e)
    {
        try
        {
            (lecteurdgview.DataSource as DataTable).DefaultView.RowFilter = String.IsNullOrEmpty(searchTb.Text) ?
                "lename IS NOT NULL" :
                String.Format("lename LIKE '{0}' OR lecni LIKE '{1}' OR ledatenais LIKE '{2}' OR lelieu LIKE '{3}'", searchTb.Text, searchTb.Text, searchTb.Text, searchTb.Text);
        }
        catch (Exception ex) {
            MessageBox.Show(ex.StackTrace);
        }
    }

Solution 8 - C#

I found a simple way to fix that problem. At binding datagridview you've just done: datagridview.DataSource = dataSetName.Tables["TableName"];

If you code like:

datagridview.DataSource = dataSetName;
datagridview.DataMember = "TableName";

the datagridview will never load data again when filtering.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionmj82View Question on Stackoverflow
Solution 1 - C#Brad BruceView Answer on Stackoverflow
Solution 2 - C#Joe SiskView Answer on Stackoverflow
Solution 3 - C#João MoreiraView Answer on Stackoverflow
Solution 4 - C#Rua'aView Answer on Stackoverflow
Solution 5 - C#epotterView Answer on Stackoverflow
Solution 6 - C#PPrView Answer on Stackoverflow
Solution 7 - C#KOUAKEP ARNOLDView Answer on Stackoverflow
Solution 8 - C#Quyền Đặng QuangView Answer on Stackoverflow