Ali Cetinturk
Ali Cetinturk

Reputation: 79

FindFirst, FindNext (Delphi Xe, Win7) rank is not correct

I have some files in a directory. I try get these files with FindFirst and FindNext but I can't get same order on Windows 7.

C:\Test
SampleFile.0.png
SampleFile.1.png
SampleFile.2.png
SampleFile.3.png
SampleFile.4.png
SampleFile.5.png
SampleFile.6.png
SampleFile.7.png
SampleFile.8.png
SampleFile.9.png
SampleFile.10.png
SampleFile.11.png
SampleFile.12.png
SampleFile.13.png
SampleFile.14.png
SampleFile.15.png
SampleFile.16.png
SampleFile.17.png
SampleFile.18.png
SampleFile.19.png
SampleFile.20.png
SampleFile.21.png
SampleFile.22.png

When I try using my code I've got

SampleFile.0.png
SampleFile.1.png
SampleFile.10.png
SampleFile.11.png
SampleFile.12.png
SampleFile.13.png
SampleFile.14.png
SampleFile.15.png
SampleFile.16.png
SampleFile.17.png
SampleFile.18.png
SampleFile.19.png
SampleFile.2.png
SampleFile.20.png
SampleFile.21.png
.
.
.

How can I get file list on correct rank order?

Procedure Test;
var
sr : TSearchRec;
i : integer;
ListFiles : TStringList;  
begin
ListFiles := TStringList.Create;
i := FindFirst('c:\test\*.png', faDirectory, sr);
while i = 0 do begin  
ListFiles.Add(ExtractFileName(sr.FindData.cFileName));
i := FindNext(sr); 
end;
FindClose(sr);
end;  

Note : Result is still wrong, if I can use ListFiles.Sorted = True


I think I've a solution, created a function.

function SortFilesByName(List: TStringList; Index1, Index2: Integer): integer;
var
FileName1, FileName2: String;
i, FileNumber1, FileNumber2: Integer;
begin
  FileName1 := ChangeFileExt(ExtractFileName(List[Index1]), '');
  FileName2 := ChangeFileExt(ExtractFileName(List[Index2]), '');
  i := POS('.', FileName1)+1;
  FileNumber1 := StrToInt(Copy(FileName1, i, MaxInt));
  i := POS('.', FileName2)+1;
  FileNumber2 := StrToInt(Copy(FileName2, i, MaxInt));
  Result := (FileNumber1 - FileNumber2);
end;

I've added another line ListFiles.CustomSort(SortFilesByName); //(ListFiles,1,2):integer); before FindClose(sr);

Upvotes: 2

Views: 1443

Answers (3)

Zoë Peterson
Zoë Peterson

Reputation: 13312

As jachguate said, the sorting is done by Explorer.exe, not the filesystem. FindFirst/FindNext does not guarantee any specific sorting, including plain ASCII based, so you shouldn't rely on it. You don't, however, need to re-implement the numeric sort in Delphi. Windows exposes the one it uses as StrCmpLogicalW, which is in shlwapi.dll. The import looks like this:

function StrCmpLogicalW(psz1, psz2: PWideChar): Integer; stdcall;
  external 'shlwapi.dll'

It is possible to disable that behavior in Windows. If you want to follow the order that Windows uses, you need to call SHRestricted with the REST_NOSTRCMPLOGICAL value. If it returns true you should use AnsiCompareStr instead.

const
  // Use default CompareString instead of StrCmpLogical
  REST_NOSTRCMPLOGICAL = $4000007E;

function SHRestricted(rest: DWORD): LongBool; stdcall; external 'shell32.dll';

So your final sort function should be something like this:

function CompareFilenames(const AFilename1, AFilename2: string): Integer;
begin
  if SHRestricted(REST_NOSTRCMPLOGICAL) then
    Result := AnsiCompareStr(AFilename1, AFilename2)
  else
    Result := StrCmpLogicalW(PWideChar(AFilename1), PWideChar(AFilename2));
end;

You can cache the result of the SHRestricted call, but if you do you need to watch for the WM_SETTINGSCHANGE broadcast message and re-read it when you get one.

Upvotes: 8

jachguate
jachguate

Reputation: 17203

The different orders you see in the windows explorer is implemented in explorer.exe and not in the file system.

The Numerical sort order is a new feature in windows 7, so if you sort by name and you have a bunch of files with a prefix followed by numbers, the explorer "identifies" that pattern and doesn't present a list sorted by name in the traditional way, but sorted by prefix and then by number (as if the string were a Integer number).

If you want to do the same in Delphi, you can do it by adding all the file names returned by FindFirst/FindNext to a TSlist and then sort the string list using this compare function:

var
  FileNames: TList<string>;
begin
  FileNames := TList<string>.Create;
  try
    SearchForFiles(FileNames); //here you add all the file names
    //sort file names a la windows 7 explorer
    FileNames.Sort(System.Generics.Defaults.TComparer<string>.Construct(
      function (const s1, s2: string): Integer
        procedure ProcessPrefix(const fn: string; var prefix, number: string);
        var
          I: Integer;
        begin
          for I := length(fn) downto 1 do
            if not TCharacter.IsDigit(fn[I]) then
            begin
              Prefix := Copy(fn, 1, I);
              number := Copy(fn, I+1, MaxInt);
              Break;
            end;
        end;
      var
        prefix1, prefix2: string;
        number1, number2: string;
        fn1, fn2: string;
      begin
        //compare filenames a la windows 7 explorer
        fn1 := TPath.GetFileNameWithoutExtension(s1);
        fn2 := TPath.GetFileNameWithoutExtension(s2);
        ProcessPrefix(fn1, prefix1, number1);
        ProcessPrefix(fn2, prefix2, number2);
        if (Number1 <> '') and (Number2 <> '') then
        begin
          Result := CompareText(prefix1, prefix2);
          if Result = 0 then
            Result := CompareValue(StrToInt(number1), StrToInt(Number2));
        end
        else
          Result := CompareText(s1, s2);
      end
      ));
    UseYourSortedFileNames(FileNames);
  finally
    FileNames.Free;
  end;
end;

Upvotes: 1

Ken White
Ken White

Reputation: 125707

By "rank", you mean sort order.

The files are sorting in the proper order (based on the ASCII value of the characters). 2 comes after 19 because the comparison is only made up to the same number of characters in both names, and '2' comes after 1.

If you want them to sort properly as numbers, you need to left-pad the numbers with zeros so they're all the same width (eg., instead of SampleFile.2.png, use SampleFile.02.png). This will cause '02' to come before 19 so they sort correctly numerically.

You can fix the numbering issue by using something like:

PngFileName := Format('SampleFile.%.2d.png', [Counter]);

Upvotes: 0

Related Questions