Recent

Author Topic: Create failed for form containing a TChart  (Read 8080 times)

mtanner

  • Sr. Member
  • ****
  • Posts: 287
Create failed for form containing a TChart
« on: November 09, 2017, 04:07:10 pm »
I have been having a problem where TabcForm.Create(Main) fails, and it seems to occur only when the form contains a TAChart. Some form with TCharts do create ok, but I cannot see what is different between the TCharts of the forms that fail/work.

howardpc

  • Hero Member
  • *****
  • Posts: 4144
Re: Create failed for form containing a TChart
« Reply #1 on: November 09, 2017, 05:00:26 pm »
You need to share a (simple) compilable project that shows the problem.

Noodly

  • Jr. Member
  • **
  • Posts: 70
Re: Create failed for form containing a TChart
« Reply #2 on: November 09, 2017, 05:13:38 pm »
Probably unrelated, but worth a look. Have you read this bug report and the workaround for it in the TaChart Legend demo;

https://bugs.freepascal.org/view.php?id=19632

Code: [Select]
procedure TForm1.FormCreate(Sender: TObject);

  procedure PrepareData;
  const
    XMIN = -10;
    XMAX = +10;
    N = 30;
  var
    i: Integer;
    x, y: Double;
    ynorm: Double;
    c: TColor;
  begin
    // Create some data and store in series' internal listsource
    for i:=0 to N-1 do begin
      x := XMIN + (XMAX - XMIN) * i / (N-1) + (random - 0.5) * 0.5;
      y := exp(-0.2*sqr(x)) + (random-0.5) * 0.1;
      GradientLineSeries.AddXY(x, y);
    end;
    // Here we define the value range mapped to the gradient
    FGradientMinValue := GradientLineSeries.ListSource.Extent.a.y;
    FGradientMaxValue := GradientLineSeries.ListSource.Extent.b.y;
    // Colorize the data points
    for i:=0 to N-1 do begin
      y := GradientLineSeries.ListSource.Item[i]^.Y;
      ynorm := (y - FGradientMinValue) / (FGradientMaxValue - FGradientMinValue);
      c := InterpolateRGB(START_COLOR, END_COLOR, ynorm);
      GradientLineSeries.ListSource.Item[i]^.Color := c;
    end;
  end;

var
  li: TLegendItem;
begin
  // Workaround for issue #19632
  Chart1FuncSeries1.Legend.OnCreate := @Chart1FuncSeries1LegendCreate;
  FItems := Chart1.GetLegendItems;
  Chart1.Legend.SortItemsByOrder(FItems);
  for li in FItems do
    cbSeries.AddItem('', nil);

  // Prepare data for chart with owner-drawn legend
  PrepareData;
end;                                               
Windows 10 Home, Lazarus 2.02 (svn 60954), FPC 3.04

mtanner

  • Sr. Member
  • ****
  • Posts: 287
Re: Create failed for form containing a TChart
« Reply #3 on: November 09, 2017, 05:27:04 pm »
To HowardPC.

I have been trying to build a simple example. Trouble is I cannot reproduce the work/fail behaviour in an example - ( it always works) (at least yet) only in my very large real apps. I'll keep woring onit, biut when I discover what casuses work/fail I will be welel on the way to at least a work-around. What I'm hoping fpor is that someone has some ideas that will narrow my search.

engkin

  • Hero Member
  • *****
  • Posts: 3112
Re: Create failed for form containing a TChart
« Reply #4 on: November 09, 2017, 07:44:19 pm »
Did you compare the TChart part in the LFM files of both working and non-working forms?

wp

  • Hero Member
  • *****
  • Posts: 11906
Re: Create failed for form containing a TChart
« Reply #5 on: November 09, 2017, 09:59:52 pm »
I have been having a problem where TabcForm.Create(Main) fails, and it seems to occur only when the form contains a TAChart.
What is TabcForm, what is Main? And before I check too many combinations: what is your Lazarus/fpc version? Which operating system? Which bitness of OS and Lazarus/fpc?

And what exactly is the "problem"?

