Solved

Creating controls on main form from a DLL

Posted on 1997-09-04
12
594 Views
Last Modified: 2010-04-04
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;
begin
   BB := TButton.Create( UserOutput );
   with UserOutput.ClientRect do begin
     BB.Left := Left + 5;
     BB.Top := Top + 35;
   end;
   BB.Font.Style := [];
   BB.Font.Size := 10;
   BB.Parent := UserOutput;
   BB.Caption := 'A PB';
   BB.Name := 'A_PB';
end;

This function is called like this:

procedure TForm1.Button3Click(Sender: TObject);
begin
     MakePB(Panel1);
(etc)

Any ideas???

(Note: The nature of my application is such that I can't create a Form inside the DLL for the button ...)
0
Comment
Question by:sonique
12 Comments
 
LVL 2

Expert Comment

by:icampbe1
ID: 1343800
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.
0
 

Author Comment

by:sonique
ID: 1343801
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.

0
 
LVL 1

Expert Comment

by:Gabor
ID: 1343802
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

Gabor
0
 

Author Comment

by:sonique
ID: 1343803
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?

Paul
0
 
LVL 5

Accepted Solution

by:
JimBob091197 earned 200 total points
ID: 1343804
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;
var
   NewButton: TButton;
begin
   NewButton := TButton.Create(nil);
   NewButton.Caption := '&Hello';
   NewButton.ParentWindow := UserOutputWnd;
   Result := NewButton;
end;

Note:
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:
AButton.Free;

(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.
0
 

Author Comment

by:sonique
ID: 1343805
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

Paul
0
How your wiki can always stay up-to-date

Quip doubles as a “living” wiki and a project management tool that evolves with your organization. As you finish projects in Quip, the work remains, easily accessible to all team members, new and old.
- Increase transparency
- Onboard new hires faster
- Access from mobile/offline

 
LVL 5

Expert Comment

by:JimBob091197
ID: 1343806
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!!
Dave
0
 

Author Comment

by:sonique
ID: 1343807
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!

Paul
0
 
LVL 5

Expert Comment

by:JimBob091197
ID: 1343808
Welcome.  Keep me posted.
0
 

Author Comment

by:sonique
ID: 1343809
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 .....
0
 
LVL 5

Expert Comment

by:JimBob091197
ID: 1343810
Hi

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.

Dave
0
 

Author Comment

by:sonique
ID: 1343811
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!

Paul
0

Featured Post

What Is Threat Intelligence?

Threat intelligence is often discussed, but rarely understood. Starting with a precise definition, along with clear business goals, is essential.

Join & Write a Comment

Suggested Solutions

This article explains how to create forms/units independent of other forms/units object names in a delphi project. Have you ever created a form for user input in a Delphi project and then had the need to have that same form in a other Delphi proj…
In this tutorial I will show you how to use the Windows Speech API in Delphi. I will only cover basic functions such as text to speech and controlling the speed of the speech. SAPI Installation First you need to install the SAPI type library, th…
Get a first impression of how PRTG looks and learn how it works.   This video is a short introduction to PRTG, as an initial overview or as a quick start for new PRTG users.
This video shows how to remove a single email address from the Outlook 2010 Auto Suggestion memory. NOTE: For Outlook 2016 and 2013 perform the exact same steps. Open a new email: Click the New email button in Outlook. Start typing the address: …

707 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

13 Experts available now in Live!

Get 1:1 Help Now