Go Premium for a chance to win a PS4. Enter to Win

x
  • Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 558
  • Last Modified:

make a form work in a thread

Hi, everyone

I need some Delphi codes to make a form work in a thread and the form also can communicate with main form.

Thanks
0
wangzheng
Asked:
wangzheng
3 Solutions
 
Russell LibbySoftware Engineer, Advisory Commented:

Delphi forms are not thread safe and should not be created by secondary threads. That is not to say they can't perform a body of work using a secondary thread. You should re-target your question in regards to the work that is to be done by the secondary form, and how threads can be used to perform that work (while keeping the Form UI responsive).

Regards,
Russell
0
 
TheRealLokiSenior DeveloperCommented:
If you are just using this form so you can put components on it, then try using a Datamodule instead of a form.
You can use that inside a thread
Although it's still a bit of an overkill, it's a quick solution if you do not want to change your code much
If you want buttons, labels, etc. then a thread is not the way to go

If you just wanted to show 2 forms at the same time, then do a Show and not a ShowModal
you can set the FormStyle to fsStayOnTop if you want it to appear over the other form
0
 
RikBarkerCommented:
I think you're not asking the right question.  I'll try to explain why and then I'll try to answer it anyway.

A thread goes off and does stuff for you in the background.  Like ordering a meal in a restaurant, you don't want to sit completely still until the food arrives back, you want to chat and drink wine and maybe even scratch your nose.   You also don't want the waiter to run up to the table every 2 minutes and say "your meal is nearly ready!"

A thread works in the background, a form is foreground so making a form work in a thread is the wrong way round.  make a thread work for a form is better.

So, that aside, the main part of your problem is probably to do with showing progress for something I'm guessing - you're not very clear.  There are several ways you can get a thread to let a form know what it's doing.

Method 1.
  Set properties of your thread so you can reference them in a timer on the form.  This is nice and simple, but does have the problem that your form will only look every now and again, not necessarily at the moment something happens in your thread.

type
  TYourThread=class(TThread)
  private
    FCurrentAction: string;
  public
    property CurrentAction:string read FCurrentAction;  
  end;

Now in your thread, at regular points you can set FCurrentAction to state what's happening and your form can read it.

in the thread:  FCurrentAction:='Doing cool stuff';
in the main form, on a timer:
  Label1.Caption:=MyThread.CurrentAction;

That allows you to update the form and the thread never has to lock the main thread to do it.  The downside, as I mentioned, is that your form may not read the action when you want it to...

which leads to events and synchronisation.

Synchronisation is where you force your thread to act in the context of the main thread.
You do it by calling a method of your thread in the synchronize() function.

Because you're in the context of the main thread, it's ok to update the onscreen variables.  Because it's a method, it can't have any variables, so if you want to pass
any info back to the main form, you're doing it through properties again.

TYourThread=class(TThread)
private
  procedure DoUpdateProgress;
public
  procedure Execute; override;
  property CurrentAction:....
end;

procedure TYourThread.Execute;
begin
  FCurrentAction:='Starting to do something thready';
   Synchronise(DoUpdateProgress);
   ... Do some stuff here
   FCurrentAction:='Done doing funky stuff';
   Synchronize(DoUpdateProgress);
end;

procedure TYourThread.DoUpdateProgress;
begin
  YourForm.Label1.Caption:=FCurrentAction.
end;

That's how to do it, now here's why you shouldn't:  It's an awful solution to the problem.  
1) You're taking over the main thread to do it - might make your app unresponsive.
2) Your thread now references your main form - hardly Object oriented coding, definitely not portable.
3) there are better ways.

2 better ways (IMO) exist.  They both have their upsides and downsides.  The first is events.  Make an event in your thread that will fire in your form.  If you don't need to update the User interface, this can be great... be aware though that your code will be called in the context of the thread, so you must leave the UI alone.

TYourThread=class(TThread)
private
  FOnProgress: TNotifyEvent;
  procedure DoUpdateProgress;
public
  procedure Execute; override;
  property CurrentAction:....
  property OnProgress: TNotifyEvent read FOnProgress write FOnProgress.
end;

procedure TYourThread.Execute;
begin
  FCurrentAction:='Starting to do something thready';
  if Assigned(FOnProgress) then
    FOnProgress(Self);
   ... Do some stuff here
end;

Attach it in your form when you create the thread...
FMyThread:=TYourThread.Create(True);
FMyThread.OnProgress:=OnThreadProgressEvent;

and add a method to your form to handle it...
YourForm=class(TForm)
private
   FMyThreadHistory: TStringList;
   FMyThread: TYourThread;
   procedure OnThreadProgressEvent(Sender: TObject);
...

procedure TYourForm.OnThreadProgressEvent(Sender:TObject);
begin
  //Note to self:  I'm still in the context of the thread - don't touch the UI!
  MyThreadHistory.Add(DateTimeToStr(Now)+' '+FMyThread.CurrentAction);
end;

That's events.  Very useful if you don't need to update the UI, great for responding to things the moment they happen in your thread, but not great if you want onscreen notification when something happens.

For that, you need the final type of communication, windows messages.  The nice thing about a message is that it sends a message to the main form, so the form doesn't have to check regularly with a timer.  It happens in the message queue of the form's thread, so you can update any UI you want, and it's not blocking like Synchronize() is.

If your form passes its window handle to the thread when it creates it, you can use SendMessage and PostMessage to inform the form that something has just happened.

const
  WM_MYPROGRESS_MESSAGE=WM_USER +1;

TYourThread=class(TThread)
private
  FNotifyHandle: Hwnd;
  FOnProgress: TNotifyEvent;
  procedure DoUpdateProgress(const Msg: string);
public
  procedure Execute; override;
  property CurrentAction:....
end;

procedure TYourThread.Execute;
begin
   DoUpdateProgress('Beginning');
   ... Do some stuff here
  DoUpdateProgress('Something else');
  ... More stuff
  DoUpdateProgress('Done!');
end;

procedure TYourThread.DoUpdateProgress(const Msg: string);
begin
  FCurrentAction:=Msg;
  if FNotifyHandle <> 0 then
  begin  
    PostMessage(FNotifyHandle,WM_MYPROGRESS_MESSAGE,0,0);
    //You can also use SendMessage here - have a read and work out which is best for
    //you.
  end;
end;

in your form, you would usually catch this in your form's WndProc, or if the message is declared such that both the form and the thread can access it, you can do it like this;

TYourForm=class(TForm)
private
  procedure MyThreadProgressEvent(var Msg: TMessage); message WM_MYPROGRESS_MESSAGE;
end;

procedure TYourForm.MyThreadProgressEvent(var Msg: TMessage);
begin
  Label1.Caption:=FMyThread.CurrentAction;
  Showmessage('Hooray - I'm in the main thread from a form!');
end;

Hope that helps.
0

Featured Post

Free Tool: SSL Checker

Scans your site and returns information about your SSL implementation and certificate. Helpful for debugging and validating your SSL configuration.

One of a set of tools we are providing to everyone as a way of saying thank you for being a part of the community.

Tackle projects and never again get stuck behind a technical roadblock.
Join Now