Link to home
Start Free TrialLog in
Avatar of mheacock
mheacock

asked on

DESPERATE: Assigning Panel.Parent in DLL to TabSheet in EXE

First, sorry about the cross-posting, but the question seems
relevant to both newsgroups.  :)

Here's what I am doing:

1)  Creating a DLL that contains a Form.  On the form is a Panel
    that contains a few buttons and a PageControl with 2 or more
    tabsheets.

2)  Creating an EXE that contains a PageControl with 2 or more
    tabsheets.

You can view the EXE as the module level and the DLLs as the
sub-module levels.

What I am doing is calling a function in the DLL passing in
a pointer to a TTabSheet in the EXE.  The DLL then assigns the
Parent property of the Panel to the EXE's tabsheet.

This works wonderfully, except for the PageControl on the
DLLs panel.

If the PageControl on the DLLs panel has more than one tabsheet,
when I click on tabs once it is in the EXEs PageControl, the
tabbing functionality no longer works as expected.  When the
PageControl is created within the EXE, all looks normal until
I click on another tab.  The tab I click comes the the forefront,
but the tabsheet associated with the tab I clicked does not appear.
The tabsheet that originally appeared upon transfer (via the
parent property) is still visible.

This whole process works as you would expect if I am doing all the
transferring within a single EXE...between two forms.  But doing
it between an EXE and a DLL causes unexpected problems.

There is something going on that I'm not aware of and I desperately
need some information or a solution.  Hopefully someone from TeamB
can help.

Here are some code snippets:

--------------------from the DLL----------------------------------
void __fastcall TSatisTestModule::TransferPanelFromDLL()
{
  pnlContainer->Parent = winPage;
}

void __fastcall TSatisTestModule::TransferPanelToDLL()
{
  pnlContainer->Parent = this;
}
//---------------------------------------------------------------------------
//                 DLL Entry Points
//---------------------------------------------------------------------------
void LoadTestModule(TWinControl* i_winPage)
{
  SatisTestModule = new TSatisTestModule(NULL);
  SatisTestModule->Page = i_winPage;
  SatisTestModule->TransferPanelFromDLL();
}

void DestroyTestModule()
{
  SatisTestModule->TransferPanelToDLL();
  if(SatisTestModule->ShutDownCallback != NULL)
  {
    SatisTestModule->ShutDownCallback(SatisTestModule->Page);
  }
  delete SatisTestModule;
}


-------------------from the EXE------------------------------------

void __fastcall TfrmSatisMain::mnuClientLocateSSNClick(TObject *Sender)
{
  FLoadModuleCB LoadModule;
  HINSTANCE hDLL = LoadLibrary("Test");
  if(hDLL != NULL)
  {
    LoadModule = (FLoadModuleCB)GetProcAddress(hDLL, "_LoadTestModule");
  }
  if(LoadModule)
  {
    // tabTest is of type TTabSheet
    LoadModule(tabTest);
  }
}
Avatar of mirek071497
mirek071497

Hi

3 months ago I was answer your question for 400pt. I work hard, but you don't respond to this. Do you grade this new question in any time or I loose my time the second once ?

Mirek.
Avatar of mheacock

ASKER

I don't recall what you are talking about...but I do plan on
grading this promptly...

A lot of things got lost in the shuffle...I moved 1.5 months
ago from BC Canada to California USA to a new job.

What was my last question that you are referring to?  Did it
have something to do with...oh right...bitmap lists or some such...I do recall that I never did use the suggestions I
received...I believe I took another totally different
view on the problem.

Anyhow, I do apologize for whatever problems I may have caused.
Like I said, I went through a big move and things got all
shuffled about.

Anyhow, I never had any intention of stiffing you out of 400
points.  Sorry for the problems.
Hi

I once had a problem with assigning the Parent of a control in a DLL to one in an EXE.  I got around the problem by using ParentWindow (in Delphi 3).  
E.g. MyDLLControl.ParentWindow := MyExeControl.Handle;

My situation was a bit different from yours, but maybe you can use this...

JB
I'm using C++Builder...which is equivalalent to Delphi2.

I'm only interested in C++Builder code or Delphi2 code.

Thanks for the comment tho.
Mmm...  I seem to recall that C++ Builder doesn't have a ParentWindow property anyway.

