How to access the owner Page from within a component

I

Ingvar Nilsen

I have a component (System.ComponentModel.Component)
and want to access the Page that "owns" it, during runtime.

Yes, I can write a public property and set that property in the
designer, and thereby get access to it at runtime, but I want to be
smarter than that.

Somehow the component has to "know" the Page it sits on.
I want access to that Page object early in the initialization process,
when the component is created.
If I have that Page object, I can for example add certain events to the
Page.

Any ideas?
 
J

John Saunders

Ingvar Nilsen said:
I have a component (System.ComponentModel.Component)
and want to access the Page that "owns" it, during runtime.

Yes, I can write a public property and set that property in the
designer, and thereby get access to it at runtime, but I want to be
smarter than that.

Somehow the component has to "know" the Page it sits on.
I want access to that Page object early in the initialization process,
when the component is created.
If I have that Page object, I can for example add certain events to the
Page.

Having the component add events to the page is backwards. The component
should expose events which the Page can listen to if it wants to. What
exactly do you mean, "add events to the page"?

John Saunders
 
I

Ingvar Nilsen

John said:
Having the component add events to the page is backwards.

Perhaps, but before we discuss that, how do I get access to the page?
I am sure there is a way, but how?

The component should expose events which the Page can listen to
if it wants to.

Yes, that is the normal case. And it also does.
What exactly do you mean, "add events to the page"?

Mostly to add an event handler for Page_Load and for Page_Unload.
I do this already, when the published property "Page" I use today is
initialized, because this happens before Page_Load. And it works great.

What I wanted to do is to avoid having to explicit set the Page
property at design time. The Page is there, at runtime, it has to be,
it "owns" the component, I just can't get hold of it because I do not
know how to, as simple as that.

BTW, this question comes up now and then (Google), no proper answer
found so far, strangely enough.
 
J

John Saunders

Ingvar Nilsen said:
Perhaps, but before we discuss that, how do I get access to the page?
I am sure there is a way, but how?

There is no way in general.
Yes, that is the normal case. And it also does.


Mostly to add an event handler for Page_Load and for Page_Unload.
I do this already, when the published property "Page" I use today is
initialized, because this happens before Page_Load. And it works great.

Ok, it sounded like you wanted to add event handlers _to_ the page, which I
couldn't see how you could do.
What I wanted to do is to avoid having to explicit set the Page
property at design time. The Page is there, at runtime, it has to be,
it "owns" the component, I just can't get hold of it because I do not
know how to, as simple as that.

If you take a look at InitializeComponent in your code behind you'll see
that the page does not "own" the component. If the component has a
"component constructor", then the "components" variable on the page contains
the components, but there's no link from "components" to the page.

John Saunders
 
I

Ingvar Nilsen

John said:
If you take a look at InitializeComponent in your code behind you'll
see that the page does not "own" the component.

Fair enough, the expression "own" is more figuratively than technically
correct. But what is "this" in this context, if not the page?

this.components = new System.ComponentModel.Container();
this.MyComponent1 = new MyComponent(this.components);

I still believe there must be a way.
When I can get access to the Response object ar runtime, I should get
access to the page too.
If the component has
a "component constructor", then the "components" variable on the page
contains the components, but there's no link from "components" to the
page.

I am not familiar with this architecture.
I am used to components always knowing who contains/created them, and
the creator in turn about its creator again and so on.
By using the "Parent" or "Owner" property which here seems to not exist.
 
J

John Saunders

Ingvar Nilsen said:
Fair enough, the expression "own" is more figuratively than technically
correct. But what is "this" in this context, if not the page?

this.components = new System.ComponentModel.Container();
this.MyComponent1 = new MyComponent(this.components);

Yes, "this" is the page. But notice that it's not "this" that's being passed
to the component, but "this.components".
I still believe there must be a way.
When I can get access to the Response object ar runtime, I should get
access to the page too.

You can get access to the response object because the HttpContext class
thoughtfully provides a static Current property. There is no Page.Current.

Ahah! I just realized that your component should be able to find "the
running page" assuming that there is one. Try HttpContext.Current.Handler.
If HttpContext.Current is non-null, and .Handler is non-null, and if it is a
page, then it's the currently-running page.

Said page may not be available in the constructor of your component, BTW.

John Saunders
 
J

John Saunders

Ingvar Nilsen said:
Fair enough, the expression "own" is more figuratively than technically
correct. But what is "this" in this context, if not the page?

Well, I've got your solution right here!

Or, more accurately, Chris Sells does. I've adapted his code from "Windows
Forms Progeaming in C#", ISBN 0-321-11620-8, Chapter 9, "Design-Time
Integration", page 314-315. The basic trick is to define a public property.
The property getter runs at design time, where it can get a reference to the
page through the designer infrastructure. This puts a reference to the page
into this property at design time.

When the designer serialzes the component to the code, it will emit code to
store "this" into the property, so you'll have it at run time:

private IComponent _page;
[Browsable(false)]
public IComponent ParentPage
{
get
{
if (_page == null && this.DesignMode)
{
System.ComponentModel.Design.IDesignerHost designer =
this.GetService(typeof(System.ComponentModel.Design.IDesignerHost))
as System.ComponentModel.Design.IDesignerHost;
if (designer != null)
{
_page = designer.RootComponent;
}
}
return _page;
}
set
{
if (!this.DesignMode)
{
if ((_page != null) && (_page != value))
{
throw new InvalidOperationException("Can't change
ParentPage at run time");
}
}
else
{
_page = value;
}
}
}

With this in place, the designer serializes the component as follows:

this.myComponent2 = new MyComponent(this.components);
//
// myComponent2
//
this.myComponent2.ParentPage = this;


Success!

John Saunders
 
I

Ingvar Nilsen

John said:
Yes, "this" is the page. But notice that it's not "this" that's being
passed to the component, but "this.components".

Yes, I have. It takes time to getting used to. I find it strange that
the component constructor doesn't have an argument for the instance
creating it. And C# doesn't have virtual constructors, AFAIK, hm...

You can get access to the response object because the HttpContext
class thoughtfully provides a static Current property. There is no
Page.Current.

Yes, have seen that.
Ahah! I just realized that your component should be able to find "the
running page" assuming that there is one. Try
HttpContext.Current.Handler.

In hindsight, I really had plans to look at it but currently my help
system is wobbly for some reason, so I skipped this property.
If HttpContext.Current is non-null

It is not!
.Handler is non-null, and if it is a page, then it's the
currently-running page.
ABSOLUTELY!

Said page may not be available in the constructor of your component,
BTW.

Oh yeah, indeed it is!!!!

Thanks a lot, John, with combined efforts we once again proved that in
computing everything is possible if you look and try hard enough!.

Now it works like magic, I can just drop a component on the web form,
and this component itself installs event handlers for the Page's
events, without writing a single line of code in the Page's code behind
file.

Exactly what I was after.
 
I

Ingvar Nilsen

John said:
Well, I've got your solution right here!

John, thanks a lot, I really appreciate your efforts!
I have saved this solution, too, it may come in handy, but as you see
form my other post, I achieved it with a couple of lines because the
Current.Handler actually is precisely what I am after: The Page.

Thanks again,
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,755
Messages
2,569,535
Members
45,007
Latest member
obedient dusk

Latest Threads

Top