Some time ago we had the problem that charts crashed if the unit TATools was not on the uses list because this unit initializes a function pointer needed by the chart for built-in zooming/panning. This has been fixed in trunk, I don't know if it has been backported to version 1.8RC. Could you make sure that the unit TATools is listed in the uses clause of the unit in which the Chart resides? If not, just add the unit and check if the crash persists.
« Last Edit: November 09, 2017, 10:31:36 pm by wp »

mtanner

  • Sr. Member
  • ****
  • Posts: 287
Re: Create failed for form containing a TChart
« Reply #6 on: November 09, 2017, 11:14:22 pm »
To WP.

TabcForm is the class name for the form containing the chart. Main is the name of the main form of the app.

I just did a quick test, adding TATools to the interface uses list for the unit defining the form that contains the chart. Not completely successful, but an improvement. Without TATools the program just hangs in the TabcForm.Create(Main) call or gives an error message without displaying the form at all. With TATools the form appears, and it looks as if the chart has been partly built, but then I get an "access errror" message.

I'll take a closer look tomorrow and report more detail.
« Last Edit: November 09, 2017, 11:16:55 pm by mtanner »

wp

  • Hero Member
  • *****
  • Posts: 11906
Re: Create failed for form containing a TChart
« Reply #7 on: November 09, 2017, 11:30:13 pm »
And which versions do you use?

mtanner

  • Sr. Member
  • ****
  • Posts: 287
Re: Create failed for form containing a TChart
« Reply #8 on: November 10, 2017, 03:19:18 pm »
I am beginning to think this problem is about strings.

The unit TAChartAxisUtils has H+, so that in the declaration of TChartAxisMarkToTextEvent
         String means AnsiString

If I have a form unit with {$mode objfpc) and no H command then
         String means shortstring
and an OnMarkToText procedure declaration generated by the form designer will then be declared effectively with a shortstring parameter in which the user modified text will be returned. So presumably the TChart code will take this shortstring returned value and try and use it, effectively typecast it, into an ansistring. So an access error is unsurprising.

If this analysis is correct then the problem is not one of creating the form, it is becaus I have changed the mode statement. The lesson is, I suppose, allways use $H+ at the top of form units, and be aware that TChart parameters should be assumed to be ansostrings.

