【问题标题】:Delphi 10.4 - Sort a dynamic array of record by 2 valuesDelphi 10.4 - 按2个值对记录的动态数组进行排序
【发布时间】:2022-10-08 20:42:25
【问题描述】:

我正在 Delphi 10.4 中编写一个程序,它将数据库中的多个表读取到动态记录数组中。 SQL 查询在数据的初始加载期间已经按名称对值进行了排序。

然后这些记录显示在不同列下的 ListView 上。我想为用户提供单击列以根据该列对值进行排序的选项。到目前为止,一切正常。我有下面的当前代码,欢迎您指出我犯的任何错误。

首先,我声明记录类型:

type
   TDDNS = record
     ID : Integer;      --the ID in the database
     Name  : String;    --the client name
     Alias : string;    --an alias for the client
     Domain : string;   --the DDNS address
     Login : String;    --DDNS login username
     Password: string;  --DDNS login password
     Renewed: TDate;    --Date DDNS account was renewed
     IsActive: Boolean; --Boolean if account is still active
   end;

其次,我创建了动态数组:

DDNSDetails : array of TDDNS;

然后将数据读入数组。

由于显而易见的原因,登录和密码数据未显示在 ListView 中。

对于排序,我使用以下代码:

procedure lvDDNSColumnClick(Sender: TObject;
  Column: TListColumn);
begin
  SortList(Column.Index);
  ReloadLV();
end;

procedure SortList(Col : Integer);
var
 i, j : Integer;
begin
  if Length(DDNSDetails) > 0 then
  begin
    for i :=  0 to Length(DDNSDetails)-1 do
    begin
      for j := i+1 to Length(DDNSDetails)-1 do
      begin
        if Col = 0 then //Name
        begin
          if UpperCase(DDNSDetails[i].Name) > UpperCase(DDNSDetails[j].Name) then
            Resort(i, j);
        end else
        if Col = 1 then //Alias
        begin
          if UpperCase(DDNSDetails[i].Alias) > UpperCase(DDNSDetails[j].Alias) then
            Resort(i, j);
        end else
        if Col = 2 then //Domain
        begin
          if UpperCase(DDNSDetails[i].Domain) > UpperCase(DDNSDetails[j].Domain) then
            Resort(i, j);
        end else
        if (Col = 3) or (Col = 4) then //Renewal date
        begin
          if DDNSDetails[i].Renewed > DDNSDetails[j].Renewed then
            Resort(i, j);
        end;
      end;
    end;
    lvDDNS.Columns[0].Caption := 'Client Name';
    lvDDNS.Columns[1].Caption := 'Trading As';
    lvDDNS.Columns[2].Caption := 'Domain Address';
    lvDDNS.Columns[3].Caption := 'Renewed';
    lvDDNS.Columns[4].Caption := 'Active';
    lvDDNS.Columns[Col].Caption := '|| '+lvDDNS.Columns[Col].Caption+' ||';
  end;
end;

procedure Resort(var i, j : Integer);
var
 tempInt : Integer;
 temp : string;
 tempDate : TDate;
 tempBool : Boolean;
begin
  tempInt := DDNSDetails[i].ID;
  DDNSDetails[i].ID := DDNSDetails[j].ID;
  DDNSDetails[j].ID := tempInt;

  temp := DDNSDetails[i].Name;
  DDNSDetails[i].Name := DDNSDetails[j].Name;
  DDNSDetails[j].Name := temp;

  temp := DDNSDetails[i].Alias;
  DDNSDetails[i].Alias := DDNSDetails[j].Alias;
  DDNSDetails[j].Alias := temp;

  temp := DDNSDetails[i].Domain;
  DDNSDetails[i].Domain := DDNSDetails[j].Domain;
  DDNSDetails[j].Domain := temp;

  tempDate := DDNSDetails[i].Renewed;
  DDNSDetails[i].Renewed := DDNSDetails[j].Renewed;
  DDNSDetails[j].Renewed := tempDate;

  tempBool := DDNSDetails[i].IsActive;
  DDNSDetails[i].IsActive := DDNSDetails[j].IsActive;
  DDNSDetails[j].IsActive := tempBool;

  temp := DDNSDetails[i].Login;
  DDNSDetails[i].Login := DDNSDetails[j].Login;
  DDNSDetails[j].Login := temp;

  temp := DDNSDetails[i].Password;
  DDNSDetails[i].Password := DDNSDetails[j].Password;
  DDNSDetails[j].Password := temp;
