Delphi VirtualTreeView 对多列进行排序?

发布于 2024-10-07 22:25:36 字数 3780 浏览 3 评论 0原文

我听到了很多关于 VirtualTreeView 组件的赞扬,并考虑在我们正在进行的重写中使用它。目前我们使用 StringGrid。

尽管单列排序效果很好,但我找不到对多列进行排序的方法。有什么方法可以执行类似单击第 1 列>排序、Ctrl+单击第 2 列>在第 1 列之后对第 2 列进行排序等操作吗?

具体来说,我想对至少三列进行排序:采购订单号、行项目、发布。

预先感谢您的帮助!

这是我正在测试理论的代码(稍微简化)(不是来自上面引用的同一项目):

注意:更新后,我还编辑了我的代码,以显示它当前的内容。下面我发布了排序结果:

type
  PBatchDetails = ^TBatchDetails;
  TBatchDetails = record
    TheBatchKey
    OperationKey,
    PO,
    Line,
    Release,
    Temp,
    Notes : String;
    TransDate : TDateTime;
end;

....
Sorting_Columns: array of TColumnIndex;
....
procedure TForm1.TreeHeaderClick(Sender: TVTHeader; HitInfo: TVTHeaderHitInfo);
var
  I: Integer;
begin
  if not CtrlDown then //function I have to test Ctrl state.
  begin
    setlength(Sorting_Columns,0);
  end;
  SetLength(Sorting_Columns,length(Sorting_Columns)+1);
  Sorting_Columns[Length(Sorting_Columns)-1] := HitInfo.Column;
  tree.SortTree(HitInfo.Column,Sender.SortDirection,True);
  if Sender.SortDirection=sdAscending then
    Sender.SortDirection:=sdDescending
  else
    Sender.SortDirection:=sdAscending
end;


procedure TForm1.TreeCompareNodes(Sender: TBaseVirtualTree; Node1,
  Node2: PVirtualNode; Column: TColumnIndex; var Result: Integer);
var
  BatchRec1 : PBatchDetails;
  BatchRec2: PBatchDetails;
  I: Integer;
begin
  if length(Sorting_Columns) > 0 then
  begin
    BatchRec1 := Tree.GetNodeData(Node1);
    BatchRec2 := Tree.GetNodeData(Node2);
    if (not Assigned(BatchRec1)) or (not Assigned(BatchRec2)) then
      Result:=0
    else
    begin
      for I := High(Sorting_Columns) downto 0 do
      begin
        case Sorting_Columns[i] of
          0,1: Result := Result + CompareDate(BatchRec1.TransDate,BatchRec2.TransDate); // col 0 is Date and col 1 is Time.
          2: Result := Result + CompareText(BatchRec1.OperationKey,BatchRec2.OperationKey);
          3: Result := Result + CompareText(BatchRec1.PO,BatchRec2.PO);
          4: Result := Result + CompareText(BatchRec1.Line,BatchRec2.Line);
          5: Result := Result + CompareText(BatchRec1.Release,BatchRec2.Release);
          6: Result := Result + CompareText(BatchRec1.Temp, BatchRec2.Temp);
          7: Result := Result + CompareText(BatchRec1.Notes,BatchRec2.Notes);
        end; //end case;
        if Result <> 0 then
          Break;
      end;
    end;
  end;
end;

这产生了以下结果(我只显示我试图在此处排序的三列):

最初加载时:
订单行发布
153 7 2
153 7 1
153 1 1
153 1 2
153 4 1
153 6 2
153 6 1
120 3 2
120 3 1
153 2 1
153 4 2
120 2 1
153 4 1
120 1 1
153 3 1
153 2 1
111 2 1
111 1 5
111 1 1
111 4 2
111 3 1
111 4 1
111 1 3
111 1 2
111 1 4

首次点击后
订单行发布
111 2 1
111 1 5
111 1 1
111 4 2
111 3 1
111 4 1
111 1 3
111 1 2
111 1 4
120 3 2
120 3 1
120 2 1
120 1 1
153 7 2
153 7 1
153 1 1
153 1 2
153 4 1
153 6 2
153 6 1
153 2 1
153 4 2
153 4 1
153 3 1
153 2 1

第二次点击后
订单行发布
153 7 2
153 7 1
153 6 2
153 6 1
153 4 1
153 4 2
153 4 1
111 4 2
111 4 1
153 3 1
120 3 2
120 3 1
111 3 1
153 2 1
153 2 1
120 2 1
111 2 1
153 1 1
153 1 2
120 1 1
111 1 5
111 1 1
111 1 3
111 1 2
111 1 4

