Watch, Follow, &
Connect with Us

For forums, blogs and more please visit our
Developer Tools Community.


Welcome, Guest
Guest Settings
Help

Thread: "Control has no parent window"



Permlink Replies: 11 - Last Post: Jan 23, 2018 10:09 AM Last Post By: Mark Williams Threads: [ Previous | Next ]
Mark Williams

Posts: 120
Registered: 5/8/10
"Control has no parent window"
Click to report abuse...   Click to reply to this thread Reply
  Posted: Jan 21, 2018 7:42 AM
I have googled for an answer to this problem with no joy. In essence I am creating a visual control which derives from a TPanel, which is a container for various other controls including a TToolBar.

I create the TPanel in the Constructor and at the end of the constructor I call a procedure called CreateComponents which creates the TPanel.

CreateComponents creates the Toolbar as follows:

FToolbar := TToolbar.create(self);
  with FToolBar do
    begin
      Parent := self;
      Height := 32;
      Align := alTop;
      ButtonHeight := 30;
      ButtonWidth := 30;
      Images := FImageList1;
      DisabledImages := FImageList2;
      ShowHint := true;
    end;


I get a "no parent window" error at the point where I set ButtonHeight. At that point I guess my TPanel needs to have its parent window.

I am setting that in my form

CustomPanel :=TCustomPanel.create(self);
CustomPanel.Parent:=MainPanel;


I understand that as I am calling CreateComponents from the constructor for TCustomPanel and its parent window is not getting set until after the constructor has completed that this is why I get the "no parent window" issue.

What I can't work put is where I should call CreateComponents.

I have tried making it a public function and calling it from the main form

CustomPanel :=TCustomPanel.create(self);
CustomPanel.Parent:=MainPanel;
CustomPanel.CreateComponents


I now get an EOSError "A call to an OS Function failed". It fails in the toolbar creation routine when it ties to set its parent to self. It seems to be a handle needed related problem.

I am sure there is something very obvious I am not doing it, but I can't work out what!
Peter Below

Posts: 1,227
Registered: 12/16/99
Re: "Control has no parent window"
Click to report abuse...   Click to reply to this thread Reply
  Posted: Jan 21, 2018 11:05 PM   in response to: Mark Williams in response to: Mark Williams
Mark Williams wrote:

I have googled for an answer to this problem with no joy. In essence
I am creating a visual control which derives from a TPanel, which is
a container for various other controls including a TToolBar.

I would use a frame for that, not a custom panel.

I create the TPanel in the Constructor and at the end of the
constructor I call a procedure called CreateComponents which creates
the TPanel.

THe sentence does not parse <g>. Your custom component is a panel, so
you create the controls it contains in the CreateComponents method
called from the custom panel constructor, no? I hope you call the
inherited constructor first?

CreateComponents creates the Toolbar as follows:

FToolbar := TToolbar.create(self);
  with FToolBar do
    begin
      Parent := self;
      Height := 32;
      Align := alTop;
      ButtonHeight := 30;
      ButtonWidth := 30;
      Images := FImageList1;
      DisabledImages := FImageList2;
      ShowHint := true;
    end;


I get a "no parent window" error at the point where I set
ButtonHeight. At that point I guess my TPanel needs to have its
parent window.

I am setting that in my form

CustomPanel :=TCustomPanel.create(self);
CustomPanel.Parent:=MainPanel;


I understand that as I am calling CreateComponents from the
constructor for TCustomPanel and its parent window is not getting set
until after the constructor has completed that this is why I get the
"no parent window" issue.

What I can't work put is where I should call CreateComponents.

I have tried making it a public function and calling it from the main
form

CustomPanel :=TCustomPanel.create(self);
CustomPanel.Parent:=MainPanel;
CustomPanel.CreateComponents


I now get an EOSError "A call to an OS Function failed". It fails in
the toolbar creation routine when it ties to set its parent to self.
It seems to be a handle needed related problem.

Have you tried to call CustomPanel.HandleNeeded before calling
CreateComponent in the construct above?

The VCL is usually able to deal with the handle problem on its own. It
has basically the same problem you are having when a form with content
is loaded from the DFM resource. Property values read from the DFM
stream are first loaded into component fields and only used to set the
API-level features of the control when the control'S window handle is
created. If you cannot find another solution you could override the
custom panel's CreateWnd method and call your CreateComponents method
from there, after calling the inherited method. BUt you have to make
sure in this scenario to call CreateComponents only the first time
CreateWnd is called. It can be called several times during the lifetime
of a control, if a window handle in the parent chain is recreated for
some reason.


I am sure there is something very obvious I am not doing it, but I
can't work out what!


