by
kirupa | 23 August 2009
In the
previous page, you saw the code for the
FindParentByType extension method and learned where
to place it in your application. In this page, let's
go ahead and wrap our general solution up and look
at what makes Behaviors different.
The FindParentByType code
works by traveling up each parent and checking if
the parent you are looking for is here. In order for
this to work, you need to ensure that your
application has fully loaded to avoid any issues in
Silverlight and WPF where you get errors because
your visual tree has not fully been generated yet.
While this sounds scary, the solution is fairly
simple. We need to make sure that our
FindParentByType code does not get called until our
user control has fully loaded. This requires hooking up the Loaded event and
assigning an event handler. If you are not familiar
with event handlers, learn more about them in my
earlier
Event Handlers in WPF tutorial. You can set up
your event handler directly in XAML using Blend, or
you can do it via code.
For the sake of simplicity, I am just going to
do it via code. Go ahead and associate the Loaded
event with an event handler inside the constructor
for your user control, and place your event handler
directly below your constructor.
Here is what my code looks like with the Loaded
event and an event handler called
ChildControl_Loaded:
-
using
System;
-
using
System.Windows;
-
using
System.Windows.Controls;
-
using
System.Windows.Documents;
-
using
System.Windows.Ink;
-
using
System.Windows.Input;
-
using
System.Windows.Media;
-
using
System.Windows.Media.Animation;
-
using
System.Windows.Shapes;
-
-
namespace
ParentRootInteraction
-
{
-
public
partial
class
ChildControl
:
UserControl
-
{
-
public
ChildControl()
-
{
-
// Required to initialize variables
-
InitializeComponent();
-
- this.Loaded
+=
new
RoutedEventHandler(ChildControl_Loaded);
-
}
-
- void
ChildControl_Loaded(object
sender,
RoutedEventArgs
e)
- {
- // What to
run after you have loaded your
application
- }
-
}
-
-
public
static
class
TreeHelper
-
{
-
public
static
T
FindParentByType<T>(this
DependencyObject
child)
where
T
:
DependencyObject
-
{
-
Type
type
=
typeof(T);
-
DependencyObject
parent
=
VisualTreeHelper.GetParent(child);
-
-
if
(parent
==
null)
-
{
-
return
null;
-
}
-
else
if
(parent.GetType()
==
type)
-
{
-
return
parent
as
T;
-
}
-
else
-
{
-
return
parent.FindParentByType<T>();
-
}
-
}
-
}
-
}
The event handler that gets called, in my
example, after everything loads is ChildControl_Loaded. Inside this method, you will
make a call to the FindParentByType extension
method. Because this is an extension method, you can
just do something like this:
- MainPage
mainPage
=
this.FindParentByType<MainPage>();
Your full event handler code would look as
follows:
- void
ChildControl_Loaded(object
sender,
RoutedEventArgs
e)
- {
- MainPage
mainPage
=
this.FindParentByType<MainPage>();
- }
The FindParentByType extension method works like
this. Let's say you have a parent/child structure as
shown here:
In this example, the extension method lives in ChildControl (kind
of like in all of my examples) and you wish to get a
reference to its parent whose type is
MyParentUserControl. Your call to FindParentByType
would look as follows:
- void
ChildControl_Loaded(object
sender,
RoutedEventArgs
e)
- {
-
MyParentUserControl
uc =
this.FindParentByType<MyParentUserControl>();
- }
Notice that FindParentByType does not take an
argument in the traditional sense. Instead, it takes
a type argument whose type matches the type of the
parent you are looking for. If I wanted to go all
the way to the top and access MainPage, I would
simply do this:
- void
ChildControl_Loaded(object
sender,
RoutedEventArgs
e)
- {
-
MainPage
uc =
this.FindParentByType<MainPage>();
- }
That is all there is to it. This general solution
allows you to access both your immediate parent as
well as any parent leading up to and including the
root. Read that last line carefully. The key words
to look for are "any parent".
Your parent does not have
to be a UserControl or Window. For example, what is
the parent of the UserControl that you see in the
following example:
You may think that the UserControl at the top of
the tree is the parent
(especially given the themes of this particular
article), but it actually is the Grid element called
LayoutRoot. LayoutRoot's parent is Border, and only
the Border has a parent whose type is UserControl.
The code for the general solution I have provided
will find the first type of any parent it encounters
- not just something that is a type belonging to
UserControl. This means that you can use this
solution to also find the Grid and Border parents
from my example!
I briefly mentioned
behaviors on the first page of this tutorial, but I
never mentioned them since then. The reason is, for the purposes of
this tutorial, you can think of a behavior as a
usercontrol that faces similar challenges when
trying to access its parents and root
element.
The only variation is that a Behavior (or Action)
contains a mechanism for easily accessing its
immediate parent - the object it is
usually attached to. Behaviors and Actions have a property called
AssociatedObject that returns the element they are
nested under without any fuss:
- this.AssociatedObject;
If your Behavior or Action is actually templated
to only work on a particular type, the
AssociatedObject property's type will be keyed to
what the Behavior or Action is looking for without
requiring any additional casting.
I mention that the AssociatedObject returns the
immediate parent which is usually the same as the
element your behavior or action is attached to.
Because of retargeting, you cannot always assume
that the parent of your behavior or action is
actually the same as the AssociatedObject, so be
aware of that.
Everything you do in Silverlight and WPF revolves
around the concept of a tree containing parents and
children. Visual and non-visual elements are nested
under something and this relationship greatly
impacts how your application looks as well as works.
Hopefully this tutorial helped you to figure out
some ways of accessing the immediate parent or the
root easily, for you will find yourself doing this
often - especially if you design your application
visually using Expression Blend where you do not
have the ability to visually modify the constructor
to take a reference to the parent element.
If you are interested in seeing an example of
this working, download the source files for a sample
Silverlight project where you have a deeply nested
usercontrol calling the root using the
FindParentByType:
Just a final word before we wrap up. If you have a question and/or want to be part of a friendly, collaborative community of over 220k other developers like yourself, post on the forums for a quick response!
|