第三次点击后
订单行发布
111 1 1
120 1 1
153 1 1
111 2 1
120 2 1
153 2 1
153 2 1
111 3 1
120 3 1
153 3 1
111 4 1
153 4 1
153 4 1
153 6 1
153 7 1
111 1 2
153 1 2
120 3 2
111 4 2
153 4 2
153 6 2
153 7 2
111 1 3
111 1 4
111 1 5

感谢您抽出时间!

I have heard a lot of praise about the VirtualTreeView component and looked at using it in a rewrite we are doing. Currently we use a StringGrid.

I can't find a way to sort multiple columns, though single column sorting works great. Is there any way to do something similar to click column 1>sort, Ctrl+click column 2>sort column 2 after column 1, etc?

Specifically, I want to sort at least three columns: PO Number, Line Item, Release.

Thanks in advance for your help!

Here is the code (slightly simplified) I am testing the theory with (not from the same project referenced above):

Note: After your update I edited my code, also, to show what it currently is. Below I posted the results of the sort:

type
  PBatchDetails = ^TBatchDetails;
  TBatchDetails = record
    TheBatchKey
    OperationKey,
    PO,
    Line,
    Release,
    Temp,
    Notes : String;
    TransDate : TDateTime;
end;

....
Sorting_Columns: array of TColumnIndex;
....
procedure TForm1.TreeHeaderClick(Sender: TVTHeader; HitInfo: TVTHeaderHitInfo);
var
  I: Integer;
begin
  if not CtrlDown then //function I have to test Ctrl state.
  begin
    setlength(Sorting_Columns,0);
  end;
  SetLength(Sorting_Columns,length(Sorting_Columns)+1);
  Sorting_Columns[Length(Sorting_Columns)-1] := HitInfo.Column;
  tree.SortTree(HitInfo.Column,Sender.SortDirection,True);
  if Sender.SortDirection=sdAscending then
    Sender.SortDirection:=sdDescending
  else
    Sender.SortDirection:=sdAscending
end;


procedure TForm1.TreeCompareNodes(Sender: TBaseVirtualTree; Node1,
  Node2: PVirtualNode; Column: TColumnIndex; var Result: Integer);
var
  BatchRec1 : PBatchDetails;
  BatchRec2: PBatchDetails;
  I: Integer;
begin
  if length(Sorting_Columns) > 0 then
  begin
    BatchRec1 := Tree.GetNodeData(Node1);
    BatchRec2 := Tree.GetNodeData(Node2);
    if (not Assigned(BatchRec1)) or (not Assigned(BatchRec2)) then
      Result:=0
    else
    begin
      for I := High(Sorting_Columns) downto 0 do
      begin
        case Sorting_Columns[i] of
          0,1: Result := Result + CompareDate(BatchRec1.TransDate,BatchRec2.TransDate); // col 0 is Date and col 1 is Time.
          2: Result := Result + CompareText(BatchRec1.OperationKey,BatchRec2.OperationKey);
          3: Result := Result + CompareText(BatchRec1.PO,BatchRec2.PO);
          4: Result := Result + CompareText(BatchRec1.Line,BatchRec2.Line);
          5: Result := Result + CompareText(BatchRec1.Release,BatchRec2.Release);
          6: Result := Result + CompareText(BatchRec1.Temp, BatchRec2.Temp);
          7: Result := Result + CompareText(BatchRec1.Notes,BatchRec2.Notes);
        end; //end case;
        if Result <> 0 then
          Break;
      end;
    end;
  end;
end;

This produced the following results (I am only showing the three columns I am trying to sort here):

When originally loaded:
PO Line Release
153 7 2
153 7 1
153 1 1
153 1 2
153 4 1
153 6 2
153 6 1
120 3 2
120 3 1
153 2 1
153 4 2
120 2 1
153 4 1
120 1 1
153 3 1
153 2 1
111 2 1
111 1 5
111 1 1
111 4 2
111 3 1
111 4 1
111 1 3
111 1 2
111 1 4

After first click
PO Line Release
111 2 1
111 1 5
111 1 1
111 4 2
111 3 1
111 4 1
111 1 3
111 1 2
111 1 4
120 3 2
120 3 1
120 2 1
120 1 1
153 7 2
153 7 1
153 1 1
153 1 2
153 4 1
153 6 2
153 6 1
153 2 1
153 4 2
153 4 1
153 3 1
153 2 1

After second click
PO Line Release
153 7 2
153 7 1
153 6 2
153 6 1
153 4 1
153 4 2
153 4 1
111 4 2
111 4 1
153 3 1
120 3 2
120 3 1
111 3 1
153 2 1
153 2 1
120 2 1
111 2 1
153 1 1
153 1 2
120 1 1
111 1 5
111 1 1
111 1 3
111 1 2
111 1 4

