unit uBafTree;

interface

uses SysUtils, Classes, uStringIniFile, contnrs, FMX.StdCtrls, System.UITypes,
  FMX.Graphics, System.Types, uBafTypes, StrUtils, FMX.Controls, FMX.Types,
  FMX.Objects, FMX.Layouts, Winapi.Windows, System.Math;

type
  TBafTree = class;
  TBafTreeNode = class;

  TBafTreeNodeEvent = procedure(ASender: TBafTree; ANode: TBafTreeNode) of object;

  TBafTreeViewItemLookupEvent = procedure(ASpecial: boolean; AName, AKey: string; var AResult: string) of object;
  TBafTreeViewFunctionEvent = procedure(var AText: string) of object;

  TBafTreeFindMode = (fmSelf, fmNext, fmPrior, fmVisibleNext, fmVisiblePrior);

  TBafTreeNode = class
  private
    FCaption: string;
    FIni: TStringIniFile;
    FChilds: TObjectList;
    FOpened: boolean;
    FTree: TBafTree;
    FLevel: integer;
    FHasOpenCommand: boolean;
    FParentNode: TBafTreeNode;
    FVisibleIndex: integer;
    FOnLookup: TBafTreeViewItemLookupEvent;
    FOnFunction: TBafTreeViewFunctionEvent;
    FObjectToRemove: boolean;
    function GetChilds(AIndex: integer): TBafTreeNode;
    procedure SetOpened(const Value: boolean);
  public
    constructor Create;
    class function CreateInsertItem(AOwner: TBafTree;
        AParent: TBafTreeNode; ACaption: string): TBafTreeNode;
    destructor Destroy; override;
    function AddNode: TBafTreeNode;
    procedure Delete;
    procedure ClearChilds;
    function ChildCount: integer;
    function NodeIndex: integer;

    function GetCaption: string;
    function GetSelectionCommand: string;
    function GetTreeFocus: boolean;
    function GetOpenCommand: string;
    function GetId: string;
    function GetTableName: string;
    function GetNodeOrParentValue(ASection, AName, ADefault: string): string;
    function GetNodeValue(ASection, AName, ADefault: string): string;
    procedure DbCancel;
    function IsValue(AFieldName, AValue: string): boolean;

    procedure OpenAllParents;
    property Text: string read FCaption write FCaption;
    property Ini: TStringIniFile read FIni write FIni;
    property Childs[AIndex: integer]: TBafTreeNode read GetChilds;
    property Opened: boolean read FOpened write SetOpened;
    property Level: integer read FLevel write FLevel;
    property Tree: TBafTree read FTree write FTree;
    property VisibleIndex: integer read FVisibleIndex write FVisibleIndex;
    property ParentNode: TBafTreeNode read FParentNode write FParentNode;
    property HasOpenCommand: boolean read FHasOpenCommand write FHasOpenCommand;
    property OnLookup: TBafTreeViewItemLookupEvent read FOnLookup write FOnLookup;
    property OnFunction: TBafTreeViewFunctionEvent read FOnFunction write FOnFunction;
    property ObjectToRemove: boolean read FObjectToRemove write FObjectToRemove;
  end;


  TBafTree = class(TPanel)
  private
    FRoot: TBafTreeNode;
    FVisibleNodes: TObjectList;
    FScale: integer;
    FSelected: TBafTreeNode;
    FOnSelect: TBafTreeNodeEvent;
    FOnOpenClose: TBafTreeNodeEvent;
    function GetVisibleNodes(AIndex: integer): TBafTreeNode;
    procedure SetScale(const Value: integer);
    procedure SetSelected(const Value: TBafTreeNode);
  protected
    FIniList: TObjectList;
    FVertScrollBar: TScrollBar;
    FPanelDesk: TPanel;
    FColorBack, FColorSelection, FColorReadonly, FColorHeader, FColorFont, FColorEdit: TAlphaColor;
    FRowHeight: integer;
    procedure CreateComponents;
    procedure TreePaint(Sender: TObject; Canvas: TCanvas; const ARect: TRectF);
    procedure LoadStyle;
    procedure ScrollChange(Sender: TObject);
    procedure PanelDeskMouseUp(Sender: TObject; Button: TMouseButton;
        Shift: TShiftState; X, Y: Single);
    procedure PanelDeskMouseDown(Sender: TObject; Button: TMouseButton;
        Shift: TShiftState; X, Y: Single);
    procedure PanelDeskMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Single);
    procedure MouseWheel(Shift: TShiftState; WheelDelta: Integer;
        var Handled: Boolean); override;
    procedure DialogKey(var Key: Word; Shift: TShiftState); override;
  protected
    procedure DoSelect(ANode: TBafTreeNode);
    procedure DoOpenClose(ANode: TBafTreeNode);
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    class function Place(AOwner: TComponent; AParent: TControl): TBafTree;
    procedure Resize; override;
    procedure RefreshVisibleNodes;
    procedure CaptionRefresh(ARoot: TBafTreeNode);
    function VisibleNodesCount: integer;
    function FindNode(ARef: TBafTreeNode; AFind: TBafTreeFindMode;
        var AResult: TBafTreeNode): boolean;
    procedure SelectIndex(AIndex: integer);
    procedure SilentTreeSelect(ASelected: TBafTreeNode);
    property Root: TBafTreeNode read FRoot;
    property VisibleNodes[AIndex: integer]: TBafTreeNode read GetVisibleNodes;
    property Scale: integer read FScale write SetScale;
    property Selected: TBafTreeNode read FSelected write SetSelected;
  published
    property OnSelect: TBafTreeNodeEvent read FOnSelect write FOnSelect;
    property OnOpenClose: TBafTreeNodeEvent read FOnOpenClose write FOnOpenClose;

  end;

  TDefNodeObject = class
    FKeyColumn: string;
    FKeyColumnValue: string;
    FLastInsertedNode: TBafTreeNode;
  end;

