按行和整数值对TStringGrid进行排序

5
我有一个包含2列的StringGrid:Player和Scores。我必须按照玩家得分来对此表进行排序。
这是情况。我尝试使用StringGrid3.SortColRow(true, 1);但它仅对字符串值进行排序。如果我有像[1、4、12、3]这样的数字,则排序后的StringGrid变为[a、12、3、4]。
我必须对整数值进行排序,而不是字符串值。此外,我的问题是我必须同时移动玩家的姓名和数字(如上图所示)。
我该如何做?
4个回答

7

如果需要按照数字排序,您可以编写一个比较器。通过将Stringgrid强制转换为其祖先TCustomgrid,您可以使用MoveRow过程来交换行。以下是一个示例,展示如何对多列进行排序:

unit StringGridSortEnh;
interface
uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, Grids, StdCtrls;

type
  TForm1 = class(TForm)
    StringGrid1: TStringGrid;
    Button1: TButton;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}
type
  TMoveSG = class(TCustomGrid);
  TSortInfo = Record
    col: Integer;
    asc: Boolean;
  End;

function CompareNumber(i1, i2: Double): Integer;
// Result: -1 if i1 < i2, 1 if i1 > i2, 0 if i1 = i2
begin
  if i1 < i2 then
    Result := -1
  else if i1 > i2 then
    Result := 1
  else
    Result := 0;
end;

// Compare Strings if possible try to interpret as numbers
function CompareValues(const S1, S2 : String;asc:Boolean): Integer;
var
  V1, V2 : Double;
  C1, C2 : Integer;
begin
  Val(S1, V1, C1);
  Val(S2, V2, C2);
  if (C1 = 0) and (C2 = 0) then  // both as numbers
     Result := CompareNumber(V1, V2)
  else  // not both as nubers
     Result := AnsiCompareStr(S1, S2);
  if not Asc then Result := Result * -1;

end;

procedure SortGridByCols(Grid: TStringGrid; ColOrder: array of TSortInfo; Fixed: Boolean);
var
  I, J, FirstRow: Integer;
  Sorted: Boolean;

  function Sort(Row1, Row2: Integer): Integer;
  var
    C: Integer;
  begin
    C := 0;
    Result := CompareValues(Grid.Cols[ColOrder[C].col][Row1], Grid.Cols[ColOrder[C].col][Row2],ColOrder[C].asc);
    if Result = 0 then
    begin
      Inc(C);
      while (C <= High(ColOrder)) and (Result = 0) do
      begin
        Result := CompareValues(Grid.Cols[ColOrder[C].col][Row1], Grid.Cols[ColOrder[C].col][Row2],ColOrder[C].asc);
        Inc(C);
      end;
    end;
  end;

begin
  for I := 0 to High(ColOrder) do
    if (ColOrder[I].col < 0) or (ColOrder[I].col >= Grid.ColCount) then
      Exit;

  if Fixed then
    FirstRow := 0
  else
    FirstRow := Grid.FixedRows;

  J := FirstRow;
  Sorted := True;
  repeat
    Inc(J);
    for I := FirstRow to Grid.RowCount - 2 do
      if Sort(I, I + 1) > 0 then
      begin
        TMoveSG(Grid).MoveRow(i + 1, i);
        Sorted := False;
      end;
  until Sorted or (J >= Grid.RowCount + 1000);
  Grid.Repaint;
end;

procedure TForm1.Button1Click(Sender: TObject);
const // we want to use only 4 columns
  MyArray: array[0..3] of TSortInfo =
    ((col: 1; asc: true),
     (col: 2; asc: true),
     (col: 3; asc: true),
     (col: 4; asc: false)
     );
begin
  SortGridByCols(StringGrid1,MyArray,true);
end;

procedure TForm1.Button2Click(Sender: TObject);
const  // we want to use only one column
  MyArray: array[0..0] of TSortInfo = ((col: 1; asc: true));
begin
  SortGridByCols(StringGrid1,MyArray,true);
end;

end.

enter image description here enter image description here


