Creating controls on main form from a DLL

I have a situation where I need to create objects on the main applications form (or a panel) from inside a DLL. When I create a button in the manner described below, I experience these 3 problems:

1) the button is not displayed until I make the panel invisible then visible again
2) If I TAB to teh button, I generate the 'EInvalid Operation' exception with the message 'Control 'BB' has no parent window'
3) The object will not get destroyed when the application exits

The prime area of my concern is the second one. The strange thing is that if I use EXACTLY the same code inside the main application, none of these problems are evident!

I have drilled down through the Parent panels properties to compare all properties of the button and compared them to when it is created from inside teh main application, and can find no significant variation. In particular, the button's FParent property is set to the Panel!

Here is the code which I used inside the DLL to create the button:

procedure MakePB( UserOutput:TWinControl ); stdcall;
   BB := TButton.Create( UserOutput );
   with UserOutput.ClientRect do begin
     BB.Left := Left + 5;
     BB.Top := Top + 35;
   BB.Font.Style := [];
   BB.Font.Size := 10;
   BB.Parent := UserOutput;
   BB.Caption := 'A PB';
   BB.Name := 'A_PB';

This function is called like this:

procedure TForm1.Button3Click(Sender: TObject);

Any ideas???

(Note: The nature of my application is such that I can't create a Form inside the DLL for the button ...)
Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

At first glance, my reaction was to have you set the BB.Parent property before you set the other stuff (still a good idea).   I believe a TWinControl's Owner should be the Form, not the panel.  The 'Parent' is the panel, the form is the 'Owner'.  If you know the panel (UserOutput) has the form as the owner, then BB.Create( UserOutput.Owner ) will set that correctly.  Then set the parent as UserOutput like you are already doing.
Now, as for the object's destruction you might consider using the Form's InsertComponent method instead.  This will have the component inserted in the Form's component array and consequently destroyed when the form cleans up.

Hope this helps,
Ian C.
soniqueAuthor Commented:
Thanks for the reply, but ....

Setting the Parent before setting the font results in: "ConvertError with message 'Cannot assign a TFont to a TFont'". This is why I placed it near the end (note that this does not happen if compiled into the main program).

Setting the owner to the form I had tried previously to no affect. Using the UserOutput.Owner.InsertComponent method does not seem to make any difference either!

This is all obviously something to do with the DLL accessing the main App's variables in a different manner - but I have no idea what it is. I even tried passing the main Apps Application.Handle and setting it to that value in the DLL to no avail.

The problem is that objects in your dll is not the same as in your app (because they are diff. .exes), so you have to make controls with your app.
To do this, write an exported procedure in your app. This proc. will make the controll.
When you call your dll pass the addres of this proc., so your dll can make controls, as it's code was in the app.

Good luck

Build an E-Commerce Site with Angular 5

Learn how to build an E-Commerce site with Angular 5, a JavaScript framework used by developers to build web, desktop, and mobile applications.

soniqueAuthor Commented:
Thanks for the suggestion, but unfortunately creating the control in the app itself is not an option because it defeats the whole purpose of the DLL. I used a pushbutton as a simple example, but in actual fact the DLL could create any sort of control, and the main APP (by design) does not know ahead of time what sort of control the DLL will create!
I have designed an architecture whereby the main app acts as a sort of interpreter, and a collection of DLL's act as "function Blocks". Some of these DLL's do non-visual work. For example one DLL is a multi-threaded comms manager for an RS422 network. Another provides printer control and status monitoring functions (displayed on a seperate form created by the DLL so that's no problem). Some of the DLL's will create controls on a common form, and the button is just an example.
There has to be a way to make the main App's VCL thread completely aware of the control - all other functionality works OK (such as mouse clicks, button text display, etc.), so why not keyboard control??

Any other takers?

I had a similar problem before, and found no suitable solution.  There is a way around the problem as follows:

In your DLL you currently declare:
procedure MakePB( UserOutput:TWinControl );

I changed this to:
function MakePB(UserOutputWnd: THandle): TButton;
   NewButton: TButton;
   NewButton := TButton.Create(nil);
   NewButton.Caption := '&Hello';
   NewButton.ParentWindow := UserOutputWnd;
   Result := NewButton;

1)  MakePB expects UserOutputWnd instead of UserOutput.
2)  No owner was given to NewButton, and I assigned NewButton.ParentWindow (NOT .Parent).
3)  I made it a function which returns a button.  The form (in my e.g.) later freed the button.

In your form you now call:
AButton := MakePB(Panel1.Handle);

In your form's Destroy event:

(You can ignore the font stuff without worrying about the "Cannot assign a TFont to a TFont" error.)

All this works fine, but there are several caveats.  I mentioned above that I never found a SUITABLE solution.  Here's why:
1) In your form, after the call to MakePB, try assigning AButton.OnClick := .....;
The OnClick event will be ignored...  (However, AButton.Caption := '&Hit Me' will change the caption!!)

2) Instead of a TButton, try a control with an Align property (e.g. TListBox).  The listbox will not be resized when Panel1 is resized.

There are a few other disadvantages too, but I'll let you experiment...  You may find many solutions that I missed.

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
soniqueAuthor Commented:
Sounds like it will do the trick ... but there doesn't seem to be a ParentWindow property ... I even searched the VCL source!

Which version of Delphi are you talking about? I'm using v2.02

Mmm... I'm using Delphi 3.  

The VCL reveals that setting the ParentWindow property uses the Windows SetParent API call.  You could try this.  

e.g.  SetParent(MyButton.Handle, MyPanel.Handle);

Hope this works for you!!
soniqueAuthor Commented:
I'll try that when I'm back at work on Monday .... if It doesn't work I'll have to use a different approach to the problem.

Thanks for the help!

Welcome.  Keep me posted.
soniqueAuthor Commented:
Well, no luck I'm afraid. If you look at the Win32 API Help, SetParent() is used to change Parents. When I try this approach I get an exception raised:

EInvalid Operation. Message 'Control '' has no parent window'

Back to the drawing board .....

The following code will give that exception:
AButton := TButton.Create(nil);
Windows.SetParent(AButton.Handle, APanel.Handle);

The reason for the exception is because at the time that you call "SetParent", AButton does not have a handle. (Put a break on the SetParent line, and look at the value of AButton.Handle.)  Delphi calls CreateHandle, which in turn calls CreateWnd.  CreateWnd raises that exception if the parent does not exist.

In Delphi 3, the call to AButton.ParentWindow (i.e. TButton.SetParentWindow in the VCL) only calls the SetParent API if there is an existing parent window.  If not, then the FParentWindow property is set, which will be used when the handle is created (actually in CreateParams).

That is why the following code does work in Delphi 3, but the code above does not work in Delphi 2 or 3:
AButton := TButton.Create(nil);
AButton.ParentWindow := APanel.Handle;

It OFTEN frustrates me that so much functionality in Delphi has been hidden in the Private sections of the classes!!  (Although many would say that this is required by the Object Oriented approach...)  The trick is to set the FParentWindow value (or the equivalent in Delphi 2), or to get Delphi to create the handle for you (via a call to HandleNeeded or something.)

I'll ask a colleague of mine who uses Delphi 2 when he gets in later.  He may have some suggestions.

soniqueAuthor Commented:
Thanks ....
I'm working on another problem today (DOS C Code), so I'll wait to see if you come up with anything new before I have another look at this.

I appreciate the assistance by the way!

It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today

From novice to tech pro — start learning today.