Search code examples
delphifiremonkey

"Object lock not owned" error while using OnPaint method


I'm trying to draw a simple image with OnPaint method. 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. 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;

Solution

  • 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". 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.

    You can use either a TImage or a TPaintBox for your drawing. 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). 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). Loading from / saving to file must be done e.g. through a separate 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. A TImage has a built in mechanism to paint itself when needed. 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. Also note, that with the TImage you must use BeginScene - EndScene correctly as documented.

    You must also set the TImage.Bitmap.Size before drawing on it. 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 e.g. in a OnClick() event of a button:

    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;