Lazarus trank: stack overflow for new revisions lazarus / 21-General ---------------------------------------------------------------------- zoltanleo · 2022-09-25 17:32 Hi folks I am getting an overflow error in my old project after updating lazarus to newer versions. Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i Project project1 raised exception class 'External: STACK OVERFLOW'. At address 10000E3BF The project compiles and builds successfully. In the test project, this happens inside the loop (this happens in the onclick event for each button): Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i   for i:= 0 to Pred(Panel1.ControlCount) do    if TControl(Panel1.Controls[i]).InheritsFrom(TToggleBox) then      TToggleBox(Panel1.Controls[i]).State:= cbUnchecked; The error is observed for Lazarus revision: main-2_3-2511-gfee5b58ce2 and older. The error is reproduced for win34/64 and gtk2_x64 (I didn't check for qt and darwin) What happened? Updated: If I add an additional flag that prevents recursion in changing the component, then no error occurs Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i   private  FInChanging: Boolean;  procedure TForm1.TglBoxClick(Sender: TObject);var  i: PtrInt = 0;begin  if FInChanging then Exit;  FInChanging := True;   if not TObject(Sender).InheritsFrom(TToggleBox) then Exit;   try    Memo1.Lines.Add('');    Memo1.Lines.Add(Format('====== %s.Click ======',[TToggleBox(Sender).Name]));     Memo1.Lines.Add('');      for i:= 0 to Pred(Panel1.ControlCount) do      if TControl(Panel1.Controls[i]).InheritsFrom(TToggleBox) then        begin          TToggleBox(Panel1.Controls[i]).State:= cbUnchecked;        Memo1.Lines.Add(Format('Panel1.Controls[%d] State property changed to ''cbUnchecked'' value (%s)',          [i, FormatDateTime('hh:nn:ss.zzz',Now)]));        end;     TToggleBox(Sender).State:= cbChecked;    Memo1.Lines.Add(Format('%s State property changed to ''cbChecked'' value (%s)',    [TToggleBox(Sender).Name, FormatDateTime('hh:nn:ss.zzz',Now)]));  finally    FInChanging:= False;  end;end; ---------------------------------------------------------------------- paweld · 2022-09-25 18:29 Modifying the state of TToggleBox triggers the OnClick event, so set TToggleBox.OnClick to nil when changing the state, or try to avoid changing the status in the OnClick event, as it falls into an infinite loop.Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure TForm1.TglBoxClick(Sender: TObject);var  i: PtrInt = 0;begin  if not TObject(Sender).InheritsFrom(TToggleBox) then Exit;   Memo1.Lines.Add('');  Memo1.Lines.Add(Format('====== %s.Click ======',[TToggleBox(Sender).Name]));   Memo1.Lines.Add('');    for i:= 0 to Pred(Panel1.ControlCount) do    if TControl(Panel1.Controls[i]).InheritsFrom(TToggleBox) then      begin        TToggleBox(Panel1.Controls[i]).OnClick:= nil;   //<--Add this      TToggleBox(Panel1.Controls[i]).State:= cbUnchecked;        TToggleBox(Panel1.Controls[i]).OnClick:= @TglBoxClick;   //<--and this        Memo1.Lines.Add(Format('Panel1.Controls[%d] State property changed to ''cbUnchecked'' value (%s)',        [i, FormatDateTime('hh:nn:ss.zzz',Now)]));      end;   TToggleBox(Sender).OnClick:= nil;   //<--and this  TToggleBox(Sender).State:= cbChecked;  TToggleBox(Sender).OnClick:= @TglBoxClick;   //<--and this  Memo1.Lines.Add(Format('%s State property changed to ''cbChecked'' value (%s)',  [TToggleBox(Sender).Name, FormatDateTime('hh:nn:ss.zzz',Now)]));end; ---------------------------------------------------------------------- PascalDragon · 2022-09-25 19:04 Or you could check inside the loop whether the control is the Sender and not change its State then. ---------------------------------------------------------------------- Arioch · 2022-09-25 20:13 https://gitlab.com/freepascal.org/lazarus/lazarus/-/issues/39914 https://forum.lazarus.freepascal.org/index.php/topic,60430.0.html ---------------------------------------------------------------------- zoltanleo · 2022-09-25 20:14 Thanks for answers. Can I consider the current behavior of the component as normal? ---------------------------------------------------------------------- Arioch · 2022-09-25 20:14 Quote from: PascalDragon on September 25, 2022, 09:04:34 pmOr you could check inside the loop whether the control is the Sender and not change its State then. or use OnChange ratehr than OnClick and turn Delphi emulation off ---------------------------------------------------------------------- Bart · 2022-09-25 21:20 Is this documented on the 2.4 release notes page? ---------------------------------------------------------------------- Arioch · 2022-09-25 21:46 And as this topic was brought, it would've been nice if ppl test a patch for TDBCheckBox on different OSes/toolkits https://gitlab.com/freepascal.org/lazarus/lazarus/-/issues/39917 ---------------------------------------------------------------------- jamie · 2022-09-25 22:43 I made the suggestion and also tested it locally to include a MODIFIED property in this control. A few other controls already have this PROPERTY for the same exact reason. When a software set is done the events can test the MODIFIED property which should be set to FALSE indicated that the user did not trigger it. Otherwise, it's set to true.  This property is READ/WRITE so when it loses focus it will retain its value so that later on you can scan some controls on the form for changes and update some user file or whatever.  Most of the time what I do is use the OnEnter event and clear this property first if needed or simply reload it software wise which will clear it.  To each their own I guess, but we really should follow some order and I think the Modified property is appropriate for this. ---------------------------------------------------------------------- zoltanleo · 2022-09-26 07:35 Quote from: paweld on September 25, 2022, 08:29:13 pmModifying the state of TToggleBox triggers the OnClick event, so set TToggleBox.OnClick to nil when changing the state, or try to avoid changing the status in the OnClick event, as it falls into an infinite loop.Thank you. This is a good idea. I did it this way: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i     for i:= 0 to Pred(Panel1.ControlCount) do      if TControl(Panel1.Controls[i]).InheritsFrom(TToggleBox) then        begin          TToggleBox(Panel1.Controls[i]).OnClick:= nil;          if TToggleBox(Panel1.Controls[i]).Equals(TToggleBox(Sender))          then              TToggleBox(Panel1.Controls[i]).State:= cbChecked          else              TToggleBox(Panel1.Controls[i]).State:= cbUnchecked;           TToggleBox(Panel1.Controls[i]).OnClick:= @TglBoxClick;         end; ---------------------------------------------------------------------- zoltanleo · 2022-09-26 07:38 I thank everyone who participated in the discussion. I hope this behavior of the component will not change in future versions of Lazarus. Otherwise, I will again have to redo the code that worked well before ---------------------------------------------------------------------- Arioch · 2022-09-26 11:44 Just make your mind, if you want to react on change or on user click, then turn Delphi emulation off and use the event you need. Delphi VCL has only OnChange (albeit wrongly named). ---------------------------------------------------------------------- Arioch · 2022-09-26 11:56 Quote from: jamie on September 26, 2022, 12:43:26 amI made the suggestion and also tested it locally to include a MODIFIED property in this control. A few other controls already have this PROPERTY for the same exact reason. did you put your patch to https://gitlab.com/freepascal.org/lazarus/lazarus/-/issues ? well, "modified" name is rather ambiguous. There are a lot of reasons why a control could be modified. Databse compontent, TAction components... Direct changes in the platform/OS outside of LCL framework. also, imagine a group of radiobuttons, a user clicks rb1 and rb2 looses its tick. Is rb2 modified by user or by rb1? what should be a concept here? if to imagine some conceptually consistent design - then perhaps all such changes should had included "originator control" parameter. ....which could've been done in original message-based OOP like in Windows GDI or Smalltak, or with member procedure calls, but can hardly be made with properties assignment like used everywhere in LCL//VCL Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i RadioButton2.Checked := True; // changed by userRadioButton2.Checked := False; // changed by another RB hello, tuples :-D Maybe VCL/LCL could've been designed in a way it never uses properties directly, leaving them as solely external API, and for internal transaction always calling setter procedures with additional parameters. But it is easy today to talk how things should've been done in 1995 :-) ---------------------------------------------------------------------- ASerge · 2022-09-26 18:07 Quote from: zoltanleo on September 26, 2022, 09:35:42 amCode: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i     for i:= 0 to Pred(Panel1.ControlCount) do      if TControl(Panel1.Controls[i]).InheritsFrom(TToggleBox) then      ... I suggest using modern language features: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure TForm1.TglBoxClick(Sender: TObject);const  CStateWhenSender: array [Boolean] of TCheckBoxState = (cbUnchecked, cbChecked);var  C: TControl;begin   for C in Panel1.GetEnumeratorControls do     if C is TToggleBox then     begin       C.OnClick := nil;       TToggleBox(C).State := CStateWhenSender[Sender = C];       C.OnClick := @TglBoxClick;     end;end; ---------------------------------------------------------------------- Arioch · 2022-09-26 19:08 Quote from: ASerge on September 26, 2022, 08:07:13 pmI suggest using modern language features: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i const  CStateWhenSender: array [Boolean] of TCheckBoxState = (cbUnchecked, cbChecked);...       TToggleBox(C).State := CStateWhenSender[Sender = C];  ...when you are paid per line :-) Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i    TToggleBox(C).Checked := Sender = C;  ...but the only thing he really needed was one line in .FormCreate:  TCheckBox.VCL_Onlick_Emulation := False; However, it is trunk,, not release, so who knows what it all would end with. The coide with OnClick's was fragile, by the way. Quote from: zoltanleo on September 26, 2022, 09:35:42 am...so set TToggleBox.OnClick to nil when changing the state Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i     for i:= 0 to Pred(Panel1.ControlCount) do      if TControl(Panel1.Controls[i]).InheritsFrom(TToggleBox) then        begin          TToggleBox(Panel1.Controls[i]).OnClick:= nil; +++ TRY           if TToggleBox(Panel1.Controls[i]).Equals(TToggleBox(Sender))          then              TToggleBox(Panel1.Controls[i]).State:= cbChecked            else              TToggleBox(Panel1.Controls[i]).State:= cbUnchecked;  +++ FINALLY         TToggleBox(Panel1.Controls[i]).OnClick:= @TglBoxClick; +++ END      end; ---------------------------------------------------------------------- JuhaManninen · 2022-09-28 06:57 Quote from: zoltanleo on September 26, 2022, 09:38:55 amI thank everyone who participated in the discussion. I hope this behavior of the component will not change in future versions of Lazarus. Otherwise, I will again have to redo the code that worked well before  The switch for turning off Delphi emulation was not meant to change the Delphi compatible default behavior. Did it really change? How does Delphi itself behave in this case? ---------------------------------------------------------------------- zoltanleo · 2022-09-28 07:23 Quote from: JuhaManninen on September 28, 2022, 08:57:07 amThe switch for turning off Delphi emulation was not meant to change the Delphi compatible default behavior. Did it really change? How does Delphi itself behave in this case? Hi Juha. I'm sorry, but I can't answer that question. I haven't used delphi for the last 10 years ---------------------------------------------------------------------- wp · 2022-09-28 08:05 Quote from: JuhaManninen on September 28, 2022, 08:57:07 amQuote from: zoltanleo on September 26, 2022, 09:38:55 amI thank everyone who participated in the discussion. I hope this behavior of the component will not change in future versions of Lazarus. Otherwise, I will again have to redo the code that worked well before  The switch for turning off Delphi emulation was not meant to change the Delphi compatible default behavior. Did it really change? How does Delphi itself behave in this case? Delphi has no TToggleBox.