简体   繁体   English

使用OnPaint方法时出现“不拥有对象锁”错误

[英]“Object lock not owned” error while using OnPaint method

I'm trying to draw a simple image with OnPaint method. 我正在尝试使用OnPaint方法绘制一个简单的图像。 The code compiles just fine, but when the application starts, it shows "Object lock not owned" error and nothing else happens. 该代码可以很好地进行编译,但是在应用程序启动时,它会显示“对象锁未拥有”错误,并且什么也没有发生。 Could you please tell me what mistake I made? 你能告诉我我犯了什么错误吗? The code shows the OnPaint event I'm using. 该代码显示了我正在使用的OnPaint事件。 Thank you all for your help. 谢谢大家的帮助。

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;

I think you get this error message, because you're drawing on the canvas at a time when you're not allowed to. 我认为您收到此错误消息,是因为您在不允许的时候在画布上绘画。 Potential causes for this are: 造成这种情况的潜在原因是:

  • You're drawing on the bitmap of the image from the paint event of the image. 您正在从图像的绘画事件中绘制图像的位图。 Images are for displaying pre-generated or loaded bitmaps, and since modifying the bitmap should trigger the OnPaint event, I think it's a bad idea to make those changes from that same event. 图像用于显示预先生成或加载的位图,并且由于修改位图应触发OnPaint事件,所以我认为从同一事件进行这些更改是一个坏主意。 It's asking for an endless loop, or other unwanted side effects. 它要求一个无尽的循环,或其他不必要的副作用。
  • You're using BeginScene/EndScene incorrectly. 您使用的BeginScene / EndScene错误。 You should only proceed drawing if BeginScene returns true. 仅当BeginScene返回true时,才应继续绘制。 And actually it's not needed to call them at all when drawing on the given canvas of a paint event. 实际上,在给定事件的画布上进行绘制时,根本不需要调用它们。
  • You're (partially) using a global instance of the form instead of the current instance (Self), which could (depending on your application), lead to drawing on the wrong instance. 您(部分)使用了表单的全局实例,而不是当前实例(Self),这可能(取决于您的应用程序)导致绘制错误的实例。

Small disclaimer: I left your code as-is as much as possible, just changed the things that I think could potentially cause your problem. 小免责声明:我尽可能地保留了您的代码,只是更改了我认为可能会导致问题的内容。 I think these changes all make sense, but I must admit I've never done much painting in FMX, so maybe some of these are a bit naive or over-protective (or blatantly wrong). 我认为这些更改都是有道理的,但是我必须承认我从未在FMX中做过很多绘画,因此其中一些可能有些天真或过分保护性(或公然错误)。

Things that are different in this code compared to yours: 与您的代码相比,此代码中的不同之处:

  • Use a TPaintbox (you'll have to add a TPaintbox named 'Paintbox1', and add this method to it's OnPaint handler). 使用TPaintbox(您必须添加一个名为'Paintbox1'的TPaintbox,并将此方法添加到它的OnPaint处理程序中)。 Paintboxes are for direct drawing. 颜料盒用于直接绘制。 You could also keep the image, if you would be able to pre-render the image's bitmap on specific events, like the start of your application, a click of a button, a timer, and so on. 如果您能够在特定事件(例如,应用程序的启动,单击按钮,计时器等)上预渲染图像的位图,则还可以保留图像。
  • Correct use of BeginScene and EndScene, with an if and a try..finally block. 正确使用BeginScene和EndScene,并使用 iftry..finally块。 BeginScene will give you a lock or not, and return a boolean depending on the success. BeginScene将为您提供锁定或不锁定,并根据成功返回布尔值。 You should only proceed if you actually acquired the lock, and only call EndScene in that case too, because they are ref counted, and doing this wrong could screw up the refcount, and therefor all further painting in your application. 您仅应在实际获得锁的情况下继续操作,并且在这种情况下也仅调用EndScene,因为它们被引用计数,并且执行此操作可能会增加引用计数,因此在您的应用程序中将进行所有进一步的绘制。
  • Stroke settings inside the scene as well. 以及在场景内笔触设置。 Not 100% sure if needed, but I guess it's part of drawing the scene too, right? 不确定是否需要100%,但我想这也是绘制场景的一部分,对吗?
  • Left out BeginScene..EndScene completely. 完全BeginScene..EndScene The Paintbox or Image control should already have called that itself. Paintbox或Image控件本身应该已经调用了它。 See FMX.Graphics.TCanvas.BeginScene docs 请参阅FMX.Graphics.TCanvas.BeginScene文档
  • Just use Canvas . 只需使用Canvas It's passed as a parameter to the event handler, so better to use that, then to try and find the right canvas yourself. 它作为参数传递给事件处理程序,因此最好使用它,然后自己尝试找到合适的画布。
  • Removed the with . 删除了with This is a bit of a long shot, but it looked like you were referring to a global TTabbedForm variable, and since you are inside a TTabbedForm method, you should be able to use the properties and methods of the current instance as-is, or prepend with Self. 这有点长,但是看起来您是在引用全局TTabbedForm变量,并且由于您位于TTabbedForm方法内,因此您应该能够按原样使用当前实例的属性和方法,或者在Self. if you run into naming conflicts. 如果您遇到命名冲突。 It's always better to not rely on those globals for forms and datamodules, and you'll actually run into problems if you want to have multiple instances of your form, in which case your original code would partially operate on the wrong instance. 最好不要依赖那些用于表单和数据模块的全局变量,并且如果您想拥有多个表单实例,实际上会遇到问题,在这种情况下,原始代码将部分在错误的实例上运行。
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;

