Неверный ранг FindFirst, FindNext (Delphi Xe, Win7)

У меня есть несколько файлов в каталоге. Я пытаюсь получить эти файлы с помощью FindFirst и FindNext, но не могу получить такой же порядок в 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

Когда я пытаюсь использовать свой код, я получаю

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
.
.
.

Как я могу получить список файлов в правильном порядке?

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;  

Примечание. Результат по-прежнему неверен, если я могу использовать ListFiles.Sorted = True


Я думаю, что у меня есть решение, создал функцию.

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;

Я добавил еще одну строку ListFiles.CustomSort(SortFilesByName); //(СписокФайлов,1,2):целое число); перед FindClose(sr);


person Ali Cetinturk    schedule 17.01.2013    source источник


Ответы (3)


Как сказал jachguate, сортировка выполняется Explorer.exe, а не файловой системой. FindFirst/FindNext не гарантирует какой-либо конкретной сортировки, в том числе простой на основе ASCII, поэтому на нее не следует полагаться. Однако вам не нужно повторно реализовывать числовую сортировку в Delphi. Windows предоставляет тот, который он использует, как StrCmpLogicalW, который находится в shlwapi.dll. Импорт выглядит так:

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

Это поведение можно отключить в Windows. Если вы хотите следовать порядку, который использует Windows, вам нужно позвонить по адресу SHRestricted со значением REST_NOSTRCMPLOGICAL. Если он возвращает true, вместо этого следует использовать AnsiCompareStr.

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

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

Итак, ваша окончательная функция сортировки должна быть примерно такой:

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;

Вы можете кэшировать результат вызова SHRestricted, но если вы это сделаете, вам нужно следить за широковещательным сообщением WM_SETTINGSCHANGE и перечитывать его, когда вы его получите.

person Zoë Peterson    schedule 17.01.2013

Различные порядки, которые вы видите в проводнике Windows, реализованы в файле explorer.exe, а не в файловой системе.

Числовой порядок сортировки — это новая функция в Windows 7, поэтому, если вы сортируете по имени и у вас есть набор файлов с префиксом, за которым следуют числа, проводник «идентифицирует» этот шаблон, а не представить список, отсортированный по имени традиционным способом, но отсортированный по префиксу, а затем по номеру (как если бы строка была целым числом).

Если вы хотите сделать то же самое в Delphi, вы можете сделать это, добавив все имена файлов, возвращаемые FindFirst/FindNext, в TSlist, а затем отсортировать список строк, используя эту функцию сравнения:

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;
person jachguate    schedule 17.01.2013

Под «рангом» вы подразумеваете порядок сортировки.

Файлы сортируются в правильном порядке (на основе значения символов ASCII). 2 идет после 19, потому что сравнение производится только до одинакового количества символов в обоих именах, а '2' идет после 1.

Если вы хотите, чтобы они правильно сортировались как числа, вам нужно дополнить числа слева нулями, чтобы они были одинаковой ширины (например, вместо SampleFile.2.png используйте SampleFile.02.png). Это приведет к тому, что «02» будет стоять перед 19, чтобы они правильно сортировались в числовом виде.

Вы можете исправить проблему с нумерацией, используя что-то вроде:

PngFileName := Format('SampleFile.%.2d.png', [Counter]);
person Ken White    schedule 17.01.2013