implementation

{ TBafTreeNode }

function TBafTreeNode.AddNode: TBafTreeNode;
begin
  result := TBafTreeNode.Create;
  result.Tree := Tree;
  result.ParentNode := Self;
  result.Level := Level + 1;
  FChilds.Add(result);
end;

function TBafTreeNode.ChildCount: integer;
begin
  if FChilds = nil then
    result := 0
  else
    result := FChilds.Count;
end;

procedure TBafTreeNode.ClearChilds;
begin
  FChilds.Clear;
end;

constructor TBafTreeNode.Create;
begin
  inherited;
  FIni := TStringIniFile.Create('');
  FChilds := TObjectList.Create(true);
end;

class function TBafTreeNode.CreateInsertItem(AOwner: TBafTree;
  AParent: TBafTreeNode; ACaption: string): TBafTreeNode;
begin
  if AParent = nil then
    AParent := AOwner.FRoot;
  result := AParent.AddNode;
  result.Text := ACaption;
end;

procedure TBafTreeNode.DbCancel;
var
  i: integer;
  sl: TStringList;
  LInserted: boolean;
begin
  if Assigned(Ini) then begin
    LInserted := Ini.ReadBool(SEC_ADD, 'ins', false);
    if LInserted then begin                            // Insert
      if Tree.Selected = Self then
        Tree.Selected := Tree.Selected.ParentNode;
      Delete;
      FTree.RefreshVisibleNodes;
      Self := nil;
    end
    else begin                                        // Update
      sl := TStringList.Create;
      try
        Ini.ReadSection(SEC_DB, sl);
        for i := 0 to sl.Count - 1 do
          Ini.WriteString(SEC_DATA, sl[i], Ini.ReadString(SEC_DB, sl[i], ''));
        Ini.WriteBool(SEC_ADD, 'ins', false);
      finally
        sl.Free;
      end;
    end;
  end;
  if Assigned(Self) then
    Text := GetCaption;
end;

procedure TBafTreeNode.Delete;
var
  LParent: TBafTreeNode;
begin
  LParent := ParentNode;
  LParent.FChilds.Remove(Self);
end;

destructor TBafTreeNode.Destroy;
begin
  FreeAndNil(FChilds);
  FreeAndNil(FIni);
  inherited;
end;

function TBafTreeNode.GetCaption: string;
var
  s1, s2, c1, c2, ld: string;
