2

I developed the following AnimateRects() method to draw an animation rectangle on the Windows desktop. I use it for animating display of a modal form, making it appear to have "grown" from a grid cell.

I call the method once with the bExpand parameter = True right before the form shows. Then when the user closes the form I call it again but with bExpand = False, to show the form "collapsing" into the grid cell.

The problem is with the bExpand = False case... In the first iteration of the loop, the first call to Rectangle(r) draws the rectangle as expected, but it's as if the second call to Rectangle(r) was never called--the first rectangle never gets XORed. So after the sequence of "collapsing" rectangles has been drawn I end up with the first rectangle remaining as an artifact on the screen.

Any ideas what I'm doing wrong?

const
  MSECS_PER_DAY = 24.0 * 60.0 * 60.0 * 1000;

procedure DelayMSecs(msecs: Word);
var
  Later:  TDateTime;
begin
  Later := Now + (msecs / MSECS_PER_DAY);
  while Now < Later do begin
    Application.ProcessMessages;
    sleep(0);     //give up remainder of our time slice
  end;
end;


procedure T_fmExplore.AnimateRects(ASourceRect, ADestRect: TRect; bExpand:
    boolean; bAdjustSourceForFrame: boolean = True);
const
  MINSTEPS = 10;
  MAXSTEPS = 30;
  MAXDELAY = 180;              //150 - 200 is about right
  MINDELAY = 1;
var
  iSteps: integer;
  DeltaHt: Integer;               //Rect size chg for each redraw of animation window
  DeltaWidth: Integer;
  DeltaTop :  integer;            //Origin change for each redraw
  DeltaLeft :  integer;
  NewWidth, NewHt: Integer;
  iTemp: Integer;
  iDelay: integer;
  r : Trect;
  ScreenCanvas: TCanvas;
begin
  r := ASourceRect;
  with r do begin
    NewWidth :=   ADestRect.Right - ADestRect.Left;           //Target rect's Width
    NewHt :=      ADestRect.Bottom - ADestRect.Top;           //Target rect's Height
        //Temporarily, Deltas hold the total chg in Width & Height
    DeltaWidth := NewWidth - (Right - Left);                //NewWidth - old width
    DeltaHt :=    NewHt - (Bottom - Top);
        //With a static number of iSteps, animation was too jerky for large windows.
        //So we adjust the number of iSteps & Delay relative to the window area.
    iSteps := Max( DeltaWidth * DeltaHt div 6500, MINSTEPS );  //eg. 10 iSteps for 250x250 deltas (62500 pixels)
    iSteps := Min( iSteps, MAXSTEPS );
        //Now convert Deltas to the delta in window rect size
    DeltaWidth := DeltaWidth div iSteps;
    DeltaHt :=    DeltaHt div iSteps;
    DeltaTop :=   (ADestRect.Top - ASourceRect.Top) div iSteps;
    DeltaLeft :=  (ADestRect.Left - ASourceRect.Left) div iSteps;

    iDelay := Max( MAXDELAY div iSteps, MINDELAY );

    ScreenCanvas := TCanvas.Create;
    try
      ScreenCanvas.Handle := GetDC( 0 );              //Desktop
      try
        with ScreenCanvas do begin
          Pen.Color := clWhite;
          Pen.Mode := pmXOR;
          Pen.Style := psSolid;
          Pen.Width := GetSystemMetrics(SM_CXFRAME);
          Brush.Style := bsClear;
          if bAdjustSourceForFrame then
            InflateRect(ASourceRect, -Pen.Width, -Pen.Width);

          repeat
            iTemp := (Bottom - Top) + DeltaHt;        //Height
            if (bExpand and (iTemp > NewHt)) or (not bExpand and (iTemp < NewHt)) then begin
              Top := ADestRect.Top;
              Bottom := Top + NewHt;
            end else begin
              Top := Top + DeltaTop;            //Assign Top first...Bottom is calc'd from it
              Bottom := Top + iTemp;
            end;

            iTemp := (Right - Left) + DeltaWidth;     //Width
            if (bExpand and (iTemp > NewWidth)) or (not bExpand and (iTemp < NewWidth)) then begin
              Left := Left + DeltaLeft;
              Right := Left + NewWidth;
            end else begin
              Left := Left + DeltaLeft;         //Assign Left first...Right is calc'd from it
              Right := Left + iTemp;
            end;

            ScreenCanvas.Rectangle(r);
            SysStuff.DelayMSecs( iDelay );
            ScreenCanvas.Rectangle(r);               //pmXOR pen ...erase ourself

          until (Right - Left = NewWidth) and (Bottom - Top = NewHt);
        end;
      finally
        ReleaseDC( 0, ScreenCanvas.Handle );
        ScreenCanvas.Handle := 0;
      end;
    finally
      ScreenCanvas.Free;
    end;
  end;
