ListView Spalten sortieren



  • ich lade Dateien nach Auswahl des Verzeichnisses über eine LMDBrowseDlg-Komponente (Dialog von LMD-Tools). Die ListView hat je eine Spalte für Dateiname, Größe und Änderungszeit. Das Laden der Dateien funktioniert bereits. Nun möchte ich die Spalten sortieren. Dies hab ich bisher:

    __fastcall TfrmImport::TfrmImport(TComponent* Owner)
       : TForm(Owner)
    {
       cmbStandort->Text = cmbStandort->Items->Strings[0]; //1. Element in Combobox anzeigen
    
       lviewDateien->ViewStyle = vsReport;
       lviewDateien->Columns->Add();
       lviewDateien->Columns->Items[0]->Caption="Datei";
       lviewDateien->Columns->Add()->Caption="Größe";
       lviewDateien->Columns->Add()->Caption="Geändert am";
       lviewDateien->Columns->Items[0]->Width=220;
       lviewDateien->Columns->Items[1]->Width=100;
       lviewDateien->Columns->Items[2]->Width=200;
    }
    //---------------------------------------------------------------------------
    void __fastcall TfrmImport::btnImportClick(TObject *Sender)
    {
       AnsiString verzeichnis;
       HANDLE hFileHandle;
       WIN32_FIND_DATA wfd;
    
       if (LMDBrowseDlg1->Execute()) //Dialog zum Auswählen des Verzeichnisses
          verzeichnis = LMDBrowseDlg1->SelectedFolder;
    
       //prüfen ob das letzte Zeichen ein \ ist ansonsten wird es hinzugefügt
       //Das Verzeichnis beginnt immer mit \\*
       if (verzeichnis.Length()>0) {
          if (verzeichnis[verzeichnis.Length()] != '\\')
             verzeichnis += "\\*";
          else
             verzeichnis += "*";
       }
       //Sucht die Erste Datei
       hFileHandle = FindFirstFile(verzeichnis.c_str(),&wfd);
    
       //Daten in ListView schreiben:
       do {
          if (!((wfd.cFileName[0]=='.' && wfd.cFileName[1]=='\0') || (wfd.cFileName[0]=='.' && wfd.cFileName[1]=='.' && wfd.cFileName[2]=='\0'))) {
             TListItem* p = lviewDateien->Items->Add();
             p->Caption = wfd.cFileName;
    
             LARGE_INTEGER c;
             c.u.LowPart = wfd.nFileSizeLow;
             c.u.HighPart = wfd.nFileSizeHigh;
             p->SubItems->Add(FloatToStr(c.QuadPart/1024) + " KB");
    
             SYSTEMTIME st;
             if (FileTimeToSystemTime(&wfd.ftLastWriteTime,&st))
                p->SubItems->Add(DateTimeToStr(SystemTimeToDateTime(st)));
          }
       }
       while (FindNextFile(hFileHandle,&wfd));
       FindClose(hFileHandle);
    }
    //---------------------------------------------------------------------------
    void __fastcall TfrmImport::lviewDateienColumnClick(TObject *Sender,
          TListColumn *Column)
    {
       lviewDateien->Items->BeginUpdate();
       lviewDateien->CustomSort((MyCustomSort, Column->Index); //hier gibts einen Error!!!
       lviewDateien->Items->EndUpdate();
    }
    //---------------------------------------------------------------------------
    int __stdcall TfrmImport::MyCustomSort(long p1, long p2, long ColumnToSort)
    {
       TListItem* Item1=(TListItem*)p1;
       TListItem* Item2=(TListItem*)p2;
       int ix = ColumnToSort - 1;
       if (ColumnToSort == 0)
          return CompareText(Item1->Caption, Item2->Caption);
       else if (ColumnToSort == 1)
          return compareAsInt(Item1->SubItems->Strings[ix], Item2->SubItems->Strings[ix]);
       else
          return compareAsDate(Item1->SubItems->Strings[ix], Item2->SubItems->Strings[ix]);
    }
    

    In der markierten Zeile gibt es folgenden Fehler:

    [C++ Fehler] unitImport.cpp(94): E2034 Konvertierung von 'int (__stdcall * (_closure )(long,long,long))(long,long,long)' nach 'int (__stdcall *)(long,long,long)' nicht möglich
    [C++ Fehler] unitImport.cpp(94): E2342 Keine Übereinstimmung des Typs beim Parameter 'SortProc' ('int (__stdcall *)(long,long,long)' erwartet, 'void' erhalten)
    

    Leider kann ich den Fehler nicht finden.

    Frage2: Wie kann ich erreichen, dass nur Dateien mit Endung txt in die ListView geladen werden?



    1. Die Sortierfunktion muss eine frei Funktion sein:
    int __stdcall MySortFunc( long p1, long p2, long d )
    {
       ...
    }
    
    1. Im Dialog gibt´s mit Sicherheit einen Filter, den man einstellen kann. Setz den mal auf den String
    Textdateien (*.txt)|*.txt
    


  • Die Komponente hat leider keinen Filter so wie TOpenDialog. Kann ich die Dateien irgendwie anders als txt filtern?

    Wenn ich die Funktion frei definiere funktioniert es. Das muss ich auch mit den Funktionen compareAsInt und compareAsDate machen. Aber warum ist das so? Ist es in OOP nicht unüblich Funktionen und Variablen global zu definieren? Wenn ich nur die Definition der Funktion mit dem Code verwende funktioniert es nicht. Dann ist die Funktion nicht bekannt. Erst nach der Deklaration (Prototyp) läuft das Programm durch. Das wundert mich, da zu meinen Anfangszeiten in C++ in der Konsole die Definition der Funktion genügte.

    Mir ist noch was aufgefallen: Wenn ich sortiere geht das nur in eine Richtung. Wie geht es anders herum?
    Hier mal noch die beiden Funktionen compareAsInt und compareAsDate: (sind auch frei definiert und deklariert)

    int compareAsInt(const AnsiString& s1, const AnsiString& s2)
    {
       if (StrToInt(s1) < StrToInt(s2))
         return -1;
       else if (StrToInt(s1)> StrToInt(s2))
         return 1;
       else
         return 0;
    }
    //---------------------------------------------------------------------------
    int compareAsDate(const AnsiString& s1, const AnsiString& s2)
    {
       if (StrToDateTime(s1)< StrToDateTime(s2))
         return -1;
       else if (StrToDateTime(s1)> StrToDateTime(s2))
         return 1;
       else
         return 0;
    }
    

    Edit: grad ein Problem selbst lösen können: Hab mir die Funktion FindFirstFile nochmal genau angesehen in der Hilfe und gesehen, das man eine Maske angeben kann:
    hier die Lösung:

    if (verzeichnis.Length()>0) {
          if (verzeichnis[verzeichnis.Length()] != '\\')
             verzeichnis += "\\*txt";
          else
             verzeichnis += "*txt";
    


  • rudpower schrieb:

    Wenn ich die Funktion frei definiere funktioniert es. Das muss ich auch mit den Funktionen compareAsInt und compareAsDate machen. Aber warum ist das so? Ist es in OOP nicht unüblich Funktionen und Variablen global zu definieren? Wenn ich nur die Definition der Funktion mit dem Code verwende funktioniert es nicht. Dann ist die Funktion nicht bekannt. Erst nach der Deklaration (Prototyp) läuft das Programm durch. Das wundert mich, da zu meinen Anfangszeiten in C++ in der Konsole die Definition der Funktion genügte.

    Vermutlich weil die VCL Komponente nur ein Windows ListView kapselt und zum Sortieren die Nachricht LVM_SORTITEMS benutzt. Dieses LVM_SORTITEMS erwartet als LPARAM eine Funktion, die die oben gepostete Signatur hat.

    rudpower schrieb:

    Mir ist noch was aufgefallen: Wenn ich sortiere geht das nur in eine Richtung. Wie geht es anders herum?

    Da denk doch mal scharf nach...



  • scharf nachgeddacht und nicht draufgekommen.

    Noch eine andere Sache: Importiere ich die Daten und wähle als Verzeichnis zB den Desktop aus, Laufwerk C, ein Netzlaufwerk oder Arbeitsplatz zeigt er mir
    "ì<»" in der Liste mit einer Dateigröße von 5320399331790779 Bytes und als Zeit: 31.01.1666 08:19:01

    Wie kommt das?



  • rudpower schrieb:

    scharf nachgeddacht und nicht draufgekommen.

    rudpower schrieb:

    int compareAsDate(const AnsiString& s1, const AnsiString& s2) 
    { 
       if (StrToDateTime(s1)< StrToDateTime(s2)) 
         return -1; 
       else if (StrToDateTime(s1)> StrToDateTime(s2)) 
         return 1; 
       else 
         return 0; 
    }
    

    *Stups*
    Wenn s1 vor s2 in der Liste auftauchen soll gibst du was zurück? Und wenn s1 nach s2 auftauchen soll musst du was zurückgeben?



  • Die Compare Funktion lässt sich noch etwas vereinfachen

    int compareAsDate(const AnsiString& s1, const AnsiString& s2)
    {
       return CompareDateTime(StrToDateTime(s1), StrToDateTime(s2));
    }
    


  • Noch eine andere Sache: Importiere ich die Daten und wähle als Verzeichnis zB den Desktop aus, Laufwerk C, ein Netzlaufwerk oder Arbeitsplatz zeigt er mir
    "ì<»" in der Liste mit einer Dateigröße von 5320399331790779 Bytes und als Zeit: 31.01.1666 08:19:01

    Das Problem habe ich lösen können. Das Phänomen tritt auf wenn in dem Order keine txt-Datei ist bzw diese versteckt sind. Ich habe dann einfach eine Abfrage für das Handle eingebaut:

    if(hFileHandle == INVALID_HANDLE_VALUE)
          ShowMessage("Das ausgewählte Verzeichnis enthält keine Dateien");
    

    Wenn s1 vor s2 in der Liste auftauchen soll gibst du was zurück?

    ich denke mal -1

    Und wenn s1 nach s2 auftauchen soll musst du was zurückgeben?

    1,

    Dann kann ich doch einfach den Rückgabewert in der MyCustomSort-Methode negieren? also:

    // Sortiermethode in ListView
    int __stdcall MyCustomSort(long p1, long p2, long ColumnToSort)
    {
       TListItem* Item1=(TListItem*)p1;
       TListItem* Item2=(TListItem*)p2;
       int ix = ColumnToSort - 1;
       if (ColumnToSort == 0)
          return -CompareText(Item1->Caption, Item2->Caption);
       else if (ColumnToSort == 1)
          return -compareAsInt(Item1->SubItems->Strings[ix], Item2->SubItems->Strings[ix]);
       else
          return -compareAsDate(Item1->SubItems->Strings[ix], Item2->SubItems->Strings[ix]);
    }
    

    Muss ich da eine 2.Funktion schreiben? In dem Klickereignis der Spalte (lviewDateienColumnClick) kann ich ja den 1. Parameter der Methode CustomSort nicht negieren, da es ja ein Funktionszeiger ist. Wie mach ich das am besten, das beim 1. Klick vorwärts sortiert wird und beim 2. Klick rückwärts? Brauch ich hier eigentlich die beiden Zeilen lviewDateien->Items->BeginUpdate(); und lviewDateien->Items->EndUpdate();?



  • Vielleicht probierst das einfach mal selber aus?



  • DocShoe schrieb:

    1. Die Sortierfunktion muss eine frei Funktion sein:
    int __stdcall MySortFunc( long p1, long p2, long d )
    {
       ...
    }
    

    ...
    [/code]

    Völlig korrekt, man könnte die Sortierfunktion aber auch problemlos als static deklarieren und damit TfrmImport zuordnen, damit könnte man sie zumindest dann als private deklarieren:

    h-Datei:

    [cpp]
    class TfrmImport : .....
      static int __stdcall MySortFunc( long p1, long p2, long d );
      ....
    [/cpp]
    

    cpp-Datei:

    [cpp]
    int __stdcall TfrmImport::MySortFunc( long p1, long p2, long d )
    {
       ...
    }
    
    [/cpp]
    


  • Warum diese Umstände? Eine freie Funktion ist doch völlig Ok. Wenn sie nur in dieser Klasse gebraucht wird kann man ja die Deklaration auch in die cpp packen, so dass auch nichts nach außen kommt.


Anmelden zum Antworten