How can I get TStringList to sort differently in Delphi

后端 未结 3 1862
太阳男子
太阳男子 2020-12-14 10:41

I have a simple TStringList. I do a TStringList.Sort on it.

Then I notice that the underscore \"_\" sorts before the capital letter \"A\". This was in contrast to a

相关标签:
3条回答
  • 2020-12-14 10:56

    Define "correctly".
    i18n sorting totally depends on your locale.
    So I totally agree with PA that this is not a bug: the default Sort behaviour works as designed to allow i18n to work properly.

    Like Gerry mentions, TStringList.Sort uses AnsiCompareStr and AnsiCompareText (I'll explain in a few lines how it does that).

    But: TStringList is flexible, it contains Sort, CustomSort and CompareStrings, which all are virtual (so you can override them in a descendant class)
    Furthermore, when you call CustomSort, you can plug in your own Compare function.

    At the of this answer is a Compare function that does what you want:

    • Case Sensitive
    • Not using any locale
    • Just compare the ordinal value of the characters of the strings

    CustomSort is defined as this:

    procedure TStringList.CustomSort(Compare: TStringListSortCompare);
    begin
      if not Sorted and (FCount > 1) then
      begin
        Changing;
        QuickSort(0, FCount - 1, Compare);
        Changed;
      end;
    end;
    

    By default, the Sort method has a very simple implementation, passing a default Compare function called StringListCompareStrings:

    procedure TStringList.Sort;
    begin
      CustomSort(StringListCompareStrings);
    end;
    

    So, if you define your own TStringListSortCompare compatible Compare method, then you can define your own sorting.
    TStringListSortCompare is defined as a global function taking the TStringList and two indexes referring the items you want to compare:

    type
      TStringListSortCompare = function(List: TStringList; Index1, Index2: Integer): Integer;
    

    You can use the StringListCompareStrings as a guideline for implementing your own:

    function StringListCompareStrings(List: TStringList; Index1, Index2: Integer): Integer;
    begin
      Result := List.CompareStrings(List.FList^[Index1].FString,
                                    List.FList^[Index2].FString);
    end;
    

    So, by default TStringList.Sort defers to TList.CompareStrings:

    function TStringList.CompareStrings(const S1, S2: string): Integer;
    begin
      if CaseSensitive then
        Result := AnsiCompareStr(S1, S2)
      else
        Result := AnsiCompareText(S1, S2);
    end;
    

    Which then use the under lying Windows API function CompareString with the default user locale LOCALE_USER_DEFAULT:

    function AnsiCompareStr(const S1, S2: string): Integer;
    begin
      Result := CompareString(LOCALE_USER_DEFAULT, 0, PChar(S1), Length(S1),
        PChar(S2), Length(S2)) - 2;
    end;
    
    function AnsiCompareText(const S1, S2: string): Integer;
    begin
      Result := CompareString(LOCALE_USER_DEFAULT, NORM_IGNORECASE, PChar(S1),
        Length(S1), PChar(S2), Length(S2)) - 2;
    end;
    

    Finally the Compare function you need. Again the limitations:

    • Case Sensitive
    • Not using any locale
    • Just compare the ordinal value of the characters of the strings

    This is the code:

    function StringListCompareStringsByOrdinalCharacterValue(List: TStringList; Index1, Index2: Integer): Integer;
    var
      First: string;
      Second: string;
    begin
      First := List[Index1];
      Second := List[Index2];
      if List.CaseSensitive then
        Result := CompareStr(First, Second)
      else
        Result := CompareText(First, Second);
    end;
    

    Delphi ain't closed, quite the opposite: often it is a really flexible architecture.
    It is often just a bit of digging to see where you can hook into the that flexibility.

    --jeroen

    0 讨论(0)
  • 2020-12-14 10:58

    AnsiCompareStr / AnsiCompareText take more than character number into account. They take the users locale into account, so "e" will sort along with "é", "ê" etc.

    To make it sort it in Ascii order, use a custom compare function as described here

    0 讨论(0)
  • 2020-12-14 11:10

    AnsiCompareStr (CompareString with LOCALE_USER_DEFAULT) has fault, because it gets characters with punctation as equal:

    e1 é1 e2 é2

    Correct order is (for example for Czech):

    e1 e2 é1 é2

    Does anybody know how to avoid this error in ordering?


    11.2.2010: I must apologize described behavior is fully according linguistic rules. Although I think it is silly and "bad" it is not error in API function.

    Explorer in Windows XP uses so called intuitive filname ordering which gives better results but it can't be used programatically.

    0 讨论(0)
提交回复
热议问题