Search code examples
delphidelphi-xe8virtualtreeviewtvirtualstringtree

TVirtualDrawTree - How to place nodes in one row?


This is follow up to my other question Here.

as i have been suggested in comment to ask new question about this subject

i have been suggested to draw different images in Row . my goal from the beginning to insert nodes side by side i have been told that this cannot be done with VDT it does not made for this purpose . but what makes me sure that there is a way because i see an online project doing it using the same VDT

here is screen shot from this project

enter image description here

with using resource viewer like PE explorer i found this data of form

    object VDT: TVirtualDrawTree
      AlignWithMargins = True
      Left = 5
      Top = 5
      Width = 457
      Height = 227
      Margins.Left = 5
      Margins.Top = 5
      Margins.Right = 5
      Margins.Bottom = 5
      Align = alClient
      BevelInner = bvNone
      BevelOuter = bvNone
      DefaultNodeHeight = 55
      Header.AutoSizeIndex = 0
      Header.Font.Charset = DEFAULT_CHARSET
      Header.Font.Color = clWindowText
      Header.Font.Height = -11
      Header.Font.Name = 'Tahoma'
      Header.Font.Style = []
      HotCursor = crHandPoint
      TabOrder = 0
      TreeOptions.PaintOptions = [toHideFocusRect, toHideSelection, toHotTrack, toShowButtons, toShowDropmark, toThemeAware, toUseBlendedImages, toAlwaysHideSelection, toUseBlendedSelection]
      TreeOptions.SelectionOptions = [toExtendedFocus, toMiddleClickSelect, toRightClickSelect]
      OnBeforeCellPaint = VDTBeforeCellPaint
      OnGetNodeWidth = VDTGetNodeWidth
      OnMouseUp = VDTMouseUp
      ExplicitLeft = 3
      ExplicitTop = 3
      Columns = <
        item
          Position = 0
          Width = 54
          WideText = '55'
        end
        item
          Position = 1
          Width = 54
          WideText = '55'
        end
        item
          Position = 2
          Width = 54
          WideText = '55'
        end
        item
          Position = 3
          Width = 54
          WideText = '55'
        end
        item
          Position = 4
          Width = 54
          WideText = '55'
        end
        item
          Position = 5
          Width = 54
          WideText = '55'
        end
        item
          Position = 6
          Width = 54
          WideText = '55'
        end
        item
          Position = 7
          Width = 54
          WideText = '55'
        end>
    end
  end

so i told to my self that i have to use Tviruaildrawtree to get the same goal then i start to create data

type
  TAnmiClass = class
  private
    Fanmigraphic : TGifImage;

  public
    property anmigraphic: TGifImage read Fanmigraphic write Fanmigraphic;

  public
    constructor Create;
    destructor Destroy; override;
  end;

type
  PAnimeData = ^TAnimeData;

  TAnimeData = record
    FObject: TAnmiClass;
  end;

as i was thinking that i have to create image object to the node because i will be downloading some image list from url then add them to the node like following , so the following code is downloading images from stringlist to desk then load it to the node Tgifimage

For i := 0 To animationimages.Count-1 do
begin
Animaturl := animationimages.Strings[i];

URI := TIdURI.Create(Animaturl);
try
ImageName := URI.Document;
finally
FreeAndNil(URI);
end;

if (ExtractFileExt(ImageName) = '.gif') then
begin
addanimation(Animaturl);
end;
end;

procedure TForm2.addanimation(AAnimationUrl: String);
var
AnmiClass: TAnmiClass;
path: string;
begin

VDTAni.BeginUpdate;
try
AnmiClass := TAnmiClass.Create;

path := AAnimationUrl;

if fileexists(path) then
begin
AnmiClass.anmigraphic.LoadFromFile(path);
AnmiClass.anmigraphic.Animate := True;
AnmiClass.anmigraphic.Transparent := True;
end;

AddAnmiToVD(VDTAni, nil, AnmiClass);

finally
VDTAni.EndUpdate;
end;

and here how i draw the nodes inside the VDT