(I have allways used shortstrings in my apps. They are sufficient for my purposes, and don't give heap reference difficulties. Seems I no longer have that luxury of string simplicity.)

wp

  • Hero Member
  • *****
  • Posts: 11906
Re: Create failed for form containing a TChart
« Reply #9 on: November 10, 2017, 04:16:31 pm »
If I have a form unit with {$mode objfpc) and no H command then
         String means shortstring
Not necessarily. There's a checkbox in the project options in which you can make the {$H+} the default.

(I have allways used shortstrings in my apps. They are sufficient for my purposes, and don't give heap reference difficulties. Seems I no longer have that luxury of string simplicity.)
A bad idea (strings limited to 255 chars, predefined string length, no UTF8, probably lots of encoding conversion issues). Never had any "heap reference difficulties" - what do you mean?

If this analysis is correct
Simply turn on the {$H+} option in the project options (page "Compiler options" > "Parsing" > "Use Ansistrings"), rebuild your project, and you'll see if it solves your issues.

mtanner

  • Sr. Member
  • ****
  • Posts: 287
Re: Create failed for form containing a TChart
« Reply #10 on: November 10, 2017, 05:10:37 pm »
The disadvantages of shortstrings that you mention are not a problem in the kind of apps that I write.

An advantage of shortstrings is that when part of record you know the length, and that the field is contiguous with other fields of the record, not a pointer to something on the heap. So if I want to pass a pointer to a record containing strings to a procedure possibly in a dll, then the dll procedure does not have to access the callers heap to get the string data. I appreciate that shortstrings are a bit limited, but they are also simpler and straightforward. 

I am told by some associates, not my first-hand knowledge,  that this can get especially complicated when you write aDLL in Lazarus to be used by a NET language. because of "data marshalling".

Useful to know about the global H+ setting.

mtanner

  • Sr. Member
  • ****
  • Posts: 287
Re: Create failed for form containing a TChart
« Reply #11 on: November 10, 2017, 05:14:21 pm »
If, for some strange reason or carelessness, I have H+ set globally, I would assume that H- in a particular module would override the global setting for that module?

mtanner

  • Sr. Member
  • ****
  • Posts: 287
Re: Create failed for form containing a TChart
« Reply #12 on: November 10, 2017, 05:17:46 pm »
The "use ansistrings" option is  checked already.

wp

  • Hero Member
  • *****
  • Posts: 11906
Re: Create failed for form containing a TChart
« Reply #13 on: November 10, 2017, 06:29:28 pm »
I think my statements on the global {$H+} are not correct.
  • Create a new LCL project; by default, it has {$H+}. In the header of the unit there's the directive {$mode objfpc}{$H+}
  • Add a label and this code for the OnCreate event of the form:
Code: Pascal  [Select][+][-]
  1. procedure TForm1.FormCreate(Sender: TObject);
  2. var
  3.   s: String;
  4. begin
  5.   s := 'Hello world';
  6.   Label1.Caption := IntToStr(SizeOf(s));
  7. end;
  • By default, the label displays the value 4 on my system - this is the size of a pointer on a 32-bit program. As expected because we have H+.
  • Then remove the {$H+}. The label displays 256, the default size of a shortstring. The 256 tells that we effectively  have H- (this is the default if mode objfpc is used). If my statement would have been correct, I'd have expected to see 4 again!
  • Remove also the {$mode objfpc}. Now I see 4 again! This means: the default value of "use ansistrings" is used only if there is no mode directive.
« Last Edit: November 10, 2017, 06:32:14 pm by wp »

mtanner

  • Sr. Member
  • ****
  • Posts: 287
Re: Create failed for form containing a TChart
« Reply #14 on: November 13, 2017, 09:47:28 am »
I shall summarise the problem and solution. (I would suggest the moderator deletes all the intermediate comments).

I had a problem with creation of forms failing. The TMyForm.Create(Owner) would crash, with no traceable info or other clues as to why.  The create failed on only some forms in my app, and not at all in another similar app.

My apps use a table of data structures, with each data structure having an associated form that can be created/hidden/deleted by the user, together with a TTreeView having a node for each data structure, and showing a hierarchical relationship between the data structures. Selecting a node in the TTreeView will automatically create the corresponding form if not already in existence and show it. So I wasted a lot of time checking for problems such as referencing data structures or tree nodes that did not actually exist, but found no such bugs. Also spent time re-definng forms to ensure the LFM files were ok.

Eventually I realised that all forms that failed to create contained a TAChart, though some forms with a TAChart did create ok. (I am sure most of you will empathise with the huge relief on finally finding a solid and consistent lead on a bug!). Further systematic trial-and-error showed that failing forms had an OnMarkToText method, successful forms did not. I tried deleting the OnMarkToText method reference in the form designer, then adding it by code in the FormCreate method for the form.  This revealed the true source of the problem.

I had been "tidying up" my code, and that included removing all $mode and $H compiler commands, my thinking being just to go with Lazarus defaults. It is important to note that the TAGraph modules are themselves Pascal code compiled with Lazarus, and subject to $mode and $H commands. TAGraph uses {$H+}, and also specifies a parameter (for returning the text to be displayed) to OnMarkToText as "string". With H+ this means the parameter is treated as ansistring. My form code, without $mode or $H+ seems to treat "string" as shortstring, despite the global compiler option being  to default to H+. The compiler does not detect a type erro in these circumstances, presumably because both specifications are "string".

On making sure all my form units (and all the others too for consistency) have {$mode objfpc}($H+} the problem goes away.

TATools was mentioned, and adding that to the form units did seem to allow the creation to get further, such that the form was displayed, and the graph partly displayed, before getting an access error. So I am leaving TATools in the uses statements, though I do not understand why that makes a difference.

I would tentatively suggest that it could be better for the TAGraph code to explicitly designate parameters as "ansistring" rather than the ambiguous "string", though there may be good reasons for specifying string which I do not realise.

My conclusions:
  Do not mess with the Lazarus defaults of {$mode objfpc}($H+} without good reason and serious thought.
  Do not trust the global compiler H+ setting.
  Br grateful for the forum support when in the depths of an intractable and obscure bug.

 

TinyPortal © 2005-2018