Adjusted points to 1500
I was playing around with this, and managed to recreate your scenario, I think.  This is what I did, in Delphi (3):
A EXE, which consists of:
    Form, with TPageControl, with 2 TTabSheets.

A DLL, which consists of:
    Form, with Panel.  Panel has its own TPageControl with 2 TTabSheets.

I got the DLL's Panel onto the Form's PageControl's TabSheet, and (as you said) no matter which DLL TabSheet I click on, it only shows the first TabSheet.

The following Delphi 3 code fixes the problem.  (It is implemented in the DLL on TPageControl.OnChange.)

procedure TDLLForm.PageControl1Change(Sender: TObject);
var
    Tab: TTabSheet;
begin
    Tab := PageControl1.ActivePage;

    Tab.Parent := nil;
    Tab.ParentWindow := PageControl1.Handle;
end;

Because Delphi 2 & C++B don't have the ParentWindow property, here is Delphi 3's code for SetParentWindow.  You may be able to do something to "simulate" this.

procedure TWinControl.SetParentWindow(Value: HWnd);
begin
  if (FParent = nil) and (FParentWindow <> Value) then
  begin
    if (FHandle <> 0) and (FParentWindow <> 0) and (Value <> 0) then
    begin
      FParentWindow := Value;
      Windows.SetParent(FHandle, Value);
    end else
    begin
      DestroyHandle;
      FParentWindow := Value;
    end;
    UpdateControlState;
  end;
end;

You can see the SetParentWindow uses the SetParent(hWndChild, hWndParent) API call, but the following by itself does NOT fix your problem in Delphi 3:

procedure TDLLForm.PageControl1Change(Sender: TObject);
var
    Tab: TTabSheet;
begin
    Tab := PageControl1.ActivePage;

    Tab.Parent := nil;
    Windows.SetParent(Tab.Handle, PageControl1.Handle);
end;

Hope you come right.
JB
I'll try and get back to you quickly on this.  Perhaps a
day or two.  Expect something by Saturday evening at the
latest...thanks again.
BTW, thanks for the effort...you DID recreate the exact problem.
I have no idea what I was doing yesterday...perhaps it was late...but Delph i2 and C++Builder do have ParentWindow properties.  I have no idea where I was looking so that I figured it did not exist.

Anyhow, working on your answer now.
It works great except for one thing...

When I initially click tab #2, tabsheet #1 still appears.  I have to click tab #1, then when I click tab #2 all is okay.

What I think is happening is that when the page control is loaded
I have to initialie the active page with the same code as in the OnChange event.

I've tried various things, but have been unable to do this.  I've tried in the Forms OnCreate, but (it was 2 hours ago) I think it simply cause a GPF type exception.

Any ideas on this one?  Where I should put the initialization code?

BTW, you can look forward to an A and 6000 points.  That'll push you up to the #7 spot.  ;)
One other thing...I'd really like to understand why the code in
the OnChange event does the trick?

How did you figure out that that was the thing to do?  Why does
it work?  Why do we have to go the API?  Actually what is API SetParent doing that a simple assignment to TWinControl.Parent is
not doing?  Because an assignment to parent works for the other controls...just not PageControl.
ASKER CERTIFIED SOLUTION
Avatar of JimBob091197
JimBob091197

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
I'm going to grade this now...if I have any little troubles I'm
hoping you help out.

But I'm feeling positve that all will be okay...even if I
haven't yet added the additional code.

Thanks for the help.
I hope you'll still help.

I've caught a couple of other components that are acting up...specifically StringGrids and ComboBoxes.

Here is how they sit in the PageControl.  On a tabsheet in the Page Control can be a number of group boxes, within the group boxes are TEdits, TStringGrids, and TComboBoxes.  The latter two
come up with error messages when I move to the tabsheet they are on:

   "StringGrid1 does not have a parent window"

At first this appeared to be similar problem to the one I was having with the TTabSheets.  I've spent half a day working on this and still haven't found a solution?

I'm hoping you can help a little further on this?  I'll keep trying things on my end and post if I find a solution.

Thanks.
Hi