begin
  result := '';
  if Self = nil then
    exit;
  result := Text;
  c1 := Ini.ReadString(SEC_ADD, 'c1', 'krzl');
  c2 := Ini.ReadString(SEC_ADD, 'c2', 'bez');
  s1 := Ini.ReadString(SEC_DATA, c1, '');
  s2 := Ini.ReadString(SEC_DATA, c2, '');

  ld := FIni.ReadString(SEC_ADD, 'ld1', '');
  if (ld <> '') and Assigned(FOnLookup) then
    FOnLookup(false, ld, s1, s1);
  ld := FIni.ReadString(SEC_ADD, 'ls1', '');
  if (ld <> '') and Assigned(FOnLookup) then
    FOnLookup(true, ld, s1, s1);
  ld := FIni.ReadString(SEC_ADD, 'ld2', '');
  if (ld <> '') and Assigned(FOnLookup) then
    FOnLookup(false, ld, s2, s2);
  ld := FIni.ReadString(SEC_ADD, 'ls2', '');
  if (ld <> '') and Assigned(FOnLookup) then
    FOnLookup(true, ld, s2, s2);

  result := s1 + IfThen((s1 <> '') and (s2 <> ''), ' - ') + s2;
  if result = '' then
    result := Text;
  if (Pos('$', result) > 0) and Assigned(FOnFunction) then
    FOnFunction(result);
end;

function TBafTreeNode.GetChilds(AIndex: integer): TBafTreeNode;
begin
  result := (FChilds[AIndex] as TBafTreeNode);
end;

function TBafTreeNode.GetId: string;
begin
  result := Ini.ReadString(SEC_DATA, 'id', '');
end;

function TBafTreeNode.GetNodeOrParentValue(ASection, AName,
  ADefault: string): string;
const
  NOFOUND = '{08B3386B-B6FE-4600-A5A6-61251D610541}';
var
  LNode: TBafTreeNode;
begin
  LNode := Self;
  result := Ini.ReadString(ASection, AName, NOFOUND);
  while (result = NOFOUND) and Assigned(LNode) do begin
    LNode := LNode.ParentNode;
    if Assigned(LNode) then
      result := LNode.Ini.ReadString(ASection, AName, NOFOUND);
  end;
  if result = NOFOUND then
    result := ADefault;
end;

function TBafTreeNode.GetNodeValue(ASection, AName, ADefault: string): string;
const
  NOFOUND = '{08B3386B-B6FE-4600-A5A6-61251D610541}';
begin
  result := Ini.ReadString(ASection, AName, NOFOUND);
  if result = NOFOUND then
    result := ADefault;
end;

function TBafTreeNode.GetOpenCommand: string;
begin
  result := Ini.ReadString(SEC_ADD, 'open', '');
end;

function TBafTreeNode.GetSelectionCommand: string;
begin
  result := Ini.ReadString(SEC_ADD, 'sel', '');
end;

function TBafTreeNode.GetTableName: string;
begin
  result := FIni.ReadString(SEC_ADD, 'table', '');
end;

function TBafTreeNode.GetTreeFocus: boolean;
begin
  result := Ini.ReadBoolJN(SEC_ADD, 'treefocus', true);
end;

function TBafTreeNode.IsValue(AFieldName, AValue: string): boolean;
begin
  result := false;
  if AFieldName <> '' then
    result := AnsiCompareText(
        Ini.ReadString(SEC_DATA, AFieldName, ''), AValue) = 0;
end;

function TBafTreeNode.NodeIndex: integer;
begin
  result := -1;
  if Assigned(FParentNode) then
    result := FParentNode.FChilds.IndexOf(Self);
end;

procedure TBafTreeNode.OpenAllParents;
var
  LParent: TBafTreeNode;
begin
  LParent := ParentNode;
  while LParent <> nil do begin
    LParent.Opened := true;
    LParent := LParent.ParentNode;
  end;
  FTree.RefreshVisibleNodes;
end;

procedure TBafTreeNode.SetOpened(const Value: boolean);
begin
  FOpened := Value;
  FTree.DoOpenClose(Self);
  FTree.RefreshVisibleNodes;
end;

{ TBafTree }

procedure TBafTree.CaptionRefresh(ARoot: TBafTreeNode);
begin

end;

constructor TBafTree.Create(AOwner: TComponent);
begin
  inherited;
  CreateComponents;
  FVisibleNodes := TObjectList.Create(false);
  FRoot := TBafTreeNode.Create;
  FRoot.Tree := Self;
  FRoot.Level := -1;
  FRoot.Opened := true;
  LoadStyle;
  ClipChildren := true;
  CanFocus := true;
  FRowHeight := 18 ;
