Strona 1 z 1

Problem z sortowanim liczb w TListViev

Nowy postNapisane: piątek, 25 lipca 2008, 14:43
przez Darek_C++
Witam Ponownie Cyfrowego Barona oraz innych użytkowników forum.

Mam problem z sortowaniem kolumn w TListView, a mianowicie po kliknięciu w nagłówki kolumn sortowanie jest dobre jeśli w kolumnie są wartości słowne, ale jeśli same liczby na czym mi też zależy niestety sortowanie jest błędne.
Zamieszczam kod:
Kod: Zaznacz cały
//---------------------------------------------------------------------------

#include <vcl.h>
#pragma hdrstop

#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
int sort; bool column[20]; int sort2; bool column2[20];
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
    : TForm(Owner)
{
}
//---------------------------------------------------------------------------

void __fastcall TForm1::FormCreate(TObject *Sender)
{
    TListColumn *ListCol;
    TListItem *ListIt;

    ListIt = ListView1->Items->Add();
    ListIt->Caption = "Beata";
    ListIt->SubItems->Add("30");

    ListIt = ListView1->Items->Add();
    ListIt->Caption = "Iwona";
    ListIt->SubItems->Add("138910");

    ListIt = ListView1->Items->Add();
    ListIt->Caption = "Syliwa";
    ListIt->SubItems->Add("60370");

    ListIt = ListView1->Items->Add();
    ListIt->Caption = "Aneta";
    ListIt->SubItems->Add("70");

    ListIt = ListView1->Items->Add();
    ListIt->Caption = "Zenobia";
    ListIt->SubItems->Add("40");

    ListIt = ListView1->Items->Add();
    ListIt->Caption = "FIlona";
    ListIt->SubItems->Add("370");

}
//---------------------------------------------------------------------------

void __fastcall TForm1::ListView1ColumnClick(TObject *Sender,
      TListColumn *Column)
{
   sort=Column->Index;
   ((TCustomListView *)Sender)->AlphaSort();
   if (column[sort]==false) column[sort]=true;
   else column[sort]=false;   
}
//---------------------------------------------------------------------------
void __fastcall TForm1::ListView1Compare(TObject *Sender, TListItem *Item1,
      TListItem *Item2, int Data, int &Compare)
{
   if (column[sort]==true)
   {
      if (sort==0)
      Compare=CompareText(Item1->Caption,Item2->Caption);
      else
      Compare=CompareText(Item1->SubItems->Strings[sort-1],Item2->SubItems->Strings[sort-1]);
      }
      else
      {
      if (sort==0)
      Compare=CompareText(Item2->Caption,Item1->Caption);
      else
      Compare=CompareText(Item2->SubItems->Strings[sort-1],Item1->SubItems->Strings[sort-1]);
   }   
}
//---------------------------------------------------------------------------

Oraz dla ułatwienia cały projekt z BCB6.
Projekt Sortowania
ListViewSortowanie.rar


Mam nadzieję, że ktoś z Was pomoże mi w tej kwestii sortowania.

Pozdrawiam

Re: Problem z sortowanim liczb w TListViev

Nowy postNapisane: piątek, 25 lipca 2008, 16:47
przez Cyfrowy Baron
Funkcja AlphaSort traktują liczby również jako tekst i sortuje je np. tak: 1, 10, 2, 3, 4, 40, itd..., czyli tekst., jest o tym napisane w plikach pomocy do BCB (czasami warto tam zajrzeć).
Jeżeli chcesz posortować liczby jako liczby musisz użyć funkcji CustomSort, ale tutaj już trzba stworzyć dwie funkcje pomocnicze. Opis korzystania z funkcji CustomSort znajduje się ► patrz serwis: Cyfrowy Baron w dziale: porady | ListBox | Sortowanie listy wg. różnych kryteriów.



Bez zagłębiania się w problem, masz tutaj gotowy kod:

Kod: Zaznacz cały
//---------------------------------------------------------------------------

#include <vcl.h>
#pragma hdrstop

