我正在尝试使用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;
答案 0 :(得分:3)
我写了一个小程序来为节点创建任何控件。我发现设置节点的最佳位置控制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: 强>
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
<强> 输出: 强>
使用Delphi 7,VT版本5.3.0,Windows 7进行测试
答案 1 :(得分:2)
所以iamjoosy的答案问题是 - 即使它有效 - 只要你用绘制的按钮/ images /滚动滚动这个树,那些应该再次离开树的那个仍然是现有的,被绘制在你离开它们的最低/最高位置。根据您刚刚滚动的数量,它会在该列中留下更小或更大的按钮杂乱。 AfterCellPaint不再移动它们,因为现在隐藏在底部/顶部上方的隐藏节点的单元格不再被绘制。
你可以做的是遍历所有树节点(如果有很多节点,可能非常昂贵)并检查它们是否实际位于树的可见区域并隐藏面板(您可能需要在面板内部按钮)相应地用你的按钮/ whatevers画在树顶而不是后面:
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;
毋庸置疑,您可以成功地在其他许多OnEvents中使用visibilityCheck进行遍历。它不一定要在AfterCellPaint中;也许另一个事件可能是更好的表现明智。
要创建一个原始Panel + Button的RunTime副本,放置在ButtonArray或您正在使用的任何结构中,您还必须复制他们的RTTI。此过程取自http://www.blong.com/Conferences/BorConUK98/DelphiRTTI/CB140.zip(http://www.blong.com/Conferences/BorConUK98/DelphiRTTI/CB140.htm处的进一步RTTI信息)和&#34;使用TypInfo&#34;:
procedure CopyObject(ObjFrom, ObjTo: TObject);
var
PropInfos: PPropList;
PropInfo: PPropInfo;
Count, Loop: Integer;
OrdVal: Longint;
StrVal: String;
FloatVal: Extended;
MethodVal: TMethod;
begin
{ Iterate thru all published fields and properties of source }
{ copying them to target }
{ Find out how many properties we'll be considering }
Count := GetPropList(ObjFrom.ClassInfo, tkAny, nil);
{ Allocate memory to hold their RTTI data }
GetMem(PropInfos, Count * SizeOf(PPropInfo));
try
{ Get hold of the property list in our new buffer }
GetPropList(ObjFrom.ClassInfo, tkAny, PropInfos);
{ Loop through all the selected properties }
for Loop := 0 to Count - 1 do
begin
PropInfo := GetPropInfo(ObjTo.ClassInfo, PropInfos^[Loop]^.Name);
{ Check the general type of the property }
{ and read/write it in an appropriate way }
case PropInfos^[Loop]^.PropType^.Kind of
tkInteger, tkChar, tkEnumeration,
tkSet, tkClass{$ifdef Win32}, tkWChar{$endif}:
begin
OrdVal := GetOrdProp(ObjFrom, PropInfos^[Loop]);
if Assigned(PropInfo) then
SetOrdProp(ObjTo, PropInfo, OrdVal);
end;
tkFloat:
begin
FloatVal := GetFloatProp(ObjFrom, PropInfos^[Loop]);
if Assigned(PropInfo) then
SetFloatProp(ObjTo, PropInfo, FloatVal);
end;
{$ifndef DelphiLessThan3}
tkWString,
{$endif}
{$ifdef Win32}
tkLString,
{$endif}
tkString:
begin
{ Avoid copying 'Name' - components must have unique names }
if UpperCase(PropInfos^[Loop]^.Name) = 'NAME' then
Continue;
StrVal := GetStrProp(ObjFrom, PropInfos^[Loop]);
if Assigned(PropInfo) then
SetStrProp(ObjTo, PropInfo, StrVal);
end;
tkMethod:
begin
MethodVal := GetMethodProp(ObjFrom, PropInfos^[Loop]);
if Assigned(PropInfo) then
SetMethodProp(ObjTo, PropInfo, MethodVal);
end
end
end
finally
FreeMem(PropInfos, Count * SizeOf(PPropInfo));
end;
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;
答案 2 :(得分:1)
OnAfterCellPaint事件处理程序中CellRect参数的坐标是相对于绘制的节点。你需要的是树窗口中节点的absoulte位置。您可以通过调用树的GetDisplayRect来获取它。 所以改变你的代码:
procedure TForm1.VirtualStringTree1AfterCellPaint(Sender: TBaseVirtualTree; TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex; CellRect: TRect);
var
NodeData: PNodeData;
R: TRect;
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));
R := Sender.GetDisplayRect(Node, Column, False);
BoundsRect := R;
end;
end;
end;