Filtrowanie danych listview z textbox - działa tylko połowicznie

0

Witam,

przeglądając duplikaty tego problemu, nie znalazłszy, opisuję co następuje:
Robię aplikację, która pobiera dane z bazy SQL i wyświetla je na liście listview. Chcę zrobić filtr wyszukujący po kliknięciu w button (lub zdarzeniem textchanged - obojętnie) wpisaną frazę do textbox. I najlepsze jest to, że to częściowo działa. Przykładowo wpisując frazę "kam" znajduje co trzeba, a jak wpiszę "ka", to nie znajduje niczego (albo wszystko). Kod wykonujący te zadanie:

foreach (ListViewItem item in listView1.Items)
{
    if (item.SubItems[1].ToString().ToLower().Contains(txtCountryFind.Text.ToLower()))

    {
        item.Selected = true;
    }
    else
    {
        listView1.Items.Remove(item);
    }
}

Dla przykładu, w kolumnie znajduje się wyraz "Litwa". Wpisując w pole wyszukiwania literę l nie znajduje wyniku. Wpisując li, również nie znajduje. Znajduje dopiero jak wpiszę lit. O co tu chodzi, bo nawet nie wiem jak ten problem sformułować? Albo działa wszystko albo nic...
Dzięki z góry!

1
Yahu napisał(a):
            else
            {
                listView1.Items.Remove(item);
            }

takie "filtrowanie", które jest niszczące dla danych ... dość to dziwne

Bardzo się obrazisz, jak napiszę, ze nie wierzę w twoją analizę sytuacji ? Tak dla zasady

1

Po pierwsze, skąd masz pewność, jaki jest wynik ToString()?

item.SubItems[1].ToString()

Operujesz na obiektach z frameworka, nawet nie konkretnym typie, który załadowałeś do ListView. Twórcy frameworka pewnie przewidzieli to i dają Ci w stringu oczekiwaną wartość, ale nie powinieneś na tym polegać. Powinieneś operować na oddzielnej kolekcji, filtrować ją i bindować pod ListView.

Na tym też bym nie polegał.

listView1.Items.Remove(item);

Skąd wiesz po czym wykona się sprawdzanie, żeby element został usunięty? Po referencji? Wartości? A może Equals i HashCode?

0

@ZrobieDobrze: dlaczego ma być niszczące?
Nie rozumiem dlaczego tak dziwnie to działa, skoro dla niektórych liter/zestawu liter działa, a dla innych nie. I nie mam już pomysłów na rozwiązanie.

@SkrzydlatyWąż: nie mam żadnej pewności, po prostu nie dostrzegam powtarzalności, gdyby nie działało w ogóle, to bym szybciej zrozumiał.

PS. W programowaniu raczkuję, więc wasze próby udowodnienia mi, że nie wiem o czym mówię z całą pewnością skończą się powodzeniem.
PS2. Ubiegając ewentualne pytania, że mogę to zrobić inaczej, po co tak to robię, i podobne odpowiem, że uczę się, i wymyślam się różne "zadania". To mi nie działa do końca i chcę to zrozumieć.

3

Z czego korzystasz, WindowsFormsy, WPF, czy jeszcze coś innego? W WPF kiedyś pisałem i jeśli to ten framework, to zacząłbym na Twoim miejscu od tutoriali dotyczących data bindingu, bindowania obiektów, kolekcji, ObservableCollection<T>, INotifyPropertyChanged, i samych kolekcji jako takich. Powodów czemu nie działa może być kilka, z takim małym kawałkiem kodu można gdybać.

Ogólnie klasy ListViewItem czy inne frameworkowe nie są po to, żeby wykonywać na nich logikę Twojego programu, bo są od warstwy prezentacji. Od logiki są Twoje własne klasy. Tylko nie mów, że ładujesz do ListView dane wprost z DataTable?

1

@SkrzydlatyWąż: Tak, piszę teraz w WinForms. OK, więc ogarnięcie kolekcji w pierwszej kolejności, dzięki. Nie lubię się uczyć na sucho samej teorii (czasem jest konieczna), wolę pisać coś prostego i uczyć się już na tym właśnie problemów, które się pojawią w trakcie.

Tak, na ten moment ładuję z DataTable. Kod:

private void refreshList()
{
    listView1.Items.Clear();
    listView1.View = View.Details;       
    string sql = "SELECT * FROM czwartaTabela";
    cmd = new SqlCommand(sql, con);
    con.Open();
    ad = new SqlDataAdapter(cmd);
    ad.Fill(dt);
    con.Close();
    foreach (DataRow dr in dt.Rows)
    {
        item = new ListViewItem(dr[0].ToString());
        for (int i = 1; i < dt.Columns.Count; i++)
        {
            item.SubItems.Add(dr[i].ToString());
        }
        listView1.Items.Add(item);
    }          
}
2
Yahu napisał(a):

@ZrobieDobrze: dlaczego ma być niszczące?
Nie rozumiem dlaczego tak dziwnie to działa, skoro dla niektórych liter/zestawu liter działa, a dla innych nie. I nie mam już pomysłów na rozwiązanie.

