Recent

Author Topic: [SOLVED] Runtime-Error 210 (Object Reference nil) in Memo1.Font.Canvas.TextWidth  (Read 2010 times)

Hartmut

  • Hero Member
  • *****
  • Posts: 905
With the following program I get Runtime-Error 210 (Object Reference is nil) in line
   n:=Memo1.Font.Canvas.TextWidth('...');     
   
Code: Pascal  [Select][+][-]
  1. unit Unit1;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls;
  8.  
  9. type
  10.  TForm1 = class(TForm)
  11.   Button1: TButton;
  12.   Memo1: TMemo;
  13.   procedure Button1Click(Sender: TObject);
  14.  private
  15.  public
  16.  end;
  17.  
  18. var Form1: TForm1;
  19.  
  20. implementation
  21.  
  22. {$R *.lfm}
  23.  
  24. procedure TForm1.Button1Click(Sender: TObject);
  25.    var n: integer;
  26.    begin
  27.    Memo1.Lines.Add('Hello');
  28.    n:=Memo1.Font.Canvas.TextWidth('Get the width of this text');
  29.    Memo1.Lines.Add(IntToStr(n));
  30.    end;
  31.  
  32. end.        
   
I tried it with several Lazarus versions on different OS:
 - Windows 7 (32-bit):
   Laz 1.8.4/FPC 3.0.4 + Laz 2.0.6/FPC 3.0.4 + Laz 2.1.0 rev=62449 / FPC 3.3.1 rev=43796
 - Linux Ubuntu 18.04 (64-bit):
   Laz 1.8.4/FPC 3.0.4 + Laz 2.0.6/FPC 3.0.4
   
Because the explanation for Runtime-Error 210 says:
   Object not initialized. When compiled with range checking on, a program will report this
   error if you call a virtual method without having called its object’s constructor.

I disabled in Project Options / Debugging all 6 checks: -Ci -Cr -Co -Ct -CR -Sa, but this made no difference.

Then I remembered, that I had enabled those Debug checks in fpc.cfg and made a try with the original fpc.cfg (which I had saved), but this made no difference too.

Can somebody reproduce the problem? Do I something wrong? Thanks for your help. I attached my demo.
« Last Edit: June 10, 2020, 03:41:56 pm by Hartmut »

Handoko

  • Hero Member
  • *****
  • Posts: 5436
  • My goal: build my own game engine using Lazarus
It should be:

   n:=Memo1.Font.GetTextWidth('Get the width of this text');

Hartmut

  • Hero Member
  • *****
  • Posts: 905
Great, this works! Thanks a lot, Handoko.

But does anybody know, why Font.Canvas.TextWidth() causes this Runtime-Error? Is this a bug, which should be reported? Or is Font.Canvas.TextWidth() deprecated?

And should I generally replace all Font.Canvas.TextWidth() with Font.GetTextWidth()?

Hartmut

  • Hero Member
  • *****
  • Posts: 905
