【问题标题】:Sorting nodes in VirtualTreeView - OnCompareNodes does not fire for all nodes to be compared在 VirtualTreeView 中对节点进行排序 - OnCompareNodes 不会针对所有要比较的节点触发
【发布时间】:2016-11-28 14:19:31
【问题描述】:

我在使用 VirtualTreView(v 6.1.0 / Delphi 10 Seattle)时遇到一个(奇怪的)排序问题。我查看了 VTV 的更新版本,没有提到类似的行为。

我将发布完整的源代码,但首先让我解释一下我要完成的工作:

  1. 我要对一些节点进行排序
  2. 每个节点都有一个阶段——节点阶段决定了节点的排序顺序(在那个阶段)
  3. 我希望在通过其阶段排序值选择特定阶段时对节点进行排序。那些未包含在舞台中的节点将被设置为不可见。

这是我正在使用的记录:

PRecord = ^TRecord;
TRecord = record
  SortOrder : array [0..4] of integer;
  PositionAdded : integer;
end;

添加节点的方法如下:

procedure TVTVSortForm.FormCreate(Sender: TObject);
var
  vn : PVirtualNode;
  pr : PRecord;
begin
  tree.NodeDataSize := SizeOf(TRecord);

  vn := tree.AddChild(nil);
  pr := PRecord(tree.GetNodeData(vn));
  pr.PositionAdded := vn.Index;
  pr.SortOrder[0] := 0; //first in 0 - removed in later
  pr.SortOrder[1] := -1;
  pr.SortOrder[2] := -1;
  pr.SortOrder[3] := -1;
  pr.SortOrder[4] := 2; //third in 4

  vn := tree.AddChild(nil);
  pr := PRecord(tree.GetNodeData(vn));
  pr.PositionAdded := vn.Index;
  pr.SortOrder[0] := 1; //second in 0
  pr.SortOrder[1] := 0; //first in 1
  pr.SortOrder[2] := 1; //second in 2
  pr.SortOrder[3] := 1; //second in 3
  pr.SortOrder[4] := 0; //first in 4

  vn := tree.AddChild(nil);
  pr := PRecord(tree.GetNodeData(vn));
  pr.PositionAdded := vn.Index;
  pr.SortOrder[0] := 2; // third in 0
  pr.SortOrder[1] := 2; //third in 1
  pr.SortOrder[2] := 0; //first in 2
  pr.SortOrder[3] := -1; //removed in 3
  pr.SortOrder[4] := 1; //second in 4

  vn := tree.AddChild(nil);
  pr := PRecord(tree.GetNodeData(vn));
  pr.PositionAdded := vn.Index;
  pr.SortOrder[0] := -1; //not in 0
  pr.SortOrder[1] := 1;  //second in 1
  pr.SortOrder[2] := 2; //third in 2
  pr.SortOrder[3] := 0; // first in 3
  pr.SortOrder[4] := -1; // not in 4

  tree.ValidateNode(nil, true);
end;

“阶段”是 SortOrder 索引。 SortOrder[x] := y 表示阶段 X 中节点的顺序应该是 y。 例如,SortOrder[2] = 0 表示在第 2 阶段该节点应该是第一个节点。 SortOrder[3] = -1 表示节点在第 3 阶段“不存在”。

