Search code examples
loopsdelphicomponents

Is there a way to switch components mid-loop?


I am trying to animate a dropdown menu of 7 images using a for loop, using a different component to change after each iteration of the code inside the for loop. For example, the first time the loop runs: imgCourses7.Top is used, but the second time the loop runs (when I = 1) then imgCourses6.Top should be used instead.

iCoursesCount := 7;
iTotalLength := (6+41)*iCoursesCount;
imgCourses7.Top := 6;
imgCourses6.Top := 6;
imgCourses5.Top := 6;
imgCourses4.Top := 6;
imgCourses3.Top := 6;
imgCourses2.Top := 6;
imgCourses1.Top := 6;
for I := 0 to iCoursesCount -1 do
begin
  while not(imgCourses7.Top = iTotalLength - 41*(I+1)) do
  begin
    imgCourses8.Top :=  imgCourses8.Top + 6;
    sleep(8);
    application.ProcessMessages;
    if imgCourses7.Top >= iTotalLength - 41*(I+1) then
    begin
      imgCourses7.Top := iTotalLength - 41*(I+1);
      break;
    end;
  end;
end;

Solution

  • Like @AndreasRejbrand said in a comment, you can use an array, eg:

    var
      Images: array[0..6] of TImage;
      TargetTop: Integer;
      ...
    
    ...
    
    Images[0] := imgCourses7;
    Images[1] := imgCourses6;
    Images[2] := imgCourses5;
    Images[3] := imgCourses4;
    Images[4] := imgCourses3;
    Images[5] := imgCourses2;
    Images[6] := imgCourses1;
    
    iCoursesCount := Length(Images);
    TargetTop := 6+(41*iCoursesCount);
    
    for I := 0 to iCoursesCount-1 do begin
      Images[I].Top := 6;
    end;
    
    for I := 0 to iCoursesCount-1 do
    begin
      Dec(TargetTop, 41);
      while Images[I].Top <> TargetTop do
      begin
        Images[I].Top := Images[I].Top + 6;
        Sleep(8);
        Application.ProcessMessages;
        if Images[I].Top >= TargetTop then
        begin
          Images[I].Top := TargetTop;
          Break;
        end;
      end;
    end;
    

    That being said, you really shouldn't be using a sleeping loop that requires Application.ProcessMessages() on each iteration. You might consider using a TTimer or TThread.ForceQueue() instead. Don't block the main UI thread unnecessarily. For example:

    published
      procedure FormCreate(Sender: TObject);
    private
      Images: array[0..6] of TImage;
      TargetTop: Integer;
      CurrentImage: Integer;
      procedure StartAnimatingMenu;
      procedure StartAnimatingNextMenuItem;
      procedure StepAnimateCurrentMenuItem;
      ...
    
    ...
    
    procedure TMyForm.FormCreate(Sender: TObject);
    begin
      Images[0] := imgCourses7;
      Images[1] := imgCourses6;
      Images[2] := imgCourses5;
      Images[3] := imgCourses4;
      Images[4] := imgCourses3;
      Images[5] := imgCourses2;
      Images[6] := imgCourses1;
    end;
    
    procedure TMyForm.StartAnimatingMenu;
    var
      I: Integer;
    begin
      for I := Low(Images) to High(Images) do begin
        Images[I].Top := 6;
      end;
      TargetTop := 6+(41*Length(Images));
      CurrentImage := -1;
      StartAnimatingNextMenuItem;
    end;
    
    procedure TMyForm.StartAnimatingNextMenuItem;
    begin
      Inc(CurrentImage);
      if CurrentImage < Length(Images) then
      begin
        Dec(TargetTop, 41);
        StepAnimateCurrentMenuItem;
      end;
    end;
    
    procedure TMyForm.StepAnimateCurrentMenuItem;
    begin
      if Images[CurrentImage].Top <> TargetTop then
      begin
        Images[CurrentImage].Top := Images[CurrentImage].Top + 6;
        TThread.ForceQueue(nil, StepAnimateCurrentMenuItem, 8);
      end
      else if Images[CurrentImage].Top >= TargetTop then
      begin
        Images[CurrentImage].Top := TargetTop;
        StartAnimatingNextMenuItem;
      end;
    end;