Thread open forms in Delphi
Asked Answered
R

3

10

I want to create new instances of form(and show them) from a Thread. But it seems that it freeze my application and my thread(my thread becomes an non syncrhonization thread, and it freeze my aplication).

Like this(but it doesn't make what i am looking for)

procedure a.Execute;
var frForm:TForm;
    B:TCriticalSection;
begin
   b:=TCriticalSection.Create;
   while 1=1 do
   begin
     b.Enter;

        frForm:=TForm.Create(Application);
        frForm.Show;
     b.Leave;
     sleep(500); //this sleep with sleep my entire application and not only the thread.
      //sleep(1000);
   end;
end;

I don't want to use Classes.TThread.Synchronize method

Rahr answered 15/3, 2012 at 12:30 Comment(6)
Don't do that. If you want to create forms from threads other than main, send e.g. a message to your already existing window and on its receive create new form.Hachman
I understand that, but there isn't any other method?Rahr
Why do you need another method?Nil
There are the two possibilities, TThread.Synchronize, PostMessage(). One works badly and is easy, the works well and is complicated. Choose your poison..Displode
Don't forget TThread.Queue(), which is like the TThread equivalent to PostMessage() without needing a window.Dispirit
Yes, I've seen TThread.Queue. I will look at it, but I'm not holding my breath. Just the fact that it requires the TThread instance is worrying. I can almost smell that a huge pile of thread micro-management has been added. I would be happy to be proved wrong, but the history of Delphi thread support is, err.. 'non-optimal'.Displode
D
16

You cannot create a notoriously thread-unsafe VCL form in this way, (note - it's not just Delphi - all GUI development I have seen has this restriction). Either use TThread.Synchronize to signal the main thread to create the form, or use some other signaling mechanism like the PostMessage() API.

Overall, it's best to try an keep GUI stuff out of secondary threads, as far as you can. Secondary threads are better used for non-GUI I/O and/or CPU-intensive operations, (especially if they can be split up and be performed in parallel).

PostMessage example, (the form has just one speedbutton on it):

unit mainForm;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, Buttons;

const
  CM_OBJECTRX=$8FF0;

type
  EmainThreadCommand=(EmcMakeBlueForm,EmcMakeGreenForm,EmcMakeRedForm);

  TformMakerThread = class(TThread)
  protected
    procedure execute; override;
  public
    constructor create;
  end;

  TForm1 = class(TForm)
    SpeedButton1: TSpeedButton;
    procedure SpeedButton1Click(Sender: TObject);
  private
    myThread:TformMakerThread;
  protected
    procedure CMOBJECTRX(var message:Tmessage); message CM_OBJECTRX;
  end;

var
  Form1: TForm1;
  ThreadPostWindow:Thandle;

implementation


{$R *.dfm}

{ TForm1 }

procedure TForm1.CMOBJECTRX(var message: Tmessage);
var thisCommand:EmainThreadCommand;

  procedure makeForm(formColor:integer);
  var newForm:TForm1;
  begin
    newForm:=TForm1.Create(self);
    newForm.Color:=formColor;
    newForm.Show;
  end;

begin
  thisCommand:=EmainThreadCommand(message.lparam);
  case thisCommand of
    EmcMakeBlueForm:makeForm(clBlue);
    EmcMakeGreenForm:makeForm(clGreen);
    EmcMakeRedForm:makeForm(clRed);
  end;
end;

function postThreadWndProc(Window: HWND; Mess, wParam, lParam: Longint): Longint; stdcall;
begin
  result:=0;
  if (Mess=CM_OBJECTRX) then
  begin
    try
      TControl(wparam).Perform(CM_OBJECTRX,0,lParam);
      result:=-1;
    except
      on e:exception do application.messageBox(PChar(e.message),PChar('PostToMainThread perform error'),MB_OK);
    end;
  end
    else
      Result := DefWindowProc(Window, Mess, wParam, lParam);
end;

var
  ThreadPostWindowClass: TWndClass = (
    style: 0;
    lpfnWndProc: @postThreadWndProc;
    cbClsExtra: 0;
    cbWndExtra: 0;
    hInstance: 0;
    hIcon: 0;
    hCursor: 0;
    hbrBackground: 0;
    lpszMenuName: nil;
    lpszClassName: 'TpostThreadWindow');

procedure TForm1.SpeedButton1Click(Sender: TObject);
begin
  TformMakerThread.create;
end;

{ TformMakerThread }

constructor TformMakerThread.create;
begin
  inherited create(true);
  freeOnTerminate:=true;
  resume;
end;

procedure TformMakerThread.execute;
begin
  while(true) do
  begin
    postMessage(ThreadPostWindow,CM_OBJECTRX,integer(Form1),integer(EmcMakeBlueForm));
    sleep(1000);
    postMessage(ThreadPostWindow,CM_OBJECTRX,integer(Form1),integer(EmcMakeGreenForm));
    sleep(1000);
    postMessage(ThreadPostWindow,CM_OBJECTRX,integer(Form1),integer(EmcMakeRedForm));
    sleep(1000);
  end;
end;

initialization
  Windows.RegisterClass(ThreadPostWindowClass);
  ThreadPostWindow:=CreateWindow(ThreadPostWindowClass.lpszClassName, '', 0,
      0, 0, 0, 0, 0, 0, HInstance, nil);
finalization
  DestroyWindow(ThreadPostWindow);
end.
Displode answered 15/3, 2012 at 12:39 Comment(4)
Oh - I missed that 'I don't want to use Classes.TThread.Sycnrhonize method' - neither do I! PostMessage a request to the main thread and, in a message-handler, create the form.Displode
Thank you, then I will use TThread.Sycnrhonize method, to solve my problem.Rahr
That means you're not really using a thread at all, dear userX.Lilithe
@Martin; Your idea of a post-message to the main thread and then create and show the form from a uI handler object seems the right approach. TThread.Synchronize is often misunderstood by new-to-thread people as some kind of magic sauce instead of as something that executes code in the foreground thread context while freezing the background thread.Lilithe
D
23

TThread.Synchronize() is the simplest solution:

procedure a.Execute;
begin
  while not Terminated do
  begin
    Synchronize(CreateAndShowForm);
    Sleep(500);
  end;
end;

procedure a.CreateAndShowForm;
var
  frForm:TForm;
begin
  frForm:=TForm.Create(Application);
  frForm.Show;
end;

If you are using a modern version of Delphi and don't need to wait for the TForm creation to complete before letting the thread move on, you could use TThread.Queue() instead:

procedure a.Execute;
begin
  while not Terminated do
  begin
    Queue(CreateAndShowForm);
    Sleep(500);
  end;
end;

Update: If you want to use PostMessage(), the safest option is to post your messages to either the TApplication window or a dedicated window created via AllocateHWnd(), eg:

const
  WM_CREATE_SHOW_FORM = WM_USER + 1;

procedure TMainForm.FormCreate(Sender: TObject);
begin
  Application.OnMessage := AppMessage;
end;

procedure TMainForm.AppMessage(var Msg: TMsg; var Handled: Boolean);
var
  frForm:TForm;
begin
  if Msg.message = WM_CREATE_SHOW_FORM then
  begin
    Handled := True;
    frForm := TForm.Create(Application);
    frForm.Show;
  end;
end;

procedure a.Execute;
begin
  while not Terminated do
  begin
    PostMessage(Application.Handle, WM_CREATE_SHOW_FORM, 0, 0);
    Sleep(500);
  end;
end;

.

const
  WM_CREATE_SHOW_FORM = WM_USER + 1;

var
  ThreadWnd: HWND = 0;

procedure TMainForm.FormCreate(Sender: TObject);
begin
  ThreadWnd := AllocateHWnd(ThreadWndProc);
end;

procedure TMainForm.FormDestroy(Sender: TObject);
begin
  DeallocateHwnd(ThreadWnd);
  ThreadWnd := 0;
end;

procedure TMainForm.ThreadWndProc(var Message: TMessage);
var
  frForm:TForm;
begin
  if Message.Msg = WM_CREATE_SHOW_FORM then
  begin
    frForm := TForm.Create(Application);
    frForm.Show;
  end else
    Message.Result := DefWindowProc(ThreadWnd, Message.Msg, Message.WParam, Message.LParam);
end;

procedure a.Execute;
begin
  while not Terminated do
  begin
    PostMessage(ThreadWnd, WM_CREATE_SHOW_FORM, 0, 0);
    Sleep(500);
  end;
end;
Dispirit answered 15/3, 2012 at 15:41 Comment(3)
+1.2 for Queue, -0.5 for synchronize, I'd vote you up more if you had a postmessage example :-)Jerold
If your version of Delphi has TThread.Queue() then why bother with PostMessage()? They accomplish the same thing, but Queue() does not require an HWND like PostMessage() does. If you use PostMessage() (or even PostThreadMessage()), you have to write extra code in the main thread to handle the post request. With Queue(), the code stays in the thread class instead and you don't have to touch the main thread code.Dispirit
Thanks Remy, that comment was most enlightening. At +1 your post is very much undervoted. I'll just hop off and study the sourcecode for tthread.queue now.Jerold
D
16

You cannot create a notoriously thread-unsafe VCL form in this way, (note - it's not just Delphi - all GUI development I have seen has this restriction). Either use TThread.Synchronize to signal the main thread to create the form, or use some other signaling mechanism like the PostMessage() API.

Overall, it's best to try an keep GUI stuff out of secondary threads, as far as you can. Secondary threads are better used for non-GUI I/O and/or CPU-intensive operations, (especially if they can be split up and be performed in parallel).

PostMessage example, (the form has just one speedbutton on it):

unit mainForm;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, Buttons;

const
  CM_OBJECTRX=$8FF0;

type
  EmainThreadCommand=(EmcMakeBlueForm,EmcMakeGreenForm,EmcMakeRedForm);

  TformMakerThread = class(TThread)
  protected
    procedure execute; override;
  public
    constructor create;
  end;

  TForm1 = class(TForm)
    SpeedButton1: TSpeedButton;
    procedure SpeedButton1Click(Sender: TObject);
  private
    myThread:TformMakerThread;
  protected
    procedure CMOBJECTRX(var message:Tmessage); message CM_OBJECTRX;
  end;

var
  Form1: TForm1;
  ThreadPostWindow:Thandle;

implementation


{$R *.dfm}

{ TForm1 }

procedure TForm1.CMOBJECTRX(var message: Tmessage);
var thisCommand:EmainThreadCommand;

  procedure makeForm(formColor:integer);
  var newForm:TForm1;
  begin
    newForm:=TForm1.Create(self);
    newForm.Color:=formColor;
    newForm.Show;
  end;

begin
  thisCommand:=EmainThreadCommand(message.lparam);
  case thisCommand of
    EmcMakeBlueForm:makeForm(clBlue);
    EmcMakeGreenForm:makeForm(clGreen);
    EmcMakeRedForm:makeForm(clRed);
  end;
end;

function postThreadWndProc(Window: HWND; Mess, wParam, lParam: Longint): Longint; stdcall;
begin
  result:=0;
  if (Mess=CM_OBJECTRX) then
  begin
    try
      TControl(wparam).Perform(CM_OBJECTRX,0,lParam);
      result:=-1;
    except
      on e:exception do application.messageBox(PChar(e.message),PChar('PostToMainThread perform error'),MB_OK);
    end;
  end
    else
      Result := DefWindowProc(Window, Mess, wParam, lParam);
end;

var
  ThreadPostWindowClass: TWndClass = (
    style: 0;
    lpfnWndProc: @postThreadWndProc;
    cbClsExtra: 0;
    cbWndExtra: 0;
    hInstance: 0;
    hIcon: 0;
    hCursor: 0;
    hbrBackground: 0;
    lpszMenuName: nil;
    lpszClassName: 'TpostThreadWindow');

procedure TForm1.SpeedButton1Click(Sender: TObject);
begin
  TformMakerThread.create;
end;

{ TformMakerThread }

constructor TformMakerThread.create;
begin
  inherited create(true);
  freeOnTerminate:=true;
  resume;
end;

procedure TformMakerThread.execute;
begin
  while(true) do
  begin
    postMessage(ThreadPostWindow,CM_OBJECTRX,integer(Form1),integer(EmcMakeBlueForm));
    sleep(1000);
    postMessage(ThreadPostWindow,CM_OBJECTRX,integer(Form1),integer(EmcMakeGreenForm));
    sleep(1000);
    postMessage(ThreadPostWindow,CM_OBJECTRX,integer(Form1),integer(EmcMakeRedForm));
    sleep(1000);
  end;
end;

initialization
  Windows.RegisterClass(ThreadPostWindowClass);
  ThreadPostWindow:=CreateWindow(ThreadPostWindowClass.lpszClassName, '', 0,
      0, 0, 0, 0, 0, 0, HInstance, nil);
finalization
  DestroyWindow(ThreadPostWindow);
end.
Displode answered 15/3, 2012 at 12:39 Comment(4)
Oh - I missed that 'I don't want to use Classes.TThread.Sycnrhonize method' - neither do I! PostMessage a request to the main thread and, in a message-handler, create the form.Displode
Thank you, then I will use TThread.Sycnrhonize method, to solve my problem.Rahr
That means you're not really using a thread at all, dear userX.Lilithe
@Martin; Your idea of a post-message to the main thread and then create and show the form from a uI handler object seems the right approach. TThread.Synchronize is often misunderstood by new-to-thread people as some kind of magic sauce instead of as something that executes code in the foreground thread context while freezing the background thread.Lilithe
R
0

Just use the "TThread.Synchronize" static method, as it is static and public it can be used even outside the thread

TThread.Synchronize(MyThread, procedure begin Myform.Show(); end);

at least in this event, in the others if "MyForm.DoubleBuffered: = true;" you will have no sync problems, but anything can call the "Application.ProcessMessages ();" method in sync.

Raster answered 11/3, 2021 at 12:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.