After Third Click
PO Line Release
111 1 1
120 1 1
153 1 1
111 2 1
120 2 1
153 2 1
153 2 1
111 3 1
120 3 1
153 3 1
111 4 1
153 4 1
153 4 1
153 6 1
153 7 1
111 1 2
153 1 2
120 3 2
111 4 2
153 4 2
153 6 2
153 7 2
111 1 3
111 1 4
111 1 5

Thanks for your time!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

转角预定愛 2024-10-14 22:25:36

一般情况下禁用所有自动排序选项。然后您需要实现 OnCompareNodes 以及 OnHeaderClick 事件。

这是我希望工作的代码(我刚刚进行了快速测试:)

目的是将排序列存储在某个变量(Sorting_Columns)中。您可以在 OnHeaderClick 事件中提供此变量。
在调用 SortTree 函数后将触发的 OnCompareNodes 事件中,从最后添加的列到第一个添加的列迭代变量,并向 Result 参数传递第一个非零比较结果。现在,从人性角度来说,您应该在“选择”列时向后遍历列,并检查它们是否相同,如果是,则转到先前选择的列,如果不是,则中断循环并传递结果。
请注意,您正在比较一个事件命中中的两个节点(行),迭代和后续比较排序列的原因是什么。

type
  PRecord = ^TRecord;
  TRecord = record
    ID: integer;
    Text_1: string;
    Text_2: string;
    Text_3: string;
    Date: TDateTime;
  end;

...

var Sorting_Columns: array of TColumnIndex;

...

procedure TForm1.VirtualStringTree1CompareNodes(Sender: TBaseVirtualTree;
  Node1, Node2: PVirtualNode; Column: TColumnIndex; var Result: Integer);
var Actual_Index: integer;
    Data_1: PRecord;
    Data_2: PRecord;

begin
  if Length(Sorting_Columns) > 0 then
    begin
      Data_1 := VirtualStringTree1.GetNodeData(Node1);
      Data_2 := VirtualStringTree1.GetNodeData(Node2);

      if Assigned(Data_1) and Assigned(Data_2) then
        for Actual_Index := High(Sorting_Columns) downto 0 do
          case Sorting_Columns[Actual_Index] of
            0: Result := Result + Data_1^.ID - Data_2^.ID;
            1: Result := Result + CompareStr(Data_1^.Text_1, Data_2^.Text_1);
            2: Result := Result + CompareStr(Data_1^.Text_2, Data_2^.Text_2);
            3: Result := Result + CompareStr(Data_1^.Text_3, Data_2^.Text_3);
            4: Result := Result + CompareDateTime(Data_1^.Date, Data_2^.Date);
          end;

      if Result <> 0 then
        Break;
    end;
end;

Disable every auto sorting options in general. Then you need to implement OnCompareNodes along with OnHeaderClick events.