end;
Mark Wilsdorf
  • 751
  • 1
  • 5
  • 18
  • 1
    Are you sure you really want to do this? The desktop isn't really yours to draw on. Also, I turn my nose up at your DelayMSecs routine. For a start `Sleep(0)` does not give up your time slice. If yours is the only runnable thread then you will sit in a little busy loop. What's wrong with MsgWaitForMultipleObjects? – David Heffernan Jan 26 '11 at 16:59
  • Yes, I really want to do this--it's an essential part of the UI for this grid-based application. (Users need visual cues about the "source" of the modal dialog.) As for DelayMSecs, it's just something that was lying around & was quick to use as I kluged together this routine. (First get something that works...then optimize.) – Mark Wilsdorf Jan 26 '11 at 17:21
  • The salient point here, is that I'm trying to figure out why my second call to Rectangle() could simply "do nothing". – Mark Wilsdorf Jan 26 '11 at 17:29
  • @Mark What is a grid-based application? It still seems dubious to me because you simply don't own the desktop - it's not yours to draw on. – David Heffernan Jan 26 '11 at 17:30
  • @Mark regarding Sleep(0), are you familiar with what Raymond Chen wrote on the subject: http://blogs.msdn.com/b/oldnewthing/archive/2005/10/04/476847.aspx – David Heffernan Jan 26 '11 at 20:01
  • @Mark You should read his book and subscribe to his blog's RSS feed. Anyone who codes for Windows should. – David Heffernan Jan 27 '11 at 16:11

1 Answers1

1

The problem, most likely, is you're starting to draw the rectangles while the modal form is still visible. At one point the form vanishes from the screen with a rectangle on it and when you draw the same rectangle to erase the previous one, it is now on the screen. Note that calling 'Free', 'Hide' etc. on a form will not hide it immediately.

(edit: this requires some explanation: the form will be hidden before the next line of the code runs, but there's no guarantee as to when the uncovered window(s) will update their invalidated regions).

The solution would be to Sleep a while after the modal form is closed and before AnimateRects is called, or perhaps call Application.ProcessMessages. The latter probably wouldn't be of much help if the modal form is not fully on a window of your own application. And the former probably wouldn't be of much help if the modal form is over an application that's continuously doing its own drawing at the same time. Like the task manager f.i...

edit: Although I might be frowned upon for this, this problem is exactly why LockWindowUpdate exists. When you think about it, you'll see that what you're doing is not different what the shell does when it shows a drag outline of a window when you're moving it (when "show window contents while dragging" is disabled).

Sertac Akyuz
  • 54,131
  • 4
  • 102
  • 169
  • 1
    Re your edit: No, that's not the same problem as the one `LockWindowUpdate()` is trying to solve. The case the OP has is neither (necessarily) user-initiated nor is it constrained to a single instance. There is no way that more than one drag and drop action could be performed at the same time, animation is not constrained in that way. It is completely possible that a real drag operation could interfere with the animation(s), so `LockWindowUpdate()` is definitely inappropriate here. Xor-painting in general is wrong in a modern (composited) GUI system. Overlay windows should be used instead. – mghie Jan 27 '11 at 04:59
  • @mghie - I don't know if OP's animations are user initiated or not, but Ok, point taken! – Sertac Akyuz Jan 27 '11 at 10:23
  • Yeah, that was it.--thanks for the help. Next I'll work on implementing this by drawing a transparent overlay window as mghie has suggested. – Mark Wilsdorf Jan 27 '11 at 16:15