Filtrowanie DataGridView bez zmiany źródła danych

95

Rozwijam kontrolę użytkownika w C # Visual Studio 2010 - rodzaj pola tekstowego „szybkiego wyszukiwania” do filtrowania datagridview. Powinien działać dla 3 typów źródeł danych datagridview: DataTable, DataBinding i DataSet. Mój problem polega na filtrowaniu DataTable z obiektu DataSet, który jest wyświetlany w DataGridView.

Mogą być 3 przypadki (przykłady dla standardowej aplikacji WinForm z DataGridView i TextBox) - pierwsze 2 działają OK, mam problem z trzecim:

1. datagridview.DataSource = dataTable: to działa,
więc mogę filtrować przez ustawienie: dataTable.DefaultView.RowFilter = "kraj 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: działa,
więc mogę filtrować przez ustawienie: 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": to nie działa
Dzieje się tak, gdy projektujesz tabelę za pomocą projektanta: umieść DataSet z toolboxa w formularzu, dodaj do niego DataTable, a następnie ustaw datagridview.DataSource = dataSource; i datagridview.DataMember = "TableName".
Kod poniżej udaje te operacje:

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());
}

Jeśli to przetestujesz - mimo że datatable jest filtrowane (zmiany ds.Tables [0] .DefaultView.Count), datagridview nie jest aktualizowany ... Długo szukałem rozwiązania, ale problem polega na tym, że DataSource nie może zmiana - ponieważ jest to dodatkowa kontrola, nie chcę, żeby zepsuła kod programisty.

Wiem, że możliwe rozwiązania to:
- powiązanie DataTable z DataSet za pomocą DataBinding i użycie go jako przykładu 2: ale to programista podczas pisania kodu,
- zmiana dataSource na BindingSource, dataGridView.DataSource = dataSet.Tables [0] lub do DefaultView programowo: jednak zmienia DataSource. Więc rozwiązanie:

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());
}

jest nie do przyjęcia, jak widać na MessageBox's dataSource się zmienia ...

Nie chcę tego robić, ponieważ jest możliwe, że programista napisze kod podobny do tego:

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
}

Może to zrobić, ponieważ zaprojektował DataGridView z DataSet i DataMember w projektancie. Kod zostanie skompilowany, jednak po zastosowaniu filtru wyrzuci wyjątek ...

Zatem pytanie brzmi: jak mogę filtrować DataTable w DataSet i wyświetlać wyniki w DataGridView bez zmiany DataSource na inny? Dlaczego mogę bezpośrednio filtrować DataTable z przykładu 1, podczas gdy filtrowanie DataTable z DataSet nie działa? Może w takim przypadku nie jest to DataTable powiązany z DataGridView?

Zwróć uwagę, że mój problem bierze się z projektowania, więc rozwiązanie MUSI DZIAŁAĆ na przykładzie 3.

mj82
źródło
1
Moje 2 centy oprócz wszystkich cennych komentarzy i rozwiązań. Oto artykuł opisujący zalety i wady filtrowania DataGridView związanego z danymi w ten sposób i przedstawia kilka pomysłów, jak to zrobić lepiej.
TecMan
Przepraszam za powtórzenie, ale myślę, że moja propozycja nie zawsze działa. Rzeczywiście, czasami wyjątek jest zniesiony, co jest mało prawdopodobne w moim kodzie. Próbując filtrować za pomocą bindingSource, masz wszelkie szanse, aby stworzyć dobry kod. Jak data: bindingSource.Filter = string.Format .....
KOUAKEP ARNOLD Kwietnia
Podoba mi się komentarz TecMan. Możesz delegować pracę filtrowania do interfejsu IBindingListView za pomocą właściwości filtra (mniej działa, ale naprawdę można używać tylko z ADO.Net Datatable) lub wykonywać całą pracę pod kontrolą (więcej działa, ale powinno działać z czymkolwiek).
Marco Guignard

Odpowiedzi:

144

Właśnie spędziłem godzinę nad podobnym problemem. Dla mnie odpowiedź okazała się żenująco prosta.

(dataGridViewFields.DataSource as DataTable).DefaultView.RowFilter = string.Format("Field = '{0}'", textBoxFilter.Text);
Brad Bruce
źródło
2
jak umieścić to wydarzenie w polu tekstowym
Arun Prasad ES
7
Składnię filtrowania można znaleźć tutaj: csharp-examples.net/dataview-rowfilter
Sal
Używanie DataTable jako źródła pozwala obejść problem konieczności implementacji IBindingListViewzgodnie z msdn.microsoft.com/en-us/library/ ...
Jeremy Thompson,
Otrzymuję ten błąd: Object reference not set to an instance of an object.dla GridView.
Si8
Jakie jest Twoje źródło danych? Mój przykład zakłada, że ​​używasz DataTable. Jeśli używasz czegoś innego, sprawdź swój casting. „as DataTable” w moim przykładzie.
Brad Bruce
23

Opracowałem ogólne stwierdzenie, aby zastosować filtr:

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

Nawiasy kwadratowe dopuszczają spacje w nazwie kolumny.

Ponadto, jeśli chcesz uwzględnić wiele wartości w filtrze, możesz dodać następujący wiersz dla każdej dodatkowej wartości:

rowFilter += string.Format(" OR [{0}] = '{1}'", columnName, additionalFilterValue);
Joe Sisk
źródło
12

Prostszym sposobem jest przecięcie danych i ukrycie linii z Visiblewłaściwością.

// 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();

Tylko pomysł ... to działa dla mnie.

João Moreira
źródło
Jako osoba, która ręcznie wypełnia DataGridView, działało to idealnie. :) Chociaż użyłem foreachi bezpośrednio przypisane row.Visible = showAll || <condition>;bez żadnego if. To showAllprawda, jeśli ciąg filtru jest pusty.
Andrew
świetny pomysł, ponieważ w tym przypadku nie jesteśmy przywiązani do typu źródła danych. ani żaden DataTable.
mshakurov
Działało doskonale, a dla poprawy logiki wyszukiwania możemy zastąpić warunek if dataGridView3.Rows [u] .Cells [4] .Value.ToString (). IndexOf ("Ciąg filtru")> = 0
Ali Ali
1

Możesz utworzyć obiekt DataView na podstawie źródła danych. Umożliwiłoby to filtrowanie i sortowanie danych bez bezpośredniej modyfikacji źródła danych.

Pamiętaj też, aby zadzwonić dataGridView1.DataBind();po ustawieniu źródła danych.

epotter
źródło
2
Dziękuję za odpowiedź. Tak, obiekt DataView można utworzyć, jednak zmienia on typ DataSource, zobacz ostatni kod. Zmodyfikowałem powód, dla którego chcę tego uniknąć w poprzednim poście. Metoda dataGridView1.DataBind () nie istnieje w WinForms, przypuszczam, że pochodzi z ASP.
mj82
0

// "Komentarz" Filtruj datagrid bez zmiany zbioru danych. Działa doskonale.

            (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
            };         
PPr
źródło
0

Mam jaśniejszą propozycję dotyczącą automatycznego wyszukiwania w DataGridView

to jest przykład

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);
        }
    }
KOUAKEP ARNOLD
źródło
Może się powielić za pomocą stackoverflow.com/questions/5843537/ ...
Tony Dong
-2

Znalazłem prosty sposób na rozwiązanie tego problemu. Przy wiązaniu datagridview właśnie zrobiłeś:datagridview.DataSource = dataSetName.Tables["TableName"];

Jeśli kodujesz jak:

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

datagridview nigdy więcej nie załaduje danych podczas filtrowania.

Quyền Đặng Quang
źródło