Delphi VirtualTreeView在单元格中嵌入按钮

Delphi VirtualTreeView在单元格中嵌入按钮,delphi,virtualtreeview,Delphi,Virtualtreeview,我正在尝试使用TButton创建节点。 我创建节点和链接到节点的按钮。 在事件TVirtualStringTree.AfterCellPaint上,我初始化按钮上的BoundsRect。但按钮始终显示在第一个节点中 你知道这个问题吗 type TNodeData = record TextValue: string; Button: TButton; end; PNodeData = ^TNodeData; procedure TForm1.FormCreate(S

我正在尝试使用TButton创建节点。 我创建节点和链接到节点的按钮。 在事件TVirtualStringTree.AfterCellPaint上,我初始化按钮上的BoundsRect。但按钮始终显示在第一个节点中

你知道这个问题吗

type
  TNodeData = record
    TextValue: string;
    Button: TButton;
  end;
  PNodeData = ^TNodeData;

procedure TForm1.FormCreate(Sender: TObject);

  procedure AddButton(__Node: PVirtualNode);
  var
    NodeData: PNodeData;
  begin
    NodeData := VirtualStringTree1.GetNodeData(__Node);
    NodeData.Button := TButton.Create(nil);
    with NodeData.Button do
    begin
      Parent := VirtualStringTree1;
      Height := VirtualStringTree1.DefaultNodeHeight;
      Caption := '+';
      Visible := false;
    end;
  end;

  procedure InitializeNodeData(__Node: PVirtualNode; __Text: string);
  var
    NodeData: PNodeData;
  begin
    NodeData := VirtualStringTree1.GetNodeData(__Node);
    NodeData.TextValue := __Text;
  end;

var
  Node: PVirtualNode;
begin
  VirtualStringTree1.NodeDataSize := SizeOf(TNodeData);

  Node := VirtualStringTree1.AddChild(nil);
  InitializeNodeData(Node, 'a');      
  Node := VirtualStringTree1.AddChild(Node);
  InitializeNodeData(Node, 'a.1');

  Node := VirtualStringTree1.AddChild(nil);
  InitializeNodeData(Node, 'b');
  Node := VirtualStringTree1.AddChild(Node);
  InitializeNodeData(Node, 'Here the button');
  AddButton(Node);
end;

procedure TForm1.VirtualStringTree1AfterCellPaint(Sender: TBaseVirtualTree;
  TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex; CellRect: TRect);
var
 NodeData: PNodeData;
begin
  if (Column = 0) then
    Exit;

  NodeData := VirtualStringTree1.GetNodeData(Node);
  if (Assigned(NodeData)) and (Assigned(NodeData.Button)) then
  begin
    with NodeData.Button Do
    begin
      Visible := (vsVisible in Node.States)
                 and ((Node.Parent = VirtualStringTree1.RootNode) or   (vsExpanded in Node.Parent.States));
      BoundsRect := CellRect;
    end;
  end;
end;

OnAfterCellPaint事件处理程序中CellRect参数的坐标相对于绘制的节点。您需要的是节点在树窗口中的绝对位置。您可以通过调用树的GetDisplayRect来获得它。 因此,请按如下方式更改代码:

过程TForm1.VirtualStringTree1AfterCellPaint(发送方:TBaseVirtualTree;TargetCanvas:TCanvas;节点:PVirtualNode;列:TColumnIndex;CellRect:TRect);
变量
NodeData:PNodeData;
R:TRect;
开始
如果(列=0),则
出口
NodeData:=VirtualStringTree1.GetNodeData(节点);
如果(已分配(NodeData))和(已分配(NodeData.Button)),则
开始
用NodeData。按钮做什么
开始
可见:=(在Node.States中可见)
和((Node.Parent=VirtualStringTree1.RootNode)或(vsExpanded in Node.Parent.States));
R:=Sender.GetDisplayRect(节点,列,False);
BoundsRect:=R;
结束;
结束;

结束因此,iamjoosy的答案的问题是——即使它有效——只要你用绘制的按钮/图像/任何东西在这棵树上滚动,应该再次离开树的那些仍然存在,被绘制在你离开它们的最低/最高位置。根据您刚才滚动的数量,它会在该列中留下更小或更大的按钮。AfterCellPaint不再移动它们,因为底部/顶部上方现在不可见的节点的单元不再绘制

您可以做的是遍历所有树节点(如果您有很多节点,可能会非常昂贵),并检查它们是否实际位于树的可见区域中,并使用相应的按钮/其他内容隐藏面板(您可能需要将面板内的按钮绘制在树的顶部而不是后面):

procedure TMyTree.MyTreeAfterCellPaint(Sender: TBaseVirtualTree;
  TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex;
  CellRect: TRect);
var
  InitialIndex: Integer;
// onInitNode I AddOrSetValue a "DataIndexList" TDictionary<PVirtualNode, LongInt>
// to preserve an original index "InitialIndex" (violating the virtual paradigm),
// because I need it for something else anyways
  Data: PMyData;
  ANode: PVirtualNode;
begin
  if Node <> nil then
  begin
    if Column = 2 then
    begin
      ANode := MyTree.GetFirst;
      while Assigned(ANode) do
      begin
        DataIndexList.TryGetValue(ANode, InitialIndex);
        if not ( CheckVisibility(Sender.GetDisplayRect(ANode, Column, False)) ) then
        begin
          MyBtnArray[InitialIndex].Visible := False;
          MyPanelArray[InitialIndex].Visible := False;
        end
        else
        begin
          MyBtnArray[InitialIndex].Visible := True;
          MyPanelArray[InitialIndex].Visible := True;
        end;
        ANode := MyTree.GetNext(ANode);
      end;
      DataIndexList.TryGetValue(Node, InitialIndex);
      Data := MyTree.GetNodeData(Node);
      MyPanelArray[InitialIndex].BoundsRect := Sender.GetDisplayRect(Node, Column, False);
    end;
  end;
end;

function TMyTree.CheckVisibility(R: TRect): Boolean;
begin
// in my case these checks are the way to go, because
// MyTree is touching the top border of the TForm.  You will have
// to adjust accordingly if your placement is different
  if (R.Bottom < MyTree.Top) or (R.Bottom > MyTree.Top + MyTree.Height) then
    Result := False
  else
    Result := True;
end;
稍后看到我的这个老答案,我现在有一个不同的解决方案运行VisibilityCheck,它更加可靠和简单:

function TFoo.IsNodeVisibleInClientRect(Node: PVirtualNode; Column: TColumnIndex = NoColumn): Boolean;
begin
  Result := VST.IsVisible[Node] and
    VST.GetDisplayRect(Node, Column, False).IntersectsWith(VST.ClientRect);
end;

我编写了一个小程序来为节点创建任何控件。我发现设置节点的最佳位置是在
OnAfterPaint
事件中控制可见性。滚动按预期工作,几乎没有闪烁

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, VirtualTrees, StdCtrls, Buttons, ExtCtrls;

type
  TForm1 = class(TForm)
    VirtualStringTree1: TVirtualStringTree;
    procedure FormCreate(Sender: TObject);            
    procedure VirtualStringTree1GetText(Sender: TBaseVirtualTree;
      Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType;
      var CellText: WideString);
    procedure VirtualStringTree1AfterPaint(Sender: TBaseVirtualTree;
      TargetCanvas: TCanvas);
    procedure VirtualStringTree1MeasureItem(Sender: TBaseVirtualTree;
      TargetCanvas: TCanvas; Node: PVirtualNode; var NodeHeight: Integer);  
  private
    procedure SetNodesControlVisibleProc(Sender: TBaseVirtualTree; Node: PVirtualNode; Data: Pointer; var Abort: Boolean);
    procedure SetNodeControlVisible(Tree: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex = NoColumn);
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

type
  TNodeData = record
    Text: WideString;
    Control: TControl;
  end;
  PNodeData = ^TNodeData;

{ Utility }
function IsNodeVisibleInClientRect(Tree: TBaseVirtualTree; Node: PVirtualNode;
  Column: TColumnIndex = NoColumn): Boolean;
var
  OutRect: TRect;
begin
  Result := Tree.IsVisible[Node] and
    Windows.IntersectRect(OutRect, Tree.GetDisplayRect(Node, Column, False), Tree.ClientRect);
end;

type
  TControlClass = class of TControl;

  TMyPanel = class(TPanel)
  public
    CheckBox: TCheckBox;
  end;

{ TForm1 }
procedure TForm1.FormCreate(Sender: TObject);

  function CreateNodeControl(Tree: TVirtualStringTree; Node: PVirtualNode; ControlClass: TControlClass): TControl;
  var
    NodeData: PNodeData;
  begin
    NodeData := Tree.GetNodeData(Node);
    NodeData.Control := ControlClass.Create(nil);
    with NodeData.Control do
    begin
      Parent := Tree; // Parent will destroy the control
      Height := Tree.DefaultNodeHeight;
      Visible := False;
    end;
    Tree.IsDisabled[Node] := True;
    Result := NodeData.Control;
  end;

  procedure InitializeNodeData(Node: PVirtualNode; const Text: WideString);
  var
    NodeData: PNodeData;
  begin
    NodeData := VirtualStringTree1.GetNodeData(Node);
    Initialize(NodeData^);
    NodeData.Text := Text;
  end;

var
  Node: PVirtualNode;
  MyPanel: TMyPanel;
  I: integer;
begin
  VirtualStringTree1.NodeDataSize := SizeOf(TNodeData);
  // trigger MeasureItem
  VirtualStringTree1.TreeOptions.MiscOptions := VirtualStringTree1.TreeOptions.MiscOptions + [toVariableNodeHeight]; 

  // Populate some nodes    
  for I := 1 to 5 do begin
    Node := VirtualStringTree1.AddChild(nil);
    InitializeNodeData(Node, Format('%d', [I]));
    Node := VirtualStringTree1.AddChild(Node);
    InitializeNodeData(Node, Format('%d.1', [I]));
  end;

  Node := VirtualStringTree1.AddChild(nil);
  InitializeNodeData(Node, '[TSpeedButton Parent]');
  Node := VirtualStringTree1.AddChild(Node);
  InitializeNodeData(Node, 'TSpeedButton');
  TSpeedButton(CreateNodeControl(VirtualStringTree1, Node, TSpeedButton)).Caption := '+';

  Node := VirtualStringTree1.AddChild(nil);
  InitializeNodeData(Node, '[TEdit Parent]');
  Node := VirtualStringTree1.AddChild(Node);
  InitializeNodeData(Node, 'TEdit');
  TEdit(CreateNodeControl(VirtualStringTree1, Node, TEdit)).Text := 'Hello';

  Node := VirtualStringTree1.AddChild(nil);
  InitializeNodeData(Node, '[TMyPanel Parent]');
  Node := VirtualStringTree1.AddChild(Node);
  InitializeNodeData(Node, 'TMyPanel');
  MyPanel := TMyPanel(CreateNodeControl(VirtualStringTree1, Node, TMyPanel));
  with MyPanel do
  begin
    Caption := 'TMyPanel';
    ParentBackground := False;
    CheckBox := TCheckBox.Create(nil);
    CheckBox.Caption := 'CheckBox';
    CheckBox.Left := 10;
    CheckBox.Top := 10;
    CheckBox.Parent := MyPanel;
  end;

  for I := 6 to 10 do begin
    Node := VirtualStringTree1.AddChild(nil);
    InitializeNodeData(Node, Format('%d', [I]));
    Node := VirtualStringTree1.AddChild(Node);
    InitializeNodeData(Node, Format('%d.1', [I]));
  end;
end;

procedure TForm1.VirtualStringTree1GetText(Sender: TBaseVirtualTree;
  Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType;
  var CellText: WideString);
var
  NodeData: PNodeData;
begin
  NodeData := Sender.GetNodeData(Node);
  if Assigned(NodeData) then
    CellText := NodeData.Text;
end;

procedure TForm1.SetNodeControlVisible(Tree: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex = NoColumn);
var
  NodeData: PNodeData;
  R: TRect;
begin
  NodeData := Tree.GetNodeData(Node);
  if Assigned(NodeData) and Assigned(NodeData.Control) then
  begin
    with NodeData.Control do
    begin
      Visible := IsNodeVisibleInClientRect(Tree, Node, Column)
                 and ((Node.Parent = Tree.RootNode) or (vsExpanded in Node.Parent.States));
      R := Tree.GetDisplayRect(Node, Column, False);
      BoundsRect := R;
    end;
  end;
end;

procedure TForm1.SetNodesControlVisibleProc(Sender: TBaseVirtualTree; Node: PVirtualNode; Data: Pointer; var Abort: Boolean);
begin
  SetNodeControlVisible(Sender, Node);
end;

procedure TForm1.VirtualStringTree1AfterPaint(Sender: TBaseVirtualTree;
  TargetCanvas: TCanvas);
begin
  // Iterate all Tree nodes and set visibility
  Sender.IterateSubtree(nil, SetNodesControlVisibleProc, nil);
end;

procedure TForm1.VirtualStringTree1MeasureItem(Sender: TBaseVirtualTree;
  TargetCanvas: TCanvas; Node: PVirtualNode; var NodeHeight: Integer);
var
  NodeData: PNodeData;
begin
  NodeData := Sender.GetNodeData(Node);
  if Assigned(NodeData) and Assigned(NodeData.Control) then
  // set node special height if control is TMyPanel
    if NodeData.Control is TMyPanel then
      NodeHeight := 50;
end;

end.
DFM:

对象格式1:t格式1
左=192
Top=124
宽度=782
高度=365
标题='Form1'
颜色=clBtnFace
Font.Charset=默认字符集
Font.Color=clWindowText
字体高度=-11
Font.Style=[]
OldCreateOrder=False
OnCreate=FormCreate
设计尺寸=(
766
327)
PixelsPerInch=96
text高度=13
对象VirtualStringTree1:TVirtualStringTree
左=8
Top=8
宽度=450
高度=277
锚定=[akLeft、akTop、akRight、akBottom]
Header.AutoSizeIndex=0
Header.Font.Charset=默认字符集
Header.Font.Color=clWindowText
Header.Font.Height=-11
Header.Font.Name='MS Sans Serif'
Header.Font.Style=[]
Header.main列=-1
TabOrder=0
OnAfterPaint=VirtualStringTree1AfterPaint
OnGetText=VirtualStringTree1GetText
OnMeasureItem=VirtualStringTree1MeasureItem
列=
结束
结束
输出:


使用Delphi 7、VT 5.3.0版和Windows 7进行测试,可以正常工作,但我仍然有一个问题。如果我扩展它的父级,我有一个按钮:OK。如果我让它的父亲倒下:按钮保持可见
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, VirtualTrees, StdCtrls, Buttons, ExtCtrls;

type
  TForm1 = class(TForm)
    VirtualStringTree1: TVirtualStringTree;
    procedure FormCreate(Sender: TObject);            
    procedure VirtualStringTree1GetText(Sender: TBaseVirtualTree;
      Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType;
      var CellText: WideString);
    procedure VirtualStringTree1AfterPaint(Sender: TBaseVirtualTree;
      TargetCanvas: TCanvas);
    procedure VirtualStringTree1MeasureItem(Sender: TBaseVirtualTree;
      TargetCanvas: TCanvas; Node: PVirtualNode; var NodeHeight: Integer);  
  private
    procedure SetNodesControlVisibleProc(Sender: TBaseVirtualTree; Node: PVirtualNode; Data: Pointer; var Abort: Boolean);
    procedure SetNodeControlVisible(Tree: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex = NoColumn);
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

type
  TNodeData = record
    Text: WideString;
    Control: TControl;
  end;
  PNodeData = ^TNodeData;

{ Utility }
function IsNodeVisibleInClientRect(Tree: TBaseVirtualTree; Node: PVirtualNode;
  Column: TColumnIndex = NoColumn): Boolean;
var
  OutRect: TRect;
begin
  Result := Tree.IsVisible[Node] and
    Windows.IntersectRect(OutRect, Tree.GetDisplayRect(Node, Column, False), Tree.ClientRect);
end;

type
  TControlClass = class of TControl;

  TMyPanel = class(TPanel)
  public
    CheckBox: TCheckBox;
  end;

{ TForm1 }
procedure TForm1.FormCreate(Sender: TObject);

  function CreateNodeControl(Tree: TVirtualStringTree; Node: PVirtualNode; ControlClass: TControlClass): TControl;
  var
    NodeData: PNodeData;
  begin
    NodeData := Tree.GetNodeData(Node);
    NodeData.Control := ControlClass.Create(nil);
    with NodeData.Control do
    begin
      Parent := Tree; // Parent will destroy the control
      Height := Tree.DefaultNodeHeight;
      Visible := False;
    end;
    Tree.IsDisabled[Node] := True;
    Result := NodeData.Control;
  end;

  procedure InitializeNodeData(Node: PVirtualNode; const Text: WideString);
  var
    NodeData: PNodeData;
  begin
    NodeData := VirtualStringTree1.GetNodeData(Node);
    Initialize(NodeData^);
    NodeData.Text := Text;
  end;

var
  Node: PVirtualNode;
  MyPanel: TMyPanel;
  I: integer;
begin
  VirtualStringTree1.NodeDataSize := SizeOf(TNodeData);
  // trigger MeasureItem
  VirtualStringTree1.TreeOptions.MiscOptions := VirtualStringTree1.TreeOptions.MiscOptions + [toVariableNodeHeight]; 

  // Populate some nodes    
  for I := 1 to 5 do begin
    Node := VirtualStringTree1.AddChild(nil);
    InitializeNodeData(Node, Format('%d', [I]));
    Node := VirtualStringTree1.AddChild(Node);
    InitializeNodeData(Node, Format('%d.1', [I]));
  end;

  Node := VirtualStringTree1.AddChild(nil);
  InitializeNodeData(Node, '[TSpeedButton Parent]');
  Node := VirtualStringTree1.AddChild(Node);
  InitializeNodeData(Node, 'TSpeedButton');
  TSpeedButton(CreateNodeControl(VirtualStringTree1, Node, TSpeedButton)).Caption := '+';

  Node := VirtualStringTree1.AddChild(nil);
  InitializeNodeData(Node, '[TEdit Parent]');
  Node := VirtualStringTree1.AddChild(Node);
  InitializeNodeData(Node, 'TEdit');
  TEdit(CreateNodeControl(VirtualStringTree1, Node, TEdit)).Text := 'Hello';

  Node := VirtualStringTree1.AddChild(nil);
  InitializeNodeData(Node, '[TMyPanel Parent]');
  Node := VirtualStringTree1.AddChild(Node);
  InitializeNodeData(Node, 'TMyPanel');
  MyPanel := TMyPanel(CreateNodeControl(VirtualStringTree1, Node, TMyPanel));
  with MyPanel do
  begin
    Caption := 'TMyPanel';
    ParentBackground := False;
    CheckBox := TCheckBox.Create(nil);
    CheckBox.Caption := 'CheckBox';
    CheckBox.Left := 10;
    CheckBox.Top := 10;
    CheckBox.Parent := MyPanel;
  end;

  for I := 6 to 10 do begin
    Node := VirtualStringTree1.AddChild(nil);
    InitializeNodeData(Node, Format('%d', [I]));
    Node := VirtualStringTree1.AddChild(Node);
    InitializeNodeData(Node, Format('%d.1', [I]));
  end;
end;

procedure TForm1.VirtualStringTree1GetText(Sender: TBaseVirtualTree;
  Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType;
  var CellText: WideString);
var
  NodeData: PNodeData;
begin
  NodeData := Sender.GetNodeData(Node);
  if Assigned(NodeData) then
    CellText := NodeData.Text;
end;

procedure TForm1.SetNodeControlVisible(Tree: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex = NoColumn);
var
  NodeData: PNodeData;
  R: TRect;
begin
  NodeData := Tree.GetNodeData(Node);
  if Assigned(NodeData) and Assigned(NodeData.Control) then
  begin
    with NodeData.Control do
    begin
      Visible := IsNodeVisibleInClientRect(Tree, Node, Column)
                 and ((Node.Parent = Tree.RootNode) or (vsExpanded in Node.Parent.States));
      R := Tree.GetDisplayRect(Node, Column, False);
      BoundsRect := R;
    end;
  end;
end;

procedure TForm1.SetNodesControlVisibleProc(Sender: TBaseVirtualTree; Node: PVirtualNode; Data: Pointer; var Abort: Boolean);
begin
  SetNodeControlVisible(Sender, Node);
end;

procedure TForm1.VirtualStringTree1AfterPaint(Sender: TBaseVirtualTree;
  TargetCanvas: TCanvas);
begin
  // Iterate all Tree nodes and set visibility
  Sender.IterateSubtree(nil, SetNodesControlVisibleProc, nil);
end;

procedure TForm1.VirtualStringTree1MeasureItem(Sender: TBaseVirtualTree;
  TargetCanvas: TCanvas; Node: PVirtualNode; var NodeHeight: Integer);
var
  NodeData: PNodeData;
begin
  NodeData := Sender.GetNodeData(Node);
  if Assigned(NodeData) and Assigned(NodeData.Control) then
  // set node special height if control is TMyPanel
    if NodeData.Control is TMyPanel then
      NodeHeight := 50;
end;

end.
object Form1: TForm1
  Left = 192
  Top = 124
  Width = 782
  Height = 365
  Caption = 'Form1'
  Color = clBtnFace
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Style = []
  OldCreateOrder = False
  OnCreate = FormCreate
  DesignSize = (
    766
    327)
  PixelsPerInch = 96
  TextHeight = 13
  object VirtualStringTree1: TVirtualStringTree
    Left = 8
    Top = 8
    Width = 450
    Height = 277
    Anchors = [akLeft, akTop, akRight, akBottom]
    Header.AutoSizeIndex = 0
    Header.Font.Charset = DEFAULT_CHARSET
    Header.Font.Color = clWindowText
    Header.Font.Height = -11
    Header.Font.Name = 'MS Sans Serif'
    Header.Font.Style = []
    Header.MainColumn = -1
    TabOrder = 0
    OnAfterPaint = VirtualStringTree1AfterPaint
    OnGetText = VirtualStringTree1GetText
    OnMeasureItem = VirtualStringTree1MeasureItem
    Columns = <>
  end
end