Continue with previous question I want to be able to show some activity indicator even if the main thread is blocked. (based on this article).
Problems based on the attached code:
- Using
Synchronize(PaintTargetWindow);
does not paint the window - I sometimes get an error:
Canvas does not allow drawing.
In the line:{FBitmap.}StretchDraw(Rect(Left, ImageRect.Top, Right, ImageRect.Bottom), FfgPattern)
here is the code I use to create the indicator thread:
unit AniThread;
interface
uses Windows, Classes, Graphics, Controls, Math;
const
ANI_GRAD_FG_COLOR_BAGIN = $00CDFFCD;
ANI_GRAD_FG_COLOR_END = $0024B105;
ANI_GRAD_BK_COLOR_BAGIN = $00F5F5F5;
ANI_GRAD_BK_COLOR_END = $00BDBDBD;
type
TAnimationThread = class(TThread)
private
FWnd: HWND;
FPaintRect: TRect;
FInterval: Integer;
FfgPattern, FbkPattern: TBitmap;
FBitmap: TBitmap;
FImageRect: TRect;
procedure UpdatePattern(Pattern: TBitmap; ColorBegin, ColorEnd: TColor);
function CreatePatternBitmap(AColorBegin, AColorEnd: TColor): TBitmap;
procedure PaintTargetWindow;
protected
procedure Execute; override;
public
procedure Animate;
constructor Create(PaintSurface: TWinControl; { Control to paint on }
PaintRect: TRect; { area for animation bar }
Interval: Integer { wait in msecs between paints}
);
destructor Destroy; override;
end;
implementation
constructor TAnimationThread.Create(PaintSurface: TWinControl;
PaintRect: TRect;
Interval: Integer);
begin
inherited Create(True); { suspended }
FreeOnterminate := True;
Priority := tpHigher;
FInterval := Interval;
FWnd := PaintSurface.Handle;
FPaintRect := PaintRect;
FfgPattern := CreatePatternBitmap(ANI_GRAD_FG_COLOR_BAGIN, ANI_GRAD_FG_COLOR_END);
FbkPattern := CreatePatternBitmap(ANI_GRAD_BK_COLOR_BAGIN, ANI_GRAD_BK_COLOR_END);
end;
destructor TAnimationThread.Destroy;
begin
inherited Destroy;
FfgPattern.Free;
FbkPattern.Free;
end;
procedure TAnimationThread.Animate;
begin
Resume;
Sleep(0);
end;
function TAnimationThread.CreatePatternBitmap(AColorBegin, AColorEnd: TColor): TBitmap;
begin
Result := TBitmap.Create;
Result.PixelFormat := pf24bit;
UpdatePattern(Result, AColorBegin, AColorEnd);
end;
type
PRGBTripleArray = ^TRGBTripleArray;
TRGBTripleArray = array[0..32767] of TRGBTriple;
TGradientColors = array[0..255] of TRGBTriple;
procedure PatternBuilder(const Colors: TGradientColors; Pattern: TBitmap);
var
Y: Integer;
Row: PRGBTripleArray;
begin
Pattern.Width := 1;
Pattern.Height := 256;
for Y := 0 to 127 do
begin
Row := PRGBTripleArray(Pattern.ScanLine[Y]);
Row[0] := Colors[Y];
Row := PRGBTripleArray(Pattern.ScanLine[Y + 128]);
Row[0] := Colors[255 - Y];
end;
end;
procedure TAnimationThread.UpdatePattern(Pattern: TBitmap; ColorBegin, ColorEnd: TColor);
var
Colors: TGradientColors;
dRed, dGreen, dBlue: Integer;
RGBColor1, RGBColor2: TColor;
RGB1, RGB2: TRGBTriple;
Index: Integer;
begin
RGBColor1 := ColorToRGB(ColorBegin);
RGBColor2 := ColorToRGB(ColorEnd);
RGB1.rgbtRed := GetRValue(RGBColor1);
RGB1.rgbtGreen := GetGValue(RGBColor1);
RGB1.rgbtBlue := GetBValue(RGBColor1);
RGB2.rgbtRed := GetRValue(RGBColor2);
RGB2.rgbtGreen := GetGValue(RGBColor2);
RGB2.rgbtBlue := GetBValue(RGBColor2);
dRed := RGB2.rgbtRed - RGB1.rgbtRed;
dGreen := RGB2.rgbtGreen - RGB1.rgbtGreen;
dBlue := RGB2.rgbtBlue - RGB1.rgbtBlue;
for Index := 0 to 255 do
with Colors[Index] do
begin
rgbtRed := RGB1.rgbtRed + (Index * dRed) div 255;
rgbtGreen := RGB1.rgbtGreen + (Index * dGreen) div 255;
rgbtBlue := RGB1.rgbtBlue + (Index * dBlue) div 255;
end;
PatternBuilder(Colors, Pattern);
end;
procedure TAnimationThread.PaintTargetWindow;
var
DC: HDC;
begin
DC := GetDC(FWnd);
if DC <> 0 then
try
BitBlt(DC,
FPaintRect.Left,
FPaintRect.Top,
FImageRect.Right,
FImageRect.Bottom,
FBitmap.Canvas.handle,
0, 0,
SRCCOPY);
finally
ReleaseDC(FWnd, DC);
end;
end;
procedure TAnimationThread.Execute;
var
Left, Right: Integer;
Increment: Integer;
State: (incRight, incLeft, decLeft, decRight);
begin
InvalidateRect(FWnd, nil, True);
FBitmap := TBitmap.Create;
try
with FBitmap do
begin
Width := FPaintRect.Right - FPaintRect.Left;
Height := FPaintRect.Bottom - FPaintRect.Top;
FImageRect := Rect(0, 0, Width, Height);
end;
Left := 0;
Right := 0;
Increment := FImageRect.Right div 50;
State := Low(State);
while not Terminated do
begin
with FBitmap.Canvas do
begin
StretchDraw(FImageRect, FbkPattern);
case State of
incRight:
begin
Inc(Right, Increment);
if Right > FImageRect.Right then begin
Right := FImageRect.Right;
Inc(State);
end;
end;
incLeft:
begin
Inc(Left, Increment);
if Left >= Right then begin
Left := Right;
Inc(State);
end;
end;
decLeft:
begin
Dec(Left, Increment);
if Left <= 0 then begin
Left := 0;
Inc(State);
end;
end;
decRight:
begin
Dec(Right, Increment);
if Right <= 0 then begin
Right := 0;
State := incRight;
end;
end;
end;
StretchDraw(Rect(Left, FImageRect.Top, Right, FImageRect.Bottom), FfgPattern);
end; { with }
// Synchronize(PaintTargetWindow); // not painting when the main thread is blocked
PaintTargetWindow;
SleepEx(FInterval, False);
end; { While }
finally
FBitmap.Free;
end;
end;
end.
Usage: drop a TButton
and a TPanel
on the main form.
uses AniThread;
procedure TForm1.Button1Click(Sender: TObject);
var
at: TAnimationThread;
begin
at := TAnimationThread.Create(Panel1, Panel1.ClientRect, 10);
Button1.Enabled := False;
try
at.Animate;
Sleep(3000); // sleep 3 sec. block main thread
finally
at.Terminate;
Button1.Enabled := True;
end;
end;
I know many of you will disapprove with this approach. But now it's mainly a challenge for me to MAKE IT WORK well. Any help with this issue will be much appreciated.
EDIT:
This is the original article (by Peter Below, TeamB). I only implemented the gradient painting.