end;

该程序的目的是显示不同 DDNS 帐户的 DDNS 记录和登录凭据,并且某些客户端拥有多个帐户。

例如,如果您按 DDNS 续订日期排序,2022 年 7 月 23 日可能有 50 个条目,客户端“f”在当天有 5 个条目,但是这 5 个条目不在一起。在名称列中,您可能会看到

z
w
g
x
f
z
a
f
.....

结果应该是

a
f
f
f
f
f
g
w
x
z
z
.....

排序对所选的每一列都非常有效。如果用户对任何其他列进行排序,我现在需要将名称列作为次要列进行排序。

编辑: 根据 dummzeuch 的评论,我将程序 Resort 更改为以下内容:

procedure SwapRecord(var i, j : Integer);
var
 temp : TDDNS;
begin
  temp := DDNSDetails[i];
  DDNSDetails[i] := DDNSDetails[j];
  DDNSDetails[j] := temp;
end;

【问题讨论】:

  • 使用 RTL 的内置分拣工具(如 TList<>.SortTArray.Sort<>);那么你只需要提供你自己的比较器。在您的情况下,如果主要列存在平局,则需要考虑次要列。
  • 你不是说你使用的是哪个 Delphi 版本。根据这一点,有几种选择。 Andreas Rejbrand 提到了一个基于泛型的解决方案,该解决方案已经使用了好几年了。但是,如果您使用古老的 Delphi 版本,这些将不适合您。
  • 通过直接分配记录而不是它们的字段,您的度假村程序可以简单得多。
  • @ dummzeuch 我看到有人编辑了我的问题“从标题中删除了多余的标签信息”,这是该信息,我将其添加回来,因为它也会帮助其他人。我不是专业的程序员。除了在学校外,一切基本上都是自学的。
  • @WackyWabbit:被删除的“冗余标签信息”是“Delphi”——这确实是多余的!您稍后添加的文本是“Delphi 10.4”,其中“10.4”是新的(并且非常重要的)信息!

标签: arrays sorting listview delphi record


【解决方案1】:

如果您使用的是 Delphi 10.4 – 尝试使用泛型类型。这里我推荐:

type
   //declare new type to store sort rule
   TSortRule = record
     ColumnID : byte; //number of column
     Desc : boolean;  //reverse sort direction
   end;

//change array to list for storing items, it's much esier to work with it
var
  xList : TList<TDDNS>;

//we need somehow passed few sort rules, i prefer TList, something like that:
var
  xSortOrder : TList<TSortRule>;

以下是对所有这些员工进行排序的程序:

procedure TForm.SortRecords(AList : TList<TDDNS>; ASortOrder : TList<TSortRule>);
begin
  AList.Sort(TComparer<TDDNS>.Construct(
             function(const Left, Right: TDDNS): Integer
             var
               LeftValue, RightValue: TDDNS;
             begin
               //we go for all sorting rules
               for var xSortItem in ASortOrder do begin
                 //check if current rule is reverse
                 if not xSortItem.Desc then begin
                   LeftValue := Left;
                   RightValue := Right;
                 end else begin
                   //it's reverse - switch sides
                   LeftValue := Right;
                   RightValue := Left;
                 end{if..else};

                 //let's do comparation by correct property
                 case xSortItem.ColumnID of
                   0:  Result := CompareStr(Left.Name, Right.Name);
                   1:  Result := CompareStr(Left.Alias, Right.Alias);
                   2:  Result := CompareStr(Left.Domain, Right.Domain);
                   3, 4:  Result := TComparer<TDate>.Default.Compare(Left.Renewed, Right.Renewed);
                 end{case};

                 //if items not equval by this rule, we skip next rules
                 if Result <> 0 then
                   break;
               end{for};
              end
            ));
end;

有关 TList<> 排序的更多信息,您可以在官方文档或 Here example 中阅读

