Sortowanie TreeView inne niż według nazwy

0

Jak w C++ posortować TreeView tak aby najpierw były elementy zawierające następne. Chodzi o to, że ładuję np. dysk i sortuje mi alfabetycznie mieszając katalogi i pliki. Chcę posortować, żeby były najpierw wszystkie katalogi, a potem pliki. Ewentualnie odwrotnie. W TreeView dodałem ikonki, żeby było to lepiej widać.
image

0
MarekR22 napisał(a):

TTreeView - RAD Studio
Więc które ma być?

Vcl.ComCtrls.TTreeView

CustomSort działa ale sortuje alfabetycznie (TreeView1->CustomSort(CompareFunc, 1))

int CALLBACK CompareFunc(long lParam1, long lParam2, long Reverse)
{
  TTreeNode *Node1 = reinterpret_cast<TTreeNode *>(lParam1);
  TTreeNode *Node2 = reinterpret_cast<TTreeNode *>(lParam2);
  if ((Node1 == NULL) || (Node2 == NULL)) return 0;
  int GT = AnsiStrIComp(Node1->Text.c_str(), Node2->Text.c_str());
  if (Reverse)
	return -GT;
  return GT;
}

Jak to teraz przerobić, żeby grupował elementy będące folderami i plikami.

0
adolf napisał(a):
MarekR22 napisał(a):

TTreeView - RAD Studio
Więc które ma być?

Vcl.ComCtrls.TTreeView

CustomSort działa ale sortuje alfabetycznie (TreeView1->CustomSort(CompareFunc, 1))

int CALLBACK CompareFunc(long lParam1, long lParam2, long Reverse)
{
  TTreeNode *Node1 = reinterpret_cast<TTreeNode *>(lParam1);
  TTreeNode *Node2 = reinterpret_cast<TTreeNode *>(lParam2);
  if ((Node1 == NULL) || (Node2 == NULL)) return 0;
  int GT = AnsiStrIComp(Node1->Text.c_str(), Node2->Text.c_str());
  if (Reverse)
	return -GT;
  return GT;
}

Jak to teraz przerobić, żeby grupował elementy będące folderami i plikami.

Zobacz, do czego służy ta funkcja CompareFunc. Jakie wartości ma zwracać i co one znaczą? Przeczytaj linijka po linijce co się dzieje w tej funkcji, co robi każda linijka?

Podpowiedź 1 - najważniejsze się dzieje w linijce z AnsiStrIComp(...). Co to robi, jaką wartość to zwraca i dlaczego?
Podpowiedź 2 - kiedy porównujesz folder z plikiem, folder musi zawsze być uznany za "mniejszy" od pliku. W przeciwnym przypadku ma być jak jest czyli alfabetycznie.

Jak to ogarniesz, to jeszcze pomyśl jak chcesz żeby to się zachowywało przy "odwróceniu" kolejności czyli z Reverse == true. Nie wiem czy w aplikacji pozwalasz na to, żeby użytkownik sobie odwracał kolejność sortowania widoku (albo i wręcz wybierał, na podstawie jakiego parametru ma być sortowanie - nazwa, rozszerzenie, data itd.), jeśli tak to też musisz to uwzględnić.

0
adolf napisał(a):
MarekR22 napisał(a):

TTreeView - RAD Studio
Więc które ma być?

Vcl.ComCtrls.TTreeView

CustomSort działa ale sortuje alfabetycznie (TreeView1->CustomSort(CompareFunc, 1))

int CALLBACK CompareFunc(long lParam1, long lParam2, long Reverse)
{
  TTreeNode *Node1 = reinterpret_cast<TTreeNode *>(lParam1);
  TTreeNode *Node2 = reinterpret_cast<TTreeNode *>(lParam2);
  if ((Node1 == NULL) || (Node2 == NULL)) return 0;
  int GT = AnsiStrIComp(Node1->Text.c_str(), Node2->Text.c_str());
  if (Reverse)
	return -GT;
  return GT;
}

Jak to teraz przerobić, żeby grupował elementy będące folderami i plikami.

  1. skąd ci się wzieło long Reverse?
  2. czemu masz tam long a nie LPARAM jak jest w dokumentacji? Zmienisz platformę docelową i będzie kwiatek.
  3. Czemu się dziwisz wynikowi, skoro napisałeś kod, który porównuje tylko Text?
    Powinno być dla ciebie oczywiste, że najpierw masz porównywać rodzaj TTreeNode - czy to jest katalog, czy plik, a dipier jak te wartości są równe to porównać Text.
0

