【问题标题】:"Object lock not owned" error while using OnPaint method使用 OnPaint 方法时出现“未拥有对象锁”错误
【发布时间】:2019-04-26 04:30:34
【问题描述】:

我正在尝试使用 OnPaint 方法绘制一个简单的图像。代码编译得很好,但是当应用程序启动时,它显示“Object lock notowned”错误并且没有其他任何事情发生。你能告诉我我犯了什么错误吗?该代码显示了我正在使用的 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;

【问题讨论】:

  • 你知道这段代码有异常吗?调试器是否停在那里(哪一行)?当你注释掉这段代码时,你仍然得到错误吗?我可能会将该错误与多线程、TTask、互斥锁相关联,但与绘画无关……
  • 我对 FMX 图形还不是很熟悉,但我想您可能想阅读docwiki.embarcadero.com/Libraries/Tokyo/en/…。它有一些有趣的注释,包括“注意在 OnPaint 事件处理程序中调用 BeginScene 无效。”。还有 BeginScene 返回一个布尔值的事实,你应该只在它返回 true 时绘制(并且在这种情况下只调用 EndScene!)......我认为最后一个可能是你的问题的原因。
  • 另外,TImage 用于显示预生成或预加载的位图。对于直接在控件上绘图,TPaintBox 是更好的选择。我认为在其绘图处理程序中更改图像的位图可能会产生各种负面影响。请参阅docs “与显示存储在文件中的图片的 TImage 不同,TPaintBox 需要应用程序直接在画布上绘制图像。使用 OnPaint 事件处理程序在绘制框的 FMX.Graphics 上进行绘制。 TCanvas,画框的绘制面。”
  • 我无法重现该错误。请提供minimal reproducible example。将您的代码减少到仍然会导致错误的最低限度。
  • 我已投票关闭此问题,因为您未能提供@TomBrunberg 要求的 MCVE。

标签: delphi firemonkey


【解决方案1】:

错误消息“未拥有对象锁”是EMonitorLockException 的消息,据记载,“每当线程试图释放非拥有监视器上的锁时”就会引发该消息。由于您没有回复我的 MCVE 请求,并且我无法重现此错误,因此我无法确认是由于通过 Canvas.BeginScene 获取锁定不成功,还是其他原因。

您可以在绘图中使用TImageTPaintBox。使用TImage 提供了许多好处,例如直接加载图像文件、在该图像上绘图以及将图像直接以各种格式保存到文件中,例如.bmp.jpg.png(可能还有其他格式)。 TPaintBox 更轻量级并且没有自己的位图,而是使用父组件表面进行绘制(因此需要 OnPaint() 处理程序)。必须从文件加载/保存到文件,例如通过单独的 TBitmap。

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

您还必须在绘制之前设置TImage.Bitmap.Size。如果这没有在您的代码中的其他地方设置您所显示的内容,那么这可能是您的代码没有生成图像的另一个原因。

Image1.Bitmap.Canvas 上绘制您的图像,例如在按钮的OnClick() 事件中:

procedure TTabbedForm.Button1Click(Sender: TObject);
var
  p1, p2, p3, p4, p5, p6: TPointF;
  prst1: TRectF;
  i: integer;
begin
  Image1.Bitmap.SetSize(300, 300); // must be set before call to BeginScene
  if Image1.Bitmap.Canvas.BeginScene then
  try
    Image1.Bitmap.Canvas.Stroke.Color := TAlphaColors.Black;
    Image1.Bitmap.Canvas.Stroke.Thickness := 1;
    p1 := TPointF.Create(px, py);

    for i := 0 to 360 do
      if (i mod 15) = 0 then
      begin
        pp := i;
        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
    ...

    for i := 0 to 400 do
    ...

    for i := 0 to 400 do
    ....

  finally
    Image1.Bitmap.Canvas.EndScene;
  end;
end;

【讨论】:

    【解决方案2】:

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

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

    小免责声明:我尽可能按原样保留您的代码,只是更改了我认为可能导致您的问题的内容。我认为这些变化都是有道理的,但我必须承认我从来没有在 FMX 中做过很多绘画,所以其中一些可能有点幼稚或过度保护(或明显错误)。

    与您的代码相比,此代码中的不同之处:

    • 使用 TPaintbox(您必须添加一个名为“Paintbox1”的 TPaintbox,并将此方法添加到它的 OnPaint 处理程序中)。油漆盒用于直接绘图。如果您能够在特定事件(例如应用程序启动、单击按钮、计时器等)上预渲染图像的位图,您也可以保留图像。
    • 正确使用 BeginScene 和 EndScene,带有 iftry..finally 块。 BeginScene 会给你一个锁或不给你一个锁,并根据成功返回一个布尔值。只有在实际获得锁时才应该继续,并且也只在这种情况下调用 EndScene,因为它们是 ref 计数的,这样做错误可能会破坏 refcount,因此在您的应用程序中进行所有进一步的绘制。
    • 场景中的描边设置也是如此。不是 100% 确定是否需要,但我想这也是绘制场景的一部分,对吧?
    • 完全忽略了BeginScene..EndScene。 Paintbox 或 Image 控件应该已经调用了它自己。见FMX.Graphics.TCanvas.BeginScene docs
    • 只需使用Canvas。它作为参数传递给事件处理程序,因此最好使用它,然后自己尝试找到合适的画布。
    • 删除了with。这有点远,但看起来您指的是全局 TTabbedForm 变量,并且由于您位于 TTabbedForm 方法中,因此您应该能够按原样使用当前实例的属性和方法, 或在遇到命名冲突时添加 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;
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-06-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多