end;

procedure TBafTree.CreateComponents;
begin
  FVertScrollBar := TScrollBar.Create(Self);
  FVertScrollBar.Parent := Self;
  FVertScrollBar.Orientation := TOrientation.Vertical;
  FVertScrollBar.Align := TAlignLayout.Right;
  FVertScrollBar.Padding.Left := -2;
  FVertScrollBar.OnChange := ScrollChange;
  FPanelDesk := TPanel.Create(Self);
  FPanelDesk.Parent := Self;
  FPanelDesk.Align := TAlignLayout.Client;
  FPanelDesk.OnPaint := TreePaint;
  FPanelDesk.ClipChildren := true;
  FPanelDesk.OnMouseUp := PanelDeskMouseUp;
  FPanelDesk.OnMouseDown := PanelDeskMouseDown;
  FPanelDesk.OnMouseMove := PanelDeskMouseMove;
  FPanelDesk.StyleLookup := 'pushpanel2';
end;

destructor TBafTree.Destroy;
begin
  FRoot.ClearChilds;
  FreeAndNil(FVisibleNodes);
  inherited;
end;

procedure TBafTree.DialogKey(var Key: Word; Shift: TShiftState);
var
  LNode: TBafTreeNode;
begin
  inherited;
  if IsFocused and (Key > 0) then begin
    case Key of
//      vkTab: if ssShift in Shift then PriorCell(Key) else NextCell(Key);
      vkRight: if not Selected.Opened then
        Selected.Opened := true;
      vkLeft: if Selected.Opened then
        Selected.Opened := false;
      vkUp: if FindNode(Selected, fmVisiblePrior, LNode) then
        Selected := LNode;
      vkDown: if FindNode(Selected, fmVisibleNext, LNode) then
        Selected := LNode;
    end; // case
    Key := 0;
  end;
end;

procedure TBafTree.DoOpenClose(ANode: TBafTreeNode);
begin
  if Assigned(FOnOpenClose) then
    FOnOpenClose(Self, ANode);
end;

procedure TBafTree.DoSelect(ANode: TBafTreeNode);
begin
  if Assigned(FOnSelect) and Enabled then
    FOnSelect(Self, ANode);
end;

function TBafTree.FindNode(ARef: TBafTreeNode; AFind: TBafTreeFindMode;
  var AResult: TBafTreeNode): boolean;
var
  ix: integer;
begin
  result := false;
  AResult := nil;
  case AFind of
    fmSelf: AResult := ARef;
    fmPrior: begin
      if Assigned(ARef) and Assigned(ARef.ParentNode) then begin
        ix := ARef.ParentNode.FChilds.IndexOf(ARef);
        if ix > 0 then
          AResult := ARef.ParentNode.Childs[ix - 1];
      end;
    end;
    fmNext: begin
      if Assigned(ARef) and Assigned(ARef.ParentNode) then begin
        ix := ARef.ParentNode.FChilds.IndexOf(ARef);
        if ix < (ARef.ParentNode.FChilds.Count - 1) then
          AResult := ARef.ParentNode.Childs[ix + 1];
      end;
    end;
    fmVisiblePrior: begin
      if Assigned(ARef) then begin
        ix := FVisibleNodes.IndexOf(ARef);
        if ix > 0 then
          AResult := VisibleNodes[ix - 1];
      end;
    end;
    fmVisibleNext: begin
      if Assigned(ARef) then begin
        ix := FVisibleNodes.IndexOf(ARef);
        if ix < (VisibleNodesCount - 1) then
          AResult := VisibleNodes[ix + 1];
      end;
    end;



  end; // case
  result := Assigned(AResult);
// function TBafTree.FindNode
end;

function TBafTree.GetVisibleNodes(AIndex: integer): TBafTreeNode;
begin
  result := nil;
  if (AIndex >= 0) and (AIndex < FVisibleNodes.Count) then
    result := (FVisibleNodes[AIndex] as TBafTreeNode);
end;

