使用OnPaint方法时出现“对象锁未归”错误

时间:2019-04-24 11:05:48

标签: delphi firemonkey

我正在尝试使用OnPaint方法绘制一个简单的图像。该代码可以很好地进行编译,但是在应用程序启动时,它会显示“对象锁未拥有”错误,并且什么也没有发生。你能告诉我我犯了什么错误吗?该代码显示了我正在使用的OnPaint事件。谢谢大家的帮助。

procedure TTabbedForm.Image1Paint(Sender: TObject; Canvas: TCanvas;
  const ARect: TRectF);
  var
  p1, p2, p3, p4, p5, p6: TPointF;
  prst1: TRectF;
  i :Integer;
begin
 Image1.Bitmap.Canvas.Stroke.Color := TAlphaColors.Black;
 Image1.Bitmap.Canvas.Stroke.Thickness := 3;
 p1 := TPointF.Create(PX, PY);
 Image1.Bitmap.Canvas.BeginScene;
  with TabbedForm do begin
      for i := 0 to 360 do
        if (i mod 15)=0 then
        begin
         p2 := TPointF.Create(Round(PX+PP*sin(i*pi/180)), Round(PY+PP*cos(i*pi/180)));
          Image1.Bitmap.Canvas.DrawLine(p1, p2, 100);
        end;
      for i := 0 to PP do
        if (i mod 20)=0 then
        begin
        prst1 := TRectF.Create(PX+i,PY+i,PX-i,PY-i);
        Image1.Bitmap.Canvas.DrawEllipse(prst1, 100);
        end;
      for i := 0 to 400 do
        if (i mod 20)=0 then
        begin
        p3 := TPointF.Create(i,2*PP);
        p4 := TPointF.Create(i,2*PP+2*PP);
        Image1.Bitmap.Canvas.DrawLine(p3, p4, 100);
        end;
      for i := 0 to 400 do
        if (i mod 20)=0 then
        begin
        p5 := TPointF.Create(0,2*PP+i);
        p6 := TPointF.Create(2*PP+2*PP,2*PP+i);
        Image1.Bitmap.Canvas.DrawLine(p5, p6, 100);
        end;
  Image1.Bitmap.Canvas.EndScene;
  end;
 end;

2 个答案:

答案 0 :(得分:0)

我认为您会收到此错误消息,因为您是在不允许的时候在画布上绘画的。造成这种情况的潜在原因是:

  • 您正在从图像的绘画事件中绘制图像的位图。图像用于显示预先生成或加载的位图,并且由于修改位图应触发OnPaint事件,所以我认为从同一事件进行这些更改是一个坏主意。它要求一个无休止的循环,或其他不必要的副作用。
  • 您使用的BeginScene / EndScene错误。仅当BeginScene返回true时,才应继续绘制。实际上,在给定事件的画布上绘制时根本不需要调用它们。
  • 您(部分)使用表单的全局实例而不是当前实例(Self),这可能(取决于您的应用程序)导致绘制错误的实例。

小的免责声明:我尽可能地保留了您的代码,只是更改了我认为可能导致您的问题的内容。我认为这些更改都是有道理的,但是我必须承认我从未在FMX中做过很多绘画,所以也许其中一些有些天真或过于保护(或者公然错误)。

