3

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;
Ian Boyd
  • 246,734
  • 253
  • 869
  • 1,219
Vlark.Lopin
  • 762
  • 10
  • 21
  • I have to ask why on earth you would want to use TVirtualDrawTree when there are so many other classes much better suited to this type of layout, including a simple TGrid? – Dsm Aug 08 '16 at 12:31
  • i am interesting to do the same approach that this project image i posted do . i know there is many other component can store images much easier but VDt have ability of record adding values to the node and more – Vlark.Lopin Aug 08 '16 at 12:35
  • Sorry - I don't understand. If you add 8 images to a node (which you seem to suggest) what do you gain? If you don't you are stuck with the tree fomat. TStringGrid, for instance can be drawn just like a TDrawGrid, and has an objects property that allows you to store extra data that can be associated with each image. – Dsm Aug 08 '16 at 13:28
  • You could also look at TGridPanel - very easy to store rows of images and other components in a grid. – Michael Vincent Aug 08 '16 at 14:02
  • What is this? The same code as in your previous example. You have not bothered the slightest to do what I suggested. I also have a feeling that you think I am wrong in what I told you. Shame on you! Anyway, I did test my suggestion and will post it shortly. – Tom Brunberg Aug 08 '16 at 14:06
  • @TomBrunberg That Part from the beginning `TImgArr = array of TBitmap;` did not even imagine Thing like that and did not understand .i asked you in comment but you told me to create new question about it . the whole question was to follow up your suggestion so you wasn't wrong your answer will definitely solve this question . – Vlark.Lopin Aug 08 '16 at 14:18

1 Answers1

3

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.

Tom Brunberg
  • 20,312
  • 8
  • 37
  • 54
  • That part of mystery . – Vlark.Lopin Aug 08 '16 at 14:18
  • Hi again this code works perfectly but i stuck with some nil pointer issue . when i manually draw images and they are up to 100 they normally got drawing . but if i load gifs path from string list i got nil pointer on set length i added the update project code to the quistion or you suggest to create new question ? – Vlark.Lopin Aug 09 '16 at 11:02
  • @Vlark.Lopin You call `SetLength(Data.FObject.ImageOBJArr, 2);` Note length is 2! Then you call the for loop `for col := 0 to 7 do` and use `col` to index the array which has only two positions. – Tom Brunberg Aug 09 '16 at 12:26
  • i changed the loop to 1 still throwing exception – Vlark.Lopin Aug 09 '16 at 12:50
  • @Vlark.Lopin What if you remove the `=` from `while row <= (Dimagelist.Count div 2) do`? Do you know how to step through code line by line and to look at values of variables hovering the mouse. Have you understood what the code is doing, and what variable values to expect? – Tom Brunberg Aug 09 '16 at 13:14
  • @Vlark.Lopin I made two corrections to the code. 1) The number of elements in the `Data.FObjects` array **must be** the same as columns in the Vdt, therefore `SetLength(Data.FObject, Vdt.Header.Columns.Count);`. You may still use less of those elements, e.g only 2 if you want to show images in only two leftmost columns . 2) In `OnBeforeCellPaint` added a check `if Assigned(Data.FObject[Column]) then` just above `StretchDraw` call. – Tom Brunberg Aug 09 '16 at 14:19