如何同步滚动 2 个不同高度的 TVirtualStringTree 控件?

How to synchronize scrolling of 2 TVirtualStringTree controls with different heights?

我有 2 个 TVirtualStringTree (VST) 控件,一个在另一个之上。中间有 TSplitter。我使用 VST1/2 的 OnScroll 在滚动第一个时滚动另一个 VST2/1:

    procedure TForm1.VST1Scroll(Sender: TBaseVirtualTree; DeltaX, DeltaY: Integer);
    begin
      VST2.OffsetY:=VST1.OffsetY;
    end;

    procedure TForm1.VST2Scroll(Sender: TBaseVirtualTree; DeltaX, DeltaY: Integer);
    begin
      VST1.OffsetY:=VST2.OffsetY;
    end;

使用滚动条上下滚动,效果很好。但前提是它们的大小相同。问题是当高度不同时,要么 VST1 滚动到最后,而 VST2 仍然有很多,反之亦然,这取决于 higher/smaller.

我尝试了多种 OffsetY * 高度百分比的组合...不同的计算,但即使高度不同,也不会同步滚动。

例如,如果 VST1.Height = 100 且 VST.Height = 200,则 VST1 上的每个滚动都应滚动 VST2 2*OffsetY,以匹配它们并同时滚动到底部。好吧,这不太好用。

它们都具有相同的 NodeCount(在附加示例 20 中,但可能有 1000 个)。

问题:如何计算一个VST中的每条滚动条应该滚动到另一个VST同步多少?或者,当不同的高度

时,是否有另一种比同步滚动两个 VST 更简单的方法

这是.pas

unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls, VirtualTrees;

type
  TForm1 = class(TForm)
    VST1: TVirtualStringTree;
    VST2: TVirtualStringTree;
    Splitter1: TSplitter;
    procedure FormCreate(Sender: TObject);
    procedure VST1GetText(Sender: TBaseVirtualTree; Node: PVirtualNode;
      Column: TColumnIndex; TextType: TVSTTextType; var CellText: string);
    procedure VST2GetText(Sender: TBaseVirtualTree; Node: PVirtualNode;
      Column: TColumnIndex; TextType: TVSTTextType; var CellText: string);
    procedure VST1Scroll(Sender: TBaseVirtualTree; DeltaX, DeltaY: Integer);
    procedure VST2Scroll(Sender: TBaseVirtualTree; DeltaX, DeltaY: Integer);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
begin
  VST1.RootNodeCount := 20;
  VST2.RootNodeCount := 20;
end;

procedure TForm1.VST1GetText(Sender: TBaseVirtualTree; Node: PVirtualNode;
  Column: TColumnIndex; TextType: TVSTTextType; var CellText: string);
begin
  CellText:=IntToStr(Node.Index+1);
end;

procedure TForm1.VST1Scroll(Sender: TBaseVirtualTree; DeltaX, DeltaY: Integer);
begin
  VST2.OffsetY:=VST1.OffsetY;
end;

procedure TForm1.VST2GetText(Sender: TBaseVirtualTree; Node: PVirtualNode;
  Column: TColumnIndex; TextType: TVSTTextType; var CellText: string);
begin
  CellText:=IntToStr(Node.Index+1);
end;

procedure TForm1.VST2Scroll(Sender: TBaseVirtualTree; DeltaX, DeltaY: Integer);
begin
  VST1.OffsetY:=VST2.OffsetY;
end;

end.

这里是 .dfm:

object Form1: TForm1
  Left = 0
  Top = 0
  Caption = 'Form1'
  ClientHeight = 337
  ClientWidth = 635
  Color = clBtnFace
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'Tahoma'
  Font.Style = []
  OldCreateOrder = False
  OnCreate = FormCreate
  PixelsPerInch = 96
  TextHeight = 13
  object Splitter1: TSplitter
    Left = 0
    Top = 100
    Width = 635
    Height = 3
    Cursor = crVSplit
    Align = alTop
    ExplicitWidth = 237
  end
  object VST1: TVirtualStringTree
    Left = 0
    Top = 0
    Width = 635
    Height = 100
    Align = alTop
    Header.AutoSizeIndex = 0
    Header.Font.Charset = DEFAULT_CHARSET
    Header.Font.Color = clWindowText
    Header.Font.Height = -11
    Header.Font.Name = 'Tahoma'
    Header.Font.Style = []
    Header.MainColumn = -1
    TabOrder = 0
    OnGetText = VST1GetText
    OnScroll = VST1Scroll
    Columns = <>
  end
  object VST2: TVirtualStringTree
    Left = 0
    Top = 103
    Width = 635
    Height = 234
    Align = alClient
    Header.AutoSizeIndex = 0
    Header.Font.Charset = DEFAULT_CHARSET
    Header.Font.Color = clWindowText
    Header.Font.Height = -11
    Header.Font.Name = 'Tahoma'
    Header.Font.Style = []
    Header.MainColumn = -1
    TabOrder = 1
    OnGetText = VST2GetText
    OnScroll = VST2Scroll
    Columns = <>
  end
end

同步顶级节点而不是尝试让两个树视图保持像素完美同步怎么样?我看到 VT 有一个 TopNode 属性,所以我会尝试这样的事情:

  • VT初始化后保存树顶节点;
  • OnScroll 事件中检查当前顶级节点是什么 - 如果它已经改变则:
    • 记住树的新顶部节点;
    • 通知必须更新其 TopNode 的另一棵树;

既然你说两棵树都有相同数量的节点,我假设它们显示相同的数据,所以可以将两棵树中的节点识别为 "same"(它们代表相同的数据)。

VST 有一个受保护的 属性 RangeY,它包含整个滚动范围,是解决方案的关键。

因此,ClientHeight - RangeY = VST 中的最大负值OffsetY

代码可能如下所示:

type
  TForm1 = class(TForm)   
  ...
  private
    FScrolling: boolean;
    procedure SyncScroll(Sender, Target: TBaseVirtualTree);
  end;

...

type
  TCustomVirtualStringTreeAccess = class(TCustomVirtualStringTree);

procedure TForm1.SyncScroll(Sender, Target: TBaseVirtualTree);
var
  SenderMaxOffsetY, TargetMaxOffsetY: Integer;
  DY: Extended;
begin
  if FScrolling then Exit; // Avoid reentrancy from Target
  SenderMaxOffsetY := Sender.ClientHeight - Integer(TCustomVirtualStringTreeAccess(Sender).RangeY);
  TargetMaxOffsetY := Target.ClientHeight - Integer(TCustomVirtualStringTreeAccess(Target).RangeY);
  if SenderMaxOffsetY = 0 then Exit;
  DY := Sender.OffsetY / SenderMaxOffsetY;
  FScrolling := True;
  try
    Target.OffsetY := Round(TargetMaxOffsetY * DY);
  finally
    FScrolling := False;
  end;
end;

procedure TForm1.VST1Scroll(Sender: TBaseVirtualTree; DeltaX, DeltaY: Integer);
begin
  SyncScroll(Sender, VST2);
end;

procedure TForm1.VST2Scroll(Sender: TBaseVirtualTree; DeltaX, DeltaY: Integer);
begin
  SyncScroll(Sender, VST1);
end;