--
Peter Below
TeamB

Mark Williams

Posts: 120
Registered: 5/8/10
Re: "Control has no parent window"
Click to report abuse...   Click to reply to this thread Reply
  Posted: Jan 22, 2018 5:47 AM   in response to: Peter Below in response to: Peter Below
Peter Below wrote:

I do hope you call the inherited constructor first?

Yes.

If you cannot find another solution you could override the
custom panel's CreateWnd method and call your CreateComponents method
from there, after calling the inherited method. BUt you have to make
sure in this scenario to call CreateComponents only the first time
CreateWnd is called. It can be called several times during the lifetime
of a control, if a window handle in the parent chain is recreated for
some reason.

I had tried CreateWnd, but it was causing other issues with the component so I gave up on it. However, nothing else seems to be working so I have reverted. The component now creates ok, but I now have an issue with another overridden procedure, ie WndProc; I am trying to intercept messages to the underlying form so that I can detect when the form has been moved.

I have the following code in the component's constructor to get a handle to the owner form events
{if (AOwner is TWinControl) then
      SetWindowLong(TWinControl(AOwner).Handle, GWL_WNDPROC, integer(MakeObjectInstance(WndProc)));


Then I override the wndProc procedure:
procedure TCustomPanel.WndProc(var Message: TMessage);
begin
 
  if (Owner is TWinControl) then
    begin
      TWinControl(Owner).WindowProc(Message);
      case Message.Msg of
        WM_MOVE: repositionNotes;
      end;
    end;
end;


This fails with an EOSError "A call to an OS function failed. It fails on the call to "inherited CreateWnd" in the createWnd procedure. It seems to fail oddly on sys,utils RaiseLastError.

If I comment out all the WndProc code I don't get the error and CreateWnd executes just fine.

Any ideas why I am getting a conflict between these two procedures?

Tframe
I haven't yet changed the component inheritance form TPanel to TFrame. The component is one I created in an old version of Delphi. It seemed to work just fine as a TPanel. What are the problems with using a TPAnel and the advantages of using a TFrame in these circumstances?

Many thanks
Remy Lebeau (Te...


Posts: 9,447
Registered: 12/23/01
Re: "Control has no parent window"
Click to report abuse...   Click to reply to this thread Reply
  Posted: Jan 22, 2018 11:19 AM   in response to: Mark Williams in response to: Mark Williams
Mark Williams wrote:

I am trying to intercept messages to the underlying form so that I
can detect when the form has been moved.

Why? Child components shouldn't need to care about things like that.

I have the following code in the component's constructor to get a
handle to the owner form events

That won't work the way you are doing it. For one thing, you are
causing a recursive loop. For another, you are using the Panel's own
WndProc, which will also receive messages intended for the Panel
itself, messages that should not be forwarded to the Owner's WndProc at
all.

At the very least, you should be using SetWindowSubclass() instead,
which is safer than using SetWindowLong().

Alternatively, consider using SetWindowsHookEx() or SetWinEventHook()
instead. Install a thread-specific hook into the thread of the Owner's
window. Either hook can then tell you when an HWND moves, and you can
compare that HWND to the owner's HWND to see if they match before doing
anything.

--
Remy Lebeau (TeamB)
Mark Williams

Posts: 120
Registered: 5/8/10
Re: "Control has no parent window"
Click to report abuse...   Click to reply to this thread Reply
  Posted: Jan 22, 2018 12:17 PM   in response to: Remy Lebeau (Te... in response to: Remy Lebeau (Te...
Remy Lebeau (TeamB) wrote:
Mark Williams wrote:

I am trying to intercept messages to the underlying form so that I
can detect when the form has been moved.

Why? Child components shouldn't need to care about things like that.

The component is quite complicated. It is an image viewing component which allows users to create and edit sticky notes. When the notes are being edited a form is created over the component. If the component moves (including when the form moves or resizes) then the note needs to stick with it.


I have the following code in the component's constructor to get a
handle to the owner form events

That won't work the way you are doing it. For one thing, you are
causing a recursive loop. For another, you are using the Panel's own
WndProc, which will also receive messages intended for the Panel
itself, messages that should not be forwarded to the Owner's WndProc at
all.

I assume it is the call to TWinControl(Owner).WindowProc(Message); which will cause the recursion?


At the very least, you should be using SetWindowSubclass() instead,
which is safer than using SetWindowLong().

Alternatively, consider using SetWindowsHookEx() or SetWinEventHook()
instead. Install a thread-specific hook into the thread of the Owner's
window. Either hook can then tell you when an HWND moves, and you can
compare that HWND to the owner's HWND to see if they match before doing
anything.

The component was originally written in Delphi 5 and is being rewritten in Delphi 10. I originally used a tbx toolbar which had no issues with parent windows. The code used is verbatim what was used in the component previously and it seemed to work ok. I am not very familiar with hooks. Can you recommend a good basic guide and examples?

BTW I can't find the appropriate unit for SetWIndowSubClass.
Remy Lebeau (Te...


Posts: 9,447
Registered: 12/23/01
Re: "Control has no parent window"
Click to report abuse...   Click to reply to this thread Reply
  Posted: Jan 22, 2018 4:04 PM   in response to: Mark Williams in response to: Mark Williams
Mark Williams wrote:

The component is quite complicated. It is an image viewing component
which allows users to create and edit sticky notes. When the notes
are being edited a form is created over the component. If the
component moves (including when the form moves or resizes) then the
note needs to stick with it.

What does that have to do with your component intercepting its Owner's
window movements? Is it the overlay skicky note that is trying to
track the Panel's movements? If so, I would not do it that way. I
would have the Panel itlself reposition the sticky notes when the Panel
is resized.

Or better, don't even use an actual Form for the sticky notes at all.
Render the image first as needed, and then just draw sticky notes on
top of it. Any time the Panel is refreshed, even during resizes, the
image has to be redrawn anyway, so just draw the new image as needed
and then draw the sticky notes on top of it again.

Using actual child controls, especially ones with their own HWNDs,
isn't always the best choice for composite controls.

BTW I can't find the appropriate unit for SetWIndowSubClass.

It is in the Winapi.CommCtrl unit.

--
Remy Lebeau (TeamB)
Mark Williams

Posts: 120
Registered: 5/8/10
Re: "Control has no parent window"
Click to report abuse...   Click to reply to this thread Reply
  Posted: Jan 23, 2018 5:16 AM   in response to: Remy Lebeau (Te... in response to: Remy Lebeau (Te...
Remy Lebeau (TeamB) wrote:
Mark Williams wrote:

The component is quite complicated. It is an image viewing component
which allows users to create and edit sticky notes. When the notes
are being edited a form is created over the component. If the
component moves (including when the form moves or resizes) then the
note needs to stick with it.

What does that have to do with your component intercepting its Owner's
window movements? Is it the overlay skicky note that is trying to
track the Panel's movements? If so, I would not do it that way. I
would have the Panel itlself reposition the sticky notes when the Panel
is resized.

It's not just a resize issue, it is a movement issue. If the underlying form is moved I need to detect so that the note form moves with the component rather than being left behind.


Or better, don't even use an actual Form for the sticky notes at all.
Render the image first as needed, and then just draw sticky notes on
top of it. Any time the Panel is refreshed, even during resizes, the
image has to be redrawn anyway, so just draw the new image as needed
and then draw the sticky notes on top of it again.

That is what I do with a static note, but the user is able to double click on it and open it to edit. This require an editor (memo), which I house in a form. Similar to the way Acrobat allows editing of notes.
Mark Williams

Posts: 120
Registered: 5/8/10
Re: "Control has no parent window"
Click to report abuse...   Click to reply to this thread Reply
  Posted: Jan 23, 2018 9:40 AM   in response to: Mark Williams in response to: Mark Williams
As possibly the simplest workaround, I have published the RepositionNotes function and I am now intercepting the owner form's WM_MOVE message and calling the reposition function from there. Not how I would prefer to do it. but looks like the simplest and least problematic way.

Thanks for your help.
Remy Lebeau (Te...


Posts: 9,447
Registered: 12/23/01
Re: "Control has no parent window"
Click to report abuse...   Click to reply to this thread Reply
  Posted: Jan 23, 2018 9:48 AM   in response to: Mark Williams in response to: Mark Williams
Mark Williams wrote:

It's not just a resize issue, it is a movement issue. If the
underlying form is moved I need to detect so that the note form moves
with the component rather than being left behind.

Then I would use a hook via SetWindowsHookEx() or SetWinEventHook().

A WH_CALLWNDPROC hook via SetWindowsHookEx() can monitor for
WM_WINDOWPOSCHANGING and WM_WINDOWPOSCHANGED messages.

A hook via SetWinEventHook() can monitor for
EVENT_OBJECT_LOCATIONCHANGE notifications.

--
Remy Lebeau (TeamB)
Mark Williams

Posts: 120
Registered: 5/8/10
Re: "Control has no parent window"
Click to report abuse...   Click to reply to this thread Reply
  Posted: Jan 23, 2018 10:09 AM   in response to: Remy Lebeau (Te... in response to: Remy Lebeau (Te...
Remy Lebeau (TeamB) wrote:

Then I would use a hook via SetWindowsHookEx() or SetWinEventHook().

A WH_CALLWNDPROC hook via SetWindowsHookEx() can monitor for
WM_WINDOWPOSCHANGING and WM_WINDOWPOSCHANGED messages.

A hook via SetWinEventHook() can monitor for
EVENT_OBJECT_LOCATIONCHANGE notifications.

Thanks I will have a look at this.
Remy Lebeau (Te...


Posts: 9,447
Registered: 12/23/01
Re: "Control has no parent window"
Click to report abuse...   Click to reply to this thread Reply
  Posted: Jan 22, 2018 10:56 AM   in response to: Mark Williams in response to: Mark Williams
Mark Williams wrote:

I get a "no parent window" error at the point where I set
ButtonHeight.

The problem is in the TToolBar.SetButtonHeight() property setter:

procedure TToolBar.SetButtonHeight(Value: Integer);
begin
  if Value <> FButtonHeight then
  begin
    FButtonHeight := Value;
    if (StyleServices.Enabled = True) and Showing then
      RecreateWnd;
    RecreateButtons; // <-- here
  end;
end;
 
procedure TToolBar.RecreateButtons;
begin
  if ([csLoading, csDestroying] * ComponentState = []) or
HandleAllocated then
  begin
    CreateButtons(FButtonWidth, FButtonHeight); // <-- here
    ResizeButtons;
  end;
end;


While initializing your TToolBar, the csLoading flag is not set,
because your ToolBar is not being streamed in from a DFM. So
CreateButtons() gets called, regardless of whether HandleAllocated is
true or false. And the first thing CreateButtons() does is to call
HandleNeeded() on the ToolBar, which forces an HWND creation, which
fails if the Parent does not have an HWND assigned yet and cannot
create one (because its Parent has no HWND, and so on).

ANYTHING that requires a component to have its HWND cannot be done
inside a constructor.

I understand that as I am calling CreateComponents from the
constructor for TCustomPanel and its parent window is not getting set
until after the constructor has completed that this is why I get the
"no parent window" issue.

Yes.

What I can't work put is where I should call CreateComponents.

Definately not in the TCustomPanel's constructor.

If the TCustomTPanel component is being created at design-time, then
override its virtual Loaded() method to call CreateComponents().

But if the TCustomTPanel component is being created in code at
run-time, then there is no way to call CreateComponents()
automatically. The user will have to call it manually after
constructing the Panel and ensuring it has a Parent available first.

Alternatively, since your TToolBar is internal to your TCustomPanel,
you might try simply enabling the csLoading flag manually while
initializing the TToolBar's properties, and then clear it when
finished, eg (untested):

FToolbar := TToolbar.create(self);
with FToolBar do
begin
  Include(ComponentState, csLoading);
  try
    Parent := self;
    Height := 32;
    Align := alTop;
    ButtonHeight := 30;
    ButtonWidth := 30;
    Images := FImageList1;
    DisabledImages := FImageList2;
    ShowHint := true;
  finally
    Exclude(ComponentState, csLoading);
  end;
end;


I have tried making it a public function and calling it from the main
form

CustomPanel :=TCustomPanel.create(self);
CustomPanel.Parent:=MainPanel;
CustomPanel.CreateComponents


I now get an EOSError "A call to an OS Function failed". It fails in
the toolbar creation routine when it ties to set its parent to self.
It seems to be a handle needed related problem.

What does the call stack actually look like when the error occurs?

--
Remy Lebeau (TeamB)
Mark Williams

Posts: 120
Registered: 5/8/10
Re: "Control has no parent window"
Click to report abuse...   Click to reply to this thread Reply
  Posted: Jan 22, 2018 11:43 AM   in response to: Remy Lebeau (Te... in response to: Remy Lebeau (Te...
Remy Lebeau (TeamB) wrote:

But if the TCustomTPanel component is being created in code at
run-time, then there is no way to call CreateComponents()
automatically. The user will have to call it manually after
constructing the Panel and ensuring it has a Parent available first.

I tried calling createcomponents from outside the component, but only to test it. It didn't strike me as a proper way to deal with it.

Alternatively, since your TToolBar is internal to your TCustomPanel,
you might try simply enabling the csLoading flag manually while
initializing the TToolBar's properties, and then clear it when
finished, eg (untested):

I have moved the call to createcomponents to the createWnd proc. This seems to be working fine aside from the issue in my above post and which you address in your mexy post. Do you see a problem with using createWNd?

--
Remy Lebeau (TeamB)
Legend
Helpful Answer (5 pts)
Correct Answer (10 pts)

Server Response from: ETNAJIVE02