此代码与您的代码有不同之处:

  • 使用TPaintbox(您必须添加一个名为'Paintbox1'的TPaintbox,并将此方法添加到其OnPaint处理程序中)。颜料盒用于直接绘制。如果您能够在特定事件(例如,应用程序的启动,单击按钮,计时器等)上预先渲染图像的位图,则还可以保留图像。
  • 正确地使用带有iftry..finally块的BeginScene和EndScene。 BeginScene将为您提供锁定或不锁定,并根据成功返回布尔值。您仅应在实际获得了锁的情况下继续操作,并且在这种情况下也仅调用EndScene,因为它们被引用计数,并且执行此操作可能会增加引用计数,并因此在您的应用程序中进行所有进一步的绘制。 < / li>
  • 还可以在场景中描边设置。不确定是否需要100%,但是我想这也是绘制场景的一部分,对吧?
  • 完全淘汰BeginScene..EndScene。 Paintbox或Image控件本身应该已经调用了它。参见FMX.Graphics.TCanvas.BeginScene docs
  • 只需使用Canvas。它作为参数传递给事件处理程序,因此最好使用它,然后自己尝试找到合适的画布。
  • 删除了with。这有点长,但是好像您正在引用全局TTabbedForm变量一样,并且由于您位于TTabbedForm方法内,因此您应该能够将当前实例的属性和方法用作-is,如果遇到命名冲突,请加上Self.。最好不要依赖那些用于表单和数据模块的全局变量,并且如果您想拥有多个表单实例,实际上会遇到问题,在这种情况下,原始代码会部分运行错误的实例。
procedure TTabbedForm.Paintbox1Paint(
  Sender: TObject; Canvas: TCanvas; const ARect: TRectF);
var
  p1, p2, p3, p4, p5, p6: TPointF;
  prst1: TRectF;
  i :Integer;
begin
  p1 := TPointF.Create(PX, PY);
  Canvas.Stroke.Color := TAlphaColors.Black;
  Canvas.Stroke.Thickness := 3;

  for i := 0 to 360 do
    if (i mod 15)=0 then
    begin
      p2 := TPointF.Create(Round(PX+PP*sin(i*pi/180)), Round(PY+PP*cos(i*pi/180)));
      Canvas.DrawLine(p1, p2, 100);
    end;
  for i := 0 to PP do
    if (i mod 20)=0 then
    begin
      prst1 := TRectF.Create(PX+i,PY+i,PX-i,PY-i);
      Canvas.DrawEllipse(prst1, 100);
    end;
  for i := 0 to 400 do
    if (i mod 20)=0 then
    begin
      p3 := TPointF.Create(i,2*PP);
      p4 := TPointF.Create(i,2*PP+2*PP);
      Canvas.DrawLine(p3, p4, 100);
    end;
  for i := 0 to 400 do
    if (i mod 20)=0 then
    begin
      p5 := TPointF.Create(0,2*PP+i);
      p6 := TPointF.Create(2*PP+2*PP,2*PP+i);
      Canvas.DrawLine(p5, p6, 100);
    end;
end;

答案 1 :(得分:0)

错误消息“对象锁未拥有”是EMonitorLockException的消息,记录为“每当线程尝试释放非拥有的监视器上的锁时,都会引发”。由于您尚未响应我对MCVE的请求,并且我无法重现此错误,因此我无法确认是否是由于通过Canvas.BeginScene进行的锁获取失败或其他原因导致的。

您可以在图形中使用TImageTPaintBox。使用TImage具有许多好处,例如直接加载图像文件,在该图像上绘图以及将图像直接以各种格式保存到文件中,例如.bmp.jpg或{{1 }}(也许其他人也可以)。 .png更轻巧,没有自己的位图,但使用父组件表面进行绘制(因此需要TPaintBox处理程序)。从/保存到文件的加载必须完成,例如通过单独的TBitmap。

是的,如果需要,您可以继续使用TImage控件,但是在这种情况下,不要像现在一样使用OnPaint()事件用于图形。 TImage具有内置的机制,可以在需要时进行自我绘制。您只需要在内置位图画布上绘制一次图形即可。在以下代码中,图像是在OnPaint事件中绘制的。还要注意,使用TImage必须按照记录正确使用ButtonClick()-BeginScene

在绘制之前,您还必须设置EndScene。如果未在显示的代码中的其他位置设置此值,则可能是代码未生成图像的另一个原因。

TImage.Bitmap.Size上绘制图像,例如在按钮的Image1.Bitmap.Canvas事件中:

OnClick()