Przeanalizowałem Wasze rady. Wykombinowałem tak jak poniżej i .... działa :) Dodałem drugą funcję która sprawdza ikonę :) Najważniejsze, że grupuje pliki i foldery i sortuje alfabetycznie pliki jak i foldery.

  TTreeNode *Node1 = reinterpret_cast<TTreeNode *>(lParam1);
  TTreeNode *Node2 = reinterpret_cast<TTreeNode *>(lParam2);
  if ((Node1 == NULL) || (Node2 == NULL)) return 0;
  if(Node2->ImageIndex == 0 && Node1->ImageIndex == 1)
   return -1;
return 1;
TreeView1->CustomSort(CompareFunc, 1);
TreeView1->CustomSort(CompareFunc2, 1);
1

To nie wygląda dobrze.

  1. if ((Node1 == NULL) || (Node2 == NULL)) return 0; jakoś wątpię, że kiedykolwiek dostaniesz tam null. Raczej nie sortuje się nieistniejących elementów.
  2. posługiwanie się ImageIndex w tym kontekście jest co najmniej podejrzane. Poczytaj dokumentację co dokłądnie zawiera TTreeNode gdy reprezentuje elementy systemu plików. Na 100% będzie właściwość pozwalająca odróżnić pliki od katalogów, bez hacków odnoszących się do ikony.
  3. Sam warunek if(Node2->ImageIndex == 0 && Node1->ImageIndex == 1) wygląda źle, bo nie jest symetryczny. Powinna być spełniona asercja (jeśli nody podane są odwrotnie, to wynik ma być przeciwny), dla wszystkich możliwych argumentów:
assert(CompareFunc(nodeA, nodeB, context) == -CompareFunc(nodeB, nodeA, context));
1

Nie pisałem w C++ Builder całe wieki. Pytając AI wyszły głupoty, ale po wklejeniu dokumentacji wyprodukował coś bardziej rozsądnego: https://chat.openai.com/c/872164b1-5cf4-4ab5-8885-32ec9b3fb96a
AI jest fajne, ale nie można mu ufać (daje złe rozwiązania, jak nie ma się odpowiedniego skilla to powoduje więcej kłopotów niż pożytku), tu masz wersję poprawioną przez mnie, powinno być ok:

int CALLBACK CompareTreeNodes(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort) {
    TTreeNode* Node1 = reinterpret_cast<TTreeNode*>(lParam1);
    TTreeNode* Node2 = reinterpret_cast<TTreeNode*>(lParam2);

    bool isDir1 = Node1->HasChildren();
    bool isDir2 = Node2->HasChildren();

    if (isDir1 == isDir2) {
        return AnsiCompareText(Node1->Text, Node2->Text);
    }
    return isDir1 - isDir2;
}

Swoja drogą API do sortowania nie projektowała osoba z dużym doświadczeniem w C++Builder, więc to strasznie wygląda.


Edit:

A teraz widzę, że jest mały bug, w tym co zaproponowała AI (i co poprawiłem) :/.
Ego trzeba pogrzebać w dokumentacji i zastąpić HasChildren czymś prawidłowym. Czy ktoś inny widzi ten bug :P ?

0

No i działa elegancko i jest sporo krótsze niż moje :) Musiałem tylko zamienić HasChildren() na HasChildren, bo wywalało błąd przy kompilacji. Dzięki za pomoc :)

0
MarekR22 napisał(a):
    bool isDir1 = Node1->HasChildren();
    bool isDir2 = Node2->HasChildren();

    if (isDir1 == isDir2) {
        return AnsiCompareText(Node1->Text, Node2->Text);
    }
    return isDir1 - isDir2;

Ja bym to napisał tak:

int result = Node1->HasChildren - Node2->HasChildren;
if (result == 0) { // można w ten sposób łączyć łatwo więcej porównań
    result = AnsiCompareText(Node1->Text, Node2->Text);
}

return result;

w javascript takie porównania można łączyć przy użyciu || i byłoby:

return (Node1->HasChildren - Node2->HasChildren) || AnsiCompareText(Node1->Text, Node2->Text);

W C++ nie można zastosować tej sztuczki bo mimo że traktuje wszystko różne od zera jako true to w takich wyrażeniach zamienia trochę imo niepotrzebnie i przedwcześnie wartości "falsy" na 0 a "truthy" na 1. Trochę niesymetryczne działanie języka, choć rozumiem że przy statycznych typach mogłoby to być trochę koślawe.

0

Odkryłem jeszcze małe niedociągnięcie. Pusty folder traktuje jako plik.

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