Delphi 动态释放聚焦的自定义控件会导致崩溃
我编写的一个自定义控件在被销毁时与崩溃有关。很难确定确切的情况,这可能是该控件由第三方控件作为父控件的一个因素 编辑2014年10月8日 我现在得到了一个更好的SSCCE,它仅使用TForm上的TMediaPlayer(来自Delphi VCL)演示了崩溃。所以我删除了很多我以前写的东西。请查看编辑历史记录。(事实证明,前一个调用堆栈中的CM_出口是一个骗局。) 以下是SSCCE:Delphi 动态释放聚焦的自定义控件会导致崩溃,delphi,delphi-xe2,Delphi,Delphi Xe2,我编写的一个自定义控件在被销毁时与崩溃有关。很难确定确切的情况,这可能是该控件由第三方控件作为父控件的一个因素 编辑2014年10月8日 我现在得到了一个更好的SSCCE,它仅使用TForm上的TMediaPlayer(来自Delphi VCL)演示了崩溃。所以我删除了很多我以前写的东西。请查看编辑历史记录。(事实证明,前一个调用堆栈中的CM_出口是一个骗局。) 以下是SSCCE: unit Unit1; interface uses System.Classes, Vcl.Controls
unit Unit1;
interface
uses
System.Classes, Vcl.Controls, Vcl.Forms, Vcl.Menus, Vcl.MPlayer;
type
TForm1 = class(TForm)
MainMenu: TMainMenu;
CrashMenuItem: TMenuItem;
procedure CrashMenuItemClick(Sender: TObject);
procedure FormShow(Sender: TObject);
private
fControl : TMediaPlayer;
end;
var
Form1: TForm1;
implementation
uses
Vcl.Dialogs;
{$R *.dfm}
procedure TForm1.CrashMenuItemClick(Sender: TObject);
begin
ShowMessage('Message');
fControl.Free;
end;
procedure TForm1.FormShow(Sender: TObject);
begin
fControl := TMediaPlayer.Create(Form1);
fControl.Parent := Form1;
end;
end.
在释放控件之前立即调用ShowMessage
,这一点至关重要
TMediaPlayer
控件将获得一个WM\u SETFOCUS
TCustomControl.Destroy
释放画布,然后继承的TWinControl.Destroy
调用TWinControl.RemoveFocus
,因此它获得一个WM\u KILLFOCUS
TMediaPlayer.WMKillFocus
直接调用Paint
,试图使用释放的画布并崩溃CMFocusChanged
名为Invalidate
。效果相同,但调用堆栈更复杂。)
我最初的3个问题,NGLN已回答如下:
FreeAndNil(fMyControl)
,我是否做错了什么?我必须先把它拆开再销毁吗?但这对于任何其他控件来说似乎都是不必要的,因此更可能只是隐藏了潜在的bugTWinControl
知道不尝试重新绘制它TWinControl.Update
。)TCustomControl
的任何后代身上。为了方便起见,我使用了TMediaPlayer
。)
仅仅调用FreeAndNil(fMyControl)
,我是否做错了什么
不,只要清除(nilled)对控件的所有引用并且实例的代码不再运行,每个控件都应该能够在任何给定的时间被释放
我必须先把它拆开再销毁吗?但这对于任何其他控件来说似乎都是不必要的,因此更可能只是隐藏了潜在的bug
不,确实没有必要
我的控件的析构函数中是否应该有一些东西来修复此问题,以便TWinControl
知道不尝试重新绘制它
不,通常没有必要。VCL已经内置了这一切。出于测试目的或作为(临时)解决方法,您可以尝试使用类似于的内容覆盖PaintWindow
,如果不是(组件状态下的csDestroying),则覆盖
第三方父控件中是否存在错误?是不是我的控件一旦开始被销毁就肯定不会收到WM_PRINTCLIENT
消息?(第三方控件似乎对其继承的TWinControl进行了显式调用。由于我的控件失去焦点,在收到CM_EXIT
时更新。)
父控件确实接收到CM\u EXIT
,因为它有一个聚焦控件,而现在不再有了(即Form.ActiveControl=nil
)。所以这是正常的行为。至于家长为什么向控件发送WM_PRINTCLIENT
(您如何知道该请求来自家长?它似乎从Update
调用开始)我不知道。要排除出现问题家长的可能性,请使用其他家长重试您的案例
更新(由于编辑问题):
TMediaPlayer.WMKillFocus
直接调用Paint
这是禁忌!这绝对是VCL中的一个bug<除了通过WM_Paint
消息请求绘制外,不得直接调用code>Paint
。我有
(以前我有一个自定义控件,其中CMFocusChanged
名为Invalidate
。效果相同,但调用堆栈更复杂。)
(顺便说一句,同样的问题也会发生在TCustomControl
的任何后代身上。为了方便起见,我使用了TMediaPlayer
。)
D7和XE2中的测试并非如此。是否从控件内的事件处理程序(例如OnClick)调用FreeAndNil?@bummi FreeAndNil(间接)从TAction事件处理程序调用。如果这是您的意思,那么就不要单击正在销毁的控件。VCL控件在其自身失去焦点时接收CM\u EXIT
,而不是在子控件失去焦点时。所以问题是,为什么tlmdockpanel
接收CM_退出
DefocusControl()
只有一个代码路径,即如果要散焦的控件是窗体的ActiveControl
。它调用TCustomForm.SetWindowFocus()
和TCustomForm.ActiveChanged()
,我在堆栈跟踪中看不到这两个函数。不能释放控件。而是使用Release
,然后将引用设置为nil。控件将在安全运行时释放自身。@NGLN:DockPanel不是开始时的焦点控件,因此它不应该接收CM\u EXIT
,除非它先获得焦点,然后又失去焦点。Update()
如果正在更新的控件的任何部分事先已无效,则会触发重新绘制。所以也许
procedure TMediaPlayer.WMKillFocus(var Message: TWMKillFocus);
begin
Paint;
end;