I followed your instructions, and indeed the controlls on the 2nd tabsheet gave the trouble you described.  (I had 2 tabsheets on my DLL PageControl; the 1st one works fine, but the 2nd with the grpbox with stringgrid gave your problem.)

The problem is related to the general problem so far, i.e. ParentWindow needs to be set for each child on the tabsheet, and each child's child, etc. etc...

The recursive routine which fixes this problem is below.  I implemented it as a nested procedure in the DLL on the PageControl's OnChange event:

procedure TfrmDLLTest.pgDLLTestChange(Sender: TObject);

    procedure SetControlParentWindows(Ctl: TWinControl);
    var
        i: Integer;
        ChildCtl: TWinControl;
    begin
        i := 0;
        while (i < Ctl.ControlCount) do
        begin
            if (Ctl.Controls[i] is TWinControl) then
            begin
                ChildCtl := TWinControl(Ctl.Controls[i]);
                ChildCtl.Parent := nil;
                ChildCtl.ParentWindow := Ctl.Handle;
                SetControlParentWindows(ChildCtl);
            end
            else Inc(i);
        end;
    end;

var
    Tab: TTabSheet;
begin
    Tab := pgDLLTest.ActivePage;

    Tab.Parent := nil;
    Tab.ParentWindow := pgDLLTest.Handle;

    SetControlParentWindows(Tab);
end;


A couple of comments:
1)  I found that the order of the Parent & ParentWindow statements in "SetControlParentWindows" is critical.
2)  The statement in the "SetControlParentWindows" procedure that sets the child control's parent to nil also decreases the owner control's ControlCount.  Thus I had to use a while loop, because the for loop kept getting index out of bounds...  Took me a while to figure that one out!!

I trust you come right with this solution.
Regards,
JB

P.S. I apologize if the indentation of the code isn't right.  Whenever I cut & paste code from my editor the tabs get lost...
I will try this code out tomorrow at work.  Thank again for all your efforts.  It is very much appreciated.  And I'm grateful for this valuable learning experience.

Now there's one more person in the world who knows how to implement this!  :)

You're welcome.

JB
;-)
I'm having some trouble getting that code to work.  I'm still working on it, but I may need the code you successfully got working.

Could you mail it at your earliest convenience to (include the .EXE and .DLL in the zip file if you can):

      mheacock@sprynet.com
      mheacock@msjcorp.com

If you could send the code to both addresses, that would be much appreciated.

I mailed the sample to you.

JB
Hey JimBob,

Well, you're solution never worked in C++Builder.  Though, through no fault of your own.  Whereas you're solution worked perfectly in Delphi 3, it never worked for certain VCL components in C++Builder.

I eventually had to call Borland Tech Support, and after a week of trying different things, they finally decided that it had to be a bug.

So we've decided that an ActiveX solution is the best bet.  It seems to work, except for a small problem.  I know the points are in the 1000+ range, but I never figured on coming back and having to ask another question.  I can always bump it to 350.

If you want to give my new question a shot, please check it out.
Hi again

I found something very interesting in Delphi.  I'm not sure that it's the same in C++ Builder.

If you create a component (TForm, TPanel, whatever...) in a DPL (Delphi Package Library) instead of a DLL, then you can call "MyPanel.Parent := MyForm;" instead of doing the whole "ParentWindow := MyForm.Handle" routine.  There are a few catches, but I can mail the sample to you if you want.  I found it quite interesting.  You can enhance it as need be...

JB
Don't worry...

Never was able to do the task in C++Builder.  Even did the Borland tech support thing.  We tried various things or a week before they decided that it was an official bug in the C++Builder VCL.  Bug #8551.  It will be fixed in the next release.

BTW, according to Borland, the normal method ot do what I was asking was to just set the DLL form's parent to the control I wanted it embedded into.  So the form was contained in the TTabSheet.  Not something I would have thought of.

That's what I'm doing now...it all works from an EXE, just not a DLL...like I said bug 8551.

We have decided to contain our sub-modules in the EXE until the next release of C++Builder.  I continued with my current method so upon the next release, breaking the sub-modules into DLLs will be a day or two worth of work.

Cheers,

PS if you want to try the form within a form thing...do this:

(convert to Delphi yourself)

  Form2 = new TForm(Form1);
  Form2->Parent = Form1;
  Form2->Show();