brecho
brecho

Reputation: 75

"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;

Upvotes: 0

Views: 2690

Answers (3)

Manfred
Manfred

Reputation: 1

I experienced the same error message and in my case the cause was the missing "bitmap.setsize' as Tom Brunberg pointed out:

Image1.Bitmap.SetSize(300, 300); // must be set before call to BeginScene

Upvotes: 0

GolezTrol
GolezTrol

Reputation: 116160

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. It's asking for an endless loop, or other unwanted side effects.
  • You're using BeginScene/EndScene incorrectly. You should only proceed drawing if BeginScene returns 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.

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).

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). 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 will give you a lock or not, and return a boolean depending on the success. 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.
  • Stroke settings inside the scene as well. Not 100% sure if needed, but I guess it's part of drawing the scene too, right?
  • Left out BeginScene..EndScene completely. The Paintbox or Image control should already have called that itself. See FMX.Graphics.TCanvas.BeginScene docs
  • Just use 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. 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. 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;

Upvotes: 1

Tom Brunberg
Tom Brunberg

Reputation: 21045

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;

Upvotes: 2

Related Questions