在OnCreate中使用窗体的句柄启动窗体/框架的线程



我有一个问题,我不知道如何解决。

我尝试在OnCreate事件中启动一个线程,或者在创建TFrame后,当它的Parent仍然是nil。当创建线程时,我传递给它一个窗口句柄,但是窗口的地址在例如OnShow事件之后改变。

procedure Form1.OnCreate(Sender: TObject);
begin
TCustomThread.Create(Self);
Label1.Caption := IntToStr(Self.Handle); //for example 10203040 
end;
procedure Form1.ButtonOnClick;
begin
Label1.Caption := IntToStr(Self.Handle); //i give 342545454 not 10203040 
end;
procedure Form1.FromThread(var Msg: TMessage); message WM_TheardComplete;
begin
{do something}
end;
constructor TCustomThread.Create(AWinControl: TWinControl);
begin
inherited Create(False);
FWinControl := AWinControl;
FreeOnTerminate := True;
end;
procedure TCustomThread.Execute;
begin
{do something}
PostMessage(FWinControl.Handle, WM_TheardComplete, 0, 0); //Handle 10203040
end;

我可以使用什么参数来启动线程,以便它以后可以向这个对象发送消息?

TWinControl.Handle属性不是线程安全的。VCL可以并且确实在控件的生存期内动态地重新创建控件的窗口,甚至可以多次重新创建。但更重要的是,窗口具有线程亲和性,对于给定窗口的消息检索和处理只在创建该窗口的线程中工作。使用控件的Handle属性的工作线程会导致一个竞争条件,如果你不小心,实际上可能导致工作线程捕获控件窗口的所有权,使控件在主UI线程中完全无用。

如果你需要给一个工作线程一个发送/发送消息的窗口,给线程一个持久的窗口,VCL不会破坏(没有你告诉它),例如通过使用主TApplication窗口,使用它的OnMessage事件来处理发送的消息,或者它的HookMainWindow()方法来处理发送的消息,例如:

procedure Form1.OnCreate(Sender: TObject);
begin
Application.OnMessage := AppMessage;
TCustomThread.Create(Application.Handle);
end;
procedure Form1.OnDestroy(Sender: TObject);
begin
Application.OnMessage := nil;
end;
procedure Form1.AppMessage(var Msg: tagMSG; var Handled: Boolean);
begin
if Msg.message = WM_TheardComplete then
begin
Handled := True;
{do something}
end;
end;
constructor TCustomThread.Create(AWnd: HWND);
begin
inherited Create(False);
FWnd := AWnd;
FreeOnTerminate := True;
end;
procedure TCustomThread.Execute;
begin
{do something}
PostMessage(FWnd, WM_TheardComplete, 0, 0);
end;

或者更好,使用VCL的AllocateHWnd()函数创建一个新的专用窗口,例如:

procedure Form1.OnCreate(Sender: TObject);
begin
ThreadWnd := AllocateHWnd(ThreadWndProc);
TCustomThread.Create(ThreadWnd);
end;
procedure Form1.OnDestroy(Sender: TObject);
begin
DeallocateHWnd(ThreadWnd);
end;
procedure Form1.ThreadWndProc(var Message: TMessage);
begin
if Message.Msg = WM_TheardComplete then
begin
{do something}
end else
Message.Result := DefWindowProc(ThreadWnd, Message.Msg, Message.WParam, Message.LParam);
end;
constructor TCustomThread.Create(AWnd: HWND);
begin
inherited Create(False);
FWnd := AWnd;
FreeOnTerminate := True;
end;
procedure TCustomThread.Execute;
begin
{do something}
PostMessage(FWnd, WM_TheardComplete, 0, 0);
end;

然而,在您所展示的示例中,而不是在线程执行结束时发送消息,我建议使用一种完全不同的方法-使用TThread.OnTerminate事件代替,它已经与主线程同步,例如:

procedure Form1.OnCreate(Sender: TObject);
var
Thread: TCustomThread;
begin
Thread := TCustomThread.Create;
Thread.OnTerminate := ThreadFinished;
Thread.Start; // or Resume() in older versions
end;
procedure Form1.ThreadFinished(Sender: TObject);
begin
{do something}
end;
constructor TCustomThread.Create;
begin
inherited Create(True);
FreeOnTerminate := True;
end;
procedure TCustomThread.Execute;
begin
{do something}
end;

或者,在现代版本的Delphi中,考虑使用TThread.CreateAnonymousThread()代替,例如:

procedure Form1.OnCreate(Sender: TObject);
var
Thread: TThread;
begin
Thread := TThread.CreateAnonymousThread(
procedure
begin
{do something}
end
);
Thread.OnTerminate := ThreadFinished;
Thread.Start;
end;
procedure Form1.ThreadFinished(Sender: TObject);
begin
{do something}
end;

甚至:

procedure Form1.OnCreate(Sender: TObject);
begin
TThread.CreateAnonymousThread(
procedure
begin
try
{do something}
finally
TThread.Queue(nil,
procedure
begin
{do something}
end
);
end;
end
).Start;
end;

最新更新