procedure TBafTree.LoadStyle;
var
  LObject, LObject2: TFmxObject;
  LRectangle: TRectangle;
  i: integer;
  s: string;

  function lokFindObject(AParent: TFmxObject; AName: string): TFmxObject;
  var
    i: integer;
    LChild: TFmxObject;
  begin
    result := nil;
    for i := 0 to AParent.ChildrenCount - 1 do begin
      LChild := AParent.Children.Items[i];
      if AnsiCompareText(LChild.StyleName, AName) = 0 then begin
        result := LChild;
        exit;
      end
      else begin
        result := lokFindObject(LChild, AName);
        if Assigned(result) then
          exit;
      end;
    end;
  end;

  function lokFarbe(AName: string): TAlphaColor;
  begin
    LObject2 := lokFindObject(LObject, AName);
    if Assigned(LObject2) and (LObject2 is TRectangle) then begin
      LRectangle := TRectangle(LObject2);
      result := LRectangle.Fill.Color;
    end;
  end;

begin
  LObject := FMX.Types.FindStyleResource('bafcolors');
  if Assigned(LObject) and (LObject is TLayout) then begin
    FColorBack := lokFarbe('backgroundcolor');
    FColorSelection := lokFarbe('selection');
    FColorHeader := lokFarbe('headerbackground');
    FColorReadonly := lokFarbe('readonly');
    FColorFont := lokFarbe('text');
    FColorEdit := lokFarbe('edit');
  end;
//procedure TBafTree.LoadStyle
end;

procedure TBafTree.MouseWheel(Shift: TShiftState; WheelDelta: Integer;
  var Handled: Boolean);
begin
  inherited;
  Handled := false;
  if FVertScrollBar.Visible then begin
    if (WheelDelta < 0) and (FVertScrollBar.Value < FVertScrollBar.ValueRange.Max) then begin
      FVertScrollBar.Value := FVertScrollBar.Value + 1;
      Handled := true;
    end
    else if (WheelDelta > 0) and (FVertScrollBar.Value > FVertScrollBar.ValueRange.Min) then begin
      FVertScrollBar.Value := FVertScrollBar.Value - 1;
      Handled := true;
    end
  end;
end;

procedure TBafTree.PanelDeskMouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Single);
var
  LRow: integer;
  LNode: TBafTreeNode;
begin
  if CanFocus then
    SetFocus;
  LRow := trunc((Y / FRowHeight) + FVertScrollBar.Value);
  if (LRow >= 0) and (LRow < VisibleNodesCount) then begin
    LNode := VisibleNodes[LRow];
    if x < ((LNode.Level + 1) * FRowHeight) then
      LNode.Opened := not LNode.Opened
    else if Enabled then
      Selected := LNode;
  end;
end;

procedure TBafTree.PanelDeskMouseMove(Sender: TObject; Shift: TShiftState; X,
  Y: Single);
begin

end;

procedure TBafTree.PanelDeskMouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Single);
begin

end;

class function TBafTree.Place(AOwner: TComponent; AParent: TControl): TBafTree;
begin
  result := TBafTree.Create(AOwner);
  result.Parent := AParent;
  result.Align := TAlignLayout.Client;
  result.Margins.Left := 8;
  result.Margins.Top := 8;
  result.Margins.Right := 8;
  result.Margins.Bottom := 8;
end;

procedure TBafTree.RefreshVisibleNodes;
var
  c, t2, t1, t: int64;
  AIndex: integer;

  procedure lokAddNodes(ARoot: TBafTreeNode);
  var
    i: integer;
    LNode: TBafTreeNode;
  begin
    if ARoot.Opened then begin
      for i := 0 to ARoot.ChildCount - 1 do begin
        LNode := ARoot.Childs[i];
        FVisibleNodes.Add(LNode);
        LNode.VisibleIndex := AIndex;
        inc(AIndex);
        lokAddNodes(LNode);
      end;
    end;
  end; // procedure lokAddNodes

  procedure lokScrollBar;
  begin
    FVertScrollBar.ViewportSize := Height / FRowHeight;
    if FVisibleNodes.Count > FVertScrollBar.ViewportSize then begin
      FVertScrollBar.Visible := true;
      FVertScrollBar.Max := FVisibleNodes.Count;
      if FVertScrollBar.Value >= FVertScrollBar.Max then
        FVertScrollBar.Value := 0;
    end
    else begin
      FVertScrollBar.Value := 0;
      FVertScrollBar.Visible := false;
    end;
  end; // procedure lokScrollBar

