As I told you in alt.comp.lang.borland-delphi earlier today, the problem is that Indy runs its event handlers in the same thread that does the blocking socket calls, which is not the same thread as your GUI. All GUI operations must take place in the same thread, but you are creating a new window in the socket thread.
To solve it, your event handler should post a notification to the main thread, which the main thread will handle asynchronously whenever it happens to next check for messages.
If you have a recent-enough Delphi version, you could try the TThread.Queue method, which works a lot like Synchronize, except the calling thread doesn't block waiting for the main thread to run the given method. They both have the same limitation regarding their method parameters, though; they only accept a zero-parameter method. That makes it cumbersome to transfer extra information for the method to use when it's eventually called. It's particularly bad for queued methods since whatever extra data you provide for them must remain intact for as long as it takes for the main thread to run it; the calling thread needs to make sure it doesn't overwrite the extra data before the queued method gets called.
A better plan is probably to just post a message to some designated window of the main thread. Application.MainForm is a tempting target, but Delphi forms are liable to be re-created without notice, so whatever window handle your other threads use might not be valid at the time they try to post a message. And reading the MainForm.Handle property on demand isn't safe, either, since if the form has no handle at the time, it will get created in the socket thread's context, which will cause all sorts of problems later. Instead, have the main thread create a new dedicated window for receiving thread messages with AllocateHWnd.
Once you have a target for messages to go to, you can arrange for threads to post and receive them. Define a message value and post them with PostMessage.
am_NewQuery = wm_App + 1;
PostMessage(TargetHandle, am_NewQuery, ...);
To send the extra data the recipient will need to fully handle the event, messages have two parameters. If you only need two pieces of information, then you can pass your data directly in those parameters. If the messages need more information, though, then you'll need to define a record to hold it all. It could look something like this:
PNewQuery = ^TNewQuery;
TNewQuery = record
Prepare and post the message like this:
procedure NewQuery(const Server, MsgFrom: string);
Data.Host := Server;
Data.FromNickname := MsgFrom;
PostMessage(TargetHandle, am_NewQuery, 0, LParam(Data));
Note that the caller allocates a new record pointer, but it does not free it. It will get freed by the recipient.
class procedure TSomeObject.HandleThreadMessage(var Message: TMessage);
case Message.Msg of
NewQueryData := PNewQuery(Message.LParam);
Child := TFrmMessage.Create(NewQueryData.Host, NewQueryData.FromNickname);
TN := GetNodeByText(ChanServTree, NewQueryData.Host, True); // Find parent node
with ChanServTree.Items.AddChild(TN, NewQueryData.FromNickname) do begin
Selected := True;
Tag := 2; // TYPE OF QUERY
Data := Child; // reference to form we created
Message.Result := DefWindowProc(TargetHandle, Message.Msg, Message.WParam, Message.LParam);
I've made a couple of other changes to your code. One is that I made the child form's constructor accept the two pieces of information it needs to create itself properly. If the form wants its caption to be the nickname, then just tell it the nickname and let the form do whatever it needs to with that information.
According to what I'm reading, AllocateHWnd is not thread safe. Check this out: 17slon.com/blogs/gabr/2007/06/ still looking at that link and trying to reconcile both the fact that I'm in uncharted territory (personally) and your code with the code on that page.
Wow I'm lost. I'll sleep on this one and come back.
You're right. AllocateHWnd is not thread-safe. So only call it from the main thread when your program starts up, before you've created all the other threads. The function is not thread-safe, but the window handle it returns can be used from whatever threads you want. The handle is associated with the thread that created it.
So to use AllocateHWnd do I just create a new Type of Form (under type) then call it from say... my main form's oncreate?