Here is I hope working code (I've made just quick test :)

The aim is to store sorting columns in some variable (Sorting_Columns). This variable you can feed in OnHeaderClick event.
In the OnCompareNodes event, which will be triggered after SortTree function call, iterate through the variable from the last added column to the first added one and to the Result parameter pass the first nonzero comparision result. Now humanly - you should go through the columns backwards as they were "selected" and check if they are same, if yes go to the previously selected, if not break the loop and pass the result.
Note that you are comparing two nodes (rows) in one event hit, what's the reason for the iteration and subsequent comparision of sorting columns.

type
  PRecord = ^TRecord;
  TRecord = record
    ID: integer;
    Text_1: string;
    Text_2: string;
    Text_3: string;
    Date: TDateTime;
  end;

...

var Sorting_Columns: array of TColumnIndex;

...

procedure TForm1.VirtualStringTree1CompareNodes(Sender: TBaseVirtualTree;
  Node1, Node2: PVirtualNode; Column: TColumnIndex; var Result: Integer);
var Actual_Index: integer;
    Data_1: PRecord;
    Data_2: PRecord;

begin
  if Length(Sorting_Columns) > 0 then
    begin
      Data_1 := VirtualStringTree1.GetNodeData(Node1);
      Data_2 := VirtualStringTree1.GetNodeData(Node2);

      if Assigned(Data_1) and Assigned(Data_2) then
        for Actual_Index := High(Sorting_Columns) downto 0 do
          case Sorting_Columns[Actual_Index] of
            0: Result := Result + Data_1^.ID - Data_2^.ID;
            1: Result := Result + CompareStr(Data_1^.Text_1, Data_2^.Text_1);
            2: Result := Result + CompareStr(Data_1^.Text_2, Data_2^.Text_2);
            3: Result := Result + CompareStr(Data_1^.Text_3, Data_2^.Text_3);
            4: Result := Result + CompareDateTime(Data_1^.Date, Data_2^.Date);
          end;

      if Result <> 0 then
        Break;
    end;
end;
指尖上的星空 2024-10-14 22:25:36

稍微修改@user532231的代码以获得有效的解决方案。

type
  PRecord = ^TRecord;
  TRecord = record
    ID: integer;
    Text_1: string;
    Text_2: string;
    Text_3: string;
    Date: TDateTime;
  end;

...

var Sorting_Columns: array of TColumnIndex;

...

procedure TForm1.VirtualStringTree1CompareNodes(Sender: TBaseVirtualTree;
  Node1, Node2: PVirtualNode; Column: TColumnIndex; var Result: Integer);
var Actual_Index: integer;
    Data_1: PRecord;
    Data_2: PRecord;
    Matrix : array of integer;
    I: Integer;
begin
  if Length(Sorting_Columns) > 0 then
    begin
      Data_1 := VirtualStringTree1.GetNodeData(Node1);
      Data_2 := VirtualStringTree1.GetNodeData(Node2);

      if Assigned(Data_1) and Assigned(Data_2) then
        begin
          SetLength(Matrix,Length(Sorting_Columns));
          for Actual_Index := 0 to High(Sorting_Columns) do
            begin
              case Sorting_Columns[Actual_Index] of
                0: Matrix[Actual_Index] := Data_1^.ID - Data_2^.ID;
                1: Matrix[Actual_Index] := CompareStr(Data_1^.Text_1, Data_2^.Text_1);
                2: Matrix[Actual_Index] := CompareStr(Data_1^.Text_2, Data_2^.Text_2);
                3: Matrix[Actual_Index] := CompareStr(Data_1^.Text_3, Data_2^.Text_3);
                4: Matrix[Actual_Index] := CompareDateTime(Data_1^.Date, Data_2^.Date);
              end;
            end;
          for I := 0 to Length(Matrix) - 1 do
            if (Matrix[i] <> 0) then
              begin
                Result:=Matrix[i];
                break;
              end;
          SetLength(Matrix,0);
        end;      
    end;
end;

不同之处在于,您需要记住每个列比较的结果,然后返回第一个最重要的非零值(最重要的是首先添加到排序的列)。您不需要从最高列循环到最低列。此代码需要 OP 的 TreeHeaderClick 过程来向 Sorting_Columns 添加/删除列。

在这里,所有列的排序方向始终相同。实现排序方向应该相当容易,通过根据其排序方向(升序或降序)反转每列的比较结果。我没有尝试这个。

Slightly modified code from @user532231 to get a working solution

type
  PRecord = ^TRecord;
  TRecord = record
    ID: integer;
    Text_1: string;
    Text_2: string;
    Text_3: string;
    Date: TDateTime;
  end;

...

var Sorting_Columns: array of TColumnIndex;

...

procedure TForm1.VirtualStringTree1CompareNodes(Sender: TBaseVirtualTree;
  Node1, Node2: PVirtualNode; Column: TColumnIndex; var Result: Integer);
var Actual_Index: integer;
    Data_1: PRecord;
    Data_2: PRecord;
    Matrix : array of integer;
    I: Integer;
begin
  if Length(Sorting_Columns) > 0 then
    begin
      Data_1 := VirtualStringTree1.GetNodeData(Node1);
      Data_2 := VirtualStringTree1.GetNodeData(Node2);

      if Assigned(Data_1) and Assigned(Data_2) then
        begin
          SetLength(Matrix,Length(Sorting_Columns));
          for Actual_Index := 0 to High(Sorting_Columns) do
            begin
              case Sorting_Columns[Actual_Index] of
                0: Matrix[Actual_Index] := Data_1^.ID - Data_2^.ID;
                1: Matrix[Actual_Index] := CompareStr(Data_1^.Text_1, Data_2^.Text_1);
                2: Matrix[Actual_Index] := CompareStr(Data_1^.Text_2, Data_2^.Text_2);
                3: Matrix[Actual_Index] := CompareStr(Data_1^.Text_3, Data_2^.Text_3);
                4: Matrix[Actual_Index] := CompareDateTime(Data_1^.Date, Data_2^.Date);
              end;
            end;
          for I := 0 to Length(Matrix) - 1 do
            if (Matrix[i] <> 0) then
              begin
                Result:=Matrix[i];
                break;
              end;
          SetLength(Matrix,0);
        end;      
    end;
end;

The difference is, you need to remember the result of each column comparison and then return the first most significant non-zero value (most significant being the column that was added to sorting first). You don't need to loop from highest to lowest column. This code needs the OP's TreeHeaderClick procedure to add/remove columns to Sorting_Columns.

Here, the sort direction is always the same for all columns. It should be fairly easy to implement sorting direction, by reversing the comparison result of each column according to its sort direction, ascending or descending. I didn't try this.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文