begin
  if FVisibleNodes = nil then
    exit;
  QueryPerformanceFrequency(c);
  QueryPerformanceCounter(t1);
  FVisibleNodes.Clear;
  AIndex := 0;
  lokAddNodes(FRoot);
  lokScrollBar;
  QueryPerformanceCounter(t2);
  t := 1000000 *  (t2 - t1) div c;
  Repaint;
end;

procedure TBafTree.Resize;
begin
  inherited;

end;

procedure TBafTree.ScrollChange(Sender: TObject);
begin
  Repaint;
end;

procedure TBafTree.SelectIndex(AIndex: integer);
begin

end;

procedure TBafTree.SetScale(const Value: integer);
begin

end;

procedure TBafTree.SetSelected(const Value: TBafTreeNode);
begin
  FSelected := Value;
  if Assigned(FSelected) then begin
    DoSelect(Value);
    if (FRowHeight > 0) and (round(FVertScrollBar.Height / FRowHeight) < FVertScrollBar.Max) then begin
      if FSelected.VisibleIndex < (FVertScrollBar.Value + 1) then
        FVertScrollBar.Value := FSelected.VisibleIndex - 1;
      if (FSelected.VisibleIndex > (FVertScrollBar.Value + 0.8 * FVertScrollBar.ViewportSize)) then
        FVertScrollBar.Value := FSelected.VisibleIndex - 0.8 * FVertScrollBar.ViewportSize;
    end;
  end;
  Repaint;
end;

procedure TBafTree.SilentTreeSelect(ASelected: TBafTreeNode);
begin
  FSelected := ASelected;
end;

procedure TBafTree.TreePaint(Sender: TObject; Canvas: TCanvas; const ARect: TRectF);
var
  LRow: integer;
  LNode: TBafTreeNode;
  LRect: TRectF;
  t, t1, t2, c: int64;
  LOpa: single;

  procedure lokNode;
  begin
    if LNode = FSelected then begin
//      if IsFocused then
      if true then
        Canvas.Fill.Color := FColorSelection
      else
        Canvas.Fill.Color := FColorReadonly;
    end
    else
      Canvas.Fill.Color := FColorBack;
//    FCan.Font.Color := IFThen(Enabled, clBlack, clGray);

    LRect := RectF(0, LRow * FRowHeight, FPanelDesk.Width, (LRow + 1) * FRowHeight);
    LRect.Left := LNode.Level * FRowHeight;
    Canvas.FillRect(LRect, 4, 4, [TCorner.TopLeft, TCorner.TopRight,
        TCorner.BottomLeft, TCorner.BottomRight], 1);
    Canvas.Fill.Color := FColorFont;
    if (LNode.HasOpenCommand) or (LNode.ChildCount > 0) then begin
      if LNode.Opened then
        Canvas.FillPolygon([PointF(LRect.Left + 8.3, LRect.Top + 13.7),
            PointF(LRect.Left + 15, LRect.Top + 7),
            PointF(LRect.Left + 15, LRect.Top + 13.7)], LOpa)
      else
        Canvas.FillPolygon([PointF(LRect.Left + 9, LRect.Top + 6),
            PointF(LRect.Left + 9, LRect.Top + 14),
            PointF(LRect.Left + 15, LRect.Top + 10)], LOpa);
    end;
//    Canvas.Font.Family := '';
    LRect.Left := LRect.Left + FRowHeight;;
    Canvas.FillText(LRect, LNode.Text, false, LOpa, [], TTextAlign.Leading);
  end; // procedure lokNode

begin
  QueryPerformanceFrequency(c);
  QueryPerformanceCounter(t1);
  LOpa := IfThen(Enabled, 1, 0.5);

  Canvas.BeginScene;
  try
    LRect := ARect;
    LRect.Inflate(1, 1);
    Canvas.Fill.Color := FColorBack;
    Canvas.FillRect(LRect, 0, 0, [], 1);

    for LRow := 0 to round(FPanelDesk.Height / FRowHeight) do begin
      if (LRow + FVertScrollBar.Value) < VisibleNodesCount then begin
        LNode := VisibleNodes[round(LRow + FVertScrollBar.Value)];
        if Assigned(LNode) then
          lokNode;
      end;
    end;

  finally
    Canvas.EndScene;

  end;
  QueryPerformanceCounter(t2);
  t := 1000 * (t2 - t1) div c;
end;

function TBafTree.VisibleNodesCount: integer;
begin
  result := FVisibleNodes.Count;
end;


end.