#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
int sort; bool column[20]; int sort2; bool column2[20];
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
    : TForm(Owner)
{
}
//---------------------------------------------------------------------------
#pragma warn +csu
int foo(int u, int i)
{
int z = u - i;
return z;
}
//--------------------------------
int __stdcall CustomSortDbInt(long Item1, long Item2, long lParam)
{
String cTemp1 = ((TListItem *)Item1)->SubItems->Strings[0];
String cTemp2 = ((TListItem *)Item2)->SubItems->Strings[0];
int rTemp1 = cTemp1.Trim().ToIntDef(0);
int rTemp2 = cTemp2.Trim().ToIntDef(0);
if(!(BOOL)lParam) return -foo(rTemp1, rTemp2);
return foo(rTemp1, rTemp2);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormCreate(TObject *Sender)
{
    TListColumn *ListCol;
    TListItem *ListIt;

    ListIt = ListView1->Items->Add();
    ListIt->Caption = "Beata";
    ListIt->SubItems->Add("30");

    ListIt = ListView1->Items->Add();
    ListIt->Caption = "Iwona";
    ListIt->SubItems->Add("138910");

    ListIt = ListView1->Items->Add();
    ListIt->Caption = "Syliwa";
    ListIt->SubItems->Add("60370");

    ListIt = ListView1->Items->Add();
    ListIt->Caption = "Aneta";
    ListIt->SubItems->Add("70");

    ListIt = ListView1->Items->Add();
    ListIt->Caption = "Zenobia";
    ListIt->SubItems->Add("40");

    ListIt = ListView1->Items->Add();
    ListIt->Caption = "FIlona";
   ListIt->SubItems->Add("370");
}
//---------------------------------------------------------------------------

void __fastcall TForm1::ListView1ColumnClick(TObject *Sender,
      TListColumn *Column)
{
   sort = Column->Index;
   if(Column->Index == 0)
     ((TCustomListView *)Sender)->AlphaSort();
   else
   {
    static bool tmp = false;
    ((TCustomListView *)Sender)->CustomSort(CustomSortDbInt, (long)tmp);
    tmp = !tmp;
   }
   
   if (column[sort]==false) column[sort]=true;
   else column[sort]=false;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::ListView1Compare(TObject *Sender, TListItem *Item1,
      TListItem *Item2, int Data, int &Compare)
{
   if (column[sort]==true)
   {
      if (sort==0)
      Compare=CompareText(Item1->Caption,Item2->Caption);
      else
      Compare=CompareText(Item1->SubItems->Strings[sort-1],Item2->SubItems->Strings[sort-1]);
      }
      else
      {
      if (sort==0)
      Compare=CompareText(Item2->Caption,Item1->Caption);
      else
      Compare=CompareText(Item2->SubItems->Strings[sort-1],Item1->SubItems->Strings[sort-1]);
   }
}
//---------------------------------------------------------------------------




Funkcja CustomSortDbInt została skonstruowana w taki sposób, że nie uwzględnia pierwszej kolumny (Column 0). Pisząc ten kod zakładałem, że w pierwszej kolumnie zawsze będzie tekst, a kolejne kolumny zawierają liczby. Możesz to oczywiście sobie dowolnie przerabiać.

Re: Problem z sortowanim liczb w TListViev

Nowy postNapisane: piątek, 25 lipca 2008, 16:53
przez Darek_C++
Wielkie dzięki Cyfrowy Baronie! Jak zwykle Okazałeś się niezawodnY
Pozdrawiam

PS Dobrze, że Reaktywowałeś forum ;)

Re: Problem z sortowanim liczb w TListViev

Nowy postNapisane: piątek, 25 lipca 2008, 22:46
przez Darek_C++
Niestety, ale pojawił się problem, a mianowicie jeśli kolumn będzie więcej niż dwie sortowanie dla pozostałych przestaje działać.
Kod: Zaznacz cały
// więcej kolumn i sortowanie tylko dziala dla pierwszej :(
void __fastcall TForm1::FormCreate(TObject *Sender)
{
   TListColumn *ListCol;
   TListItem *ListIt;

   ListIt = ListView1->Items->Add();
   ListIt->Caption = "Beata";
   ListIt->SubItems->Add("Katowice");
   ListIt->SubItems->Add("30");

   ListIt = ListView1->Items->Add();
   ListIt->Caption = "Iwona";
   ListIt->SubItems->Add("Poznań");
   ListIt->SubItems->Add("138910");

   ListIt = ListView1->Items->Add();
   ListIt->Caption = "Syliwa";
   ListIt->SubItems->Add("Warszawa");
   ListIt->SubItems->Add("60370");

   ListIt = ListView1->Items->Add();
   ListIt->Caption = "Aneta";
   ListIt->SubItems->Add("Antoninek");
   ListIt->SubItems->Add("70");

   ListIt = ListView1->Items->Add();
   ListIt->Caption = "Zenobia";
   ListIt->SubItems->Add("Inowrocław");
   ListIt->SubItems->Add("40");

   ListIt = ListView1->Items->Add();
   ListIt->Caption = "FIlona";
   ListIt->SubItems->Add("Lublin");
   ListIt->SubItems->Add("370");
}


Docelowo powinno być możliwe sortowanie w kolumnach stringowych, liczbowych oraz zawierających daty w formacie 2008-07-22 22:23:54 lub data jako sama data bez minut 2008-05-12 i była by możliwość wybrania typu sortowania dla danej kolumny niezależnie od ich ilości. Czyli chodzi o uniwersalną implementacje tego sortowania.

Wiem, że dużo wymagam, ale wyjątkowo mi ta kwestia nie wychodzi :(

Re: Problem z sortowanim liczb w TListViev

Nowy postNapisane: sobota, 26 lipca 2008, 09:51
przez Cyfrowy Baron
Sądziłem, że mając ten kod, poradzisz sobie z jego przerobieniem, no ale skoro nie poradziłeś sobie, to pewnie będziesz częstym gościem tego forum.
Odpowiedź na Twój problem znajduje się w zasadzie już w ► patrz serwis: Cyfrowy Baron, w poradzie o której wspomniałem, ale jak się domyślam, nawet tam nie zajrzałeś. :|((

Co do sortowania daty, to należy ją traktować jako tekst nie liczbę, a będzie sortowana prawidłowo.
Stworzyłem uniwersalną funkcję sortującą, zrezygnowałem przy tym z funkcji AlphaSort(). Teraz funkcja sprawdza która kolumna została kliknięta i odpowiednio do tego się przełącza. Funkcja sprawdza również czy w kolumnie znajduje się tekst czy liczba i w zależności od zawartości zmienia sposób sortowania, w efekcie jeżeli kolumna zawiera wartości mieszane, czyli część wierszy w kolumnie zawiera tekst, a część liczby, to najpierw zostaną posortowana liczby jako liczby, a potem tekst jako tekst. W zależności od potrzeb może to być pożądane lub nie. Jeżeli w Twoim przypadku nie będzie to pożądane, to musisz zastanowić się trochę nad mechanizmem działania funkcji, gdyż zmiana tego stanu rzeczy jest banalnie (wręcz śmiesznie) prosta.
Zmienna sDirection służy do sterowania kierunkiem sortowania, a konkretnie jego automatycznym przełączaniem - rosnąco - malejąco. Jest to zmienna globalna i nie można jej zawrzeć wewnątrz funkcji, gdyż sam fakt zmiany jej stanu (true - fale) wewnątrz tejże funkcji sprawi, że przestanie ona działać prawidłowo.


Gotowiec:
Kod: Zaznacz cały
//---------------------------------------------------------------------------

#include <vcl.h>
#pragma hdrstop

#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
int sort; bool column[20]; int sort2; bool column2[20];
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
    : TForm(Owner)
{
}
//---------------------------------------------------------------------------
#pragma warn +csu
int foo(double u, double i)
{
double z = u - i;
return z;
}
//--------------------------------
BOOL sDirection = true;
int __stdcall CustomSortDbInt(long Item1, long Item2, long lParam)
{
bool test = true;
String cTemp1, cTemp2;
if(lParam == -1)
{
  cTemp1 = ((TListItem *)Item1)->Caption;
  cTemp2 = ((TListItem *)Item2)->Caption;
}
else
{
  cTemp1 = ((TListItem *)Item1)->SubItems->Strings[lParam];
  cTemp2 = ((TListItem *)Item2)->SubItems->Strings[lParam];
}

double rTemp1, rTemp2;

try{ rTemp1 = cTemp1.Trim().ToDouble(); } catch(...){ test = false;}
try{ rTemp2 = cTemp2.Trim().ToDouble(); } catch(...){ test = false;}

if(!test)
{
  if(!sDirection) return CompareText(cTemp1.Trim(), cTemp2.Trim());
  return -CompareText(cTemp1.Trim(), cTemp2.Trim());
}

if(!sDirection) return foo(rTemp1, rTemp2);
return -foo(rTemp1, rTemp2);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormCreate(TObject *Sender)
{
    TListColumn *ListCol;
   TListItem *ListIt;

   ListIt = ListView1->Items->Add();
   ListIt->Caption = "Beata";
   ListIt->SubItems->Add("Katowice");
   ListIt->SubItems->Add("30");
   ListIt->SubItems->Add("100");
   ListIt->SubItems->Add("2008-07-22");
   ListIt->SubItems->Add("1000");

   ListIt = ListView1->Items->Add();
   ListIt->Caption = "Iwona";
   ListIt->SubItems->Add("Poznań");
   ListIt->SubItems->Add("138910");
   ListIt->SubItems->Add("50,1");
   ListIt->SubItems->Add("2008-04-11");
   ListIt->SubItems->Add("tekst 1");

   ListIt = ListView1->Items->Add();
   ListIt->Caption = "Syliwa";
   ListIt->SubItems->Add("Warszawa");
   ListIt->SubItems->Add("60370");
   ListIt->SubItems->Add("111");
   ListIt->SubItems->Add("2006-01-31");
   ListIt->SubItems->Add("17");

   ListIt = ListView1->Items->Add();
   ListIt->Caption = "Aneta";
   ListIt->SubItems->Add("Antoninek");
   ListIt->SubItems->Add("70");
   ListIt->SubItems->Add("90");
   ListIt->SubItems->Add("2007-11-01");
   ListIt->SubItems->Add("5");

   ListIt = ListView1->Items->Add();
   ListIt->Caption = "Zenobia";
   ListIt->SubItems->Add("Inowrocław");
   ListIt->SubItems->Add("40");
   ListIt->SubItems->Add("21");
   ListIt->SubItems->Add("2008-04-06");
   ListIt->SubItems->Add("Antoni");

   ListIt = ListView1->Items->Add();
   ListIt->Caption = "FIlona";
   ListIt->SubItems->Add("Lublin");
   ListIt->SubItems->Add("370");
   ListIt->SubItems->Add("113");
   ListIt->SubItems->Add("2008-01-31");
   ListIt->SubItems->Add("Ananas");
}
//---------------------------------------------------------------------------

void __fastcall TForm1::ListView1ColumnClick(TObject *Sender,
      TListColumn *Column)
{
sort = Column->Index;

sDirection = !sDirection;
((TCustomListView *)Sender)->CustomSort(CustomSortDbInt, (long)Column->Index - 1);

if (column[sort]==false) column[sort]=true;
else column[sort]=false;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::ListView1Compare(TObject *Sender, TListItem *Item1,
      TListItem *Item2, int Data, int &Compare)
{
   if (column[sort]==true)
   {
      if (sort==0)
      Compare=CompareText(Item1->Caption,Item2->Caption);
      else
      Compare=CompareText(Item1->SubItems->Strings[sort-1],Item2->SubItems->Strings[sort-1]);
      }
      else
      {
      if (sort==0)
      Compare=CompareText(Item2->Caption,Item1->Caption);
      else
      Compare=CompareText(Item2->SubItems->Strings[sort-1],Item1->SubItems->Strings[sort-1]);
   }
}
//---------------------------------------------------------------------------

Re: Problem z sortowanim liczb w TListViev

Nowy postNapisane: sobota, 26 lipca 2008, 10:25
przez Darek_C++
Naprawdę jesteś "Cyfrowy Baron"! Jeszcze raz wielkie dzięki za pomoc no i gotowca.

Warto jeszcze dodać, że przedstawiony kod działa jako program, bo w środowisku testowym zgłaszany jest wyjątek
Kod: Zaznacz cały
try{ rTemp1 = cTemp1.Trim().ToDouble(); } catch(...){ test = false;}

zresztą obsługiwany przez kod, co nieźle mnie zmyliło na samym początku.
/* to pewnie będziesz częstym gościem tego forum.*/
Odpowiem w ten sposób -> dobrze, że Reaktywowałeś forum, bo Twoja pomoc bardzo ułatwia tworzenie aplikacji w BCB C++ ;)

Re: Problem z sortowanim liczb w TListViev

Nowy postNapisane: sobota, 26 lipca 2008, 10:45
przez Cyfrowy Baron
Metoda try{;}catch(...){;} zawsze zgłasza wyjątek, gdyż taka jest jej konstrukcja: spróbuj wykonać polecenie zawarte w pierwszym bloku (instrukcja warunkowa), a jeżeli się nie uda wykonaj polecenie zawarte w drugim bloku (instrukcja bezwarunkowa). Komunikaty wyjątku są zgłaszane tylko gdy program zostanie uruchomiony w środowisku. Można te komunikaty wyłączyć w ustawieniach środowiska BCB.
Zdecydowałem się na użycie tej metody, żeby nie tworzyć dodatkowej funkcji sprawdzającej czy mamy do czynienia z tekstem czy też z liczbą.



Kolejna sprawa o której zapomniałem wspomnieć, jeżeli używasz mojej funkcji, to używanie zdarzenie OnCompare i tej zmiennej sort do zmiany kierunku sortowania jest tutaj zupełnie zbędne, gdyż to zdarzenie ma zastosowanie tylko w przypadku funkcji AlphaSort(). Jak to wyjaśniałem w poprzednim poście, u mnie kierunkiem sortowania steruje zmienna globalna sDirection.

Re: Problem z sortowanim liczb w TListViev

Nowy postNapisane: sobota, 26 lipca 2008, 11:28
przez Darek_C++
Cyfrowy Baron napisał(a):Metoda try{;}catch(...){;} zawsze zgłasza wyjątek, gdyż taka jest jej konstrukcja:

Tutaj wyraziłeś się niejasno. Blok try{}catch(...) nie zgłasza wyjątku, ale go przechwytuje oczywiście wtedy jeśli w klamrach try{}catch(...){} wywołamy funkcje, metodę która takowy wyjątek zgłosi np:
Kod: Zaznacz cały
try
{
   String xx="tekst";
   xx.ToInt();
   // Lub wyrzucenie wyjątku
   // throw Exception("Komunikat Wujątku");
}
catch(Exception &e)
{
   ShowMessage(e.Message);
}

Re: Problem z sortowanim liczb w TListViev

Nowy postNapisane: sobota, 26 lipca 2008, 12:09
przez Cyfrowy Baron
Ani o jedno, ani o drugie mi nie chodziło, tylko o to, że jeżeli skompilujesz program i uruchomisz go w środowisku BCB, to kompilator zgłosi ostrzeżenie, gdy natknie się na wyjątek, tak więc "zgłaszanie wyjątku", było swego rodzaju nieprecyzyjnym skrótem myślowym.
Zakładam, że każdy korzystający z tej metody wie o tym, że przechwytuje ona wyjątki.