如何同步滚动 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;
我有 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;