procedure TForm2.VDTAniBeforeCellPaint(Sender: TBaseVirtualTree;
  TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex;
  CellPaintMode: TVTCellPaintMode; CellRect: TRect; var ContentRect: TRect);
var
Data: PAnimeData;
NewRect: TRect;
R: TRect;
begin
//
if not Assigned(Node) then
begin
exit;
end;

Data := VDTAni.GetNodeData(Node);

case Column of

0 :
begin
NewRect := ContentRect;
NewRect.Left := NewRect.Left +2;
NewRect.Width := 55;
NewRect.Height := 55;
NewRect.Top := NewRect.Top + 2;
NewRect.Bottom := NewRect.Bottom;
TargetCanvas.StretchDraw( NewRect, Data.FObject.anmigraphic);
end;

end;
end;

but i cannot arrange the nodes same as the image that i show above

and its seems its not thing can be made in onbeforecellpanit .

in my other question Tom Brunberg suggested to divide the images into 10 nodes if the image added is 80 and the need was 8 per Row as example, each having 8 images and each image displayed in its own column. but i don't know how to do that in coding or from where to start .

Issue of Current Code

unit Unit1;

interface

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


type
  TImageOBJArr = array of TGifimage;


type
  TaniDataclass = class
  ImageOBJArr: TImageOBJArr;
  private
    FAnirefrence: String;
    FAniIMage: TGifimage;
  public
    property Anirefrence: String read FAnirefrence write FAnirefrence;
    property AniIMage: TGifImage read FAniIMage write FAniIMage;

  public
    constructor Create;
    destructor Destroy; override;
  end;

type
Panidata = ^Tanidata;

Tanidata = record
FObject: TaniDataclass;
end;