有一个广播组有 5 个元素 (0...4) 呈现舞台。选择阶段后,应根据所选阶段的 SortOrder 值对节点进行排序(我还隐藏了阶段中不存在的节点:

procedure TVTVSortForm.rgFilterAndSortClick(Sender: TObject);
begin
  tree.Sort(tree.RootNode, 0, sdAscending);

  tree.IterateSubtree(
  nil,
  procedure (Sender: TBaseVirtualTree; Node: PVirtualNode; Data: Pointer; var Abort: Boolean)
  var
    d : PRecord;
  begin
    d := PRecord(tree.GetNodeData(Node));
    //tree.IsVisible[Node] := -1 <> d.SortOrder[rgFilterAndSort.ItemIndex];
  end,
  nil);
end;

OnCompare 看起来像:

procedure TVTVSortForm.treeCompareNodes(Sender: TBaseVirtualTree; Node1, Node2: PVirtualNode; Column: TColumnIndex; var Result: Integer);
var
  d1, d2 : PRecord;
begin
  d1 := PRecord(Sender.GetNodeData(Node1));
  d2 := PRecord(Sender.GetNodeData(Node2));

  if (-1 <> d1.SortOrder[rgFilterAndSort.ItemIndex]) AND (-1 <> d2.SortOrder[rgFilterAndSort.ItemIndex]) then
    result := d1.SortOrder[rgFilterAndSort.ItemIndex] - d2.SortOrder[rgFilterAndSort.ItemIndex];
end;

因此,我只比较存在于所选阶段的那些节点(即 SortOrder[stage] -1)。

GetText 显示所选阶段的排序顺序 + 添加到树时节点的原始位置。

procedure TVTVSortForm.treeGetText(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType; var CellText: string);
var
  Data: PRecord;
begin
  Data := Sender.GetNodeData(Node);
  CellText := Format('s:%d, p:%d', [Data.SortOrder[rgFilterAndSort.ItemIndex], Data.PositionAdded]);
end;

现在,当您启动程序并转到(选择单选按钮/单击)到阶段 (3) 时会发生什么,顺序是 1,0 而不是 0,1(我省略了“-1”节点)。

如果在选择阶段 (3) 后选择 (1),则 (3) -> 阶段 3 排序正常。但是然后进入阶段(0),(2),(4),顺序是1,2,0而不是0,1,2。

任何想法为什么这不起作用(如预期的那样)?

我注意到,当两个节点进入 OnCompare 时:如果其中一个节点对于所选阶段的值为 -1,则另一个节点将不再与所选阶段中存在的其余节点进行比较。如何强制其他节点仍然与其他同阶段节点匹配?

【问题讨论】:

  • SortOrder 是一个节点而不是另一个节点的–1 时,Result 是未定义的。比较必须定义一个总排序;如果你没有定义所有个可能的对之间的比较,那么底层的排序算法会给出意想不到的结果,比如在所有节点都被排序之前停止。
  • @Rob,抱歉,结果总是预设为 0(Node1
  • 这会产生同样的问题。结果为零表示节点相等。假设您有节点,并且节点 1 设置为 –1。节点 2 将与它进行比较,节点 3 也将与之比较,因为您已跳过比较。通过传递性,这必须意味着节点 2 等于节点 3,但是如果您的比较函数返回不同的答案,那么您没有总排序,并且排序算法可能会失败。
  • 是的,有道理,谢谢!

标签: sorting delphi virtualtreeview


【解决方案1】:

天哪,过去几天我一直在试图找出这里出了什么问题,当然,一旦我在这里发布了我的问题,我就已经弄清楚了。

OnCompare 需要比较“所有”节点 - 所以跳过一些节点(当节点“不在阶段”时)是我的代码有问题的地方。

我需要确保当阶段节点没有与匹配的不同阶段节点进行比较时,它仍然与其他同阶段节点进行比较。

最后,我像这样重写了 OnCompare 方法,目前(我有一个更大的实际应用程序进行测试)一切似乎都还可以:

procedure TVTVSortForm.treeCompareNodes(Sender: TBaseVirtualTree; Node1, Node2: PVirtualNode; Column: TColumnIndex; var Result: Integer);
var
  d1, d2 : PRecord;
begin
  d1 := PRecord(Sender.GetNodeData(Node1));
  d2 := PRecord(Sender.GetNodeData(Node2));

  if (-1 = d1.SortOrder[rgFilterAndSort.ItemIndex]) then result := -1
  else if (-1 = d2.SortOrder[rgFilterAndSort.ItemIndex]) then result := 1
  else
    if (-1 <> d1.SortOrder[rgFilterAndSort.ItemIndex]) AND (-1 <> d2.SortOrder[rgFilterAndSort.ItemIndex]) then
      result := d1.SortOrder[rgFilterAndSort.ItemIndex] - d2.SortOrder[rgFilterAndSort.ItemIndex]
  else
  begin
    //just testing if this ever happens 
    if (-1 = d1.SortOrder[rgFilterAndSort.ItemIndex]) then Inc(d1.PositionAdded);
    if (-1 = d2.SortOrder[rgFilterAndSort.ItemIndex]) then Inc(d2.PositionAdded);
  end;
end;

不过,坦率地说,这不是我对比较节点的工作方式所期望的。

【讨论】:

  • 您能否更详细地解释一下“这不是我所期望的比较节点的工作原理。”?您觉得与其他排序算法有什么不同吗?
  • @JoachimMarder:我想我在这里用了不好的措辞。我想说的是,乍一看,我不希望我仍然需要比较我“不感兴趣”的节点。一旦你知道排序是如何工作的——那么当然,一切都说得通了。
猜你喜欢
  • 2019-05-21
  • 1970-01-01
  • 1970-01-01
  • 2018-01-16
  • 1970-01-01
  • 1970-01-01
  • 2019-07-14
  • 1970-01-01
  • 2017-09-11
相关资源
最近更新 更多