【发布时间】:2011-12-26 18:21:37
【问题描述】:
继续previous question 我希望能够显示一些活动指示器即使主线程被阻塞。 (基于this article)。
根据附件代码的问题:
- 使用
Synchronize(PaintTargetWindow);不会绘制窗口 - 我有时会收到错误消息:
Canvas does not allow drawing.在线:{FBitmap.}StretchDraw(Rect(Left, ImageRect.Top, Right, ImageRect.Bottom), FfgPattern)
这是我用来创建指标线程的代码:
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.
用法:在主窗体上放置一个TButton 和一个TPanel。
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;
我知道你们中的许多人会不赞成这种方法。 但现在,让它运作良好对我来说主要是一个挑战。 对此问题的任何帮助将不胜感激。
编辑:
这是original article(作者:Peter 下面,TeamB)。我只实现了渐变画。
【问题讨论】:
-
这不是批准或不批准该方法的问题。只是你不能从除主线程之外的任何东西上绘制 VCL 控件,而你正试图从你的第二个线程绘制。不要那样做——再读一遍:你不能从除主线程之外的任何地方绘制 VCL 控件。您需要正确使用
Synchronize,或者将消息发回主窗口并让绘图代码在那里工作。 -
@Ken:“很简单,除了主线程,你不能从任何东西上绘制 VCL 控件”这与事实相去甚远。查看
TCanvas.Lock方法的Delphi 帮助,甚至还有一组方法可以帮助处理此类代码。您还忽略了抛出异常的行与绘制到 VCL 控件画布无关的事实,它绘制到位图(局部变量)的画布。顺便说一句:代码由 Peter below (TeamB) 编写,在这里工作得很好...... -
@mghie 窗口(即具有 HWND 的事物)与创建它们的线程具有亲和力。除非 MSDN 文档中另有明确说明,否则任何具有 HWND 参数的 win32 api 调用都必须遵守该参数。
TCanvas包装了一个不受相同限制约束的 DC。但是,如果 DC 是窗口的 DC,那么您将回到 UI 线程限制。不仅如此,直接在 WM_PAINT 之外的窗口 DC 上绘画也是自找麻烦。如果数据库查询在工作线程上运行,而 UI 在主线程上运行,这个问题就变得微不足道了。 -
@David:不正确。来自不同线程的 GDI 调用完全有可能,只要它们正确同步 - 因此画布锁定。如果你相信这一点,你如何解释画布锁定的存在?
-
@mghie 有问题的 DC 归 Windows 所有,你认为它在窗口上绘画时会调用
TCanvas.Lock吗?比如说,位图 DC 没问题。而且你真的是说windows对创建线程的亲和不是真的吗?
标签: multithreading delphi thread-safety delphi-5