【讨论】:

    【解决方案2】:

    根据我的初始代码,我设法对其进行了修改并使其以更简单的方式工作。

    首先,我为记录类型和排序过程创建了一个新类,并将它们声明如下:

    type
      TRec = record
        dbID : Integer;
        Name  : String;
        Alias : string;
        Domain : string;
        Login : String;
        Password: string;
        Renewed: TDate;
        IsActive: Boolean;
      end;
    
    type
      TData = array of TRec;
    
    procedure SortData(Data : TData; const Field : Integer);
    procedure SwapRecords(var Data : TData; const i, j : Integer);
    

    SortData 执行排序比较,SwapRecords 在排序过程中交换条目。排序数据使用一旦找到需要排序的字段,就转到循环的底部,以节省时间并开始下一个循环。

    程序脚本如下:

    procedure SortData(Data : TData; const Field : Integer);
    var
      n, newn, i : integer;
    label
      bottom;
    begin
      n := length(Data);
      repeat
        newn := 0;
        for i := 1 to n-1 do
        begin
          if Field = 1 then //Name
          begin
            if UpperCase(Data[i-1].Name) > UpperCase(Data[i].Name) then
            begin
              SwapRecords(Data, i-1, i);
              newn := i;
              Goto bottom;
            end;
          end;
    
          if Field = 2 then //Alias
          begin
            if UpperCase(Data[i-1].Alias) > UpperCase(Data[i].Alias) then
            begin
              SwapRecords(Data, i-1, i);
              newn := i;
              Goto bottom;
            end;
          end;
    
          if Field = 3 then //Domain
          begin
            if UpperCase(Data[i-1].Domain) > UpperCase(Data[i].Domain) then
            begin
              SwapRecords(Data, i-1, i);
              newn := i;
              Goto bottom;
            end;
          end;
    
          if Field = 3 then //Login
          begin
            if UpperCase(Data[i-1].Login) > UpperCase(Data[i].Login) then
            begin
              SwapRecords(Data, i-1, i);
              newn := i;
              Goto bottom;
            end;
          end;
    
          if Field = 4 then //Password
          begin
            if UpperCase(Data[i-1].Password) > UpperCase(Data[i].Password) then
            begin
              SwapRecords(Data, i-1, i);
              newn := i;
              Goto bottom;
            end;
          end;
    
          if Field = 5 then //Renewed
          begin
            if Data[i-1].Renewed > Data[i].Renewed then
            begin
              SwapRecords(Data, i-1, i);
              newn := i;
              Goto bottom;
            end;
          end;
    
          if Field = 6 then  //IsActive
          begin
            if Data[i-1].IsActive > Data[i].IsActive then
            begin
              SwapRecords(Data, i-1, i);
              newn := i;
              Goto bottom;
            end;
          end;
    
          bottom:
        end;
        n := newn;
      until n < 1;
    end;
    
    procedure SwapRecords(var Data : TData; const i, j : Integer);
    var
      temp : TRec;
    begin
      temp := Data[i];
      Data[i] := Data[j];
      Data[j] := temp;
    end;
    

    最后,在主窗体中,我在创建和填充变量(数据的 DDNS 信息)后调用此过程。

    procedure TfrmDDNS.lvDDNSColumnClick(Sender: TObject;
      Column: TListColumn);
    var
      Field : Integer;
    begin
      ColActive := Column.Index;//ColActive is a global Integer in the form to keep track of which column is selected in the TListView
      case ColActive of
        0 : Field := 1;//Client Name
        1 : Field := 2;//Trading As
        2 : Field := 3;//Domain Address
        3 : Field := 6;//Renewed
        4 : Field := 7;//Active
      else
        Field := 0;
      end;
    
      //Sort array
      if Field = 6 then
      begin
        SortData(DDNSInfo,1);//Sort according to Client Name
        SortData(DDNSInfo,Field);//Sort according to Renewal Date
      end else
        SortData(DDNSInfo,Field);//Sort only according to selected column
    
      //Output new array
      UpdateLV(lvDDNS,DDNSInfo);//This is another procedure for updating the data displayed in the TListView
    end;
    

    我将其设置为根据多个列进行排序,并且可以对其进行扩展以适应更多场景,例如网站、其他类型的密码等。

    【讨论】:

      猜你喜欢
      • 2015-03-16
      • 2021-11-14
      • 1970-01-01
      • 2022-12-13
      • 1970-01-01
      • 1970-01-01
      • 2019-12-10
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多