Hmmm, meanwhile I found out, that Font.GetTextWidth() causes no Runtime-Error, but the result is always "16", independently of the length of the text :-((

When looking into the source code, I found this in /.../fpcsrc/packages/fcl-image/src/fpfont.inc of Lazarus 2.0.6:

Code: Pascal  [Select][+][-]
  1. function TFPCustomFont.GetTextWidth (text:string) : integer;
  2. begin
  3.   if inheritsFrom (TFPCustomDrawFont) then
  4.     result := TFPCustomDrawFont(self).DoGetTextWidth (text)
  5.   else
  6.    if assigned(FCanvas) then
  7.      result := FCanvas.GetTextWidth (text)
  8.    else
  9.      result :=16; // *some* default better than none.
  10. end;

So unfortunately my problem is not solved yet...

lucamar

  • Hero Member
  • *****
  • Posts: 4219
But does anybody know, why Font.Canvas.TextWidth() causes this Runtime-Error? Is this a bug, which should be reported? Or is Font.Canvas.TextWidth() deprecated?

It's not a bug; that canvas comes from TFPCanvasHelper which should normally be dimmed as "for the internal use of the LCL". It's not a general property like, say, the form's canvas.

Quote
And should I generally replace all Font.Canvas.TextWidth() with Font.GetTextWidth()?

Yes, that's the proper way to do it.
Turbo Pascal 3 CP/M - Amstrad PCW 8256 (512 KB !!!) :P
Lazarus/FPC 2.0.8/3.0.4 & 2.0.12/3.2.0 - 32/64 bits on:
(K|L|X)Ubuntu 12..18, Windows XP, 7, 10 and various DOSes.

rvk

  • Hero Member
  • *****
  • Posts: 6762
When looking into the source code, I found this in /.../fpcsrc/packages/fcl-image/src/fpfont.inc of Lazarus 2.0.6:
And there you have exactly the reason why you've got am exception on Memo1.Font.Canvas.GetTextWidth().
(because Canvas is not assigned for Font)

I'm also not sure why a Font would need a Canvas?
Normally you would put a font on a Canvas. Not a Canvas on a Font.

The problem here is that TMemo doesn't have a Canvas.
You need to assign TMemo.Font to something else and calculate the Width there.

Something like:

Code: Pascal  [Select][+][-]
  1. var
  2.   F: TForm;
  3.   N: Integer;
  4. begin
  5.   F := TForm.Create(nil);
  6.   try
  7.     F.Font.Assign(Memo1.Font);
  8.     N := F.Canvas.TextWidth('Get the width of this text');
  9.     Showmessage(N.ToString);
  10.   finally
  11.     F.Free;
  12.   end;
  13. end;

(Not sure if there are easier methods)

Quote
And should I generally replace all Font.Canvas.TextWidth() with Font.GetTextWidth()?
Yes, that's the proper way to do it.
No, that won't work either. Look at the given code by poster. When FCanvas is not assigned, 16 is always returned.
You'll need to assign Font to another object to do the calculations.

Hartmut

  • Hero Member
  • *****
  • Posts: 905
Thanks a lot to lucamar and rvk for your explainations and suggestions.

I'm a little disappointed that GUI-Controls like TMemo come with 2 TextWidth() funktions like
 - Memo1.Font.Canvas.TextWidth() and   
 - Memo1.Font.GetTextWidth()
but one causes a Runtime-Error and the other returns a wrong result.
From my opinion, either they should work correctly or should not exist...

I will use as solution the workaround from wp in reply #4 of https://forum.lazarus.freepascal.org/index.php?topic=36579.0 (which is related to the idea of rvk):

Code: Pascal  [Select][+][-]
  1. function myTextWidth(AText: String; AFont: TFont): Integer;
  2. var
  3.   bmp: TBitmap;
  4. begin
  5.   Result := 0;
  6.   bmp := TBitmap.Create;
  7.   try
  8.     bmp.Canvas.Font.Assign(AFont);
  9.     Result := bmp.Canvas.TextWidth(AText);
  10.   finally
  11.     bmp.Free;
  12.   end;
  13. end;

rvk

  • Hero Member
  • *****
  • Posts: 6762
I'm a little disappointed that GUI-Controls like TMemo come with 2 TextWidth() funktions like
 - Memo1.Font.Canvas.TextWidth() and   
 - Memo1.Font.GetTextWidth()
but one causes a Runtime-Error and the other returns a wrong result.
From my opinion, either they should work correctly or should not exist...
The reason for this is that IF you access Font for a TLabel of TForm, the Canvas does exists. And in that case Font.Canvas.TextWidth() and Font.GetTextWidth() DO work. So the function does have meaning for lots of components.

But because TMemo just doesn't have a Canvas (blame Microsoft), the TextWidth() just doesn't work (it need a Canvas for that).

Quote
I will use as solution the workaround from wp in reply #4 of https://forum.lazarus.freepascal.org/index.php?topic=36579.0 (which is related to the idea of rvk):
Yep. That one will work too.

PascalDragon

  • Hero Member
  • *****
  • Posts: 6004
  • Compiler Developer
I'm a little disappointed that GUI-Controls like TMemo come with 2 TextWidth() funktions like
 - Memo1.Font.Canvas.TextWidth() and   
 - Memo1.Font.GetTextWidth()
but one causes a Runtime-Error and the other returns a wrong result.
From my opinion, either they should work correctly or should not exist...

It's not the fault TMemo. Any other control with a Font property of type TFont will provide these two methods as well (though one is provided indirectly through the font's Canvas property). And TFont.GetTextWidth is a convenience method for accessing the TextWidth method of the font's canvas. Ideally you wouldn't have to deal with TFont.Canvas.

Also the code of TFont.GetTextWidth you showed demonstrates the problem as rvk stated: Memo1.Font.Canvas is Nil, thus of course you'll get an exception when accessing it and of course you'll get the dummy result, because there is no canvas to calculate the width with. But that would be the case for any control that does not have a canvas (only those that descend from TCustomControl or TGraphicControl have a canvas).

Hartmut

  • Hero Member
  • *****
  • Posts: 905
Thanks a lot to rvk and PascalDragon for your additional informations. It helps me to understand how things work under the hood and to learn.

@PascalDragon: do you have the open answer to reply #11 in https://forum.lazarus.freepascal.org/index.php/topic,49670.0.html still on your list? I would be very happy if you could find the time to check this question definitely :-)

 

TinyPortal © 2005-2018