Search code examples
delphidelphi-2005

Form creates 2 Frames - How to call procedure in Frame 2 from inside Frame 1?


Right now Frame 1 is in a loop (looking for data from Serial Comport) and writes to a string variable A in a separate unit. Frame1 then loops until another boolean variable B is true meaning Frame2 has processed its routine. Frame 2 uses a timer to check for changes in variable A then executes a procedure when the variable has changed and sets boolean variable B to true. Looping in Frame 1 and checking for variable B to become true leads to Frame 2 can't fire it's timer anymore because probably the message queue doesn't become empty anymore.

Right now i can only help myself with sleep(xxx). But i want better performance.

Please help :)

Thank you

Edit1: i forgot to mention the point from the topic header. i want to get rid of the timer and call the procedure in frame2 directly.

Edit2: code:

Frame1:

procedure TFrmSerial.TimerSerialTimer(Sender: TObject);
var
resultserial:string;
sl:Tstringlist;
iloop:integer;
begin
  if CheckBox1.Checked  then
  begin
              TimerSerialTimer.Enabled:=false;
              readString(resultserial); //reads comport data to string
      
              if (resultserial<>'')  then
              begin
                      sl:=TStringList.Create;
                      sl.Sorted:=true;
                      sl.Duplicates:=dupIgnore;

                      try
                        sl.Text:=resultserial;
                        unit3.DataProcessed:=true;
                        
                 repeat
                         
                         if (unit3.DataProcessed=true) then
                         begin
                             edit1.Text:=sl[0];
                             sl.Delete(0);
                             unit3.DataProcessed:=false;
                         end
                         else if (unit3.DataProcessed=false) then
                         begin
                               sleep(800);
                               unit3.DataProcessed:=true;  //ugly workaround
                         end                        
                         else
                         begin
                             showmessage('undefined state');
                         end;
                
                 until (sl.Count=0);
                      finally
                        sl.Free;
                      end;

                end;

                TimerSerialTimer.Enabled:=true;
  end;
end;

Frame2: code:

procedure TFrmProcessing.Timer1Timer(Sender: TObject);
begin
  if self.Visible then
  begin
    timer1.enabled:=false;
    if   ProcessString<>ProcessStringBefore then
    begin
      ProcessStringBefore:=ProcessString;
      if length(ProcessString)>2 then DoWork;
    end;
    unit3.DataProcessed:=true;
    timer1.enabled:=true;
  end;
end;

Solution

  • I think your problem can be solved with callbacks. Something like this:

    type
    ...
    TMyCallback = procedure of Object;
    ...
    

    of Object means that this procedure should be class method.

    If you define variable with this type and than assign some procedure with the same attributes you can call it by calling this variable:

    type
      TMyCallback = procedure of Object;
      TForm2 = class(TForm)
        private
          ...
        protected
          ...
        public
          callback:TMyCallback;
          ...
        end;
    
    ...
    
    procedure Form1.DoSomething;
    begin
    // do something
    end;
    
    procedure Form1.DoSomethingWithEvent;
    begin
       callback := DoSomething; //assign procedure to variable
       if assigned(callback)
          callback;             //call procedure DoSomething
    end;
    

    You should do something like this in your case. It's just example because I didn't see all your code, but I'll try to make it workable:

    Frame1:

    type
    TSerialEvent = function(aResult:String):Boolean of Object;
    
    Frame1 = class(TFrame)
      private
         ...
      protected
         ...
      public
         ...
         Callback:TSerialEvent;
    end;
    
    ...
    
    procedure TFrmSerial.TimerSerialTimer(Sender: TObject);
    var
      resultserial:string;
      sl:Tstringlist;
      iloop:integer;
    begin
      if CheckBox1.Checked  then
      begin
        TimerSerialTimer.Enabled:=false;
        readString(resultserial); //reads comport data to string
          
        if (resultserial<>'')  then
        begin
          sl:=TStringList.Create;
          sl.Sorted:=true;
          sl.Duplicates:=dupIgnore;
    
          try
            sl.Text:=resultserial;
                            
            repeat   
            edit1.Text := sl[0];
            sl.Delete(0);
            if assigned(Callback) then
              begin
              //Let's call Process method of TFrmProcessing:
              if not Callback(edit1.text) then  //it's not good idea to use edit1.text as proxy, but we have what we have
                raise Exception.Create('Serial string was not processed');     
              end
            else
              raise Exception.Create('No Callback assigned');           
                    
            until (sl.Count=0);
            finally
              sl.Free;
            end;
    
          end;
    
        TimerSerialTimer.Enabled:=true;
      end;
    end;
    

    Frame2: You don't need Timer anymore. Everything will be processed in event:

    type
    TFrmProcessing = class(TFrame)
       private
       ...
       protected
       ...
       public
       ...
       function Process(aResult:String):Boolean;
    end;
    
    function TFrmProcessing.Process(aResult:String):Boolean;
    begin
     result := false;
     if self.Visible then
      begin
        if aResult <> ProcessStringBefore then
        begin
          ProcessStringBefore := aResult;
          if length(ProcessString) > 2 then DoWork;
          result := true;
        end;
      end; 
    end;
    

    And the last thing: you have to assign method Process of TFrmProcessing to Callback of Frame1. I think you should do it at Form1.Create or another method you are using for initialization:

    ...
    procedure Form1.FormCreate(Sender:TObject);
    begin
    ...
    Frame1.Callback := FrmProcessing.Process;
    ...
    end;