你还没有在任何地方声明TMoveSG(Grid).MoveRow();。请稍等。 - Alberto Rossi
@AlbertoRossi 没有必要声明它,因为您可以从同一单元访问受保护的过程。TMoveSG = class(TCustomGrid); 在同一单元中具有受保护的 MoveRow() 就可以解决问题了。 - bummi
谢谢!但我仍然有错误(我使用Lazarus)。它说“标识符idents没有成员“MoveRow”。 - Alberto Rossi
1
@AlbertoRossi 快速查看 Lazarus(我不熟悉)TMoveSG(StringGrid1).DoOPMoveColRow(false,1,3); 应该可以解决问题,因为 MoveRow 没有实现。您需要进行验证... - bummi
注意:MoveRow仅移动一行。它不会交换指定的行。 - Gabriel

2
在Lazarus中,您可以使用OnCompareCells事件定义TStringGrid单元格的排序方式,有关如何使用数字和更多信息的示例,请参见以下链接。

http://wiki.lazarus.freepascal.org/Grids_Reference_Page#Sorting_Columns_or_Rows


如果您想在Delphi中对行进行排序,则需要重新实现Lazarus中的SortColRow方法。
您可以使用类助手来完成此操作。
请注意,这尚未经过完全测试!
下面程序中的AnsiCompareStr可以被替换为比较整数的函数。
type
  TStringGridHelper = class helper  for TStringGrid
  public
    procedure SortColRow(IsColumn: Boolean; Index: Integer); overload;
    procedure SortColRow(IsColumn: Boolean; Index: Integer; FromIndex: Integer; ToIndex: Integer); overload;
end;


implementation


procedure TStringGridHelper.SortColRow(IsColumn: Boolean; Index: Integer);
begin
     if (IsColumn) then SortColRow(IsColumn, Index, FixedCols, ColCount - 1)
     else SortColRow(IsColumn, Index, FixedRows, RowCount - 1)
end;

procedure TStringGridHelper.SortColRow(IsColumn: Boolean; Index: Integer; FromIndex: Integer; ToIndex: Integer);
    var i, p, x, c : Integer;
    s1, s2 : String;
begin

    if (IsColumn) then
    begin
        for x := ToIndex downto FromIndex do
            for I := FromIndex to ToIndex - 1 do
            begin
                s1 := Cells[i, index];
                s2 := Cells[i + 1, index];

                c := AnsiCompareStr(s1, s2);
                if (c > 0) then
                begin
                    p := i + 1;
                    p := Max(p, FromIndex);
                    p := Min(p, ToIndex);

                    MoveColumn(i, p);
                end;
            end;
    end
    else
    begin
        for x := ToIndex downto FromIndex do
            for I := FromIndex to ToIndex - 1 do
            begin
                s1 := Cells[index, i];
                s2 := Cells[index, i + 1];
                c := AnsiCompareStr(s1, s2);
                if (c > 0) then
                begin
                    p := i + 1;
                    p := Max(p, FromIndex);
                    p := Min(p, ToIndex);

                    MoveRow(i, p);
                end;
            end;
    end;
end;

1
你是否正在使用 Lazarus 而不是 Delphi?我不知道标准的 Delphi StringGrid 实现中是否有排序功能。
问题在于,StringGrid 只是一个字符串网格。它并不理解这些字符串实际上是数字,因此只能按字母顺序对它们进行排序。
如果您需要更复杂的排序功能,可以选择覆盖 TStringGrid 类并创建自己专门的版本以按您想要的方式进行排序,或者可以在将记录放入网格之前简单地对其进行排序。
例如,如果您有一个 TPlayer 类来表示玩家和他们的得分,则可以使用 TList.Sort(..) 按任意顺序排序,然后循环遍历并填充网格。
// 
// Sort the player scores by their *numeric* value, not by the string representation
//
function SortByPlayerScore(A, B: Pointer) : integer
begin
    Result := TPlayer(A).Score - TPlayer(B).Score;
end;

// ....

list.Sort(SortByPlayerScore);

grid.RowCount := list.Count + 1;
for i := 0 to list.Count - 1 do begin
    grid.Cells[0, i + 1] := list[i].Name;
    grid.Cells[1, i + 1] := IntToStr(list[i].Score);
end;

0
似乎Sorted := True应该放在repeat循环内部。 现在repeat只能通过(J >= Grid.RowCount + 1000)停止。
  repeat
    Sorted := True;
    Inc(J);
    for I := FirstRow to Grid.RowCount - 2 do
      if Sort(I, I + 1) > 0 then
      begin
        TMoveSG(Grid).MoveRow(i + 1, i);
        Sorted := False;
      end;
  until Sorted or (J >= Grid.RowCount + 1000);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接