Search code examples
delphistack-overflowdelphi-6

Delphi stack overflow due to a cycle in event handling


I am working on the application which has two listboxes.I load the two listboxes with values and when i keep on clicking the items from the list box i get the following error while debugging.

enter image description here

Running the exe causes the application to close.Sometimes i get the "Access Violation" message.

so what should I do to get rid of this error from my aaplication?

EDIT

..

The main form has timer that refresh all the controls timer_RefreshCOntrol (intervali 1).

whenver the editBox_one is modified(value) this function is called

Procedure TStringSetting.SetValue (const AValue : String);
  Begin
   ...
    If FValueControl <> Nil then
    Begin
     FValueControl.OnChange := VoidNotifyEvent;
     FValueControl.Text := NewValue;
     FValueControl.OnChange := EditChange;        //<--here the stackoverflow error comes....
    end;
  end;




 Procedure EditChange (Sender: TObject);
   Begin
       Value := FValueControl.Text;
       If Not EditIsValid then FValueControl.Font.Color := clRed
       else If Dirty  then FValueControl.Font.Color := clBlue
                  else FValueControl.Font.Color := clWindowText;

       If @OldCustomEditChange <> Nil then OldCustomEditChange(Sender);
    end;`


   the EditChange (Sender: TObject); <--keeps geting called and the stackoverflow error comes

EditChange is assigned to the editbox on FormCreate

EDIT2

I am not the original developer.I just handled code sometimes back, major refactoring is not possible.

edit 3 The call stack value but what is the "???" enter image description here

EDIT 4

after going through @Cosmin Prund and @david

i got the place where the infinity call start

   Procedure TFloatSetting.EditChange (Sender: TObject);
  Begin
    SkipNextOnChange := True;
  Inherited EditChange(Sender);
  IfValidThenStore(FValueControl.Text);
  Inherited EditChange(Sender);  {<-------This is where it start}
 end;


 Procedure TStringSetting.EditChange (Sender: TObject);
  Begin
   Value := FValueControl.Text;
   If Not EditIsValid then FValueControl.Font.Color := clRed
     else If Dirty  then FValueControl.Font.Color := clBlue
                  else FValueControl.Font.Color := clWindowText;

   If @OldCustomEditChange <> Nil then OldCustomEditChange(Sender); {<---this keeps calling  Procedure TFloatSetting.EditChange (Sender: TObject);}
 end;

Solution

  • Based in the posted call stack it's obvious why the error is happening: TStringSetting.EditChange triggers TFloatSetting.EditChange and that in turn triggers TStringSetting.EditChange. The loop goes on like this until all stack space is exhausted.

    Here are some tips on why that might happen, and tips on how to debug and fix it:

    • Maybe the controls involved trigger the OnChange event handler when the Value is changed progrmatically. If the two editors are supposed to display the same data in two formats and you're using the respective OnChange event handlers to keep them in sync, this might be the cause.
    • Maybe you're directly calling one event handler from the other.

    Ways to debug this:

    • You should first try the breakpoint solution, as suggested by paulsm4. If the stack overflow happens every time one of the OnChange handlers is called, this solution would easily work.
    • Comment-out the code for one of the event handlers. Run the program, the error should no longer appear. Un-comment the code in tiny (but logical) amounts, test and repeat. When the error shows up again, you know you fund the line that's causing the error. If you can't figure it out yourself, edit the question, add the code and mark the line that you just found out it's giving you trouble.

    If the controls you're using are triggering the OnChange event handler when there value is changed programatically, you should make your event handlers non-reentrant: that would stop the infinite recursive loop for sure. I almost always assume controls trigger OnChange or equivalent events when properties are changed from code and always protect myself from re-entry using something like this:

    // Somewhere in the private section of your form's class:
    FProcessingEventHandler: Boolean;
    
    // This goes in your event handler
    procedure TYourForm.EventHandler(Sender:TObject);
    begin
      if FProcessingEventHandler then Exit; // makes code non-reentrant
      FProcessingEventHandler := True;
      try
        // old code goes here ...
      finally FProcessingEventHandler := False;
      end;
    end;