The error message "Object lock not owned" is the message of EMonitorLockException , which is documented to be raised "whenever a thread tries to release the lock on a non-owned monitor". 错误消息“对象锁未拥有”是EMonitorLockException的消息, EMonitorLockException ,该记录将在“每当线程尝试释放非拥有的监视器上的锁时引发”。 Since you have not responded to my request for an MCVE, and I have not been able to reproduce this error, I can not confirm whether it is due to an unsuccessful lock aquisition through Canvas.BeginScene , or something else. 由于您尚未响应我对MCVE的请求,并且无法重现此错误,因此我无法确认是否是由于Canvas.BeginScene或其他原因导致的锁Canvas.BeginScene失败。

You can use either a TImage or a TPaintBox for your drawing. 您可以将TImageTPaintBox用于图形。 Using a TImage provides many benefits such as directly loading an image file, drawing on that image and saving your image to a file directly in various formats, like .bmp , .jpg or .png (maybe others too). 使用TImage许多好处,例如直接加载图像文件,在该图像上绘图以及将您的图像直接以.bmp.jpg.png (也可能是其他格式)的各种格式保存到文件中。 A TPaintBox is more lightweight and doesnt have an own bitmap, but uses the parent components surface to draw on (therefore the need for an OnPaint() handler). TPaintBox更轻巧,没有自己的位图,但使用父组件表面进行绘制(因此需要OnPaint()处理程序)。 Loading from / saving to file must be done eg through a separate TBitmap. 从/保存到文件的加载必须通过单独的TBitmap完成。

So yes, you may continue to use a TImage control if you want, but in that case, do not use the OnPaint event for the drawing as you are now. 因此,是的,您可以根据需要继续使用TImage控件,但是在这种情况下,请不要像现在一样使用OnPaint事件作为图形。 A TImage has a built in mechanism to paint itself when needed. TImage具有内置的机制,可以在需要时进行自我绘制。 You only need to draw your drawing once to the built-in bitmap canvas. 您只需要在内置位图画布上绘制一次图形即可。 In the following code the image is drawn in a ButtonClick() event. 在以下代码中,图像是在ButtonClick()事件中绘制的。 Also note, that with the TImage you must use BeginScene - EndScene correctly as documented. 另请注意,必须使用TImage正确记录BeginScene - EndScene

You must also set the TImage.Bitmap.Size before drawing on it. 您还必须在绘制之前设置TImage.Bitmap.Size If this was not set elsewhere in your code of what you have shown, then that may be another reason why your code produced no image. 如果未在显示的代码中的其他位置设置此值,则可能是代码未生成图像的另一个原因。

Draw your image on Image1.Bitmap.Canvas eg in a OnClick() event of a button: 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;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM