Recent

Author Topic: invalid cast  (Read 4164 times)

rvk

  • Hero Member
  • *****
  • Posts: 6056
Re: invalid cast
« Reply #30 on: December 03, 2020, 02:17:48 pm »
You shouldn't use AddChild because that one uses CreateNode to create a TTreeNode.
(or you still need the DoCreateNodeClass.

Furthermore... you don't even need to WhoIam.

Look at this example.
No WhoIam and it uses ClassType to determine the node-type.
So there is also no need for the Create in the child-node-classes.

Code: Pascal  [Select][+][-]
  1. unit Unit1;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, SysUtils, Forms, Controls, Graphics, Dialogs, ComCtrls, StdCtrls;
  9. type
  10.  
  11.   { TMyNode }
  12.   TMyNode = class(TTreeNode)
  13.   public
  14.   end;
  15.  
  16.   { TTreeNodeInt }
  17.  
  18.   TTreeNodeInt = class(TMyNode)
  19.   public
  20.     ValueI: integer;
  21.   end;
  22.  
  23.   { TTreeNodeDbl }
  24.  
  25.   TTreeNodeDbl = class(TMyNode)
  26.   public
  27.     ValueD: double;
  28.   end;
  29.  
  30.   { TTreeNodeBool }
  31.  
  32.   TTreeNodeBool = class(TMyNode)
  33.   public
  34.     ValueB: boolean;
  35.   end;
  36.  
  37.   { TMyTreeView }
  38.   TMyTreeView = class(TTreeView)
  39.   public
  40.     procedure CreateMyBool(AFatherNode : TTreeNode; ABoolValue: boolean);
  41.     procedure CreateMyInt(AFatherNode : TTreeNode; AIntValue: integer);
  42.     procedure CreateMyDbl(AFatherNode : TTreeNode; ADblValue: double);
  43.     procedure Click; override;
  44.   end;
  45.  
  46.   { TForm1 }
  47.  
  48.   TForm1 = class(TForm)
  49.     Button1: TButton;
  50.     procedure Button1Click(Sender: TObject);
  51.     procedure FormCreate(Sender: TObject);
  52.   private
  53.  
  54.   public
  55.     TV: TMyTreeView;
  56.   end;
  57.  
  58. var
  59.   Form1: TForm1;
  60.  
  61. implementation
  62.  
  63. {$R *.lfm}
  64.  
  65. { TMyTreeView }
  66.  
  67. procedure TMyTreeView.CreateMyBool(AFatherNode : TTreeNode; ABoolValue: boolean);
  68. var
  69.   TmpNewMyNode : TTreeNodeBool;
  70. begin
  71.   TmpNewMyNode:=TTreeNodeBool.Create(Items);
  72.   TmpNewMyNode.ValueB:=ABoolValue;
  73.   Items.AddNode(TmpNewMyNode, nil, BoolToStr(TmpNewMyNode.ValueB), AFatherNode, naAdd);
  74. end;
  75.  
  76. procedure TMyTreeView.CreateMyInt(AFatherNode : TTreeNode; AIntValue: integer);
  77. var
  78.   TmpNewMyNode : TTreeNodeInt;
  79. begin
  80.   TmpNewMyNode:=TTreeNodeInt.Create(Items);
  81.   TmpNewMyNode.ValueI:=AIntValue;
  82.   Items.AddNode(TmpNewMyNode, nil, IntToStr(TmpNewMyNode.ValueI), AFatherNode, naAdd);
  83. end;
  84.  
  85. procedure TMyTreeView.CreateMyDbl(AFatherNode : TTreeNode; ADblValue: double);
  86. var
  87.   TmpNewMyNode : TTreeNodeDbl;
  88. begin
  89.   TmpNewMyNode:=TTreeNodeDbl.Create(Items);
  90.   TmpNewMyNode.ValueD:=ADblValue;
  91.   Items.AddNode(TmpNewMyNode, nil, FloatToStr(TmpNewMyNode.ValueD), AFatherNode, naAdd);
  92. end;
  93.  
  94. procedure TMyTreeView.Click;
  95. var
  96.   m: String;
  97. begin
  98.   if Selected = nil then exit;
  99.   m := '';
  100.   Showmessage(Selected.ClassName);
  101.   if Selected is TTreeNodeInt then m := 'I am an Integer = ' + TTreeNodeInt(Selected).ValueI.ToString;
  102.   if Selected is TTreeNodeDbl then m:= 'I am a Double = ' + TTreeNodeDbl(Selected).ValueD.ToString;
  103.   if Selected is TTreeNodeBool then m := 'I am a Boolean = ' + BoolToStr(TTreeNodeBool(Selected).ValueB);
  104.   if m <> '' then Showmessage(m);
  105. end;
  106.  
  107. { TForm1 }
  108.  
  109. procedure TForm1.FormCreate(Sender: TObject);
  110. begin
  111.   TV := TMyTreeView.Create(Self);
  112.   TV.Parent := Self;
  113.   TV.Top := 10;
  114.   TV.Left := 10;
  115.   TV.Height := 400;
  116.   TV.Width := 300;
  117. end;
  118.  
  119. procedure TForm1.Button1Click(Sender: TObject);
  120. begin
  121.   TV.CreateMyBool(nil, true);
  122.   TV.CreateMyInt(nil, 1);
  123.   TV.CreateMyDbl(nil, 2.1);
  124. end;
  125.  
  126. end.


Paolo

  • Sr. Member
  • ****
  • Posts: 499
Re: invalid cast
« Reply #31 on: December 03, 2020, 03:16:47 pm »
many thanks rvk.

Addnode seems what I am looking for... in any case this morning I was too frenetic ! I'll study in deep the code.

One thing let me some doubt:
as side effect of this topic was the fact that the compiler complains on this statement in my original code
Code: Pascal  [Select][+][-]
  1. ANode:=TTreeNodeInt(TrvMain.Items.AddChild(AFatherNode, ANodeName))  //Invalid type cast
  2.  
because ANode was TTreeNodeInt and AddChild gives TTreeNode (only in debug mode, run-time error)

but now the code below compiles, is not the same situation ? TmpNewMyNode is TTreeNodeInt and AddNode gives TTreeNode, no ?
Code: Pascal  [Select][+][-]
  1. var
  2.   TmpNewMyNode : TTreeNodeInt;
  3. begin
  4.    ...
  5.    ...
  6.   TmpNewMyNode :=TTreeNodeInt(Items.AddNode(TmpNewMyNode, nil, FloatToStr(TmpNewMyNode.ValueI), AFatherNode, naAdd));
  7. end;
  8.  

or the compiler, as said PascalDragon, puts an "as" in the compiled version and this explain the things, in the first case bad typecsat in the second one the create object is really a TTreeNodeInt ?

thanks again.
« Last Edit: December 03, 2020, 03:27:00 pm by Paolo »

rvk

  • Hero Member
  • *****
  • Posts: 6056
Re: invalid cast
« Reply #32 on: December 03, 2020, 03:44:43 pm »
Was it a compile error or did you get a runtime error???

Although AddNode returns a TTreeNode as classtype, THAT variable can hold a descendant of TTreeNode (i.e. TMyTreeNodeInt etc). That's probably why there is no complaint with AddNode (because it returns such child class).

AddChild really returns a TTreeNode (and nothing else).
Casting a TTreeNode to one of its descendants is really dangerous because the memory contains a TTreeNode and you are going to access fields which are not on that memoryprint (TMuTreeNodeInt.ValueI). So you would be accessing inaccessible memory. That's why you shouldn't (and can't?) cast down when the variable contains a parent class.


Paolo

  • Sr. Member
  • ****
  • Posts: 499
Re: invalid cast
« Reply #33 on: December 03, 2020, 04:05:22 pm »
only in "DEBUG" and run-time (EInvalidCast) for the line 3

Code: Pascal  [Select][+][-]
  1. ANode : TTreeNodeInt;
  2. ...
  3. ANode:=TTreeNodeInt(TrvMain.Items.AddChild(AFatherNode, ANodeName))
  4.  

and just to understand, how can I know that AddNode hold also descendant and AddChild no (as I suspected, but just after the discussion we did here) ? I mean just looking to the definition both are equal in ouptut type.

Note that in my typecasting I am 100% sure that the object is of the intended class.

thank you very much.

rvk

  • Hero Member
  • *****
  • Posts: 6056
Re: invalid cast
« Reply #34 on: December 03, 2020, 04:13:43 pm »
and just to understand, how can I know that AddNode hold also descendant and AddChild no (as I suspected, but just after the discussion we did here) ? I mean just looking to the definition both are equal in ouptut type.

Note that in my typecasting I am 100% sure that the object is of the intended class.
You should first check the instance if you are save to typecase.

But again... AddChild ALWAYS returns a TTreeNode (unless overridden in DoCreateNodeClass).

So you could do something like this:

Code: Pascal  [Select][+][-]
  1. var
  2.   ANode : TTreeNode;
  3.   MyNode: TTreeNodeInt;
  4. ...
  5.   ANode := TrvMain.Items.AddChild(AFatherNode, ANodeName);
  6.   if ANode is TTreeNodeInt then
  7.     MyNode:=TTreeNodeInt(ANode)

I also gave you the example for the click. Selected is TTreeNode variable but can hold descendants.
You can check that.
Code: Pascal  [Select][+][-]
  1. if Selected is TTreeNodeInt then m := 'I am an Integer = ' + TTreeNodeInt(Selected).ValueI.ToString;
  2. if Selected is TTreeNodeDbl then m:= 'I am a Double = ' + TTreeNodeDbl(Selected).ValueD.ToString;
  3. if Selected is TTreeNodeBool then m := 'I am a Boolean = ' + BoolToStr(TTreeNodeBool(Selected).ValueB);

Sieben

  • Sr. Member
  • ****
  • Posts: 310
Re: invalid cast
« Reply #35 on: December 03, 2020, 04:19:41 pm »
Quote from: rvk
That's why you shouldn't (and can't?) cast down when the variable contains a parent class.

You can, if the contents of the variable (of type TTreeNode here) really IS of type TTreeNodeInt for example. If it really IS TTreeNodeInt it will also hold it's additional fields and values in it's real memory print. But you should always test for it:

Code: Pascal  [Select][+][-]
  1. var ANode: TTreeNode;
  2. ...
  3. if (ANode is TTreeNodeInt) then
  4.   TTreeNodeInt(ANode).ValueI := 7;
  5.  
 

The cast will never be executed if the memory behind ANode is not that of a TTreeNodeInt. And the 'is'-test is in so far also superior to an 'as'-cast as it will never throw a runtime exception. This is also true for functions that are supposed to return just a TTreeNode but in fact return a TTreeNodeInt.

This is not meant to be a correction, just a clarification. Thus posted nevertheless...
Lazarus 2.2.0, FPC 3.2.2, .deb install on Ubuntu Xenial 32 / Gtk2 / Unity7

Paolo

  • Sr. Member
  • ****
  • Posts: 499
Re: invalid cast
« Reply #36 on: December 03, 2020, 05:35:43 pm »
Thnks both, that is clear.

What is happend here is that I have written in Deplhi such code some years ago and I didn't have any problem, then when converted to Lazarus it works fine too, until I switched on "DEBUG" mode, so even in Lazarus (default and release) the code below works fine

Code: Pascal  [Select][+][-]
  1. var
  2. ANode: TTreeNode; //inherited from TTreeNode
  3. ..
  4. ANode:=TTreeNodeInt(TrvMain.Items.AddChild(AFatherNode, ANodeName))
  5.  

even if I now realize that is very dangerous usage ! (My TTreeNodeInt has not any field is exactly as TTreeNode, it just do something on the DATA field of TTreeNode, it dosen't hold nothing else, neither the intger values as in the examples here given).

thanks for the hints given.

 

TinyPortal © 2005-2018