Działa dziwnie na własne życzenie
wpisujesz tekst "ab"
wszystko, co nie jest zgodne z "ab" ulega skasowaniu
rozmyślasz sie z "B", backspace, zmieniasz na "ac"

bez urazy, ale analiza jest w rodzaju "jak załozyłem kapelusz, spowodowało to padanie deszczu"

Yahu napisał(a):

PS. W programowaniu raczkuję, więc wasze próby udowodnienia mi, że ...

Przed wejściem w te obszary, MOCNO uzpełnic wiedzę (z dokuemnatcji) i praktykę, na prostszych projektach, być może nie dających tyle psychologicznego "szczęścia", ale wiedze warstwa za warstwą, pietro za piętrem.

Za jakiś powodów z bazami danych to imponuje, to tak sie czuje jakby szybciej być prawidzwym programistą, ale uczysz się tak, jak się ludzie źle uczą pływać czy grać na istsrumencie.
Trzeba będzie b. duży wysiłku aby tego oduczyć, wrócić do poziomu wyjsciowego.

Srodowisko ma bardzo ciekawe możliwosci integarcji z bazą danych (@SkrzydlatyWąż ), ale trzeba ich użyć prawidłowo

2

Są dwa sposoby. Opisze je oba:
Sposób 1:
Wyświetlasz tylko te elementy w liście, które spełniają warunek (frazę którą wpisałeś w polu wyszukaj)
Sposób 2:
Wyświetlasz wszystkie elementy w liście, ale pierwszy element spełniający warunek (frazę którą wpisałeś w polu wyszukaj) oznacza jako selected

Sposób 1:

  1. Tworzysz klasę z właściwościami reprezentującymi komórkę w tabeli Twojej bazy danych np. Person
  2. Robisz sobie metodę public async Task<List<Person>> GetPersons(string SearchText)
    Ta metoda ma zadanie wysłać odpowiednie zapytanie do bazy i pobrać tylko te wyniki, które spełniają warunek. Po pobraniu przeklejasz wynik do widoku i tyle. Cała operacja polega wtedy na zapytaniu SQL lub innym.

Sposób 2:

  1. Tworzysz klasę z właściwościami reprezentującymi komórkę w tabeli Twojej bazy danych np. Person
  2. Pobierasz wszystkie elementy z bazy danych i wklejasz je do widoku jako sparsowane obiekty na Twój obiekt np. Person
  3. Tworzysz metodę SelectFirstOrDefault(string SearchText)
    Ta metoda wykonuje 3 kroki:
    a) odznacza aktualnie zaznaczony element
    b) wyszukuje pierwszy obiekt Person spełniający warunek
    c) zaznacza znaleziony obiekt, jeżeli został znaleziony. Tu nie ma mowy o usuwania czegokolwiek z kolekcji bo wtedy tracisz to bezpowrotnie

ps. Polecam odejść od WinForms i przenieść się na WPF. Wszystko da się uzyskać dużo prościej niż w WinForms.

2

tego typu filtrowania robiłem na zasadzie takiej gdzie trzymałem jedna kolekcje jako prywatną 'oryginalną pełną' a poprzez wyrażenia z textbox, filtrowałem poprzez linq z tej oryginalnej kolekcji i zapisywałem i wynik zapisywałem w drugiej kolekcji która była źródłem danych kontrolki na ui i tyle
jeden warunek był taki ze jak w textbox nie było nic to cala kolekcje oryginalna kopiowałem do tej podpiętej do kontrolki jako źródło danych.

dosyć łatwo jest tez 'zaprządz' do tego typu filtrowania symbol zastępujący dowolne znaki np. '*' i poprzez wyrażenia regularne i jeszcze bardziej zrobić takie filtrowanie intuicyjnym.

PS. potwierdzam ze lepiej przesiasc sie na WPF, nie ma tam cudow z odswiezaniem kolekcji czy kontrolek po prostu dzialasz w viewmodelu swoje a dependencyproperty robią swoje na widoku - super wygodne. Ale fakt ciężko było siebie przekonać do przesiadki z 'intuicyjnego C#'arpowego WinForm' na 'XAMLowego dziwoląga' który okazał się finalnie duzo lepszym od drag'n'drop kontrolek.

2
gswidwa1 napisał(a):

Są dwa sposoby. Opisze je oba:

Dodam 3-ci sposób, używam go w rozwiązaniach produkcyjnych, użytkownicy bardzo chwalą.
Po zmianie filtra:

  • Zmieniasz sortowanie z: (dotychczasowe) na: (spełniająceWarunek,dotychczasowe)
  • Zmieniasz kolor tła dla wierszy spełniających warunek.

Jako wynik - spełniające warunek są na początku listy z wyróżnionym tłem, posortowane wg ustawionego klucza, dalej idzie reszta też posortowana wg ustawionego klucza.

0

Dzięki za wszystkie odpowiedzi!

1 użytkowników online, w tym zalogowanych: 0, gości: 1