type
  TForm1 = class(TForm)
    VDTani: TVirtualStringTree;
    Button1: TButton;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure FormShow(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure VDTaniBeforeCellPaint(Sender: TBaseVirtualTree;
      TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex;
      CellPaintMode: TVTCellPaintMode; CellRect: TRect; var ContentRect: TRect);
    procedure FormCreate(Sender: TObject);
    procedure VDTaniFreeNode(Sender: TBaseVirtualTree; Node: PVirtualNode);
    procedure VDTaniGetNodeDataSize(Sender: TBaseVirtualTree;
      var NodeDataSize: Integer);
  private
    { Private declarations }
   ImageOBJArr: TImageOBJArr;  // Main storage of images
  public
    { Public declarations }
      Dimagelist : Tstringlist;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

{ TaniDataclass }

constructor TaniDataclass.Create;
begin
FAniIMage := TGifImage.Create;
end;

destructor TaniDataclass.Destroy;
begin
FAniIMage.Free;
  inherited;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  Node: PVirtualNode;
  Data: Panidata;
  i, row, col: integer;
  fn: String;
begin

  // Load images to main store ImgArr
SetLength(ImageOBJArr, Dimagelist.Count);

for i := 0 to Dimagelist.Count -1 do
begin
fn := Dimagelist[I];
ImageOBJArr[i] := TGifimage.Create;
ImageOBJArr[i].LoadFromFile(fn);
end;

  // Setup vdt nodes and assign images eight in a row
  // hardcoded for now. You may want to add dynamics
  // for varying window and image sizes
  row := 0;
  while row <= (Dimagelist.Count div 8) do
  begin
    Node := VDTani.AddChild(nil);
    Data := VDTani.GetNodeData(Node);
    SetLength(Data.FObject.ImageOBJArr, 8);
    for col := 0 to 7 do
      Data.FObject.ImageOBJArr[col] := ImageOBJArr[row * 8 + col];
    inc(row);
  end;

end;


procedure TForm1.FormCreate(Sender: TObject);
begin
Dimagelist :=  Tstringlist.Create;
VDtAni.NodeDataSize := SizeOf(Tanidata);
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
if Assigned(Dimagelist) then
begin
  FreeAndNil(Dimagelist);
end;
end;

procedure TForm1.FormShow(Sender: TObject);
begin

Dimagelist.Add('1mm.gif');
Dimagelist.Add('2mm.gif');
Dimagelist.Add('3mm.gif');
Dimagelist.Add('4mm.gif');
Dimagelist.Add('5mm.gif');
Dimagelist.Add('6mm.gif');


end;

procedure TForm1.VDTaniBeforeCellPaint(Sender: TBaseVirtualTree;
  TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex;
  CellPaintMode: TVTCellPaintMode; CellRect: TRect; var ContentRect: TRect);
var
  Data: Panidata;
begin
if not Assigned(Node) then
begin
exit;
end;

  Data := VDTani.GetNodeData(Node);
  Sender.NodeHeight[Node] := 54;
  CellRect.Height := 54;

  TargetCanvas.StretchDraw( CellRect, Data.FObject.ImageOBJArr[Column]);
end;

procedure TForm1.VDTaniFreeNode(Sender: TBaseVirtualTree; Node: PVirtualNode);
var
Data: Panidata;
begin
Data := VDTani.GetNodeData(Node);
if Assigned(Data) then
Data.FObject.Free;
end;
procedure TForm1.VDTaniGetNodeDataSize(Sender: TBaseVirtualTree;
  var NodeDataSize: Integer);
begin
NodeDataSize := SizeOf(Tanidata);
end;

end.

i got an exception here at the following code i added 6 images path to the string list then try to draw on each columns

row := 0;
  while row <= (Dimagelist.Count div 2) do
  begin
    Node := VDTani.AddChild(nil);
    Data := VDTani.GetNodeData(Node);
    SetLength(Data.FObject.ImageOBJArr, 2);
    for col := 0 to 7 do
      Data.FObject.ImageOBJArr[col] := ImageOBJArr[row * 2 + col];
    inc(row);
  end;

Solution

  • Here's an implementation of what I suggested.

    type
      TImgArr = array of TBitmap;
    
      TVdtData = record
        FObject: TimgArr;
      end;
      PVdtData = ^TVdtData;
    
      TForm2 = class(TForm)
        Vdt: TVirtualDrawTree;
        ...
      private
        { Private declarations }
        ImgArr: TImgArr;  // Main storage of images
    
    implementation
    
    procedure TForm2.Button1Click(Sender: TObject);
    var
      Node: PVirtualNode;
      Data: PVdtData;
      p: pointer;
      i, row, col: integer;
      fn: TFileName;
    begin
      // Load images to main store ImgArr
      SetLength(ImgArr, 100);
      for i := 0 to 99 do
      begin
        fn := Format('c:\tmp\nums\%.2d.bmp',[i]);
        ImgArr[i] := TBitmap.Create;
        ImgArr[i].LoadFromFile(fn);
      end;
    
      // Setup vdt nodes and assign images eight in a row
      // hardcoded for now. You may want to add dynamics
      // for varying window and image sizes
      row := 0;
      while row <= (100 div 8) do
      begin
        Node := Vdt.AddChild(nil);
        p := Node.GetData;
        Data := Vdt.GetNodeData(Node);
        // SetLength(Data.FObject, 8);
        SetLength(Data.FObject, Vdt.Header.Columns.Count);
        for col := 0 to 7 do
          Data.FObject[col] := ImgArr[row * 8 + col];
        inc(row);
      end;
    end;
    
    procedure TForm2.VdtBeforeCellPaint(Sender: TBaseVirtualTree;
      TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex;
      CellPaintMode: TVTCellPaintMode; CellRect: TRect; var ContentRect: TRect);
    var
      Data: PVdtData;
    begin
      if not Assigned(Node) then exit;
    
      Data := Vdt.GetNodeData(Node);
      Sender.NodeHeight[Node] := 64;
      CellRect.Height := 64;
    
      if Assigned(Data.FObject[Column]) then
        TargetCanvas.StretchDraw( CellRect, Data.FObject[Column]);
    end;
    

    No warranty of not carrying errors.

    And the result

    enter image description here

    But seriously, as others have pointed out, it would be much easier to just use a TDrawGrid or TStringGrid. Of course it's your call.