"Object lock not owned" 使用 OnPaint 方法时出错
"Object lock not owned" error while using OnPaint method
我正在尝试使用 OnPaint 方法绘制一个简单的图像。代码编译得很好,但是当应用程序启动时,它显示 "Object lock not owned" 错误并且没有其他任何事情发生。你能告诉我我犯了什么错误吗?该代码显示了我正在使用的 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;
我认为您收到了此错误消息,因为您在不允许的时候使用 canvas 绘图。造成这种情况的可能原因是:
- 您正在通过图像的绘制事件在图像的位图上绘图。图像用于显示预先生成或加载的位图,并且由于修改位图应该触发 OnPaint 事件,我认为从同一事件进行这些更改是个坏主意。它要求无限循环或其他不需要的副作用。
- 您使用 BeginScene/EndScene 不正确。只有当 BeginScene return 为 true 时,您才应该继续绘图。实际上,在绘制事件的给定 canvas 上绘制时根本不需要调用它们。
- 您(部分)使用了表单的全局实例而不是当前实例 (Self),这可能(取决于您的应用程序)导致在错误的实例上绘图。
小免责声明:我尽可能按原样保留了您的代码,只是更改了我认为可能会导致您出现问题的内容。我认为这些变化都是有道理的,但我必须承认我从来没有在 FMX 中做过太多绘画,所以也许其中一些有点天真或过度保护(或公然错误)。
此代码与您的不同之处:
- 使用 TPaintbox(您必须添加一个名为 'Paintbox1' 的 TPaintbox,并将此方法添加到它的 OnPaint 处理程序)。颜料盒用于直接绘图。如果您能够在特定事件(例如应用程序启动、单击按钮、计时器等)上预渲染图像的位图,您也可以保留图像。
正确使用 BeginScene 和 EndScene,使用 if
和 try..finally
块。 BeginScene 是否会给你一个锁,return 一个布尔值取决于是否成功。你应该只在你真正获得了锁的情况下继续,并且在这种情况下也只调用 EndScene,因为它们是引用计数的,做错了可能会搞砸引用计数,并因此在你的应用程序中进行所有进一步的绘制。
场景中的描边设置也是如此。不能 100% 确定是否需要,但我想这也是绘制场景的一部分,对吧?
- 完全遗漏了
BeginScene..EndScene
。 Paintbox 或 Image 控件应该已经调用了它自己。参见 FMX.Graphics.TCanvas.BeginScene docs
- 只需使用
Canvas
。它作为参数传递给事件处理程序,所以最好使用它,然后自己尝试找到正确的 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;
错误消息"Object lock not owned"是EMonitorLockException
的消息,记录在案"whenever a thread tries to release the lock on a non-owned monitor"。由于你没有回复我的MCVE请求,我也无法重现这个错误,我无法确认是通过Canvas.BeginScene
获取锁不成功,还是其他原因[=28] =]
您可以为绘图使用 TImage
或 TPaintBox
。使用 TImage
有很多好处,例如直接加载图像文件、在该图像上绘图以及将图像直接保存到各种格式的文件中,例如 .bmp
、.jpg
或 [=18] =](也许其他人也是)。 TPaintBox
更轻量,没有自己的位图,但使用父组件表面进行绘制(因此需要 OnPaint()
处理程序)。从文件加载/保存到文件必须完成,例如通过单独的 TBitmap。
所以是的,如果您愿意,您可以继续使用 TImage 控件,但在那种情况下,不要像现在这样使用 OnPaint
绘图事件。 TImage 有一个内置机制,可以在需要时自行绘制。您只需要将绘图绘制一次到内置位图 canvas。在以下代码中,图像是在 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;
我正在尝试使用 OnPaint 方法绘制一个简单的图像。代码编译得很好,但是当应用程序启动时,它显示 "Object lock not owned" 错误并且没有其他任何事情发生。你能告诉我我犯了什么错误吗?该代码显示了我正在使用的 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;
我认为您收到了此错误消息,因为您在不允许的时候使用 canvas 绘图。造成这种情况的可能原因是:
- 您正在通过图像的绘制事件在图像的位图上绘图。图像用于显示预先生成或加载的位图,并且由于修改位图应该触发 OnPaint 事件,我认为从同一事件进行这些更改是个坏主意。它要求无限循环或其他不需要的副作用。
- 您使用 BeginScene/EndScene 不正确。只有当 BeginScene return 为 true 时,您才应该继续绘图。实际上,在绘制事件的给定 canvas 上绘制时根本不需要调用它们。
- 您(部分)使用了表单的全局实例而不是当前实例 (Self),这可能(取决于您的应用程序)导致在错误的实例上绘图。
小免责声明:我尽可能按原样保留了您的代码,只是更改了我认为可能会导致您出现问题的内容。我认为这些变化都是有道理的,但我必须承认我从来没有在 FMX 中做过太多绘画,所以也许其中一些有点天真或过度保护(或公然错误)。
此代码与您的不同之处:
- 使用 TPaintbox(您必须添加一个名为 'Paintbox1' 的 TPaintbox,并将此方法添加到它的 OnPaint 处理程序)。颜料盒用于直接绘图。如果您能够在特定事件(例如应用程序启动、单击按钮、计时器等)上预渲染图像的位图,您也可以保留图像。
正确使用 BeginScene 和 EndScene,使用if
和try..finally
块。 BeginScene 是否会给你一个锁,return 一个布尔值取决于是否成功。你应该只在你真正获得了锁的情况下继续,并且在这种情况下也只调用 EndScene,因为它们是引用计数的,做错了可能会搞砸引用计数,并因此在你的应用程序中进行所有进一步的绘制。场景中的描边设置也是如此。不能 100% 确定是否需要,但我想这也是绘制场景的一部分,对吧?- 完全遗漏了
BeginScene..EndScene
。 Paintbox 或 Image 控件应该已经调用了它自己。参见 FMX.Graphics.TCanvas.BeginScene docs - 只需使用
Canvas
。它作为参数传递给事件处理程序,所以最好使用它,然后自己尝试找到正确的 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;
错误消息"Object lock not owned"是EMonitorLockException
的消息,记录在案"whenever a thread tries to release the lock on a non-owned monitor"。由于你没有回复我的MCVE请求,我也无法重现这个错误,我无法确认是通过Canvas.BeginScene
获取锁不成功,还是其他原因[=28] =]
您可以为绘图使用 TImage
或 TPaintBox
。使用 TImage
有很多好处,例如直接加载图像文件、在该图像上绘图以及将图像直接保存到各种格式的文件中,例如 .bmp
、.jpg
或 [=18] =](也许其他人也是)。 TPaintBox
更轻量,没有自己的位图,但使用父组件表面进行绘制(因此需要 OnPaint()
处理程序)。从文件加载/保存到文件必须完成,例如通过单独的 TBitmap。
所以是的,如果您愿意,您可以继续使用 TImage 控件,但在那种情况下,不要像现在这样使用 OnPaint
绘图事件。 TImage 有一个内置机制,可以在需要时自行绘制。您只需要将绘图绘制一次到内置位图 canvas。在以下代码中,图像是在 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;